diff --git a/DEPS b/DEPS
index 0e1da20..cb04a1f 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,11 @@
   # 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': '89bf1547f3aa3ff3c142d197c14bbce1e199db2b',
+  'skia_revision': '81158535ed1ba414458e2b8b7980b7d5a8f55bb8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '8c170824164c4917fb99a9c38b34609b152e949c',
+  'v8_revision': '4c53f9a51444393133ff303952f1296603d44ab7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -187,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': '01dd6f6ca430a5c5bfd9d237b5569abd835694fc',
+  'angle_revision': 'fca5a005aa880a51672c091fcb82f084b620670a',
   # 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': '6e3a387c01598240a7a8722ab8cbd1015ec1cd0a',
+  'swiftshader_revision': '59465799210b3f4962af1a9dc44a4ffecb422c10',
   # 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': '189074525f4700d6700feef3d2fd5ea07eb64985',
+  'catapult_revision': '263d57d37615e12c492bdec884b23e46dbdd1fee',
   # 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': '451736382565ecf11f295b8220f4ef5d033e0e0e',
+  'devtools_frontend_revision': 'b015b7d12040e53bb0c987a4db6c9cbb774bde41',
   # 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.
@@ -310,7 +310,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '85240a12ed2b9ccb08ae449bca1bbf9eb93c8a12',
+  'quiche_revision': '50ea92a176baa696e88b519609584214317745eb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -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' + '@' + '63b6be2bbc9985f8be563bff093acc2b3821ba35',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5b94048566ab2a458316af49f35655586fe503b0',
       'condition': 'checkout_linux',
   },
 
@@ -1483,7 +1483,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '53655df4cde60b121fc530842ba9a6d5dfec1ae1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '26762d0425ffd15af9ddc3ae669373668827ea00',
+    Var('webrtc_git') + '/src.git' + '@' + 'f2dc05978f1dda877d811b81d1ee9cedb6d38be1',
 
   '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@198da190b2a329168f2b86d8413cd0308f828d88',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@aca963f8ae300ae9658968d09ed2b8cd1f175e92',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/tools/apk_merger.py b/android_webview/tools/apk_merger.py
index 42b2379e..5acfc8a 100755
--- a/android_webview/tools/apk_merger.py
+++ b/android_webview/tools/apk_merger.py
@@ -252,9 +252,9 @@
   try:
     MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64)
 
-    apksigner_path = os.path.join(
-        os.path.dirname(args.zipalign_path), 'apksigner')
-    finalize_apk.FinalizeApk(apksigner_path, args.zipalign_path,
+    apksigner_jar = os.path.join(
+        os.path.dirname(args.zipalign_path), 'lib', 'apksigner.jar')
+    finalize_apk.FinalizeApk(apksigner_jar, args.zipalign_path,
                              tmp_apk, new_apk, args.keystore_path,
                              args.key_password, args.key_name)
   finally:
diff --git a/ash/system/status_area_widget_delegate.cc b/ash/system/status_area_widget_delegate.cc
index 87579a9..18468d6 100644
--- a/ash/system/status_area_widget_delegate.cc
+++ b/ash/system/status_area_widget_delegate.cc
@@ -7,11 +7,11 @@
 #include "ash/focus_cycler.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shelf/shelf_widget.h"
 #include "ash/shell.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/tray/tray_constants.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
@@ -86,6 +86,7 @@
   set_owned_by_client();  // Deleted by DeleteDelegate().
 
   ShelfConfig::Get()->AddObserver(this);
+  shelf_->shelf_layout_manager()->AddObserver(this);
 
   // Allow the launcher to surrender the focus to another window upon
   // navigation completion by the user.
@@ -96,6 +97,7 @@
 
 StatusAreaWidgetDelegate::~StatusAreaWidgetDelegate() {
   ShelfConfig::Get()->RemoveObserver(this);
+  shelf_->shelf_layout_manager()->RemoveObserver(this);
 }
 
 void StatusAreaWidgetDelegate::SetFocusCyclerForTesting(
@@ -178,6 +180,21 @@
   UpdateLayout();
 }
 
+void StatusAreaWidgetDelegate::OnHotseatStateChanged(HotseatState old_state,
+                                                     HotseatState new_state) {
+  // Update the border of the last visible child so it has the right
+  // padding depending of the state of the shelf (See
+  // https://crbug.com/1025270). Don't layout as it will cause the whole
+  // transition to snap instead of animate (See https://crbug.com/1032770).
+  auto it = std::find_if(children().crbegin(), children().crend(),
+                         [](const View* v) { return v->GetVisible(); });
+  if (it == children().crend())
+    return;
+
+  View* last_visible_child = *it;
+  SetBorderOnChild(last_visible_child, /*is_child_on_edge=*/true);
+}
+
 void StatusAreaWidgetDelegate::UpdateLayout() {
   // Use a grid layout so that the trays can be centered in each cell, and
   // so that the widget gets laid out correctly when tray sizes change.
@@ -257,13 +274,11 @@
   // items also takes care of padding at the edge of the shelf.
   int right_edge = kPaddingBetweenWidgetsNewUi;
 
-  const bool tablet_mode =
-      Shell::Get()->tablet_mode_controller() &&
-      Shell::Get()->tablet_mode_controller()->InTabletMode();
-
-  if (is_child_on_edge && !tablet_mode &&
-      chromeos::switches::ShouldShowShelfHotseat()) {
-    right_edge = kPaddingBetweenWidgetAndRightScreenEdge;
+  if (is_child_on_edge && chromeos::switches::ShouldShowShelfHotseat()) {
+    right_edge =
+        shelf_->shelf_layout_manager()->hotseat_state() == HotseatState::kShown
+            ? kPaddingBetweenWidgetAndRightScreenEdge
+            : 0;
   }
 
   // Swap edges if alignment is not horizontal (bottom-to-top).
diff --git a/ash/system/status_area_widget_delegate.h b/ash/system/status_area_widget_delegate.h
index f5f6779..16dc765 100644
--- a/ash/system/status_area_widget_delegate.h
+++ b/ash/system/status_area_widget_delegate.h
@@ -8,6 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_types.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
 #include "ash/system/status_area_widget.h"
 #include "base/macros.h"
 #include "ui/gfx/image/image_skia.h"
@@ -21,7 +22,8 @@
 // The View for the status area widget.
 class ASH_EXPORT StatusAreaWidgetDelegate : public views::AccessiblePaneView,
                                             public views::WidgetDelegate,
-                                            public ShelfConfig::Observer {
+                                            public ShelfConfig::Observer,
+                                            public ShelfLayoutManagerObserver {
  public:
   explicit StatusAreaWidgetDelegate(Shelf* shelf);
   ~StatusAreaWidgetDelegate() override;
@@ -59,6 +61,10 @@
   // Overridden from ShelfConfig::Observer:
   void OnShelfConfigUpdated() override;
 
+  // ShelfLayoutManagerObserver:
+  void OnHotseatStateChanged(HotseatState old_state,
+                             HotseatState new_state) override;
+
   void set_default_last_focusable_child(bool default_last_focusable_child) {
     default_last_focusable_child_ = default_last_focusable_child;
   }
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 7077cc2f..0999dbc4 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -44,7 +44,6 @@
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
 #include "ash/wm/window_util.h"
-#include "ash/wm/work_area_insets.h"
 #include "ash/wm/workspace/backdrop_controller.h"
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
@@ -231,54 +230,6 @@
   }
 }
 
-// Returns the bounds for the overview window grid according to the split view
-// state. If split view mode is active, the overview window should open on the
-// opposite side of the default snap window. If |divider_changed| is true, maybe
-// clamp the bounds to a minimum size and shift the bounds offscreen.
-gfx::Rect GetGridBoundsInScreen(aura::Window* root_window,
-                                bool divider_changed) {
-  const gfx::Rect work_area =
-      WorkAreaInsets::ForWindow(root_window)->ComputeStableWorkArea();
-  SplitViewController* split_view_controller =
-      SplitViewController::Get(root_window);
-  if (!split_view_controller->InSplitViewMode())
-    return work_area;
-
-  SplitViewController::SnapPosition opposite_position =
-      (split_view_controller->default_snap_position() ==
-       SplitViewController::LEFT)
-          ? SplitViewController::RIGHT
-          : SplitViewController::LEFT;
-  gfx::Rect bounds = split_view_controller->GetSnappedWindowBoundsInScreen(
-      opposite_position, /*window_for_minimum_size=*/nullptr);
-  if (!divider_changed)
-    return bounds;
-
-  const bool horizontal = SplitViewController::IsLayoutHorizontal();
-  const int min_length =
-      (horizontal ? work_area.width() : work_area.height()) / 3;
-  const int current_length = horizontal ? bounds.width() : bounds.height();
-
-  if (current_length > min_length)
-    return bounds;
-
-  // Clamp bounds' length to the minimum length.
-  if (horizontal)
-    bounds.set_width(min_length);
-  else
-    bounds.set_height(min_length);
-
-  if (SplitViewController::IsPhysicalLeftOrTop(opposite_position)) {
-    // If we are shifting to the left or top we need to update the origin as
-    // well.
-    const int offset = min_length - current_length;
-    bounds.Offset(horizontal ? gfx::Vector2d(-offset, 0)
-                             : gfx::Vector2d(0, -offset));
-  }
-
-  return bounds;
-}
-
 gfx::Insets GetGridInsets(const gfx::Rect& grid_bounds) {
   const int horizontal_inset =
       gfx::ToFlooredInt(std::min(kOverviewInsetRatio * grid_bounds.width(),
@@ -375,7 +326,7 @@
           ShouldAllowSplitView()
               ? std::make_unique<SplitViewDragIndicators>(root_window)
               : nullptr),
-      bounds_(GetGridBoundsInScreen(root_window, /*divider_changed=*/false)) {
+      bounds_(GetGridBoundsInScreen(root_window)) {
   for (auto* window : windows) {
     if (window->GetRootWindow() != root_window)
       continue;
@@ -646,12 +597,13 @@
         overview_session_->window_drag_controller()->item()) {
       ignored_items.insert(overview_session_->window_drag_controller()->item());
     }
-    const gfx::Rect grid_bounds = GetGridBoundsInScreenForSplitview(
+    const gfx::Rect grid_bounds = GetGridBoundsInScreen(
         root_window_,
         split_view_drag_indicators_
             ? base::make_optional(
                   split_view_drag_indicators_->current_window_dragging_state())
-            : base::nullopt);
+            : base::nullopt,
+        /*divider_changed=*/false);
     SetBoundsAndUpdatePositions(grid_bounds, ignored_items, /*animate=*/true);
   }
 }
@@ -712,8 +664,9 @@
   }
 
   // Update the grid's bounds.
-  const gfx::Rect wanted_grid_bounds = GetGridBoundsInScreenForSplitview(
-      root_window_, base::make_optional(window_dragging_state));
+  const gfx::Rect wanted_grid_bounds = GetGridBoundsInScreen(
+      root_window_, base::make_optional(window_dragging_state),
+      /*divider_changed=*/false);
   if (bounds_ != wanted_grid_bounds) {
     SetBoundsAndUpdatePositions(wanted_grid_bounds,
                                 {GetOverviewItemContaining(dragged_window)},
@@ -865,7 +818,7 @@
   // Update the grid bounds and reposition windows. Since the grid bounds might
   // be updated based on the preview area during drag, but the window finally
   // didn't be snapped to the preview area.
-  SetBoundsAndUpdatePositions(GetGridBoundsInScreenForSplitview(root_window_),
+  SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
                               /*ignored_items=*/{},
                               /*animate=*/true);
 }
@@ -912,9 +865,8 @@
   // updated in |OnSplitViewDividerPositionChanged|.
   if (SplitViewController::Get(root_window_)->InSplitViewMode())
     return;
-  SetBoundsAndUpdatePositions(
-      GetGridBoundsInScreen(root_window_, /*divider_changed=*/false),
-      /*ignored_items=*/{}, /*animate=*/false);
+  SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
+                              /*ignored_items=*/{}, /*animate=*/false);
 }
 
 void OverviewGrid::OnSplitViewStateChanged(
@@ -952,9 +904,8 @@
 
   // Update the cannot snap warnings and adjust the grid bounds.
   UpdateCannotSnapWarningVisibility();
-  SetBoundsAndUpdatePositions(
-      GetGridBoundsInScreen(root_window_, /*divider_changed=*/false),
-      /*ignored_items=*/{}, /*animate=*/false);
+  SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_),
+                              /*ignored_items=*/{}, /*animate=*/false);
 
   // Activate the overview focus window, to match the behavior of entering
   // overview mode in the beginning.
@@ -962,9 +913,11 @@
 }
 
 void OverviewGrid::OnSplitViewDividerPositionChanged() {
-  SetBoundsAndUpdatePositions(GetGridBoundsInScreen(root_window_,
-                                                    /*divider_changed=*/true),
-                              /*ignored_items=*/{}, /*animate=*/false);
+  SetBoundsAndUpdatePositions(
+      GetGridBoundsInScreen(root_window_,
+                            /*window_dragging_state=*/base::nullopt,
+                            /*divider_changed=*/true),
+      /*ignored_items=*/{}, /*animate=*/false);
 }
 
 void OverviewGrid::OnScreenCopiedBeforeRotation() {
diff --git a/ash/wm/overview/overview_grid_unittest.cc b/ash/wm/overview/overview_grid_unittest.cc
index cc18049..028770c 100644
--- a/ash/wm/overview/overview_grid_unittest.cc
+++ b/ash/wm/overview/overview_grid_unittest.cc
@@ -258,6 +258,9 @@
 
 // Tests that only one window animates when entering overview from splitview
 // double snapped.
+// TODO(sammiequon): The way this test is setup causes an unnatural state by
+// calling |GetGridBoundsInScreen| with split view state both snapped. Find a
+// way to re-enable this.
 TEST_F(OverviewGridTest, DISABLED_SnappedWindow) {
   auto window1 = CreateTestWindow(gfx::Rect(100, 100));
   auto window2 = CreateTestWindow(gfx::Rect(100, 100));
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index b71d54d8..e2451e1 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -25,6 +25,7 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/work_area_insets.h"
 #include "base/no_destructor.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
@@ -222,12 +223,17 @@
   }
 }
 
-// Get the grid bounds if a window is snapped in splitview, or what they will be
-// when snapped based on |target_root| and |indicator_state|.
-gfx::Rect GetGridBoundsInScreenForSplitview(
+gfx::Rect GetGridBoundsInScreen(aura::Window* target_root) {
+  return GetGridBoundsInScreen(target_root,
+                               /*window_dragging_state=*/base::nullopt,
+                               /*divider_changed=*/false);
+}
+
+gfx::Rect GetGridBoundsInScreen(
     aura::Window* target_root,
     base::Optional<SplitViewDragIndicators::WindowDraggingState>
-        window_dragging_state) {
+        window_dragging_state,
+    bool divider_changed) {
   auto* split_view_controller = SplitViewController::Get(target_root);
   auto state = split_view_controller->state();
 
@@ -246,17 +252,58 @@
     }
   }
 
+  gfx::Rect bounds;
+  gfx::Rect work_area =
+      WorkAreaInsets::ForWindow(target_root)->ComputeStableWorkArea();
+  base::Optional<SplitViewController::SnapPosition> opposite_position =
+      base::nullopt;
   switch (state) {
     case SplitViewController::State::kLeftSnapped:
-      return split_view_controller->GetSnappedWindowBoundsInScreen(
+      bounds = split_view_controller->GetSnappedWindowBoundsInScreen(
           SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr);
+      opposite_position = base::make_optional(SplitViewController::RIGHT);
+      break;
     case SplitViewController::State::kRightSnapped:
-      return split_view_controller->GetSnappedWindowBoundsInScreen(
+      bounds = split_view_controller->GetSnappedWindowBoundsInScreen(
           SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr);
-    default:
-      return screen_util::
-          GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(target_root);
+      opposite_position = base::make_optional(SplitViewController::LEFT);
+      break;
+    case SplitViewController::State::kNoSnap:
+      bounds = work_area;
+      break;
+    case SplitViewController::State::kBothSnapped:
+      // When this function is called, SplitViewController should have already
+      // handled the state change.
+      NOTREACHED();
   }
+
+  if (!divider_changed)
+    return bounds;
+
+  DCHECK(opposite_position);
+  const bool horizontal = SplitViewController::IsLayoutHorizontal();
+  const int min_length =
+      (horizontal ? work_area.width() : work_area.height()) / 3;
+  const int current_length = horizontal ? bounds.width() : bounds.height();
+
+  if (current_length > min_length)
+    return bounds;
+
+  // Clamp bounds' length to the minimum length.
+  if (horizontal)
+    bounds.set_width(min_length);
+  else
+    bounds.set_height(min_length);
+
+  if (SplitViewController::IsPhysicalLeftOrTop(*opposite_position)) {
+    // If we are shifting to the left or top we need to update the origin as
+    // well.
+    const int offset = min_length - current_length;
+    bounds.Offset(horizontal ? gfx::Vector2d(-offset, 0)
+                             : gfx::Vector2d(0, -offset));
+  }
+
+  return bounds;
 }
 
 base::Optional<gfx::RectF> GetSplitviewBoundsMaintainingAspectRatio(
@@ -281,8 +328,9 @@
     return base::nullopt;
   }
 
-  return base::make_optional(gfx::RectF(GetGridBoundsInScreenForSplitview(
-      root_window, base::make_optional(window_dragging_state))));
+  return base::make_optional(gfx::RectF(GetGridBoundsInScreen(
+      root_window, base::make_optional(window_dragging_state),
+      /*divider_changed=*/false)));
 }
 
 bool ShouldUseTabletModeGridLayout() {
diff --git a/ash/wm/overview/overview_utils.h b/ash/wm/overview/overview_utils.h
index 562fbda..893f04a 100644
--- a/ash/wm/overview/overview_utils.h
+++ b/ash/wm/overview/overview_utils.h
@@ -83,11 +83,15 @@
 void MaximizeIfSnapped(aura::Window* window);
 
 // Get the grid bounds if a window is snapped in splitview, or what they will be
-// when snapped based on |target_root| and |indicator_state|.
-gfx::Rect GetGridBoundsInScreenForSplitview(
+// when snapped based on |target_root| and |indicator_state|. If
+// |divider_changed| is true, maybe clamp the bounds to a minimum size and shift
+// the bounds offscreen.
+gfx::Rect GetGridBoundsInScreen(aura::Window* target_root);
+gfx::Rect GetGridBoundsInScreen(
     aura::Window* target_root,
     base::Optional<SplitViewDragIndicators::WindowDraggingState>
-        window_dragging_state = base::nullopt);
+        window_dragging_state,
+    bool divider_changed);
 
 // Gets the bounds of a window if it were to be snapped or about to be snapped
 // in splitview. Returns nothing if we are not in tablet mode, or if we aren't
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 36ab0f1..6cd38ff1 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -106,8 +106,7 @@
     OverviewGrid* overview_grid = GetOverviewGrid(dragged_window_);
     if (overview_grid) {
       overview_grid->RemoveDropTarget();
-      const gfx::Rect grid_bounds =
-          GetGridBoundsInScreenForSplitview(dragged_window_, base::nullopt);
+      const gfx::Rect grid_bounds = GetGridBoundsInScreen(dragged_window_);
       overview_grid->SetBoundsAndUpdatePositions(
           grid_bounds, /*ignored_items=*/{}, /*animate=*/true);
     }
diff --git a/build/android/gyp/apkbuilder.py b/build/android/gyp/apkbuilder.py
index 280d5ce..0d9405b 100755
--- a/build/android/gyp/apkbuilder.py
+++ b/build/android/gyp/apkbuilder.py
@@ -89,8 +89,8 @@
       choices=['true', 'True', 'false', 'False'],
       help='Whether to uncompress native shared libraries. Argument must be '
            'a boolean value.')
-  parser.add_argument('--apksigner-path',
-                      help='Path to the apksigner executable.')
+  parser.add_argument(
+      '--apksigner-jar', help='Path to the apksigner executable.')
   parser.add_argument('--zipalign-path',
                       help='Path to the zipalign executable.')
   parser.add_argument('--key-path',
@@ -112,11 +112,12 @@
   options.secondary_native_libs = build_utils.ParseGnList(
       options.secondary_native_libs)
 
-  # --apksigner-path, --zipalign-path, --key-xxx arguments are
+  # --apksigner-jar, --zipalign-path, --key-xxx arguments are
   # required when building an APK, but not a bundle module.
   if options.format == 'apk':
-    required_args = ['apksigner_path', 'zipalign_path', 'key_path',
-                     'key_passwd', 'key_name']
+    required_args = [
+        'apksigner_jar', 'zipalign_path', 'key_path', 'key_passwd', 'key_name'
+    ]
     for required in required_args:
       if not vars(options)[required]:
         raise Exception('Argument --%s is required for APKs.' % (
@@ -383,7 +384,7 @@
                 data=java_resource_jar.read(apk_path))
 
     if options.format == 'apk':
-      finalize_apk.FinalizeApk(options.apksigner_path, options.zipalign_path,
+      finalize_apk.FinalizeApk(options.apksigner_jar, options.zipalign_path,
                                f.name, f.name, options.key_path,
                                options.key_passwd, options.key_name)
 
diff --git a/build/android/gyp/finalize_apk.py b/build/android/gyp/finalize_apk.py
index 2440fe4..b6be025 100644
--- a/build/android/gyp/finalize_apk.py
+++ b/build/android/gyp/finalize_apk.py
@@ -8,6 +8,8 @@
 import subprocess
 import tempfile
 
+from util import build_utils
+
 
 def FinalizeApk(apksigner_path, zipalign_path, unsigned_apk_path,
                 final_apk_path, key_path, key_passwd, key_name):
@@ -19,14 +21,23 @@
         zipalign_path, '-p', '-f', '4',
         unsigned_apk_path, staging_file.name])
     subprocess.check_output([
-        apksigner_path, 'sign',
-        '--in', staging_file.name,
-        '--out', staging_file.name,
-        '--ks', key_path,
-        '--ks-key-alias', key_name,
-        '--ks-pass', 'pass:' + key_passwd,
+        build_utils.JAVA_PATH,
+        '-jar',
+        apksigner_path,
+        'sign',
+        '--in',
+        staging_file.name,
+        '--out',
+        staging_file.name,
+        '--ks',
+        key_path,
+        '--ks-key-alias',
+        key_name,
+        '--ks-pass',
+        'pass:' + key_passwd,
         # Force SHA-1 (makes signing faster; insecure is fine for local builds).
-        '--min-sdk-version', '1',
+        '--min-sdk-version',
+        '1',
     ])
     shutil.move(staging_file.name, final_apk_path)
     staging_file.delete = False
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index ddde6c68..1e30826 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2577,7 +2577,7 @@
 
       script = "//build/android/gyp/apkbuilder.py"
       depfile = "$target_gen_dir/$target_name.d"
-      _apksigner = "$android_sdk_build_tools/apksigner"
+      _apksigner = "$android_sdk_build_tools/lib/apksigner.jar"
       _zipalign = "$android_sdk_build_tools/zipalign"
       data_deps = [
         "//tools/android/md5sum",
@@ -2606,7 +2606,7 @@
         rebase_path(depfile, root_build_dir),
         "--resource-apk=$_rebased_compiled_resources_path",
         "--output-apk=$_rebased_packaged_apk_path",
-        "--apksigner-path",
+        "--apksigner-jar",
         rebase_path(_apksigner, root_build_dir),
         "--zipalign-path",
         rebase_path(_zipalign, root_build_dir),
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 36c61c4..ee91bb80 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8892979714213714096
\ No newline at end of file
+8892911758248661344
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 89c374a..5433cae2 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8892979713590755584
\ No newline at end of file
+8892911759098097872
\ No newline at end of file
diff --git a/chrome/VERSION b/chrome/VERSION
index 99f2afa..493dc084 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=81
 MINOR=0
-BUILD=4009
+BUILD=4010
 PATCH=0
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index 5d2bf16..a0d19511 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -100,6 +100,8 @@
     private StartSurface.StateObserver mStateObserver;
     @OverviewModeState
     private int mOverviewModeState;
+    @OverviewModeState
+    private int mPreviousOverviewModeState;
     private boolean mIsOmniboxFocused;
     @Nullable
     private TabModel mNormalTabModel;
@@ -216,6 +218,7 @@
             };
         }
         mController.addOverviewModeObserver(this);
+        mPreviousOverviewModeState = OverviewModeState.NOT_SHOWN;
         mOverviewModeState = OverviewModeState.NOT_SHOWN;
     }
 
@@ -239,12 +242,35 @@
         return mController.overviewVisible();
     }
 
-    @VisibleForTesting
+    @Override
     public void setOverviewState(@OverviewModeState int state) {
-        if (state == mOverviewModeState) return;
+        if (mPropertyModel == null || state == mOverviewModeState) return;
+
+        // Cache previous state.
+        if (mOverviewModeState != OverviewModeState.NOT_SHOWN) {
+            mPreviousOverviewModeState = mOverviewModeState;
+        }
 
         mOverviewModeState = state;
         setOverviewStateInternal();
+
+        // Immediately transition from SHOWING to SHOWN state if overview is visible but state not
+        // SHOWN.
+        if (mPropertyModel.get(IS_SHOWING_OVERVIEW)
+                && mOverviewModeState != OverviewModeState.NOT_SHOWN) {
+            // Compute SHOWN state.
+            @OverviewModeState
+            int shownState = computeOverviewStateShown();
+
+            // Nothing to do here.
+            if (shownState == mOverviewModeState) return;
+
+            // Cache previous state
+            mPreviousOverviewModeState = mOverviewModeState;
+
+            mOverviewModeState = shownState;
+            setOverviewStateInternal();
+        }
         notifyStateChange();
     }
 
@@ -269,7 +295,8 @@
             setMVTilesVisibility(false);
             setFakeBoxVisibility(false);
             setSecondaryTasksSurfaceVisibility(true);
-        } else if (mOverviewModeState == OverviewModeState.SHOWN_TWO_PANES) {
+
+        } else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
             RecordUserAction.record("StartSurface.TwoPanes");
             String defaultOnUserActionString = mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
                     ? "ExploreSurface"
@@ -287,7 +314,7 @@
                                            .getDimensionPixelSize(R.dimen.ss_bottom_bar_height));
             mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, !mIsIncognito);
 
-        } else if (mOverviewModeState == OverviewModeState.SHOWN_TASKS_ONLY) {
+        } else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY) {
             RecordUserAction.record("StartSurface.TasksOnly");
             setMVTilesVisibility(!mIsIncognito);
             setExploreSurfaceVisibility(false);
@@ -296,7 +323,7 @@
             if (mSecondaryTasksSurfaceController != null) setSecondaryTasksSurfaceVisibility(false);
         }
 
-        if (mOverviewModeState != OverviewModeState.NOT_SHOWN) {
+        if (isShownState(mOverviewModeState)) {
             setIncognitoModeDescriptionVisibility(
                     mIsIncognito && mTabModelSelector.getModel(true).getCount() <= 0);
         }
@@ -327,13 +354,15 @@
     public void showOverview(boolean animate) {
         // TODO(crbug.com/982018): Animate the bottom bar together with the Tab Grid view.
         if (mPropertyModel != null) {
-            int state = computeOverviewStateShown();
 
             // update incognito
             mIsIncognito = mTabModelSelector.isIncognitoSelected();
             mPropertyModel.set(IS_INCOGNITO, mIsIncognito);
 
-            setOverviewState(state);
+            // set OverviewModeState
+            @OverviewModeState
+            int shownState = computeOverviewStateShown();
+            setOverviewState(shownState);
 
             // Make sure FeedSurfaceCoordinator is built before the explore surface is showing by
             // default.
@@ -357,13 +386,15 @@
         if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER
                 // Secondary tasks surface is used as the main surface in incognito mode.
                 && !mIsIncognito) {
-            // TODO: differentiate between "more tabs" and tabswitcher
-            setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
-            return true;
+            // If we reached tabswitcher from HomePage.
+            if (mPreviousOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
+                setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
+                return true;
+            }
         }
 
         if (mPropertyModel != null && mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
-                && mOverviewModeState == OverviewModeState.SHOWN_TWO_PANES) {
+                && mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
             setExploreSurfaceVisibility(false);
             notifyStateChange();
             return true;
@@ -413,10 +444,10 @@
 
     @Override
     public void finishedHiding() {
+        setOverviewState(OverviewModeState.NOT_SHOWN);
         for (StartSurface.OverviewModeObserver observer : mObservers) {
             observer.finishedHiding();
         }
-        setOverviewState(OverviewModeState.NOT_SHOWN);
     }
 
     private void destroyFeedSurfaceCoordinator() {
@@ -454,7 +485,7 @@
 
         mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, isVisible);
 
-        if (mOverviewModeState == OverviewModeState.SHOWN_TWO_PANES) {
+        if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
             // Update the 'BOTTOM_BAR_SELECTED_TAB_POSITION' property to reflect the change. This is
             // needed when clicking back button on the explore surface.
             mPropertyModel.set(BOTTOM_BAR_SELECTED_TAB_POSITION, isVisible ? 1 : 0);
@@ -502,8 +533,8 @@
     private boolean hasFakeSearchBox() {
         // No fake search box on the explore pane in two panes mode.
         if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE
-                || mOverviewModeState == OverviewModeState.SHOWN_TASKS_ONLY
-                || (mOverviewModeState == OverviewModeState.SHOWN_TWO_PANES
+                || mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY
+                || (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
                         && !mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE))) {
             return true;
         }
@@ -516,7 +547,7 @@
         if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER) return true;
 
         // Never show on explore pane.
-        if (mOverviewModeState == OverviewModeState.SHOWN_TWO_PANES
+        if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
                 && mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)) {
             return false;
         }
@@ -569,10 +600,37 @@
     }
 
     @OverviewModeState
+
     private int computeOverviewStateShown() {
-        if (mSurfaceMode == SurfaceMode.SINGLE_PANE) return OverviewModeState.SHOWN_HOMEPAGE;
-        if (mSurfaceMode == SurfaceMode.TWO_PANES) return OverviewModeState.SHOWN_TWO_PANES;
-        if (mSurfaceMode == SurfaceMode.TASKS_ONLY) return OverviewModeState.SHOWN_TASKS_ONLY;
+        if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
+            if (mOverviewModeState == OverviewModeState.SHOWING_PREVIOUS) {
+                assert (isShownState(mPreviousOverviewModeState));
+                return mPreviousOverviewModeState;
+            } else if (mOverviewModeState == OverviewModeState.SHOWING_START) {
+                return OverviewModeState.SHOWN_HOMEPAGE;
+            } else if (mOverviewModeState == OverviewModeState.SHOWING_TABSWITCHER) {
+                return OverviewModeState.SHOWN_TABSWITCHER;
+            } else if (mOverviewModeState == OverviewModeState.SHOWING_HOMEPAGE) {
+                return OverviewModeState.SHOWN_HOMEPAGE;
+            } else {
+                assert (isShownState(mOverviewModeState)
+                        || mOverviewModeState == OverviewModeState.NOT_SHOWN);
+                return mOverviewModeState;
+            }
+        }
+        if (mSurfaceMode == SurfaceMode.TWO_PANES) {
+            return OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES;
+        }
+        if (mSurfaceMode == SurfaceMode.TASKS_ONLY) {
+            return OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY;
+        }
         return OverviewModeState.DISABLED;
     }
+
+    private boolean isShownState(@OverviewModeState int state) {
+        return state == OverviewModeState.SHOWN_HOMEPAGE
+                || state == OverviewModeState.SHOWN_TABSWITCHER
+                || state == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
+                || state == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY;
+    }
 }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 811e49b6..c6b9b657 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -31,6 +31,7 @@
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
 import org.chromium.chrome.browser.flags.FeatureUtilities;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -68,6 +69,12 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/tasksonly"})
     public void testShowAndHideTasksOnlySurface() {
         TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> mActivityTestRule.getActivity()
+                                   .getStartSurface()
+                                   .getController()
+                                   .setOverviewState(OverviewModeState.SHOWING_TABSWITCHER));
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
         assertTrue(mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
 
@@ -89,6 +96,12 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/single"})
     public void testShowAndHideSingleSurface() {
         TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> mActivityTestRule.getActivity()
+                                   .getStartSurface()
+                                   .getController()
+                                   .setOverviewState(OverviewModeState.SHOWING_HOMEPAGE));
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
         assertTrue(mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
 
@@ -136,6 +149,12 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/twopanes"})
     public void testShowAndHideTwoPanesSurface() {
         TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> mActivityTestRule.getActivity()
+                                   .getStartSurface()
+                                   .getController()
+                                   .setOverviewState(OverviewModeState.SHOWING_TABSWITCHER));
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
         assertTrue(mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
 
diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index a737f32..6b5fdb5e 100644
--- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -169,7 +169,8 @@
         mediator.showOverview(false);
         verify(mMainTabGridController).showOverview(eq(false));
         verify(mFakeBoxDelegate).addUrlFocusChangeListener(mUrlFocusChangeListenerCaptor.capture());
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TASKS_ONLY));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(false));
         assertThat(mPropertyModel.get(IS_VOICE_RECOGNITION_BUTTON_VISIBLE), equalTo(true));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -216,7 +217,8 @@
         mediator.showOverview(false);
         verify(mMainTabGridController).showOverview(eq(false));
         verify(mFakeBoxDelegate).addUrlFocusChangeListener(mUrlFocusChangeListenerCaptor.capture());
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TWO_PANES));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(false));
         assertThat(mPropertyModel.get(IS_VOICE_RECOGNITION_BUTTON_VISIBLE), equalTo(true));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -341,6 +343,7 @@
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
 
         doReturn(0).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
         mediator.showOverview(false);
         mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
         verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
@@ -359,6 +362,7 @@
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
 
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
         verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
@@ -386,8 +390,11 @@
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
 
         doReturn(1).when(mNormalTabModel).getCount();
+
         mediator.setSecondaryTasksSurfacePropertyModel(mSecondaryTasksSurfacePropertyModel);
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
+
         verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
 
         mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
@@ -420,6 +427,7 @@
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
         verify(mNormalTabModel, never()).addObserver(mTabModelObserverCaptor.capture());
 
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
 
@@ -439,6 +447,7 @@
 
         verify(mTabModelSelector, never()).addObserver(mTabModelSelectorObserverCaptor.capture());
 
+        mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
 
@@ -458,6 +467,7 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
@@ -508,6 +518,7 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
@@ -566,7 +577,8 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         mediator.showOverview(false);
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TASKS_ONLY));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(true));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -588,6 +600,7 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
@@ -646,7 +659,8 @@
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
 
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TASKS_ONLY));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(false));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -659,7 +673,8 @@
         mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
                 mIncognitoTabModel, mNormalTabModel);
 
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TASKS_ONLY));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(true));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -672,7 +687,8 @@
         mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
                 mNormalTabModel, mIncognitoTabModel);
 
-        assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_TASKS_ONLY));
+        assertThat(mediator.getOverviewState(),
+                equalTo(OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY));
         assertThat(mPropertyModel.get(IS_SHOWING_OVERVIEW), equalTo(true));
         assertThat(mPropertyModel.get(IS_INCOGNITO), equalTo(false));
         assertThat(mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE), equalTo(false));
@@ -695,6 +711,7 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.SHOWN_HOMEPAGE));
@@ -731,6 +748,7 @@
 
         doReturn(30).when(mChromeFullscreenManager).getBottomControlsHeight();
         doReturn(2).when(mNormalTabModel).getCount();
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         assertThat(mPropertyModel.get(BOTTOM_BAR_HEIGHT), equalTo(30));
         assertThat(mSecondaryTasksSurfacePropertyModel.get(BOTTOM_BAR_HEIGHT), equalTo(0));
@@ -820,6 +838,7 @@
         doReturn(true).when(mLocationBarVoiceRecognitionHandler).isVoiceSearchEnabled();
 
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
 
@@ -858,6 +877,7 @@
         doReturn(true).when(mLocationBarVoiceRecognitionHandler).isVoiceSearchEnabled();
 
         StartSurfaceMediator mediator = createStartSurfaceMediator(SurfaceMode.SINGLE_PANE);
+        mediator.setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
         mediator.showOverview(false);
         verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
 
@@ -953,7 +973,7 @@
         assertThat(mediator.getOverviewState(), equalTo(OverviewModeState.NOT_SHOWN));
 
         mediator.setSecondaryTasksSurfacePropertyModel(mSecondaryTasksSurfacePropertyModel);
-
+        mediator.setOverviewState(OverviewModeState.SHOWING_HOMEPAGE);
         mediator.showOverview(false);
         verify(mMainTabGridController).showOverview(eq(false));
         verify(mFakeBoxDelegate).addUrlFocusChangeListener(mUrlFocusChangeListenerCaptor.capture());
diff --git a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
index 3ff3f80..322470ea 100644
--- a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
+++ b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
@@ -100,6 +100,12 @@
         void showOverview(boolean animate);
 
         /**
+         * Sets the state {@link OverviewModeState}.
+         * @param state the {@link OverviewModeState} to show.
+         */
+        void setOverviewState(@OverviewModeState int state);
+
+        /**
          * Called by the TabSwitcherLayout when the system back button is pressed.
          * @return Whether or not the TabSwitcher consumed the event.
          */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 3c85f725..ea59c4eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -692,7 +692,11 @@
                     }
                 }
 
-                toggleOverview();
+                if (isInOverviewMode() && !FeatureUtilities.isGridTabSwitcherEnabled()) {
+                    hideOverview();
+                } else {
+                    showOverview(OverviewModeState.SHOWING_TABSWITCHER);
+                }
             };
             OnClickListener newTabClickHandler = v -> {
                 getTabModelSelector().getModel(false).commitAllTabClosures();
@@ -711,10 +715,20 @@
             };
             OnClickListener bookmarkClickHandler = v -> addOrEditBookmark(getActivityTab());
 
+            Supplier<Boolean> showStartSurfaceSupplier = () -> {
+                if (ReturnToChromeExperimentsUtil.shouldShowStartSurfaceAsTheHomePage()
+                        && !isTablet()) {
+                    assert ReturnToChromeExperimentsUtil.shouldShowStartSurfaceAsTheHomePage();
+                    showOverview(OverviewModeState.SHOWING_HOMEPAGE);
+                    return true;
+                }
+                return false;
+            };
+
             getToolbarManager().initializeWithNative(mTabModelSelectorImpl,
                     getFullscreenManager().getBrowserVisibilityDelegate(), mOverviewModeController,
                     mLayoutManager, tabSwitcherClickHandler, newTabClickHandler,
-                    bookmarkClickHandler, null);
+                    bookmarkClickHandler, null, showStartSurfaceSupplier);
 
             mLayoutManager.setToolbarManager(getToolbarManager());
 
@@ -999,12 +1013,12 @@
                 mStartSurface.getController().enableRecordingFirstMeaningfulPaint(
                         getOnCreateTimestampMs());
             }
-            toggleOverview();
+            showOverview(OverviewModeState.SHOWING_START);
             return;
         }
 
         if (getActivityTab() == null && !isOverviewVisible) {
-            toggleOverview();
+            showOverview(OverviewModeState.SHOWING_START);
         }
     }
 
@@ -1853,7 +1867,7 @@
         // the tab and go back to the start surface.
         if (type == TabLaunchType.FROM_START_SURFACE) {
             getCurrentTabModel().closeTab(currentTab);
-            toggleOverview();
+            showOverview(OverviewModeState.SHOWING_PREVIOUS);
             return true;
         }
 
@@ -2024,29 +2038,37 @@
         }
     }
 
-    private void toggleOverview() {
+    private void showOverview(@OverviewModeState int state) {
+        assert (state == OverviewModeState.SHOWING_TABSWITCHER
+                || state == OverviewModeState.SHOWING_HOMEPAGE
+                || state == OverviewModeState.SHOWING_PREVIOUS
+                || state == OverviewModeState.SHOWING_START);
+        if (mStartSurface != null) mStartSurface.getController().setOverviewState(state);
+
+        if (mOverviewModeController.overviewVisible()) return;
+
         Tab currentTab = getActivityTab();
         // If we don't have a current tab, show the overview mode.
         if (currentTab == null) {
             mOverviewModeController.showOverview(false);
-            return;
-        }
-
-        if (!mOverviewModeController.overviewVisible()) {
+        } else {
             getCompositorViewHolder().hideKeyboard(
                     () -> mOverviewModeController.showOverview(true));
             updateAccessibilityState(false);
             TasksUma.recordTabLaunchType(getCurrentTabModel());
-        } else {
-            Layout activeLayout = mLayoutManager.getActiveLayout();
-            if (activeLayout instanceof StackLayout) {
-                ((StackLayout) activeLayout).commitOutstandingModelState(LayoutManager.time());
-            }
-            if (getCurrentTabModel().getCount() != 0) {
-                // Don't hide overview if current tab stack is empty()
-                mOverviewModeController.hideOverview(true);
-                updateAccessibilityState(true);
-            }
+        }
+    }
+
+    private void hideOverview() {
+        assert (mOverviewModeController.overviewVisible());
+        Layout activeLayout = mLayoutManager.getActiveLayout();
+        if (activeLayout instanceof StackLayout) {
+            ((StackLayout) activeLayout).commitOutstandingModelState(LayoutManager.time());
+        }
+        if (getCurrentTabModel().getCount() != 0) {
+            // Don't hide overview if current tab stack is empty()
+            mOverviewModeController.hideOverview(true);
+            updateAccessibilityState(true);
         }
     }
 
@@ -2216,6 +2238,11 @@
         return getLayoutManager().getOverviewListLayout();
     }
 
+    @VisibleForTesting
+    public StartSurface getStartSurface() {
+        return mStartSurface;
+    }
+
     private ComposedBrowserControlsVisibilityDelegate getAppBrowserControlsVisibilityDelegate() {
         if (mAppBrowserControlsVisibilityDelegate == null) {
             mAppBrowserControlsVisibilityDelegate = new ComposedBrowserControlsVisibilityDelegate();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index e1b1ac4..0a73d06e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -8,7 +8,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.net.Uri;
 import android.provider.Browser;
 import android.text.TextUtils;
@@ -16,7 +15,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.BuildInfo;
-import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordHistogram;
@@ -27,6 +25,8 @@
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.snackbar.Snackbar;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
@@ -44,8 +44,6 @@
  * A class holding static util functions for bookmark.
  */
 public class BookmarkUtils {
-    private static final String PREF_LAST_USED_URL = "enhanced_bookmark_last_used_url";
-    private static final String PREF_LAST_USED_PARENT = "enhanced_bookmark_last_used_parent_folder";
     private static final String TAG = "BookmarkUtils";
 
     /**
@@ -200,8 +198,8 @@
      * {@link #getLastUsedUrl(Context)}
      */
     static void setLastUsedUrl(Context context, String url) {
-        ContextUtils.getAppSharedPreferences().edit()
-                .putString(PREF_LAST_USED_URL, url).apply();
+        SharedPreferencesManager.getInstance().writeString(
+                ChromePreferenceKeys.BOOKMARKS_LAST_USED_URL, url);
     }
 
     /**
@@ -209,16 +207,16 @@
      */
     @VisibleForTesting
     static String getLastUsedUrl(Context context) {
-        return ContextUtils.getAppSharedPreferences().getString(
-                PREF_LAST_USED_URL, UrlConstants.BOOKMARKS_URL);
+        return SharedPreferencesManager.getInstance().readString(
+                ChromePreferenceKeys.BOOKMARKS_LAST_USED_URL, UrlConstants.BOOKMARKS_URL);
     }
 
     /**
      * Save the last used {@link BookmarkId} as a folder to put new bookmarks to.
      */
     static void setLastUsedParent(Context context, BookmarkId bookmarkId) {
-        ContextUtils.getAppSharedPreferences().edit()
-                .putString(PREF_LAST_USED_PARENT, bookmarkId.toString()).apply();
+        SharedPreferencesManager.getInstance().writeString(
+                ChromePreferenceKeys.BOOKMARKS_LAST_USED_PARENT, bookmarkId.toString());
     }
 
     /**
@@ -226,11 +224,11 @@
      *         has never selected a parent folder to use.
      */
     static BookmarkId getLastUsedParent(Context context) {
-        SharedPreferences preferences = ContextUtils.getAppSharedPreferences();
-        if (!preferences.contains(PREF_LAST_USED_PARENT)) return null;
+        SharedPreferencesManager preferences = SharedPreferencesManager.getInstance();
+        if (!preferences.contains(ChromePreferenceKeys.BOOKMARKS_LAST_USED_PARENT)) return null;
 
         return BookmarkId.getBookmarkIdFromString(
-                preferences.getString(PREF_LAST_USED_PARENT, null));
+                preferences.readString(ChromePreferenceKeys.BOOKMARKS_LAST_USED_PARENT, null));
     }
 
     /** Starts an {@link BookmarkEditActivity} for the given {@link BookmarkId}. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/OverviewModeState.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/OverviewModeState.java
index 2284810e..fe352d6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/OverviewModeState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/OverviewModeState.java
@@ -14,13 +14,26 @@
  * OverviewModeBehavior.OverviewModeObserver}.
  */
 @IntDef({OverviewModeState.NOT_SHOWN, OverviewModeState.SHOWN_HOMEPAGE,
-        OverviewModeState.SHOWN_TABSWITCHER, OverviewModeState.DISABLED})
+        OverviewModeState.SHOWN_TABSWITCHER, OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES,
+        OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY, OverviewModeState.DISABLED,
+        OverviewModeState.SHOWING_TABSWITCHER, OverviewModeState.SHOWING_START,
+        OverviewModeState.SHOWING_HOMEPAGE, OverviewModeState.SHOWING_PREVIOUS})
 @Retention(RetentionPolicy.SOURCE)
 public @interface OverviewModeState {
     int NOT_SHOWN = 0;
+
+    // When overview is visible, it will be in one of the SHOWN states.
     int SHOWN_HOMEPAGE = 1;
     int SHOWN_TABSWITCHER = 2;
-    int SHOWN_TWO_PANES = 3;
-    int SHOWN_TASKS_ONLY = 4;
+    int SHOWN_TABSWITCHER_TWO_PANES = 3;
+    int SHOWN_TABSWITCHER_TASKS_ONLY = 4;
+
     int DISABLED = 5;
+
+    // SHOWING states are intermediary states that will immediately transition
+    // to one of the SHOWN states when overview is/becomes visible.
+    int SHOWING_TABSWITCHER = 6;
+    int SHOWING_START = 7;
+    int SHOWING_HOMEPAGE = 8;
+    int SHOWING_PREVIOUS = 9;
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
index 31883c0d..711a0bc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarCoordinator.java
@@ -173,7 +173,7 @@
     private void onCompositorContentInitialized(LayoutManager layoutDriver) {
         mToolbarManager.initializeWithNative(mTabController.getTabModelSelector(),
                 mFullscreenManager.get().getBrowserVisibilityDelegate(), null, layoutDriver, null,
-                null, null, v -> onCloseButtonClick());
+                null, null, v -> onCloseButtonClick(), null);
         mInitializedToolbarWithNative = true;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/flags/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/flags/FeatureUtilities.java
index 12abf76..012624c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/flags/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/flags/FeatureUtilities.java
@@ -403,6 +403,11 @@
     private static void cacheStartSurfaceEnabled() {
         cacheFlag(ChromePreferenceKeys.FLAGS_CACHED_START_SURFACE_ENABLED,
                 ChromeFeatureList.START_SURFACE_ANDROID);
+        String feature = ChromeFeatureList.getFieldTrialParamByFeature(
+                ChromeFeatureList.START_SURFACE_ANDROID, "start_surface_variation");
+        SharedPreferencesManager.getInstance().writeBoolean(
+                ChromePreferenceKeys.START_SURFACE_SINGLE_PANE_ENABLED_KEY,
+                feature.equals("single"));
     }
 
     /**
@@ -421,6 +426,14 @@
         }
     }
 
+    /**
+     * @return Whether the Start Surface SinglePane is enabled.
+     */
+    public static boolean isStartSurfaceSinglePaneEnabled() {
+        return isStartSurfaceEnabled()
+                && isFlagEnabled(ChromePreferenceKeys.START_SURFACE_SINGLE_PANE_ENABLED_KEY, false);
+    }
+
     @VisibleForTesting
     static void cacheGridTabSwitcherEnabled() {
         SharedPreferencesManager sharedPreferencesManager = SharedPreferencesManager.getInstance();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
index db993e17..3e6001a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tasks;
 
 import android.app.Activity;
+import android.text.TextUtils;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -16,6 +17,8 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.FeatureUtilities;
 import org.chromium.chrome.browser.locale.LocaleManager;
+import org.chromium.chrome.browser.ntp.NewTabPage;
+import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -171,4 +174,16 @@
 
         return chromeActivity;
     }
+
+    /**
+     * Check whether we should show Start Surface as the home page.
+     * @return Whether Start Surface should be shown as the home page, otherwise false.
+     */
+    public static boolean shouldShowStartSurfaceAsTheHomePage() {
+        // Note that we should only show StartSurface as the HomePage if Single Pane is enabled and
+        // HomePage is not customized.
+        String homePageUrl = HomepageManager.getHomepageUri();
+        return FeatureUtilities.isStartSurfaceSinglePaneEnabled()
+                && (TextUtils.isEmpty(homePageUrl) || NewTabPage.isNTPUrl(homePageUrl));
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 883e4616..b743cb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -30,6 +30,7 @@
 import org.chromium.base.MathUtils;
 import org.chromium.base.ObservableSupplier;
 import org.chromium.base.ObservableSupplierImpl;
+import org.chromium.base.Supplier;
 import org.chromium.base.metrics.CachedMetrics.ActionEvent;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
@@ -249,6 +250,9 @@
 
     private int mCurrentOrientation;
 
+    /** Runnable for the home button when Start Surface home page is enabled. */
+    private Supplier<Boolean> mShowStartSurfaceSupplier;
+
     /**
      * Creates a ToolbarManager object.
      *
@@ -980,11 +984,14 @@
             BrowserStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate,
             OverviewModeBehavior overviewModeBehavior, LayoutManager layoutManager,
             OnClickListener tabSwitcherClickHandler, OnClickListener newTabClickHandler,
-            OnClickListener bookmarkClickHandler, OnClickListener customTabsBackClickHandler) {
+            OnClickListener bookmarkClickHandler, OnClickListener customTabsBackClickHandler,
+            Supplier<Boolean> showStartSurfaceSupplier) {
         assert !mInitializedWithNative;
 
         mTabModelSelector = tabModelSelector;
 
+        mShowStartSurfaceSupplier = showStartSurfaceSupplier;
+
         OnLongClickListener tabSwitcherLongClickHandler = null;
 
         if (mTabGroupPopupUi != null) {
@@ -1562,6 +1569,9 @@
             RecordUserAction.record("MobileTopToolbarHomeButton");
         }
 
+        if (mShowStartSurfaceSupplier.get()) {
+            return;
+        }
         Tab currentTab = mLocationBarModel.getTab();
         if (currentTab == null) return;
         String homePageUrl = HomepageManager.getHomepageUri();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
index 7b26423..26ce4afc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
@@ -94,11 +95,15 @@
         mTabSwitcherModeCoordinator = new TabSwitcherBottomToolbarCoordinator(mTabSwitcherModeStub,
                 topToolbarRoot, incognitoStateProvider, mThemeColorProvider, newTabClickListener,
                 closeTabsClickListener, menuButtonHelper, tabCountProvider);
-        mOverviewModeBehavior = overviewModeBehavior;
-        mOverviewModeObserver = new BottomToolbarAnimationCoordinator(
-                mBrowsingModeCoordinator, mTabSwitcherModeCoordinator);
-        if (mOverviewModeBehavior != null) {
-            mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+        // Do not change bottom bar if StartSurface Single Pane is enabled and HomePage is not
+        // customized.
+        if (!ReturnToChromeExperimentsUtil.shouldShowStartSurfaceAsTheHomePage()) {
+            mOverviewModeBehavior = overviewModeBehavior;
+            mOverviewModeObserver = new BottomToolbarAnimationCoordinator(
+                    mBrowsingModeCoordinator, mTabSwitcherModeCoordinator);
+            if (mOverviewModeBehavior != null) {
+                mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+            }
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 9427eae..f9134e0b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -1657,7 +1657,8 @@
         if (mHomeButton == null) return;
 
         boolean hideHomeButton = !mIsHomeButtonEnabled || mIsBottomToolbarVisible
-                || FeatureUtilities.isStartSurfaceEnabled();
+                || (FeatureUtilities.isStartSurfaceEnabled()
+                        && !FeatureUtilities.isStartSurfaceSinglePaneEnabled());
         if (hideHomeButton) {
             removeHomeButton();
         } else {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
index 5b9f9815..d4a4060 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninTest.java
@@ -4,8 +4,14 @@
 
 package org.chromium.chrome.browser.signin;
 
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.RootMatchers.isDialog;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.Matchers.sameInstance;
+
 import android.app.Instrumentation.ActivityMonitor;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -28,7 +34,6 @@
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
@@ -37,18 +42,17 @@
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.sync.AccountManagementFragment;
 import org.chromium.chrome.browser.settings.sync.SignInPreference;
-import org.chromium.chrome.browser.sync.ProfileSyncService;
 import org.chromium.chrome.browser.tab.TabImpl;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ActivityUtils;
+import org.chromium.chrome.test.util.BookmarkTestUtil;
 import org.chromium.chrome.test.util.ChromeRestriction;
 import org.chromium.chrome.test.util.browser.signin.SigninTestUtil;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.metrics.SignoutReason;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.content_public.browser.test.util.TestTouchUtils;
 
 /**
  * Test suite for sign in tests.
@@ -150,27 +154,13 @@
      */
     private static class TestBookmarkModelObserver extends BookmarkBridge.BookmarkModelObserver {
         private final Object mLock = new Object();
-        private boolean mIsLoaded;
         private boolean mAdded;
 
-        public TestBookmarkModelObserver(BookmarkBridge bookmarks) {
-            mIsLoaded = bookmarks.isBookmarkModelLoaded();
+        TestBookmarkModelObserver() {
             mAdded = false;
         }
 
-        public void waitForBookmarkModelToLoad() {
-            synchronized (mLock) {
-                while (!mIsLoaded) {
-                    try {
-                        mLock.wait();
-                    } catch (InterruptedException exception) {
-                        // Ignore.
-                    }
-                }
-            }
-        }
-
-        public void waitForBookmarkAdded() {
+        void waitForBookmarkAdded() {
             synchronized (mLock) {
                 while (!mAdded) {
                     try {
@@ -183,14 +173,6 @@
         }
 
         @Override
-        public void bookmarkModelLoaded() {
-            synchronized (mLock) {
-                mIsLoaded = true;
-                mLock.notify();
-            }
-        }
-
-        @Override
         public void bookmarkNodeAdded(BookmarkBridge.BookmarkItem parent, int index) {
             synchronized (mLock) {
                 mAdded = true;
@@ -211,7 +193,6 @@
         }
     }
 
-    private Context mContext;
     private SigninManager mSigninManager;
     private PrefServiceBridge mPrefService;
     private BookmarkBridge mBookmarks;
@@ -224,7 +205,6 @@
         SigninTestUtil.setUpAuthForTest();
 
         mActivityTestRule.startMainActivityOnBlankPage();
-        mContext = InstrumentationRegistry.getTargetContext();
         final TestSignInAllowedObserver signinAllowedObserver = new TestSignInAllowedObserver();
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -241,11 +221,13 @@
             Profile profile =
                     ((TabImpl) mActivityTestRule.getActivity().getActivityTab()).getProfile();
             mBookmarks = new BookmarkBridge(profile);
-
+            mBookmarks.loadFakePartnerBookmarkShimForTesting();
+        });
+        BookmarkTestUtil.waitForBookmarkModelLoaded();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Add a test bookmark, to verify later if sign out cleared the bookmarks.
-            mTestBookmarkModelObserver = new TestBookmarkModelObserver(mBookmarks);
+            mTestBookmarkModelObserver = new TestBookmarkModelObserver();
             mBookmarks.addObserver(mTestBookmarkModelObserver);
-            mTestBookmarkModelObserver.waitForBookmarkModelToLoad();
             Assert.assertEquals(0, mBookmarks.getChildCount(mBookmarks.getMobileFolderId()));
             BookmarkId mTestBookmark = mBookmarks.addBookmark(
                     mBookmarks.getMobileFolderId(), 0, "Test Bookmark", "http://google.com");
@@ -335,22 +317,8 @@
         SigninActivity signinActivity =
                 (SigninActivity) InstrumentationRegistry.getInstrumentation().waitForMonitor(
                         monitor);
-        Button positiveButton = (Button) signinActivity.findViewById(R.id.positive_button);
-        // Press 'sign in'.
-        TestTouchUtils.performClickOnMainSync(
-                InstrumentationRegistry.getInstrumentation(), positiveButton);
+        onView(withId(R.id.positive_button)).perform(click());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // Press 'ok, got it' (the same button is reused).
-        TestTouchUtils.performClickOnMainSync(
-                InstrumentationRegistry.getInstrumentation(), positiveButton);
-
-        // Sync doesn't actually start up until we finish the sync setup. This usually happens
-        // in the resume of the Main activity, but we forcefully do this here.
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        TestThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> ProfileSyncService.get().setFirstSetupComplete(
-                                SyncFirstSetupCompleteSource.BASIC_FLOW));
         settingsActivity.finish();
 
         // Verify that signin succeeded.
@@ -417,10 +385,10 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         DialogFragment fragment = ActivityUtils.waitForFragment(activity, tag);
         AlertDialog dialog = (AlertDialog) fragment.getDialog();
-        Assert.assertTrue(dialog != null);
+        Assert.assertNotNull(dialog);
         Assert.assertTrue(dialog.isShowing());
         Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
         Assert.assertNotNull("Could not find the accept button.", button);
-        TestTouchUtils.performClickOnMainSync(InstrumentationRegistry.getInstrumentation(), button);
+        onView(sameInstance(button)).inRoot(isDialog()).perform(click());
     }
 }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index d95d01a..72673c6 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-81.0.4005.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-81.0.4008.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e632e05..1c12092 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -3720,15 +3720,9 @@
       <message name="IDS_EXTENSION_ALERT_TITLE" desc="Titlebar of the extension notification alert">
         Confirm Changes
       </message>
-      <message name="IDS_EXTENSION_ALERT_ITEM_EXTERNAL" desc="A statement that an external extension has been newly installed. End users have no idea what an 'external' extension is, so we simply call them extensions.">
-        The extension "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" has been added.
-      </message>
       <message name="IDS_EXTENSION_ALERT_ITEM_BLACKLISTED" desc="A statement that an extension has been newly blacklisted. https://support.google.com/chrome/answer/1210215 contains the language on which we're basing the phrasing.">
         The extension "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" was automatically disabled.
       </message>
-      <message name="IDS_APP_ALERT_ITEM_EXTERNAL" desc="A statement that an external packaged app has been newly installed. End users have no idea what an 'external' app is, so we simply call them apps.">
-        The app "<ph name="EXTENSION_NAME">$1<ex>Gmail</ex></ph>" has been added.
-      </message>
       <message name="IDS_APP_ALERT_ITEM_BLACKLISTED" desc="A statement that a packaged app has been newly blacklisted. https://support.google.com/chrome/answer/1210215 contains the language on which we're basing the phrasing.">
         The app "<ph name="EXTENSION_NAME">$1<ex>Gmail</ex></ph>" was automatically removed.
       </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 6a1724b..24265cfd 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2261,18 +2261,12 @@
     {"webxr-ar-module", flag_descriptions::kWebXrArModuleName,
      flag_descriptions::kWebXrArModuleDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kWebXrArModule)},
-    {"webxr-ar-dom-overlay", flag_descriptions::kWebXrArDOMOverlayName,
-     flag_descriptions::kWebXrArDOMOverlayDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(features::kWebXrArDOMOverlay)},
     {"webxr-hit-test", flag_descriptions::kWebXrHitTestName,
      flag_descriptions::kWebXrHitTestDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kWebXrHitTest)},
-    {"webxr-anchors", flag_descriptions::kWebXrAnchorsName,
-     flag_descriptions::kWebXrAnchorsDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kWebXrAnchors)},
-    {"webxr-plane-detection", flag_descriptions::kWebXrPlaneDetectionName,
-     flag_descriptions::kWebXrPlaneDetectionDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kWebXrPlaneDetection)},
+    {"webxr-incubations", flag_descriptions::kWebXrIncubationsName,
+     flag_descriptions::kWebXrIncubationsDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kWebXrIncubations)},
     {"webxr-orientation-sensor-device",
      flag_descriptions::kWebXrOrientationSensorDeviceName,
      flag_descriptions::kWebXrOrientationSensorDeviceDescription, kOsDesktop,
diff --git a/chrome/browser/chromeos/login/reset_browsertest.cc b/chrome/browser/chromeos/login/reset_browsertest.cc
index eae7352..9e7bc9f 100644
--- a/chrome/browser/chromeos/login/reset_browsertest.cc
+++ b/chrome/browser/chromeos/login/reset_browsertest.cc
@@ -449,12 +449,9 @@
 }
 
 // TODO(http://crbug.com/990362): Times out on MSAN buildbots.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_RollbackAvailable DISABLED_RollbackAvailable
-#else
-#define MAYBE_RollbackAvailable RollbackAvailable
-#endif
-IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback, RollbackAvailable) {
+// TODO(http://crbug.com/990362): Flaky failures.
+IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback,
+                       DISABLED_RollbackAvailable) {
   PrefService* prefs = g_browser_process->local_state();
 
   // PRE test triggers start with Reset screen.
@@ -553,13 +550,9 @@
 }
 
 // TODO(http://crbug.com/1025926): Times out on MSAN buildbots.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_RevertAfterCancel DISABLED_RevertAfterCancel
-#else
-#define MAYBE_RevertAfterCancel RevertAfterCancel
-#endif
+// TODO(http://crbug.com/990362): Flaky failures.
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback,
-                       MAYBE_RevertAfterCancel) {
+                       DISABLED_RevertAfterCancel) {
   OobeScreenWaiter(ResetView::kScreenId).Wait();
   EXPECT_TRUE(login_prompt_visible_observer_->signal_emitted());
 
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index b5b4f12..c1e9516f 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -827,7 +827,7 @@
                  : embedded_test_server()->GetURL(
                        content::SlowDownloadHttpResponse::kUnknownSizeUrl));
     GURL finish_url = embedded_test_server()->GetURL(
-        content::SlowDownloadHttpResponse::kFinishDownloadUrl);
+        content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
 
     // TODO(ahendrickson) -- |expected_title_in_progress| and
     // |expected_title_finished| need to be checked.
@@ -876,8 +876,9 @@
       return false;
 
     // Check the file contents.
-    size_t file_size = content::SlowDownloadHttpResponse::kFirstDownloadSize +
-                       content::SlowDownloadHttpResponse::kSecondDownloadSize;
+    size_t file_size =
+        content::SlowDownloadHttpResponse::kFirstResponsePartSize +
+        content::SlowDownloadHttpResponse::kSecondResponsePartSize;
     std::string expected_contents(file_size, '*');
     EXPECT_TRUE(VerifyFile(download_path, expected_contents, file_size));
 
@@ -2077,7 +2078,7 @@
 
   // Finsih the download.
   GURL finish_url = embedded_test_server()->GetURL(
-      content::SlowDownloadHttpResponse::kFinishDownloadUrl);
+      content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
   ui_test_utils::NavigateToURL(browser(), finish_url);
 
   download_observer->WaitForFinished();
@@ -2107,8 +2108,8 @@
   EXPECT_GE(end, row1.end_time);
   EXPECT_EQ(0, row1.received_bytes);  // There's no ETag. So the intermediate
                                       // state is discarded.
-  EXPECT_EQ(content::SlowDownloadHttpResponse::kFirstDownloadSize +
-                content::SlowDownloadHttpResponse::kSecondDownloadSize,
+  EXPECT_EQ(content::SlowDownloadHttpResponse::kFirstResponsePartSize +
+                content::SlowDownloadHttpResponse::kSecondResponsePartSize,
             row1.total_bytes);
   EXPECT_EQ(history::DownloadState::INTERRUPTED, row1.state);
   EXPECT_EQ(history::ToHistoryDownloadInterruptReason(
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 850eeb2a..a0a030c 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -769,9 +769,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, CloseBackgroundPage) {
+  ExtensionTestMessageListener listener("ready", /*will_reply=*/false);
   ASSERT_TRUE(LoadExtension(
       test_data_dir_.AppendASCII("browser_action/close_background")));
   const Extension* extension = GetSingleLoadedExtension();
+  ASSERT_TRUE(listener.WaitUntilSatisfied());
 
   // There is a background page and a browser action with no badge text.
   extensions::ProcessManager* manager =
diff --git a/chrome/browser/extensions/api_binding_perf_browsertest.cc b/chrome/browser/extensions/api_binding_perf_browsertest.cc
index f2ae95c3..d542b49 100644
--- a/chrome/browser/extensions/api_binding_perf_browsertest.cc
+++ b/chrome/browser/extensions/api_binding_perf_browsertest.cc
@@ -47,19 +47,19 @@
   DISALLOW_COPY_AND_ASSIGN(APIBindingPerfBrowserTest);
 };
 
-const char kSimpleContentScriptManifest[] =
-    "{"
-    "  'name': 'Perf test extension',"
-    "  'version': '0',"
-    "  'manifest_version': 2,"
-    "  'content_scripts': [ {"
-    "    'all_frames': true,"
-    "    'matches': [ '<all_urls>' ],"
-    "    'run_at': 'document_end',"
-    "    'js': [ 'content_script.js' ]"
-    "  } ],"
-    "  'permissions': [ 'storage' ]"
-    "}";
+constexpr char kSimpleContentScriptManifest[] =
+    R"({
+         "name": "Perf test extension",
+         "version": "0",
+         "manifest_version": 2,
+         "content_scripts": [ {
+           "all_frames": true,
+           "matches": [ "<all_urls>" ],
+           "run_at": "document_end",
+           "js": [ "content_script.js" ]
+         } ],
+         "permissions": [ "storage" ]
+       })";
 
 IN_PROC_BROWSER_TEST_F(APIBindingPerfBrowserTest,
                        LOCAL_TEST(ManyFramesWithNoContentScript)) {
@@ -74,7 +74,7 @@
 IN_PROC_BROWSER_TEST_F(APIBindingPerfBrowserTest,
                        LOCAL_TEST(ManyFramesWithEmptyContentScript)) {
   TestExtensionDir extension_dir;
-  extension_dir.WriteManifestWithSingleQuotes(kSimpleContentScriptManifest);
+  extension_dir.WriteManifest(kSimpleContentScriptManifest);
   extension_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
                           "// This space intentionally left blank.");
   ASSERT_TRUE(LoadExtension(extension_dir.UnpackedPath()));
@@ -90,7 +90,7 @@
 IN_PROC_BROWSER_TEST_F(APIBindingPerfBrowserTest,
                        LOCAL_TEST(ManyFramesWithStorageAndRuntime)) {
   TestExtensionDir extension_dir;
-  extension_dir.WriteManifestWithSingleQuotes(kSimpleContentScriptManifest);
+  extension_dir.WriteManifest(kSimpleContentScriptManifest);
   extension_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
                           "chrome.storage.onChanged.addListener;"
                           "chrome.runtime.onMessage.addListener;");
diff --git a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
index 4b1a657..a8aef05f 100644
--- a/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
+++ b/chrome/browser/extensions/cross_origin_read_blocking_browsertest.cc
@@ -38,6 +38,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "services/network/cross_origin_read_blocking.h"
+#include "services/network/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -45,6 +46,15 @@
 
 namespace extensions {
 
+namespace {
+
+enum TestParam {
+  kAllowlisted = 1 << 0,
+  kOutOfBlinkCors = 1 << 1,
+};
+
+}  // namespace
+
 using CORBAction = network::CrossOriginReadBlocking::Action;
 
 class CrossOriginReadBlockingExtensionTest : public ExtensionBrowserTest {
@@ -342,21 +352,28 @@
   DISALLOW_COPY_AND_ASSIGN(CrossOriginReadBlockingExtensionTest);
 };
 
-enum class AllowlistingParam {
-  kAllowlisted,
-  kNotAllowlisted,
-};
-
 class CrossOriginReadBlockingExtensionAllowlistingTest
     : public CrossOriginReadBlockingExtensionTest,
-      public ::testing::WithParamInterface<AllowlistingParam> {
+      public ::testing::WithParamInterface<TestParam> {
  public:
   using Base = CrossOriginReadBlockingExtensionTest;
 
-  CrossOriginReadBlockingExtensionAllowlistingTest() {}
+  CrossOriginReadBlockingExtensionAllowlistingTest() {
+    if (IsOutOfBlinkCorsEnabled()) {
+      scoped_feature_list_.InitAndEnableFeature(
+          network::features::kOutOfBlinkCors);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(
+          network::features::kOutOfBlinkCors);
+    }
+  }
 
   bool IsExtensionAllowlisted() {
-    return GetParam() == AllowlistingParam::kAllowlisted;
+    return (GetParam() & TestParam::kAllowlisted) != 0;
+  }
+
+  bool IsOutOfBlinkCorsEnabled() {
+    return (GetParam() & TestParam::kOutOfBlinkCors) != 0;
   }
 
   const Extension* InstallExtension(
@@ -439,6 +456,8 @@
   }
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   DISALLOW_COPY_AND_ASSIGN(CrossOriginReadBlockingExtensionAllowlistingTest);
 };
 
@@ -501,8 +520,8 @@
 }
 
 // Test that verifies the current, baked-in (but not necessarily desirable
-// behavior) where an extension that has permission to inject a content script
-// to any page can also fetch (without CORS!) any cross-origin resource.
+// behavior) where a content script injected by an extension can bypass
+// CORS (and CORB) for any hosts the extension has access to.
 // See also https://crbug.com/846346.
 IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest,
                        FromProgrammaticContentScript_NoSniffXml) {
@@ -531,6 +550,40 @@
                                "nosniff.xml - body\n");
 }
 
+// Test that verifies the current, baked-in (but not necessarily desirable
+// behavior) where a content script injected by an extension can bypass
+// CORS (and CORB) for any hosts the extension has access to.
+// See also https://crbug.com/1034408 and https://crbug.com/846346.
+IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest,
+                       FromProgrammaticContentScript_RedirectToNoSniffXml) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(InstallExtension());
+
+  // Navigate to a fetch-initiator.com page.
+  GURL page_url = GetTestPageUrl("fetch-initiator.com");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_EQ(page_url,
+            active_web_contents()->GetMainFrame()->GetLastCommittedURL());
+  ASSERT_EQ(url::Origin::Create(page_url),
+            active_web_contents()->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Inject a content script that performs a cross-origin fetch to
+  // cross-site.com.
+  base::HistogramTester histograms;
+  GURL cross_site_resource(
+      embedded_test_server()->GetURL("cross-site.com", "/nosniff.xml"));
+  GURL redirecting_url(embedded_test_server()->GetURL(
+      "other-with-permission.com",
+      std::string("/server-redirect?") + cross_site_resource.spec()));
+  std::string fetch_result =
+      FetchViaContentScript(redirecting_url, active_web_contents());
+
+  // Verify whether the fetch worked or not (expectations differ depending on
+  // various factors - see the body of VerifyFetchFromContentScript).
+  VerifyFetchFromContentScript(histograms, fetch_result,
+                               "nosniff.xml - body\n");
+}
+
 // Test that verifies CORS-allowed fetches work for targets that are not
 // covered by the extension permissions.
 IN_PROC_BROWSER_TEST_P(CrossOriginReadBlockingExtensionAllowlistingTest,
@@ -1368,11 +1421,18 @@
   EXPECT_EQ("cors-allowed-body", fetch_result);
 }
 
-INSTANTIATE_TEST_SUITE_P(Allowlisted,
+INSTANTIATE_TEST_SUITE_P(Allowlisted_OorCors,
                          CrossOriginReadBlockingExtensionAllowlistingTest,
-                         ::testing::Values(AllowlistingParam::kAllowlisted));
-INSTANTIATE_TEST_SUITE_P(NotAllowlisted,
+                         ::testing::Values(TestParam::kAllowlisted |
+                                           TestParam::kOutOfBlinkCors));
+INSTANTIATE_TEST_SUITE_P(NotAllowlisted_OorCors,
                          CrossOriginReadBlockingExtensionAllowlistingTest,
-                         ::testing::Values(AllowlistingParam::kNotAllowlisted));
+                         ::testing::Values(TestParam::kOutOfBlinkCors));
+INSTANTIATE_TEST_SUITE_P(Allowlisted_InBlinkCors,
+                         CrossOriginReadBlockingExtensionAllowlistingTest,
+                         ::testing::Values(TestParam::kAllowlisted));
+INSTANTIATE_TEST_SUITE_P(NotAllowlisted_InBlinkCors,
+                         CrossOriginReadBlockingExtensionAllowlistingTest,
+                         ::testing::Values(0));
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_error_controller.cc b/chrome/browser/extensions/extension_error_controller.cc
index e080397..f676b80 100644
--- a/chrome/browser/extensions/extension_error_controller.cc
+++ b/chrome/browser/extensions/extension_error_controller.cc
@@ -62,10 +62,6 @@
   return browser_context_;
 }
 
-const ExtensionSet& ExtensionErrorController::GetExternalExtensions() {
-  return external_extensions_;
-}
-
 const ExtensionSet& ExtensionErrorController::GetBlacklistedExtensions() {
   return blacklisted_extensions_;
 }
diff --git a/chrome/browser/extensions/extension_error_controller.h b/chrome/browser/extensions/extension_error_controller.h
index 356f6652..53e3af4 100644
--- a/chrome/browser/extensions/extension_error_controller.h
+++ b/chrome/browser/extensions/extension_error_controller.h
@@ -35,7 +35,6 @@
  private:
   // ExtensionErrorUI::Delegate implementation:
   content::BrowserContext* GetContext() override;
-  const ExtensionSet& GetExternalExtensions() override;
   const ExtensionSet& GetBlacklistedExtensions() override;
   void OnAlertDetails() override;
   void OnAlertAccept() override;
@@ -45,10 +44,6 @@
   // extensions).
   void IdentifyAlertableExtensions();
 
-  // TODO(rdevlin.cronin): We never seem to use |external_extensions_| here,
-  // but we do warn about them. Investigate more.
-  ExtensionSet external_extensions_;
-
   // The extensions that are blacklisted and need user approval.
   ExtensionSet blacklisted_extensions_;
 
diff --git a/chrome/browser/extensions/extension_error_ui.h b/chrome/browser/extensions/extension_error_ui.h
index 1ade18ed..e9872d9 100644
--- a/chrome/browser/extensions/extension_error_ui.h
+++ b/chrome/browser/extensions/extension_error_ui.h
@@ -27,9 +27,6 @@
     // Get the BrowserContext associated with this UI.
     virtual content::BrowserContext* GetContext() = 0;
 
-    // Get the set of external extensions to warn the user about.
-    virtual const ExtensionSet& GetExternalExtensions() = 0;
-
     // Get the set of blacklisted extensions to warn the user about.
     virtual const ExtensionSet& GetBlacklistedExtensions() = 0;
 
diff --git a/chrome/browser/extensions/extension_error_ui_default.cc b/chrome/browser/extensions/extension_error_ui_default.cc
index 984955ca..14ed11c 100644
--- a/chrome/browser/extensions/extension_error_ui_default.cc
+++ b/chrome/browser/extensions/extension_error_ui_default.cc
@@ -6,38 +6,29 @@
 
 #include "base/logging.h"
 #include "base/strings/string_util.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/global_error/global_error_bubble_view_base.h"
 #include "chrome/grit/generated_resources.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace extensions {
 
 namespace {
 
-std::vector<base::string16> GenerateMessage(const ExtensionSet& external,
-                                            const ExtensionSet& forbidden) {
-  std::vector<base::string16> message(external.size() + forbidden.size());
-  auto it = std::transform(
-      external.begin(), external.end(), message.begin(),
-      [](const auto& extension) {
-        int id = extension->is_app() ? IDS_APP_ALERT_ITEM_EXTERNAL
-                                     : IDS_EXTENSION_ALERT_ITEM_EXTERNAL;
-        base::string16 name = base::UTF8ToUTF16(extension->name());
-        return l10n_util::GetStringFUTF16(id, name);
-      });
-  std::transform(forbidden.begin(), forbidden.end(), it,
-                 [](const auto& extension) {
-                   int id = extension->is_app()
-                                ? IDS_APP_ALERT_ITEM_BLACKLISTED
-                                : IDS_EXTENSION_ALERT_ITEM_BLACKLISTED;
-                   base::string16 name = base::UTF8ToUTF16(extension->name());
-                   return l10n_util::GetStringFUTF16(id, name);
-                 });
+std::vector<base::string16> GenerateMessage(const ExtensionSet& forbidden) {
+  std::vector<base::string16> message;
+  message.reserve(forbidden.size());
+  for (const auto& extension : forbidden) {
+    int id = extension->is_app() ? IDS_APP_ALERT_ITEM_BLACKLISTED
+                                 : IDS_EXTENSION_ALERT_ITEM_BLACKLISTED;
+    message.push_back(
+        l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension->name())));
+  }
   return message;
 }
 
@@ -69,8 +60,7 @@
   }
 
   std::vector<base::string16> GetBubbleViewMessages() override {
-    return GenerateMessage(delegate_->GetExternalExtensions(),
-                           delegate_->GetBlacklistedExtensions());
+    return GenerateMessage(delegate_->GetBlacklistedExtensions());
   }
 
   base::string16 GetBubbleViewAcceptButtonLabel() override {
diff --git a/chrome/browser/extensions/extension_error_ui_default.h b/chrome/browser/extensions/extension_error_ui_default.h
index b790948..b7abcf4 100644
--- a/chrome/browser/extensions/extension_error_ui_default.h
+++ b/chrome/browser/extensions/extension_error_ui_default.h
@@ -5,11 +5,11 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_ERROR_UI_DEFAULT_H_
 #define CHROME_BROWSER_EXTENSIONS_EXTENSION_ERROR_UI_DEFAULT_H_
 
-#include "base/compiler_specific.h"
+#include <memory>
+
 #include "base/macros.h"
 #include "chrome/browser/extensions/extension_error_ui.h"
 #include "chrome/browser/ui/global_error/global_error.h"
-#include "extensions/common/extension.h"
 
 class Browser;
 class Profile;
diff --git a/chrome/browser/extensions/extension_error_ui_default_unittest.cc b/chrome/browser/extensions/extension_error_ui_default_unittest.cc
index 2494f90..5c53b3b4 100644
--- a/chrome/browser/extensions/extension_error_ui_default_unittest.cc
+++ b/chrome/browser/extensions/extension_error_ui_default_unittest.cc
@@ -17,9 +17,6 @@
  public:
   // extensions::ExtensionErrorUI::Delegate:
   content::BrowserContext* GetContext() override { return &profile_; }
-  const extensions::ExtensionSet& GetExternalExtensions() override {
-    return external_;
-  }
   const extensions::ExtensionSet& GetBlacklistedExtensions() override {
     return forbidden_;
   }
@@ -27,10 +24,6 @@
   void OnAlertAccept() override {}
   void OnAlertClosed() override {}
 
-  void InsertExternal(scoped_refptr<const extensions::Extension> ext) {
-    external_.Insert(ext);
-  }
-
   void InsertForbidden(scoped_refptr<const extensions::Extension> ext) {
     forbidden_.Insert(ext);
   }
@@ -38,7 +31,6 @@
  private:
   content::BrowserTaskEnvironment environment_;
   TestingProfile profile_;
-  extensions::ExtensionSet external_;
   extensions::ExtensionSet forbidden_;
 };
 
@@ -52,7 +44,6 @@
 TEST(ExtensionErrorUIDefaultTest, BubbleMessageMentionsExtension) {
   TestErrorUIDelegate delegate;
 
-  delegate.InsertExternal(extensions::ExtensionBuilder("Foo").Build());
   delegate.InsertForbidden(extensions::ExtensionBuilder("Bar").Build());
   delegate.InsertForbidden(extensions::ExtensionBuilder("Baz").Build());
 
@@ -61,8 +52,7 @@
 
   std::vector<base::string16> messages = bubble->GetBubbleViewMessages();
 
-  ASSERT_EQ(3U, messages.size());
-  EXPECT_TRUE(ContainsString(messages[0], "\"Foo\""));
-  EXPECT_TRUE(ContainsString(messages[1], "\"Bar\""));
-  EXPECT_TRUE(ContainsString(messages[2], "\"Baz\""));
+  ASSERT_EQ(2u, messages.size());
+  EXPECT_TRUE(ContainsString(messages[0], "\"Bar\""));
+  EXPECT_TRUE(ContainsString(messages[1], "\"Baz\""));
 }
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index c8055b18..697a0fa 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -24,6 +24,7 @@
 #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"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/notification_service.h"
@@ -355,6 +356,41 @@
   SendKeyPressToAction(browser(), *extension, ui::VKEY_F, expect_dispatch);
 }
 
+// Tests that a page action that is overflowed will still properly trigger when
+// the keybinding is used.
+IN_PROC_BROWSER_TEST_F(CommandsApiTest, OverflowedPageActionTriggers) {
+  base::AutoReset<bool> disable_toolbar_animations(
+      &ToolbarActionsBar::disable_animations_for_testing_, true);
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(RunExtensionTest("keybinding/page_action")) << message_;
+  const Extension* extension = GetSingleLoadedExtension();
+  ASSERT_TRUE(extension) << message_;
+
+  // With the old toolbar, we need to explicitly overflow the extension.
+  if (!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu)) {
+    ToolbarActionsModel* toolbar_actions_model =
+        ToolbarActionsModel::Get(profile());
+    toolbar_actions_model->SetVisibleIconCount(0);
+  }
+  std::unique_ptr<BrowserActionTestUtil> test_helper =
+      BrowserActionTestUtil::Create(browser());
+  EXPECT_EQ(0, test_helper->VisibleBrowserActions());
+
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL("/extensions/test_file.txt"));
+  const int tab_id = SessionTabHelper::FromWebContents(
+                         browser()->tab_strip_model()->GetActiveWebContents())
+                         ->session_id()
+                         .id();
+  SetActionVisibleOnTab(profile(), *extension, tab_id);
+
+  ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1));
+
+  constexpr bool kExpectDispatch = true;
+  SendKeyPressToAction(browser(), *extension, ui::VKEY_F, kExpectDispatch);
+}
+
 IN_PROC_BROWSER_TEST_F(CommandsApiTest, PageActionKeyUpdated) {
   ASSERT_TRUE(embedded_test_server()->Start());
   ASSERT_TRUE(RunExtensionTest("keybinding/page_action")) << message_;
diff --git a/chrome/browser/extensions/extension_loading_browsertest.cc b/chrome/browser/extensions/extension_loading_browsertest.cc
index f8b5967..fc01c37 100644
--- a/chrome/browser/extensions/extension_loading_browsertest.cc
+++ b/chrome/browser/extensions/extension_loading_browsertest.cc
@@ -44,22 +44,21 @@
   ASSERT_TRUE(embedded_test_server()->Start());
 
   TestExtensionDir extension_dir;
-  const char kManifestTemplate[] =
-      "{"
-      "  'name': 'Overrides New Tab',"
-      "  'version': '%d',"
-      "  'description': 'Overrides New Tab',"
-      "  'manifest_version': 2,"
-      "  'background': {"
-      "    'persistent': false,"
-      "    'scripts': ['event.js']"
-      "  },"
-      "  'chrome_url_overrides': {"
-      "    'newtab': 'newtab.html'"
-      "  }"
-      "}";
-  extension_dir.WriteManifestWithSingleQuotes(
-      base::StringPrintf(kManifestTemplate, 1));
+  constexpr char kManifestTemplate[] =
+      R"({
+           "name": "Overrides New Tab",
+           "version": "%d",
+           "description": "Overrides New Tab",
+           "manifest_version": 2,
+           "background": {
+             "persistent": false,
+             "scripts": ["event.js"]
+           },
+           "chrome_url_overrides": {
+             "newtab": "newtab.html"
+           }
+         })";
+  extension_dir.WriteManifest(base::StringPrintf(kManifestTemplate, 1));
   extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), "");
   extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"),
                           "<h1>Overridden New Tab Page</h1>");
@@ -81,8 +80,7 @@
                      test_link_from_NTP);
 
   // Increase the extension's version.
-  extension_dir.WriteManifestWithSingleQuotes(
-      base::StringPrintf(kManifestTemplate, 2));
+  extension_dir.WriteManifest(base::StringPrintf(kManifestTemplate, 2));
 
   // Upgrade the extension.
   new_tab_extension = UpdateExtension(
@@ -107,16 +105,15 @@
   ASSERT_TRUE(embedded_test_server()->Start());
 
   TestExtensionDir extension_dir;
-  const char kManifestTemplate[] =
-      "{"
-      "  'name': 'Overrides New Tab',"
-      "  'version': '%d',"
-      "  'description': 'Will override New Tab soon',"
-      "  %s"  // Placeholder for future NTP url override block.
-      "  'manifest_version': 2"
-      "}";
-  extension_dir.WriteManifestWithSingleQuotes(
-      base::StringPrintf(kManifestTemplate, 1, ""));
+  constexpr char kManifestTemplate[] =
+      R"({
+           "name": "Overrides New Tab",
+           "version": "%d",
+           "description": "Will override New Tab soon",
+           %s  // Placeholder for future NTP url override block.
+           "manifest_version": 2
+         })";
+  extension_dir.WriteManifest(base::StringPrintf(kManifestTemplate, 1, ""));
   extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), "");
   extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"),
                           "<h1>Overridden New Tab Page</h1>");
@@ -139,12 +136,12 @@
 
   // Increase the extension's version and add the NTP url override which will
   // add the kNewTabPageOverride permission.
-  const char ntp_override_string[] =
-      "  'chrome_url_overrides': {"
-      "    'newtab': 'newtab.html'"
-      "  },";
-  extension_dir.WriteManifestWithSingleQuotes(
-      base::StringPrintf(kManifestTemplate, 2, ntp_override_string));
+  constexpr char kNtpOverrideString[] =
+      R"("chrome_url_overrides": {
+            "newtab": "newtab.html"
+         },)";
+  extension_dir.WriteManifest(
+      base::StringPrintf(kManifestTemplate, 2, kNtpOverrideString));
 
   // Upgrade the extension, ensure that the upgrade 'worked' in the sense that
   // the extension is still present and not disabled and that it now has the
@@ -168,17 +165,17 @@
 
   TestExtensionDir extension_dir;
   const char manifest_contents[] =
-      "{"
-      "  'name': 'Test With Lazy Background Page',"
-      "  'version': '0',"
-      "  'manifest_version': 2,"
-      "  'app': {"
-      "    'background': {"
-      "       'scripts': ['event.js']"
-      "    }"
-      "  }"
-      "}";
-  extension_dir.WriteManifestWithSingleQuotes(manifest_contents);
+      R"({
+           "name": "Test With Lazy Background Page",
+           "version": "0",
+           "manifest_version": 2,
+           "app": {
+             "background": {
+                "scripts": ["event.js"]
+             }
+           }
+         })";
+  extension_dir.WriteManifest(manifest_contents);
   extension_dir.WriteFile(FILE_PATH_LITERAL("event.js"), "");
 
   const Extension* extension =
@@ -235,41 +232,41 @@
   TestExtensionDir devtools_dir;
   TestExtensionDir inspect_dir;
 
-  const char kDevtoolsManifest[] =
-      "{"
-      "  'name': 'Devtools',"
-      "  'version': '1',"
-      "  'manifest_version': 2,"
-      "  'devtools_page': 'devtools.html'"
-      "}";
+  constexpr char kDevtoolsManifest[] =
+      R"({
+           "name": "Devtools",
+           "version": "1",
+           "manifest_version": 2,
+           "devtools_page": "devtools.html"
+         })";
 
-  const char kDevtoolsJs[] =
-      "setInterval(function() {"
-      "  chrome.devtools.inspectedWindow.eval('1', function() {"
-      "  });"
-      "}, 4);"
-      "chrome.test.sendMessage('devtools_page_ready');";
+  constexpr char kDevtoolsJs[] =
+      R"(setInterval(function() {
+           chrome.devtools.inspectedWindow.eval('1', function() {
+           });
+         }, 4);
+         chrome.test.sendMessage('devtools_page_ready');)";
 
-  const char kTargetManifest[] =
-      "{"
-      "  'name': 'Inspect target',"
-      "  'version': '1',"
-      "  'manifest_version': 2,"
-      "  'background': {"
-      "    'scripts': ['background.js']"
-      "  }"
-      "}";
+  constexpr char kTargetManifest[] =
+      R"({
+           "name": "Inspect target",
+           "version": "1",
+           "manifest_version": 2,
+           "background": {
+             "scripts": ["background.js"]
+           }
+         })";
 
   // A script to duck-type whether it runs in a background page.
   const char kTargetJs[] =
       "var is_valid = !!(chrome.tabs && chrome.tabs.create);";
 
-  devtools_dir.WriteManifestWithSingleQuotes(kDevtoolsManifest);
+  devtools_dir.WriteManifest(kDevtoolsManifest);
   devtools_dir.WriteFile(FILE_PATH_LITERAL("devtools.js"), kDevtoolsJs);
   devtools_dir.WriteFile(FILE_PATH_LITERAL("devtools.html"),
                          "<script src='devtools.js'></script>");
 
-  inspect_dir.WriteManifestWithSingleQuotes(kTargetManifest);
+  inspect_dir.WriteManifest(kTargetManifest);
   inspect_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kTargetJs);
   const Extension* devtools_ext = LoadExtension(devtools_dir.UnpackedPath());
   ASSERT_TRUE(devtools_ext);
diff --git a/chrome/browser/extensions/fetch_apitest.cc b/chrome/browser/extensions/fetch_apitest.cc
index 2196c55..f76f99b 100644
--- a/chrome/browser/extensions/fetch_apitest.cc
+++ b/chrome/browser/extensions/fetch_apitest.cc
@@ -116,13 +116,14 @@
 
 IN_PROC_BROWSER_TEST_F(ExtensionFetchTest, ExtensionCanFetchExtensionResource) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'ExtensionCanFetchExtensionResource',"
-      "'version': '1'"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "ExtensionCanFetchExtensionResource",
+           "version": "1"
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -135,14 +136,15 @@
 IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
                        ExtensionCanFetchHostedResourceWithHostPermissions) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'ExtensionCanFetchHostedResourceWithHostPermissions',"
-      "'permissions': ['http://example.com/*'],"
-      "'version': '1'"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "ExtensionCanFetchHostedResourceWithHostPermissions",
+           "permissions": ["http://example.com/*"],
+           "version": "1"
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -156,13 +158,14 @@
     ExtensionFetchTest,
     ExtensionCannotFetchHostedResourceWithoutHostPermissions) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'ExtensionCannotFetchHostedResourceWithoutHostPermissions',"
-      "'version': '1'"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "ExtensionCannotFetchHostedResourceWithoutHostPermissions",
+           "version": "1"
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -178,14 +181,15 @@
 IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
                        HostCanFetchWebAccessibleExtensionResource) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'HostCanFetchWebAccessibleExtensionResource',"
-      "'version': '1',"
-      "'web_accessible_resources': ['text']"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "HostCanFetchWebAccessibleExtensionResource",
+           "version": "1",
+           "web_accessible_resources": ["text"]
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -209,15 +213,15 @@
     ExtensionFetchTest,
     HostCanFetchWebAccessibleExtensionResource_FetchFromServiceWorker) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'HostCanFetchWebAccessibleExtensionResource_"
-      "FetchFromServiceWorker',"
-      "'version': '1',"
-      "'web_accessible_resources': ['text']"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "FetchFromServiceWorker",
+           "version": "1",
+           "web_accessible_resources": ["text"]
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -235,13 +239,14 @@
 IN_PROC_BROWSER_TEST_F(ExtensionFetchTest,
                        HostCannotFetchNonWebAccessibleExtensionResource) {
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'HostCannotFetchNonWebAccessibleExtensionResource',"
-      "'version': '1'"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "HostCannotFetchNonWebAccessibleExtensionResource",
+           "version": "1"
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
@@ -267,14 +272,15 @@
       GetQuotedTestServerURL("example.com", "/extensions/test_file.txt")
           .data());
   TestExtensionDir dir;
-  dir.WriteManifestWithSingleQuotes(
-      "{"
-      "'background': {'scripts': ['bg.js']},"
-      "'manifest_version': 2,"
-      "'name': 'FetchResponseType',"
-      "'permissions': ['http://example.com/*'],"
-      "'version': '1'"
-      "}");
+  constexpr char kManifest[] =
+      R"({
+           "background": {"scripts": ["bg.js"]},
+           "manifest_version": 2,
+           "name": "FetchResponseType",
+           "permissions": ["http://example.com/*"],
+           "version": "1"
+         })";
+  dir.WriteManifest(kManifest);
   const Extension* extension = WriteFilesAndLoadTestExtension(&dir);
   ASSERT_TRUE(extension);
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 800c6baa9..2bea577e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3702,16 +3702,6 @@
     "expiry_milestone": 82
   },
   {
-    "name": "webxr-anchors",
-    "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
-    "expiry_milestone": 82
-  },
-  {
-    "name": "webxr-ar-dom-overlay",
-    "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
-    "expiry_milestone": 81
-  },
-  {
     "name": "webxr-ar-module",
     "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
     "expiry_milestone": 82
@@ -3722,13 +3712,13 @@
     "expiry_milestone": 82
   },
   {
-    "name": "webxr-orientation-sensor-device",
-    "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
+    "name": "webxr-incubations",
+    "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
     "expiry_milestone": 82
   },
   {
-    "name": "webxr-plane-detection",
-    "owners": [ "//third_party/blink/renderer/modules/xr/OWNERS", "xr-dev@chromium.org" ],
+    "name": "webxr-orientation-sensor-device",
+    "owners": [ "//device/vr/OWNERS", "xr-dev@chromium.org" ],
     "expiry_milestone": 82
   },
   {
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 66f48fc1..1fb54cc7 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2345,21 +2345,13 @@
     "Enables access to Augmented Reality features defined in the WebXR AR "
     "Module";
 
-const char kWebXrArDOMOverlayName[] = "WebXR AR DOM Overlay";
-const char kWebXrArDOMOverlayDescription[] =
-    "Enables experimental use of a DOM overlay in WebXR AR sessions";
-
-const char kWebXrAnchorsName[] = "WebXR Anchors";
-const char kWebXrAnchorsDescription[] =
-    "Enables access to anchors via WebXR API.";
-
 const char kWebXrHitTestName[] = "WebXR Hit Test";
 const char kWebXrHitTestDescription[] =
     "Enables access to raycasting against estimated XR scene geometry.";
 
-const char kWebXrPlaneDetectionName[] = "WebXR Plane Detection";
-const char kWebXrPlaneDetectionDescription[] =
-    "Enables access to planes detected in the user's environment.";
+const char kWebXrIncubationsName[] = "WebXR Incubations";
+const char kWebXrIncubationsDescription[] =
+    "Enables experimental features for WebXR.";
 
 const char kZeroCopyName[] = "Zero-copy rasterizer";
 const char kZeroCopyDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 29d5632..416c70c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1379,17 +1379,11 @@
 extern const char kWebXrArModuleName[];
 extern const char kWebXrArModuleDescription[];
 
-extern const char kWebXrArDOMOverlayName[];
-extern const char kWebXrArDOMOverlayDescription[];
-
 extern const char kWebXrHitTestName[];
 extern const char kWebXrHitTestDescription[];
 
-extern const char kWebXrAnchorsName[];
-extern const char kWebXrAnchorsDescription[];
-
-extern const char kWebXrPlaneDetectionName[];
-extern const char kWebXrPlaneDetectionDescription[];
+extern const char kWebXrIncubationsName[];
+extern const char kWebXrIncubationsDescription[];
 
 extern const char kZeroCopyName[];
 extern const char kZeroCopyDescription[];
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
index cbeaee6..6e0c19d 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
@@ -467,14 +467,14 @@
   // Create an barebones extension with a background page for the given name.
   const Extension* CreateExtension(const std::string& name) {
     auto dir = std::make_unique<TestExtensionDir>();
-    dir->WriteManifestWithSingleQuotes(
-        base::StringPrintf("{"
-                           "'name': '%s',"
-                           "'version': '1',"
-                           "'manifest_version': 2,"
-                           "'background': {'page': 'bg.html'}"
-                           "}",
-                           name.c_str()));
+    constexpr char kManifestTemplate[] =
+        R"({
+             "name": "%s",
+             "version": "1",
+             "manifest_version": 2,
+             "background": {"page": "bg.html"}
+           })";
+    dir->WriteManifest(base::StringPrintf(kManifestTemplate, name.c_str()));
     dir->WriteFile(FILE_PATH_LITERAL("bg.html"), "");
 
     const Extension* extension = LoadExtension(dir->UnpackedPath());
@@ -485,15 +485,17 @@
 
   const Extension* CreateHostedApp(const std::string& name,
                                    const GURL& app_url) {
-    std::unique_ptr<TestExtensionDir> dir(new TestExtensionDir);
-    dir->WriteManifestWithSingleQuotes(base::StringPrintf(
-        "{"
-        "'name': '%s',"
-        "'version': '1',"
-        "'manifest_version': 2,"
-        "'app': {'urls': ['%s'], 'launch': {'web_url': '%s'}}"
-        "}",
-        name.c_str(), app_url.spec().c_str(), app_url.spec().c_str()));
+    auto dir = std::make_unique<TestExtensionDir>();
+    constexpr char kManifestTemplate[] =
+        R"({
+             "name": "%s",
+             "version": "1",
+             "manifest_version": 2,
+             "app": {"urls": ["%s"], "launch": {"web_url": "%s"}}
+           })";
+    dir->WriteManifest(base::StringPrintf(kManifestTemplate, name.c_str(),
+                                          app_url.spec().c_str(),
+                                          app_url.spec().c_str()));
 
     const Extension* extension = LoadExtension(dir->UnpackedPath());
     EXPECT_TRUE(extension);
diff --git a/chrome/browser/optimization_guide/optimization_guide_navigation_data.cc b/chrome/browser/optimization_guide/optimization_guide_navigation_data.cc
index acbec3d..756a80c5 100644
--- a/chrome/browser/optimization_guide/optimization_guide_navigation_data.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_navigation_data.cc
@@ -58,7 +58,8 @@
       was_host_covered_by_fetch_at_commit_(
           other.was_host_covered_by_fetch_at_commit_),
       was_hint_for_host_attempted_to_be_fetched_(
-          other.was_hint_for_host_attempted_to_be_fetched_) {
+          other.was_hint_for_host_attempted_to_be_fetched_),
+      is_same_origin_navigation_(other.is_same_origin_navigation_) {
   if (other.has_page_hint_value()) {
     page_hint_ = std::make_unique<optimization_guide::proto::PageHint>(
         *other.page_hint());
diff --git a/chrome/browser/optimization_guide/optimization_guide_navigation_data.h b/chrome/browser/optimization_guide/optimization_guide_navigation_data.h
index 1560845..352d93b 100644
--- a/chrome/browser/optimization_guide/optimization_guide_navigation_data.h
+++ b/chrome/browser/optimization_guide/optimization_guide_navigation_data.h
@@ -139,6 +139,12 @@
         was_hint_for_host_attempted_to_be_fetched;
   }
 
+  // Whether the initiation of the navigation was from a same origin URL or not.
+  bool is_same_origin_navigation() const { return is_same_origin_navigation_; }
+  void set_is_same_origin_navigation(bool is_same_origin_navigation) {
+    is_same_origin_navigation_ = is_same_origin_navigation;
+  }
+
  private:
   // Records the hint cache and fetch coverage based on data currently held in
   // |this|.
@@ -210,6 +216,9 @@
   // during the navigation.
   base::Optional<bool> was_hint_for_host_attempted_to_be_fetched_;
 
+  // Whether the initiation of the navigation was from a same origin URL or not.
+  bool is_same_origin_navigation_ = false;
+
   DISALLOW_ASSIGN(OptimizationGuideNavigationData);
 };
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
index 7ad063c..57487b71 100644
--- a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
@@ -85,6 +85,12 @@
   if (!navigation_handle->IsInMainFrame())
     return;
 
+  content::WebContents* web_contents = navigation_handle->GetWebContents();
+  bool is_same_origin =
+      web_contents &&
+      web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS() &&
+      url::IsSameOriginWith(navigation_handle->GetURL(),
+                            web_contents->GetLastCommittedURL());
   OptimizationGuideTopHostProvider::MaybeUpdateTopHostBlacklist(
       navigation_handle);
 
@@ -100,6 +106,7 @@
       GetOrCreateOptimizationGuideNavigationData(navigation_handle);
   nav_data->set_was_host_covered_by_fetch_at_navigation_start(
       was_host_covered_by_fetch);
+  nav_data->set_is_same_origin_navigation(is_same_origin);
 }
 
 void OptimizationGuideWebContentsObserver::DidRedirectNavigation(
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index f0b2cca..d8e0f59 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -10,6 +10,7 @@
 #include "base/containers/flat_set.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_macros_local.h"
 #include "base/rand_util.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/stringprintf.h"
@@ -288,8 +289,16 @@
           engagement_service->GetScore(navigation_handle->GetURL()));
     }
     case proto::CLIENT_MODEL_FEATURE_SAME_ORIGIN_NAVIGATION: {
-      return static_cast<float>(url::IsSameOriginWith(
-          navigation_handle->GetURL(), navigation_handle->GetPreviousURL()));
+      OptimizationGuideNavigationData* nav_data =
+          OptimizationGuideNavigationData::GetFromNavigationHandle(
+              navigation_handle);
+
+      bool is_same_origin = nav_data && nav_data->is_same_origin_navigation();
+
+      LOCAL_HISTOGRAM_BOOLEAN(
+          "OptimizationGuide.PredictionManager.IsSameOrigin", is_same_origin);
+
+      return static_cast<float>(is_same_origin);
     }
     case proto::CLIENT_MODEL_FEATURE_FIRST_CONTENTFUL_PAINT_SESSION_MEAN: {
       if (session_fcp_.GetNumberOfSamples() == 0) {
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index bbf52c4..03ee7591 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -49,22 +49,20 @@
 }
 
 // Retries fetching |histogram_name| until it contains at least |count| samples.
-int RetryForHistogramUntilCountReached(
+void RetryForHistogramUntilCountReached(
     const base::HistogramTester* histogram_tester,
     const std::string& histogram_name,
     int count) {
-  int total = 0;
   while (true) {
     base::ThreadPoolInstance::Get()->FlushForTesting();
     base::RunLoop().RunUntilIdle();
 
-    total = GetTotalHistogramSamples(histogram_tester, histogram_name);
-    if (total >= count)
-      return total;
-
     content::FetchHistogramsFromChildProcesses();
     SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
-    base::RunLoop().RunUntilIdle();
+
+    int total = GetTotalHistogramSamples(histogram_tester, histogram_name);
+    if (total >= count)
+      return;
   }
 }
 
@@ -292,6 +290,12 @@
   GURL https_url_with_content() { return https_url_with_content_; }
   GURL https_url_without_content() { return https_url_without_content_; }
 
+ protected:
+  // Feature that the model server should return in response to
+  // GetModelsRequest.
+  proto::ClientModelFeature client_model_feature_ =
+      optimization_guide::proto::CLIENT_MODEL_FEATURE_SITE_ENGAGEMENT_SCORE;
+
  private:
   std::unique_ptr<net::test_server::HttpResponse> HandleGetModelsRequest(
       const net::test_server::HttpRequest& request) {
@@ -305,8 +309,7 @@
     std::unique_ptr<optimization_guide::proto::GetModelsResponse>
         get_models_response = BuildGetModelsResponse(
             {"example1.com", https_server_->GetURL("/").host()},
-            {optimization_guide::proto::
-                 CLIENT_MODEL_FEATURE_SITE_ENGAGEMENT_SCORE});
+            {client_model_feature_});
     if (response_type_ == PredictionModelsFetcherRemoteResponseType::
                               kSuccessfulWithFeaturesAndNoModels) {
       get_models_response->clear_models();
@@ -469,12 +472,9 @@
 
   // Wait until histograms have been updated before performing checks for
   // correct behavior based on the response.
-  EXPECT_GE(
-      RetryForHistogramUntilCountReached(
-          &histogram_tester,
-          "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status",
-          1),
-      1);
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
 
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status",
@@ -499,23 +499,17 @@
 
   // Wait until histograms have been updated before performing checks for
   // correct behavior based on the response.
-  EXPECT_GE(
-      RetryForHistogramUntilCountReached(
-          &histogram_tester,
-          "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status",
-          1),
-      1);
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
 
-  EXPECT_GE(
-      RetryForHistogramUntilCountReached(
-          &histogram_tester,
-          "OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1),
-      1);
-  EXPECT_GE(
-      RetryForHistogramUntilCountReached(
-          &histogram_tester,
-          "OptimizationGuide.PredictionManager.PredictionModelsStored", 1),
-      1);
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
+
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
 
   ui_test_utils::NavigateToURL(browser(), https_url_with_content());
 
@@ -534,4 +528,70 @@
       1);
 }
 
+class PredictionManagerBrowserSameOriginTest
+    : public PredictionManagerBrowserTest {
+ public:
+  PredictionManagerBrowserSameOriginTest() = default;
+  ~PredictionManagerBrowserSameOriginTest() override = default;
+
+  void SetUp() override {
+    client_model_feature_ =
+        optimization_guide::proto::CLIENT_MODEL_FEATURE_SAME_ORIGIN_NAVIGATION;
+    PredictionManagerBrowserTest::SetUp();
+  }
+};
+
+// Regression test for https://crbug.com/1037945. Tests that the origin of the
+// previous navigation is computed correctly.
+IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserSameOriginTest,
+                       DISABLE_ON_WIN_MAC_CHROMEOS(IsSameOriginNavigation)) {
+  base::HistogramTester histogram_tester;
+  OptimizationGuideKeyedService* keyed_service =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
+
+  PredictionManager* prediction_manager = keyed_service->GetPredictionManager();
+  EXPECT_TRUE(prediction_manager);
+  RegisterWithKeyedService();
+
+  // Wait until histograms have been updated before performing checks for
+  // correct behavior based on the response.
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
+
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
+
+  RetryForHistogramUntilCountReached(
+      &histogram_tester,
+      "OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
+
+  ui_test_utils::NavigateToURL(browser(), https_url_with_content());
+  RetryForHistogramUntilCountReached(
+      &histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 1);
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PredictionManager.IsSameOrigin", false, 1);
+
+  // Navigate to the same URL in the same tab. This should count as a
+  // same-origin navigation.
+  ui_test_utils::NavigateToURL(browser(), https_url_with_content());
+  RetryForHistogramUntilCountReached(
+      &histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 2);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionManager.IsSameOrigin", false, 1);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionManager.IsSameOrigin", true, 1);
+
+  // Navigate to a cross-origin URL. This should count as a cross-origin
+  // navigation.
+  ui_test_utils::NavigateToURL(browser(), GURL("https://www.google.com/"));
+  RetryForHistogramUntilCountReached(
+      &histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 3);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionManager.IsSameOrigin", false, 2);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.PredictionManager.IsSameOrigin", true, 1);
+}
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
index 31580f2..f8ffc7ef 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
@@ -1425,12 +1425,6 @@
   navigation_handle->set_url(previous_url);
   navigation_handle->set_page_transition(
       ui::PageTransition::PAGE_TRANSITION_RELOAD);
-  ON_CALL(*navigation_handle, GetPreviousURL())
-      .WillByDefault(testing::ReturnRef(previous_url));
-
-  if (IsSameOriginNavigationFeature()) {
-    EXPECT_CALL(*navigation_handle, GetPreviousURL()).Times(1);
-  }
 
   CreatePredictionManager({});
   prediction_manager()->SetPredictionModelFetcherForTesting(
@@ -1477,8 +1471,6 @@
   navigation_handle->set_url(previous_url);
   navigation_handle->set_page_transition(
       ui::PageTransition::PAGE_TRANSITION_RELOAD);
-  ON_CALL(*navigation_handle, GetPreviousURL())
-      .WillByDefault(testing::ReturnRef(previous_url));
 
   pref_service()->SetDouble(optimization_guide::prefs::kSessionStatisticFCPMean,
                             200.0);
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 470ff5c..68dca20 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -48,6 +48,10 @@
      */
     public static final String ACCESSIBILITY_TAB_SWITCHER = "accessibility_tab_switcher";
 
+    public static final String BOOKMARKS_LAST_USED_URL = "enhanced_bookmark_last_used_url";
+    public static final String BOOKMARKS_LAST_USED_PARENT =
+            "enhanced_bookmark_last_used_parent_folder";
+
     /**
      * Whether Chrome is set as the default browser.
      * Default value is false.
@@ -342,6 +346,14 @@
      * Default value is false.
      */
     public static final String FLAGS_CACHED_START_SURFACE_ENABLED = "start_surface_enabled";
+
+    /**
+     * Whether or not the start surface single pane is enabled.
+     * Default value is false.
+     */
+    public static final String START_SURFACE_SINGLE_PANE_ENABLED_KEY =
+            "start_surface_single_pane_enabled";
+
     /**
      * Key to cache whether SWAP_PIXEL_FORMAT_TO_FIX_CONVERT_FROM_TRANSLUCENT is enabled.
      */
@@ -468,6 +480,8 @@
         // clang-format off
         return Arrays.asList(
                 ACCESSIBILITY_TAB_SWITCHER,
+                BOOKMARKS_LAST_USED_URL,
+                BOOKMARKS_LAST_USED_PARENT,
                 CHROME_DEFAULT_BROWSER,
                 CONTENT_SUGGESTIONS_SHOWN,
                 CONTEXTUAL_SEARCH_ALL_TIME_OPEN_COUNT,
@@ -565,6 +579,7 @@
                 SIGNIN_PROMO_NTP_PROMO_SUPPRESSION_PERIOD_START,
                 SIGNIN_PROMO_PERSONALIZED_DECLINED,
                 SIGNIN_PROMO_SETTINGS_PERSONALIZED_DISMISSED,
+                START_SURFACE_SINGLE_PANE_ENABLED_KEY,
                 TWA_DIALOG_NUMBER_OF_DISMISSALS_ON_CLEAR_DATA,
                 TWA_DIALOG_NUMBER_OF_DISMISSALS_ON_UNINSTALL,
                 TWA_DISCLOSURE_ACCEPTED_PACKAGES,
@@ -622,6 +637,8 @@
         // clang-format off
         return Arrays.asList(
                 ACCESSIBILITY_TAB_SWITCHER,
+                BOOKMARKS_LAST_USED_URL,
+                BOOKMARKS_LAST_USED_PARENT,
                 CHROME_DEFAULT_BROWSER,
                 CONTENT_SUGGESTIONS_SHOWN,
                 CONTEXTUAL_SEARCH_ALL_TIME_OPEN_COUNT,
@@ -712,6 +729,7 @@
                 SIGNIN_PROMO_NTP_PROMO_SUPPRESSION_PERIOD_START,
                 SIGNIN_PROMO_PERSONALIZED_DECLINED,
                 SIGNIN_PROMO_SETTINGS_PERSONALIZED_DISMISSED,
+                START_SURFACE_SINGLE_PANE_ENABLED_KEY,
                 TWA_DIALOG_NUMBER_OF_DISMISSALS_ON_CLEAR_DATA,
                 TWA_DIALOG_NUMBER_OF_DISMISSALS_ON_UNINSTALL,
                 TWA_DISCLOSURE_ACCEPTED_PACKAGES,
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 69c4025..6d18f89e 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_DEVTOOLS_ATTACHED:
     case FINAL_STATUS_PAGE_BEING_CAPTURED:
     case FINAL_STATUS_NAVIGATION_UNCOMMITTED:
     case FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE:
@@ -1145,20 +1144,6 @@
                                false);
 }
 
-// Checks that the prerendering of a page is canceled correctly when a
-// Javascript alert is called.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderAlertBeforeOnload) {
-  PrerenderTestURL("/prerender/prerender_alert_before_onload.html",
-                   FINAL_STATUS_JAVASCRIPT_ALERT, 0);
-}
-
-// Checks that the prerendering of a page is canceled correctly when a
-// Javascript alert is called.
-IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderAlertAfterOnload) {
-  PrerenderTestURL("/prerender/prerender_alert_after_onload.html",
-                   FINAL_STATUS_JAVASCRIPT_ALERT, 1);
-}
-
 // Checks that plugins are not loaded while a page is being preloaded, but
 // are loaded when the page is displayed.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderDelayLoadPlugin) {
diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc
index 11e7f40f..89fe0a3c 100644
--- a/chrome/browser/prerender/prerender_contents.cc
+++ b/chrome/browser/prerender/prerender_contents.cc
@@ -134,15 +134,6 @@
     return false;
   }
 
-  bool ShouldSuppressDialogs(WebContents* source) override {
-    // We still want to show the user the message when they navigate to this
-    // page, so cancel this prerender.
-    prerender_contents_->Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
-    // Always suppress JavaScript messages if they're triggered by a page being
-    // prerendered.
-    return true;
-  }
-
   void RegisterProtocolHandler(WebContents* web_contents,
                                const std::string& protocol,
                                const GURL& url,
diff --git a/chrome/browser/prerender/prerender_final_status.h b/chrome/browser/prerender/prerender_final_status.h
index 8d50045..e8206c1 100644
--- a/chrome/browser/prerender/prerender_final_status.h
+++ b/chrome/browser/prerender/prerender_final_status.h
@@ -25,7 +25,7 @@
   FINAL_STATUS_CREATE_NEW_WINDOW = 5,
   FINAL_STATUS_PROFILE_DESTROYED = 6,
   FINAL_STATUS_APP_TERMINATING = 7,
-  FINAL_STATUS_JAVASCRIPT_ALERT = 8,
+  // Obsolete: FINAL_STATUS_JAVASCRIPT_ALERT = 8,
   FINAL_STATUS_AUTH_NEEDED = 9,
   // Obsolete: FINAL_STATUS_HTTPS = 10,
   FINAL_STATUS_DOWNLOAD = 11,
@@ -52,7 +52,7 @@
   FINAL_STATUS_CANCELLED = 32,
   FINAL_STATUS_SSL_ERROR = 33,
   // Obsolete: FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING = 34,
-  FINAL_STATUS_DEVTOOLS_ATTACHED = 35,
+  // Obsolete: FINAL_STATUS_DEVTOOLS_ATTACHED = 35,
   // Obsolete: FINAL_STATUS_SESSION_STORAGE_NAMESPACE_MISMATCH = 36,
   // Obsolete: FINAL_STATUS_NO_USE_GROUP = 37,
   // Obsolete: FINAL_STATUS_MATCH_COMPLETE_DUMMY = 38,
diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc
index f2e61d21..f406a71 100644
--- a/chrome/browser/prerender/prerender_manager.cc
+++ b/chrome/browser/prerender/prerender_manager.cc
@@ -51,7 +51,6 @@
 #include "chrome/common/prerender_types.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -129,19 +128,9 @@
     ScheduleWebContentsForDeletion(/*timeout=*/false);
   }
 
-  bool ShouldSuppressDialogs(WebContents* source) override {
-    // Use this as a proxy for getting statistics on how often we fail to honor
-    // the beforeunload event.
-    DCHECK_EQ(tab_.get(), source);
-    suppressed_dialog_ = true;
-    return true;
-  }
-
  private:
   void ScheduleWebContentsForDeletion(bool timeout) {
     UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
-    UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
-                          suppressed_dialog_);
     tab_->SetDelegate(nullptr);
     manager_->ScheduleDeleteOldWebContents(std::move(tab_), this);
     // |this| is deleted at this point.
@@ -149,7 +138,6 @@
 
   PrerenderManager* const manager_;
   std::unique_ptr<WebContents> tab_;
-  bool suppressed_dialog_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
 };
@@ -449,15 +437,6 @@
 
   DCHECK(prerender_data->contents()->prerendering_has_started());
 
-  // Don't use prerendered pages if debugger is attached to the tab.
-  // See http://crbug.com/98541
-  if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
-    histograms_->RecordFinalStatus(prerender_data->contents()->origin(),
-                                   FINAL_STATUS_DEVTOOLS_ATTACHED);
-    prerender_data->contents()->Destroy(FINAL_STATUS_DEVTOOLS_ATTACHED);
-    return nullptr;
-  }
-
   // At this point, we've determined that we will use the prerender.
   content::RenderProcessHost* process_host =
       prerender_data->contents()->GetRenderViewHost()->GetProcess();
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.html b/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.html
index 67989fb5..4c49622 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.html
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.html
@@ -55,6 +55,23 @@
              name="speakTextUnderMouse" aria-labelledby="speakMouseTextLabel">
   </div>
 
+  <div class="option" id="capitalStrategyOption">
+    <label id="capitalStrategyLabel" class="i18n"
+          msgid="options_capital_strategy_select_label">
+      When reading capitals:
+    </label>
+    <select id="capitalStrategy" class="pref"
+          aria-labelledby="capitalStrategyLabel">
+      <option id="announceCapitals" class="i18n"
+            msgid="options_announce_capitals">
+        Speak "cap" before letter
+      </option>
+      <option id="increasePitch" class="i18n" msgid="options_increase_pitch">
+        Increase pitch
+      </option>
+    </select>
+  </div>
+
   <div class="option">
     <label id="announceDownloadsLabel" class="i18n"
           msgid="options_announce_download">
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.js
index 5da5e5c7..575f0c3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/options/options.js
@@ -94,6 +94,13 @@
       }
     }
   }
+  if (localStorage['capitalStrategy']) {
+    for (var i = 0, opt; opt = $('capitalStrategy').options[i]; ++i) {
+      if (opt.id == localStorage['capitalStrategy']) {
+        opt.setAttribute('selected', '');
+      }
+    }
+  }
 
   chrome.commandLinePrivate.hasSwitch(
       'enable-experimental-accessibility-chromevox-language-switching',
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
index 1f751ae..309bf8b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/prefs.js
@@ -73,6 +73,7 @@
   // TODO(dtseng): Leaking state about multiple key maps here until we have a
   // class to manage multiple key maps. Also, this doesn't belong as a pref;
   // should just store in local storage.
+  'capitalStrategy': 'increasePitch',
   'currentKeyMap': KeyMap.DEFAULT_KEYMAP,
   'cvoxKey': '',
   'enableBrailleLogging': false,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
index db92037..4b26a8b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
@@ -201,10 +201,15 @@
  */
 AbstractTts.prototype.preprocess = function(text, properties) {
   if (text.length == 1 && text >= 'A' && text <= 'Z') {
-    for (var prop in AbstractTts.PERSONALITY_CAPITAL) {
-      if (properties[prop] === undefined) {
-        properties[prop] = AbstractTts.PERSONALITY_CAPITAL[prop];
+    // Describe capital letters according to user's setting.
+    if (localStorage['capitalStrategy'] == 'increasePitch') {
+      for (var prop in AbstractTts.PERSONALITY_CAPITAL) {
+        if (properties[prop] === undefined) {
+          properties[prop] = AbstractTts.PERSONALITY_CAPITAL[prop];
+        }
       }
+    } else if (localStorage['capitalStrategy'] == 'announceCapitals') {
+      text = Msgs.getMsg('announce_capital_letter', [text]);
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js
index 0e5e880..064abae 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js
@@ -207,6 +207,10 @@
 TtsBackground.prototype.speak = function(textString, queueMode, properties) {
   goog.base(this, 'speak', textString, queueMode, properties);
 
+  // |textString| gets manipulated throughout this function. Save the original
+  // value for functions that may need it.
+  var originalTextString = textString;
+
   if (this.ttsProperties[AbstractTts.VOLUME] === 0) {
     return this;
   }
@@ -258,7 +262,7 @@
   this.speakUsingQueue_(utterance, queueMode);
   // Attempt to queue phonetic speech with property['delay']. This ensures that
   // phonetic hints are delayed when we process them.
-  this.pronouncePhonetically_(textString, properties);
+  this.pronouncePhonetically_(originalTextString, properties);
   return this;
 };
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background_test.js
index 447611e..ae2d709 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background_test.js
@@ -146,3 +146,19 @@
           'Please do the shopping at 3 a thing came up at work',
           preprocess('Please do the shopping at 3 a thing came up at work'));
     });
+
+SYNC_TEST_F('ChromeVoxTtsBackgroundTest', 'AnnounceCapitalLetters', function() {
+  var tts = new TtsBackground(false);
+  var preprocess = tts.preprocess.bind(tts);
+
+  assertEquals('A', preprocess('A'));
+
+  // Only announce capital for solo capital letters.
+  localStorage['capitalStrategy'] = 'announceCapitals';
+  assertEquals('Cap A', preprocess('A'));
+  assertEquals('Cap Z', preprocess('Z'));
+
+  // Do not announce capital for the following inputs.
+  assertEquals('BB', preprocess('BB'));
+  assertEquals('A.', preprocess('A.'));
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/accessibility/chromevox/strings/chromevox_strings.grd
index 6aab9e9..97edf0fcc 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/strings/chromevox_strings.grd
@@ -3796,6 +3796,18 @@
       <message desc="The description of the openKeyboardShortcutsMenu key. Displayed in the ChromeVox menu" name="IDS_CHROMEVOX_OPEN_KEYBOARD_SHORTCUTS_MENU">
         Open keyboard shortcuts menu
       </message>
+      <message desc="Describes the multi select option for how to describe capital letters." name="IDS_CHROMEVOX_OPTIONS_CAPITAL_STRATEGY_SELECT_LABEL">
+        When reading capitals:
+      </message>
+      <message desc="Sets capital description strategy to announce presence of capital letters" name="IDS_CHROMEVOX_OPTIONS_ANNOUNCE_CAPITALS">
+        Speak "cap" before letter
+      </message>
+      <message desc="Sets capital description strategy to increase pitch" name="IDS_CHROMEVOX_OPTIONS_INCREASE_PITCH">
+        Increase pitch
+      </message>
+      <message desc="Used to describe capital letters" name="IDS_CHROMEVOX_ANNOUNCE_CAPITAL_LETTER">
+        Cap <ph name="letter">$1<ex>A</ex></ph>
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/resources/chromeos/camera/BUILD.gn b/chrome/browser/resources/chromeos/camera/BUILD.gn
index 9eab015..c15ecf2 100644
--- a/chrome/browser/resources/chromeos/camera/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/BUILD.gn
@@ -134,6 +134,8 @@
 
 copy("chrome_camera_app_js_browser_proxy") {
   sources = [
+    # TODO(b/129956426): Remove dependency used only in closure compiler check.
+    "src/js/browser_proxy/browser_proxy_interface.js",
     "src/js/browser_proxy/browser_proxy.js",
   ]
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js b/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
index 4e4360a..fc77b9d3 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/device/device_info_updater.js
@@ -2,44 +2,41 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-'use strict';
-
-/**
- * Namespace for the Camera app.
- */
-var cca = cca || {};
-
-/**
- * Namespace for device.
- */
-cca.device = cca.device || {};
+import {DeviceOperator} from '../mojo/device_operator.js';
+// eslint-disable-next-line no-unused-vars
+import {ResolutionList} from '../type.js';
+import {Camera3DeviceInfo} from './camera3_device_info.js';
+import {PhotoConstraintsPreferrer,  // eslint-disable-line no-unused-vars
+        VideoConstraintsPreferrer,  // eslint-disable-line no-unused-vars
+} from './constraints_preferrer.js';
+import {LegacyVCDError} from './error.js';
 
 /**
  * Contains information of all cameras on the device and will updates its value
  * when any plugin/unplug external camera changes.
  */
-cca.device.DeviceInfoUpdater = class {
+export class DeviceInfoUpdater {
   /**
-   * @param {!cca.device.PhotoConstraintsPreferrer} photoPreferrer
-   * @param {!cca.device.VideoConstraintsPreferrer} videoPreferrer
+   * @param {!PhotoConstraintsPreferrer} photoPreferrer
+   * @param {!VideoConstraintsPreferrer} videoPreferrer
    * @public
    * */
   constructor(photoPreferrer, videoPreferrer) {
     /**
-     * @type {!cca.device.PhotoConstraintsPreferrer}
+     * @type {!PhotoConstraintsPreferrer}
      * @private
      */
     this.photoPreferrer_ = photoPreferrer;
 
     /**
-     * @type {!cca.device.VideoConstraintsPreferrer}
+     * @type {!VideoConstraintsPreferrer}
      * @private
      */
     this.videoPreferrer_ = videoPreferrer;
 
     /**
      * Listeners to be called after new camera information is available.
-     * @type {!Array<!function(!cca.device.DeviceInfoUpdater): Promise>}
+     * @type {!Array<!function(!DeviceInfoUpdater): Promise>}
      * @private
      */
     this.deviceChangeListeners_ = [];
@@ -68,7 +65,7 @@
     /**
      * Camera3DeviceInfo of all available video devices. Is null on HALv1 device
      * without mojo api support.
-     * @type {!Promise<?Array<cca.device.Camera3DeviceInfo>>}
+     * @type {!Promise<?Array<Camera3DeviceInfo>>}
      * @private
      */
     this.camera3DevicesInfo_ = this.queryMojoDevicesInfo_();
@@ -150,7 +147,7 @@
 
   /**
    * Queries Camera3DeviceInfo of available devices through private mojo API.
-   * @return {!Promise<?Array<!cca.device.Camera3DeviceInfo>>} Camera3DeviceInfo
+   * @return {!Promise<?Array<!Camera3DeviceInfo>>} Camera3DeviceInfo
    *     of available devices. Maybe null on HALv1 devices without supporting
    *     private mojo api.
    * @throws {Error} Thrown when camera unplugging happens between enumerating
@@ -158,17 +155,16 @@
    * @private
    */
   async queryMojoDevicesInfo_() {
-    if (!await cca.mojo.DeviceOperator.isSupported()) {
+    if (!await DeviceOperator.isSupported()) {
       return null;
     }
     const deviceInfos = await this.devicesInfo_;
-    return Promise.all(
-        deviceInfos.map((d) => cca.device.Camera3DeviceInfo.create(d)));
+    return Promise.all(deviceInfos.map((d) => Camera3DeviceInfo.create(d)));
   }
 
   /**
    * Registers listener to be called when state of available devices changes.
-   * @param {!function(!cca.device.DeviceInfoUpdater)} listener
+   * @param {!function(!DeviceInfoUpdater)} listener
    */
   addDeviceChangeListener(listener) {
     this.deviceChangeListeners_.push(listener);
@@ -178,9 +174,9 @@
    * Requests to lock update of device information. This function is preserved
    * for device information reader to lock the update capability so as to ensure
    * getting consistent data between all information providers.
-   * @param {!function(!cca.device.DeviceInfoUpdater): Promise} callback Called
-   *     after update capability is locked. Getting information from all
-   *     providers in callback are guaranteed to be consistent.
+   * @param {!function(!DeviceInfoUpdater): Promise} callback Called after
+   *     update capability is locked. Getting information from all providers in
+   *     callback are guaranteed to be consistent.
    */
   async lockDeviceInfo(callback) {
     await this.firstUpdate_;
@@ -222,7 +218,7 @@
 
   /**
    * Gets Camera3DeviceInfo for all available video devices.
-   * @return {!Promise<?Array<!cca.device.Camera3DeviceInfo>>}
+   * @return {!Promise<?Array<!Camera3DeviceInfo>>}
    */
   async getCamera3DevicesInfo() {
     return this.camera3DevicesInfo_;
@@ -231,17 +227,20 @@
   /**
    * Gets supported photo and video resolutions for specified video device.
    * @param {string} deviceId Device id of the video device.
-   * @return {!Promise<!{photo: !cca.ResolutionList, video:
-   *     !cca.ResolutionList}>} Supported photo and video resolutions.
+   * @return {!Promise<!{photo: !ResolutionList, video: !ResolutionList}>}
+   *     Supported photo and video resolutions.
    * @throws {Error} May fail on HALv1 device without capability of querying
    *     supported resolutions.
    */
   async getDeviceResolutions(deviceId) {
     const devices = await this.getCamera3DevicesInfo();
     if (!devices) {
-      throw new cca.device.LegacyVCDError();
+      throw new LegacyVCDError();
     }
     const info = devices.find((info) => info.deviceId === deviceId);
     return {photo: info.photoResols, video: info.videoResols};
   }
-};
+}
+
+/** @const */
+cca.device.DeviceInfoUpdater = DeviceInfoUpdater;
diff --git a/chrome/browser/resources/chromeos/camera/src/js/main.js b/chrome/browser/resources/chromeos/camera/src/js/main.js
index 21a93df..b51adf2 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/main.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/main.js
@@ -161,8 +161,9 @@
               });
         })
         .then((external) => {
-          cca.assert(cca.models.FileSystem.externalDir !== null);
-          this.galleryButton_.initialize(cca.models.FileSystem.externalDir);
+          const externalDir = cca.models.FileSystem.getExternalDirectory();
+          cca.assert(externalDir !== null);
+          this.galleryButton_.initialize(externalDir);
           cca.nav.open(cca.views.ViewName.CAMERA);
         })
         .catch((error) => {
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn b/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
index 9dc708a..2e74d1e6 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/BUILD.gn
@@ -21,7 +21,7 @@
 
 js_library("filesystem") {
   deps = [
-    "..:util",
+    "../browser_proxy:browser_proxy",
   ]
 }
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
index 224f4a3d..c611eda 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
@@ -2,92 +2,129 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-'use strict';
-
-/**
- * Namespace for the Camera app.
- */
-var cca = cca || {};
-
-/**
- * Namespace for models.
- */
-cca.models = cca.models || {};
-
-/**
- * Creates the file system controller.
- * @constructor
- */
-cca.models.FileSystem = function() {
-  // End of properties, seal the object.
-  Object.seal(this);
-};
+import {browserProxy} from '../browser_proxy/browser_proxy.js';
+import {assert} from '../chrome_util.js';
+import {Filenamer, IMAGE_PREFIX, VIDEO_PREFIX} from './filenamer.js';
 
 /**
  * The prefix of thumbnail files.
  * @type {string}
- * @const
  */
-cca.models.FileSystem.THUMBNAIL_PREFIX = 'thumb-';
+const THUMBNAIL_PREFIX = 'thumb-';
+
+/**
+ * Checks if the entry's name has the video prefix.
+ * @param {!FileEntry} entry File entry.
+ * @return {boolean} Has the video prefix or not.
+ */
+export function hasVideoPrefix(entry) {
+  return entry.name.startsWith(VIDEO_PREFIX);
+}
+
+/**
+ * Checks if the entry's name has the image prefix.
+ * @param {!FileEntry} entry File entry.
+ * @return {boolean} Has the image prefix or not.
+ */
+function hasImagePrefix(entry) {
+  return entry.name.startsWith(IMAGE_PREFIX);
+}
+
+/**
+ * Checks if the entry's name has the thumbnail prefix.
+ * @param {!FileEntry} entry File entry.
+ * @return {boolean} Has the thumbnail prefix or not.
+ */
+function hasThumbnailPrefix(entry) {
+  return entry.name.startsWith(THUMBNAIL_PREFIX);
+}
 
 /**
  * Directory in the internal file system.
- * @type {DirectoryEntry}
+ * @type {?DirectoryEntry}
  */
-cca.models.FileSystem.internalDir = null;
+let internalDir = null;
 
 /**
  * Temporary directory in the internal file system.
- * @type {DirectoryEntry}
+ * @type {?DirectoryEntry}
  */
-cca.models.FileSystem.internalTempDir = null;
+let internalTempDir = null;
 
 /**
  * Directory in the external file system.
- * @type {DirectoryEntry}
+ * @type {?DirectoryEntry}
  */
-cca.models.FileSystem.externalDir = null;
+let externalDir = null;
+
+/**
+ * Gets global external directory used by CCA.
+ * @return {?DirectoryEntry}
+ */
+export function getExternalDirectory() {
+  return externalDir;
+}
 
 /**
  * Initializes the directory in the internal file system.
- * @return {!Promise<DirectoryEntry>} Promise for the directory result.
- * @private
+ * @return {!Promise<!DirectoryEntry>} Promise for the directory result.
  */
-cca.models.FileSystem.initInternalDir_ = function() {
+function initInternalDir() {
   return new Promise((resolve, reject) => {
     webkitRequestFileSystem(
         window.PERSISTENT, 768 * 1024 * 1024 /* 768MB */,
         (fs) => resolve(fs.root), reject);
   });
-};
+}
 
 /**
  * Initializes the temporary directory in the internal file system.
- * @return {!Promise<DirectoryEntry>} Promise for the directory result.
- * @private
+ * @return {!Promise<!DirectoryEntry>} Promise for the directory result.
  */
-cca.models.FileSystem.initInternalTempDir_ = function() {
+function initInternalTempDir() {
   return new Promise((resolve, reject) => {
     webkitRequestFileSystem(
         window.TEMPORARY, 768 * 1024 * 1024 /* 768MB */,
         (fs) => resolve(fs.root), reject);
   });
-};
+}
+
+/**
+ * Reads file entries from the directory.
+ * @param {?DirectoryEntry} dir Directory entry to be read.
+ * @return {!Promise<!Array<!FileEntry>>} Promise for the read file entries.
+ */
+function readDir(dir) {
+  return !dir ? Promise.resolve([]) : new Promise((resolve, reject) => {
+    const dirReader = dir.createReader();
+    const entries = [];
+    const readEntries = () => {
+      dirReader.readEntries((inEntries) => {
+        if (inEntries.length === 0) {
+          resolve(entries);
+          return;
+        }
+        entries.push(...inEntries);
+        readEntries();
+      }, reject);
+    };
+    readEntries();
+  });
+}
 
 /**
  * Initializes the directory in the external file system.
  * @return {!Promise<?DirectoryEntry>} Promise for the directory result.
- * @private
  */
-cca.models.FileSystem.initExternalDir_ = function() {
+function initExternalDir() {
   return new Promise((resolve) => {
-           cca.proxy.browserProxy.getVolumeList((volumes) => {
+           browserProxy.getVolumeList((volumes) => {
              if (volumes) {
-               for (var i = 0; i < volumes.length; i++) {
-                 var volumeId = volumes[i].volumeId;
+               for (let i = 0; i < volumes.length; i++) {
+                 const volumeId = volumes[i].volumeId;
                  if (volumeId.indexOf('downloads:Downloads') !== -1 ||
                      volumeId.indexOf('downloads:MyFiles') !== -1) {
-                   cca.proxy.browserProxy.requestFileSystem(
+                   browserProxy.requestFileSystem(
                        volumes[i], (fs) => resolve([fs && fs.root, volumeId]));
                    return;
                  }
@@ -98,14 +135,117 @@
          })
       .then(([dir, volumeId]) => {
         if (volumeId && volumeId.indexOf('downloads:MyFiles') !== -1) {
-          return cca.models.FileSystem.readDir_(dir).then((entries) => {
+          return readDir(dir).then((entries) => {
             return entries.find(
                 (entry) => entry.name === 'Downloads' && entry.isDirectory);
           });
         }
         return dir;
       });
-};
+}
+
+/**
+ * Regulates the picture name to the desired format if it's in legacy formats.
+ * @param {!FileEntry} entry Picture entry whose name to be regulated.
+ * @return {string} Name in the desired format.
+ */
+function regulatePictureName(entry) {
+  if (hasVideoPrefix(entry) || hasImagePrefix(entry)) {
+    const match = entry.name.match(/(\w{3}_\d{8}_\d{6})(?:_(\d+))?(\..+)?$/);
+    if (match) {
+      const idx = match[2] ? ' (' + match[2] + ')' : '';
+      const ext = match[3] ? match[3].replace(/\.webm$/, '.mkv') : '';
+      return match[1] + idx + ext;
+    }
+  } else {
+    // Early pictures are in legacy file name format (crrev.com/c/310064).
+    const match = entry.name.match(/(\d+).(?:\d+)/);
+    if (match) {
+      return (new Filenamer(parseInt(match[1], 10))).newImageName();
+    }
+  }
+  return entry.name;
+}
+
+/**
+ * Gets the thumbnail name of the given picture.
+ * @param {!FileEntry} entry Picture's file entry.
+ * @return {string} Thumbnail name.
+ */
+function getThumbnailName(entry) {
+  const thumbnailName = THUMBNAIL_PREFIX + entry.name;
+  return (thumbnailName.substr(0, thumbnailName.lastIndexOf('.')) ||
+          thumbnailName) +
+      '.jpg';
+}
+
+/**
+ * Parses and filters the internal entries to thumbnail and picture entries.
+ * @param {!Array<!FileEntry>} internalEntries Internal file entries.
+ * @param {!Object<string, !FileEntry>} thumbnailEntriesByName Result thumbanil
+ *     entries mapped by thumbnail names, initially empty.
+ * @param {!Array<!FileEntry>=} pictureEntries Result picture entries, initially
+ *     empty.
+ */
+function parseInternalEntries(
+    internalEntries, thumbnailEntriesByName, pictureEntries) {
+  let thumbnailEntries = [];
+  if (pictureEntries) {
+    for (let index = 0; index < internalEntries.length; index++) {
+      if (hasThumbnailPrefix(internalEntries[index])) {
+        thumbnailEntries.push(internalEntries[index]);
+      } else {
+        pictureEntries.push(internalEntries[index]);
+      }
+    }
+  } else {
+    thumbnailEntries = internalEntries.filter(hasThumbnailPrefix);
+  }
+  for (let index = 0; index < thumbnailEntries.length; index++) {
+    const thumbnailEntry = thumbnailEntries[index];
+    thumbnailEntriesByName[thumbnailEntry.name] = thumbnailEntry;
+  }
+}
+
+/**
+ * Migrates all picture-files from internal storage to external storage.
+ * @return {!Promise} Promise for the operation.
+ */
+function migratePictures() {
+  const migratePicture = (pictureEntry, thumbnailEntry) => {
+    const name = regulatePictureName(pictureEntry);
+    const targetDir = externalDir;
+    assert(targetDir !== null);
+    return getFile(targetDir, name, true).then((entry) => {
+      return new Promise((resolve, reject) => {
+        pictureEntry.copyTo(targetDir, entry.name, (result) => {
+          if (result.name !== pictureEntry.name && thumbnailEntry) {
+            // Thumbnails can be recreated later if failing to rename them here.
+            thumbnailEntry.moveTo(internalDir, getThumbnailName(result));
+          }
+          pictureEntry.remove(() => {});
+          resolve();
+        }, reject);
+      });
+    });
+  };
+
+  return readDir(internalDir).then((internalEntries) => {
+    const pictureEntries = [];
+    const thumbnailEntriesByName = {};
+    parseInternalEntries(
+        internalEntries, thumbnailEntriesByName, pictureEntries);
+
+    const migrated = [];
+    for (let index = 0; index < pictureEntries.length; index++) {
+      const entry = pictureEntries[index];
+      const thumbnailName = getThumbnailName(entry);
+      const thumbnailEntry = thumbnailEntriesByName[thumbnailName];
+      migrated.push(migratePicture(entry, thumbnailEntry));
+    }
+    return Promise.all(migrated);
+  });
+}
 
 /**
  * Initializes file systems, migrating pictures if needed. This function
@@ -114,15 +254,15 @@
        prompts users to migrate pictures if no acknowledgement yet.
  * @return {!Promise<boolean>} Promise for the external-fs result.
  */
-cca.models.FileSystem.initialize = function(promptMigrate) {
-  var checkAcked = new Promise((resolve) => {
+export function initialize(promptMigrate) {
+  const checkAcked = new Promise((resolve) => {
     // ack 0: User has not yet acknowledged to migrate pictures.
     // ack 1: User acknowledges to migrate pictures to Downloads.
-    cca.proxy.browserProxy.localStorageGet(
+    browserProxy.localStorageGet(
         {ackMigratePictures: 0},
         (values) => resolve(values.ackMigratePictures >= 1));
   });
-  var checkMigrated = new Promise((resolve) => {
+  const checkMigrated = new Promise((resolve) => {
     if (chrome.chromeosInfoPrivate) {
       chrome.chromeosInfoPrivate.get(
           ['cameraMediaConsolidated'],
@@ -131,37 +271,35 @@
       resolve(false);
     }
   });
-  var ackMigrate = () =>
-      cca.proxy.browserProxy.localStorageSet({ackMigratePictures: 1});
-  var doneMigrate = () => chrome.chromeosInfoPrivate &&
+  const ackMigrate = () =>
+      browserProxy.localStorageSet({ackMigratePictures: 1});
+  const doneMigrate = () => chrome.chromeosInfoPrivate &&
       chrome.chromeosInfoPrivate.set('cameraMediaConsolidated', true);
 
   return Promise
       .all([
-        cca.models.FileSystem.initInternalDir_(),
-        cca.models.FileSystem.initInternalTempDir_(),
-        cca.models.FileSystem.initExternalDir_(),
+        initInternalDir(),
+        initInternalTempDir(),
+        initExternalDir(),
         checkAcked,
         checkMigrated,
       ])
-      .then(([internalDir, internalTempDir, externalDir, acked, migrated]) => {
-        cca.models.FileSystem.internalDir = internalDir;
-        cca.models.FileSystem.internalTempDir = internalTempDir;
-        cca.models.FileSystem.externalDir = externalDir;
+      .then((results) => {
+        let /** boolean */ acked;
+        let /** boolean */ migrated;
+        [internalDir, internalTempDir, externalDir, acked, migrated] = results;
         if (migrated && !externalDir) {
           throw new Error('External file system should be available.');
         }
         // Check if acknowledge-prompt and migrate-pictures are needed.
-        if (migrated || !cca.models.FileSystem.externalDir) {
+        if (migrated || !externalDir) {
           return [false, false];
         }
         // Check if any internal picture other than thumbnail needs migration.
         // Pictures taken by old Camera App may not have IMG_ or VID_ prefix.
-        var dir = cca.models.FileSystem.internalDir;
-        return cca.models.FileSystem.readDir_(dir)
+        return readDir(internalDir)
             .then((entries) => {
-              return entries.some(
-                  (entry) => !cca.models.FileSystem.hasThumbnailPrefix_(entry));
+              return entries.some((entry) => !hasThumbnailPrefix(entry));
             })
             .then((migrateNeeded) => {
               if (migrateNeeded) {
@@ -183,118 +321,24 @@
             });
           })
       .then((migrateNeeded) => {  // Migrate pictures if needed.
-        const external = cca.models.FileSystem.externalDir !== null;
-        return !migrateNeeded ? external :
-                                cca.models.FileSystem.migratePictures()
-                                    .then(doneMigrate)
-                                    .then(() => external);
+        const external = externalDir !== null;
+        return !migrateNeeded ?
+            external :
+            migratePictures().then(doneMigrate).then(() => external);
       });
-};
-
-/**
- * Reads file entries from the directory.
- * @param {DirectoryEntry} dir Directory entry to be read.
- * @return {!Promise<!Array<FileEntry>>} Promise for the read file entries.
- * @private
- */
-cca.models.FileSystem.readDir_ = function(dir) {
-  return !dir ? Promise.resolve([]) : new Promise((resolve, reject) => {
-    var dirReader = dir.createReader();
-    var entries = [];
-    var readEntries = () => {
-      dirReader.readEntries((inEntries) => {
-        if (inEntries.length === 0) {
-          resolve(entries);
-          return;
-        }
-        entries = entries.concat(inEntries);
-        readEntries();
-      }, reject);
-    };
-    readEntries();
-  });
-};
-
-/**
- * Migrates all picture-files from internal storage to external storage.
- * @return {!Promise} Promise for the operation.
- */
-cca.models.FileSystem.migratePictures = function() {
-  var internalDir = cca.models.FileSystem.internalDir;
-  var externalDir = cca.models.FileSystem.externalDir;
-
-  var migratePicture = (pictureEntry, thumbnailEntry) => {
-    var name = cca.models.FileSystem.regulatePictureName(pictureEntry);
-    return cca.models.FileSystem.getFile(externalDir, name, true)
-        .then((entry) => {
-          return new Promise((resolve, reject) => {
-            pictureEntry.copyTo(externalDir, entry.name, (result) => {
-              if (result.name !== pictureEntry.name && thumbnailEntry) {
-                // Thumbnails can be recreated later if failing to rename them
-                // here.
-                thumbnailEntry.moveTo(
-                    internalDir,
-                    cca.models.FileSystem.getThumbnailName(result));
-              }
-              pictureEntry.remove(() => {});
-              resolve();
-            }, reject);
-          });
-        });
-  };
-
-  return cca.models.FileSystem.readDir_(internalDir).then((internalEntries) => {
-    var pictureEntries = [];
-    var thumbnailEntriesByName = {};
-    cca.models.FileSystem.parseInternalEntries_(
-        internalEntries, thumbnailEntriesByName, pictureEntries);
-
-    var migrated = [];
-    for (var index = 0; index < pictureEntries.length; index++) {
-      var entry = pictureEntries[index];
-      var thumbnailName = cca.models.FileSystem.getThumbnailName(entry);
-      var thumbnailEntry = thumbnailEntriesByName[thumbnailName];
-      migrated.push(migratePicture(entry, thumbnailEntry));
-    }
-    return Promise.all(migrated);
-  });
-};
-
-/**
- * Regulates the picture name to the desired format if it's in legacy formats.
- * @param {FileEntry} entry Picture entry whose name to be regulated.
- * @return {string} Name in the desired format.
- */
-cca.models.FileSystem.regulatePictureName = function(entry) {
-  if (cca.models.FileSystem.hasVideoPrefix(entry) ||
-      cca.models.FileSystem.hasImagePrefix_(entry)) {
-    var match = entry.name.match(/(\w{3}_\d{8}_\d{6})(?:_(\d+))?(\..+)?$/);
-    if (match) {
-      var idx = match[2] ? ' (' + match[2] + ')' : '';
-      var ext = match[3] ? match[3].replace(/\.webm$/, '.mkv') : '';
-      return match[1] + idx + ext;
-    }
-  } else {
-    // Early pictures are in legacy file name format (crrev.com/c/310064).
-    var match = entry.name.match(/(\d+).(?:\d+)/);
-    if (match) {
-      return (new cca.models.Filenamer(parseInt(match[1], 10))).newImageName();
-    }
-  }
-  return entry.name;
-};
+}
 
 /**
  * Saves the blob to the given file name. Name of the actually saved file
  * might be different from the given file name if the file already exists.
- * @param {DirectoryEntry} dir Directory to be written into.
+ * @param {!DirectoryEntry} dir Directory to be written into.
  * @param {string} name Name of the file.
  * @param {!Blob} blob Data of the file to be saved.
  * @return {!Promise<?FileEntry>} Promise for the result.
  * @private
  */
-cca.models.FileSystem.saveToFile_ = function(dir, name, blob) {
-  return cca.models.FileSystem.getFile(dir, name, true).then((entry) => {
+function saveToFile(dir, name, blob) {
+  return getFile(dir, name, true).then((entry) => {
     return new Promise((resolve, reject) => {
       entry.createWriter((fileWriter) => {
         fileWriter.onwriteend = () => resolve(entry);
@@ -303,7 +347,7 @@
       }, reject);
     });
   });
-};
+}
 
 /**
  * Saves photo blob or metadata blob into predefined default location.
@@ -311,69 +355,67 @@
  * @param {string} filename Filename of the photo to be saved.
  * @return {!Promise<?FileEntry>} Promise for the result.
  */
-cca.models.FileSystem.saveBlob = function(blob, filename) {
-  const dir =
-      cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir;
-  return cca.models.FileSystem.saveToFile_(dir, filename, blob);
-};
+export function saveBlob(blob, filename) {
+  const dir = externalDir || internalDir;
+  assert(dir !== null);
+  return saveToFile(dir, filename, blob);
+}
+
 
 /**
  * Gets metadata of the file.
  * @param {!FileEntry} file
  * @return {!Promise<!Object>}
  */
-cca.models.FileSystem.getMetadata = function(file) {
+export function getMetadata(file) {
   return new Promise((resolve) => file.getMetadata(resolve));
-};
+}
 
 /**
  * Creates a file for saving temporary video recording result.
  * @return {!Promise<!FileEntry>} Newly created temporary file.
  * @throws {Error} If failed to create video temp file.
  */
-cca.models.FileSystem.createTempVideoFile = async function() {
-  const dir =
-      cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir;
-  const filename = new cca.models.Filenamer().newVideoName();
-  const file = await cca.models.FileSystem.getFile(dir, filename, true);
+export async function createTempVideoFile() {
+  const dir = externalDir || internalDir;
+  assert(dir !== null);
+  const filename = new Filenamer().newVideoName();
+  const file = await getFile(dir, filename, true);
   if (file === null) {
     throw new Error('Failed to create video temp file.');
   }
   return file;
-};
+}
 
 /**
- * @const {string}
+ * @type {string}
  */
-cca.models.FileSystem.PRIVATE_TEMPFILE_NAME = 'video-intent.mkv';
+const PRIVATE_TEMPFILE_NAME = 'video-intent.mkv';
 
 /**
  * @return {!Promise<!FileEntry>} Newly created temporary file.
  * @throws {Error} If failed to create video temp file.
  */
-cca.models.FileSystem.createPrivateTempVideoFile = async function() {
+export async function createPrivateTempVideoFile() {
   // TODO(inker): Handles running out of space case.
-  const dir = cca.models.FileSystem.internalTempDir;
-  const file = await cca.models.FileSystem.getFile(
-      dir, cca.models.FileSystem.PRIVATE_TEMPFILE_NAME, true);
+  const dir = internalTempDir;
+  assert(dir !== null);
+  const file = await getFile(dir, PRIVATE_TEMPFILE_NAME, true);
   if (file === null) {
     throw new Error('Failed to create private video temp file.');
   }
   return file;
-};
+}
 
 /**
  * Saves temporary video file to predefined default location.
- * @param {FileEntry} tempfile Temporary video file to be saved.
+ * @param {!FileEntry} tempfile Temporary video file to be saved.
  * @param {string} filename Filename to be saved.
- * @return {Promise<?FileEntry>} Saved video file.
+ * @return {!Promise<!FileEntry>} Saved video file.
  */
-cca.models.FileSystem.saveVideo = async function(tempfile, filename) {
-  var dir =
-      cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir;
-  if (!dir) {
-    return await null;
-  }
+export async function saveVideo(tempfile, filename) {
+  const dir = externalDir || internalDir;
+  assert(dir !== null);
 
   // Non-null version for the Closure Compiler.
   let nonNullDir = dir;
@@ -387,146 +429,18 @@
   return new Promise(
       (resolve, reject) =>
           tempfile.moveTo(nonNullDir, filename, resolve, reject));
-};
-
-/**
- * Gets the thumbnail name of the given picture.
- * @param {FileEntry} entry Picture's file entry.
- * @return {string} Thumbnail name.
- */
-cca.models.FileSystem.getThumbnailName = function(entry) {
-  var thumbnailName = cca.models.FileSystem.THUMBNAIL_PREFIX + entry.name;
-  return (thumbnailName.substr(0, thumbnailName.lastIndexOf('.')) ||
-          thumbnailName) +
-      '.jpg';
-};
-
-/**
- * Checks if the entry's name has the video prefix.
- * @param {FileEntry} entry File entry.
- * @return {boolean} Has the video prefix or not.
- */
-cca.models.FileSystem.hasVideoPrefix = function(entry) {
-  return entry.name.startsWith(cca.models.Filenamer.VIDEO_PREFIX);
-};
-
-/**
- * Checks if the entry's name has the image prefix.
- * @param {FileEntry} entry File entry.
- * @return {boolean} Has the image prefix or not.
- * @private
- */
-cca.models.FileSystem.hasImagePrefix_ = function(entry) {
-  return entry.name.startsWith(cca.models.Filenamer.IMAGE_PREFIX);
-};
-
-/**
- * Checks if the entry's name has the thumbnail prefix.
- * @param {FileEntry} entry File entry.
- * @return {boolean} Has the thumbnail prefix or not.
- * @private
- */
-cca.models.FileSystem.hasThumbnailPrefix_ = function(entry) {
-  return entry.name.startsWith(cca.models.FileSystem.THUMBNAIL_PREFIX);
-};
-
-/**
- * Parses and filters the internal entries to thumbnail and picture entries.
- * @param {Array<FileEntry>} internalEntries Internal file entries.
- * @param {Object<string, FileEntry>} thumbnailEntriesByName Result thumbanil
- *     entries mapped by thumbnail names, initially empty.
- * @param {Array<FileEntry>=} pictureEntries Result picture entries, initially
- *     empty.
- * @private
- */
-cca.models.FileSystem.parseInternalEntries_ = function(
-    internalEntries, thumbnailEntriesByName, pictureEntries) {
-  var isThumbnail = cca.models.FileSystem.hasThumbnailPrefix_;
-  var thumbnailEntries = [];
-  if (pictureEntries) {
-    for (var index = 0; index < internalEntries.length; index++) {
-      if (isThumbnail(internalEntries[index])) {
-        thumbnailEntries.push(internalEntries[index]);
-      } else {
-        pictureEntries.push(internalEntries[index]);
-      }
-    }
-  } else {
-    thumbnailEntries = internalEntries.filter(isThumbnail);
-  }
-  for (var index = 0; index < thumbnailEntries.length; index++) {
-    var thumbnailEntry = thumbnailEntries[index];
-    thumbnailEntriesByName[thumbnailEntry.name] = thumbnailEntry;
-  }
-};
-
-/**
- * Gets the picture entries.
- * @return {!Promise<!Array<!FileEntry>>} Promise for the picture entries.
- */
-cca.models.FileSystem.getEntries = function() {
-  return cca.models.FileSystem.readDir_(cca.models.FileSystem.externalDir)
-      .then((entries) => {
-        return entries.filter((entry) => {
-          if (!cca.models.FileSystem.hasVideoPrefix(entry) &&
-              !cca.models.FileSystem.hasImagePrefix_(entry)) {
-            return false;
-          }
-          return entry.name.match(/_(\d{8})_(\d{6})(?: \((\d+)\))?/);
-        });
-      });
-};
-
-/**
- * Returns an URL for a picture.
- * @param {FileEntry} entry File entry.
- * @return {!Promise<string>} Promise for the result.
- */
-cca.models.FileSystem.pictureURL = function(entry) {
-  return new Promise((resolve) => {
-    if (cca.models.FileSystem.externalDir) {
-      entry.file((file) => resolve(URL.createObjectURL(file)));
-    } else {
-      resolve(entry.toURL());
-    }
-  });
-};
-
-/**
- * Gets the file by the given name, avoiding name conflicts if necessary.
- * @param {DirectoryEntry} dir Directory to get the file from.
- * @param {string} name File name. Result file may have a different name.
- * @param {boolean} create True to create file, false otherwise.
- * @return {!Promise<?FileEntry>} Promise for the result.
- */
-cca.models.FileSystem.getFile = function(dir, name, create) {
-  return new Promise((resolve, reject) => {
-           var options =
-               create ? {create: true, exclusive: true} : {create: false};
-           dir.getFile(name, options, resolve, reject);
-         })
-      .catch((error) => {
-        if (create && error.name === 'InvalidModificationError') {
-          // Avoid name conflicts for creating files.
-          return cca.models.FileSystem.getFile(
-              dir, cca.models.FileSystem.incrementFileName_(name), create);
-        } else if (!create && error.name === 'NotFoundError') {
-          return null;
-        }
-        throw error;
-      });
-};
+}
 
 /**
  * Increments the file index of a given file name to avoid name conflicts.
  * @param {string} name File name.
  * @return {string} File name with incremented index.
- * @private
  */
-cca.models.FileSystem.incrementFileName_ = function(name) {
-  var [base, ext] = ['', ''];
-  var idx = 0;
-  var match = name.match(/^([^.]+)(\..+)?$/);
+function incrementFileName(name) {
+  let base = '';
+  let ext = '';
+  let idx = 0;
+  let match = name.match(/^([^.]+)(\..+)?$/);
   if (match) {
     base = match[1];
     ext = match[2];
@@ -537,4 +451,81 @@
     }
   }
   return base + ' (' + (idx + 1) + ')' + ext;
-};
+}
+
+/**
+ * Gets the file by the given name, avoiding name conflicts if necessary.
+ * @param {!DirectoryEntry} dir Directory to get the file from.
+ * @param {string} name File name. Result file may have a different name.
+ * @param {boolean} create True to create file, false otherwise.
+ * @return {!Promise<?FileEntry>} Promise for the result.
+ */
+export function getFile(dir, name, create) {
+  return new Promise((resolve, reject) => {
+           const options =
+               create ? {create: true, exclusive: true} : {create: false};
+           dir.getFile(name, options, resolve, reject);
+         })
+      .catch((error) => {
+        if (create && error.name === 'InvalidModificationError') {
+          // Avoid name conflicts for creating files.
+          return getFile(dir, incrementFileName(name), create);
+        } else if (!create && error.name === 'NotFoundError') {
+          return null;
+        }
+        throw error;
+      });
+}
+
+/**
+ * Gets the picture entries.
+ * @return {!Promise<!Array<!FileEntry>>} Promise for the picture entries.
+ */
+export function getEntries() {
+  return readDir(externalDir).then((entries) => {
+    return entries.filter((entry) => {
+      if (!hasVideoPrefix(entry) && !hasImagePrefix(entry)) {
+        return false;
+      }
+      return entry.name.match(/_(\d{8})_(\d{6})(?: \((\d+)\))?/);
+    });
+  });
+}
+
+/**
+ * Returns an URL for a picture.
+ * @param {!FileEntry} entry File entry.
+ * @return {!Promise<string>} Promise for the result.
+ */
+export function pictureURL(entry) {
+  return new Promise((resolve) => {
+    if (externalDir) {
+      entry.file((file) => resolve(URL.createObjectURL(file)));
+    } else {
+      resolve(entry.toURL());
+    }
+  });
+}
+
+/** @const */
+cca.models.FileSystem.hasVideoPrefix = hasVideoPrefix;
+/** @const */
+cca.models.FileSystem.getExternalDirectory = getExternalDirectory;
+/** @const */
+cca.models.FileSystem.initialize = initialize;
+/** @const */
+cca.models.FileSystem.saveBlob = saveBlob;
+/** @const */
+cca.models.FileSystem.getMetadata = getMetadata;
+/** @const */
+cca.models.FileSystem.createTempVideoFile = createTempVideoFile;
+/** @const */
+cca.models.FileSystem.createPrivateTempVideoFile = createPrivateTempVideoFile;
+/** @const */
+cca.models.FileSystem.saveVideo = saveVideo;
+/** @const */
+cca.models.FileSystem.getFile = getFile;
+/** @const */
+cca.models.FileSystem.getEntries = getEntries;
+/** @const */
+cca.models.FileSystem.pictureURL = pictureURL;
diff --git a/chrome/browser/resources/chromeos/camera/src/js/namespace.js b/chrome/browser/resources/chromeos/camera/src/js/namespace.js
index 5c50974..24571d9 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/namespace.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/namespace.js
@@ -11,7 +11,9 @@
 var cca = {
   device: {},
   intent: {},
-  models: {},
+  models: {
+    FileSystem: {},
+  },
   mojo: {},
   nav: {},
   perf: {},
diff --git a/chrome/browser/resources/chromeos/camera/src/views/main.html b/chrome/browser/resources/chromeos/camera/src/views/main.html
index 90aa4a9f..302358f 100644
--- a/chrome/browser/resources/chromeos/camera/src/views/main.html
+++ b/chrome/browser/resources/chromeos/camera/src/views/main.html
@@ -25,10 +25,10 @@
     <script type="module" src="../js/device/error.js"></script>
     <script type="module" src="../js/device/camera3_device_info.js"></script>
     <script type="module" src="../js/device/constraints_preferrer.js"></script>
-    <script defer src="../js/device/device_info_updater.js"></script>
+    <script type="module" src="../js/device/device_info_updater.js"></script>
     <script type="module" src="../js/models/filenamer.js"></script>
     <script defer src="../js/gallerybutton.js"></script>
-    <script defer src="../js/models/filesystem.js"></script>
+    <script type="module" src="../js/models/filesystem.js"></script>
     <script defer src="../js/models/result_saver.js"></script>
     <script defer src="../js/models/video_saver_interface.js"></script>
     <script defer src="../js/models/file_video_saver.js"></script>
diff --git a/chrome/browser/resources/print_preview/ui/highlight_utils.js b/chrome/browser/resources/print_preview/ui/highlight_utils.js
index dfc0eb2f..a2668db 100644
--- a/chrome/browser/resources/print_preview/ui/highlight_utils.js
+++ b/chrome/browser/resources/print_preview/ui/highlight_utils.js
@@ -40,20 +40,19 @@
       if (query.test(textContent)) {
         // Don't highlight <select> nodes, yellow rectangles can't be
         // displayed within an <option>.
-        if (node.parentNode.nodeName !== 'OPTION') {
-          result.highlights.push(highlight(node, textContent.split(query)));
-        } else {
-          const selectNode = node.parentNode.parentNode;
+        if (node.parentNode.nodeName === 'OPTION') {
           // The bubble should be parented by the select node's parent.
           // Note: The bubble's ::after element, a yellow arrow, will not
           // appear correctly in print preview without SPv175 enabled. See
           // https://crbug.com/817058.
           const bubble = highlightControlWithBubble(
-              /** @type {!HTMLElement} */ (assert(selectNode.parentNode)),
-              textContent.match(query)[0]);
+              /** @type {!HTMLElement} */ (assert(node.parentNode.parentNode)),
+              textContent.match(query)[0], /*horizontallyCenter=*/ true);
           if (bubble) {
             result.bubbles.push(bubble);
           }
+        } else {
+          result.highlights.push(highlight(node, textContent.split(query)));
         }
       }
     });
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 21d65d4..1e7f5c5 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -179,8 +179,10 @@
 
 js_library("search_settings") {
   deps = [
+    "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:search_highlight_utils",
+    "//ui/webui/resources/js:util",
   ]
   externs_list = [ "$externs_path/pending_polymer.js" ]
 }
diff --git a/chrome/browser/resources/settings/search_settings.js b/chrome/browser/resources/settings/search_settings.js
index 60facec..24c584b 100644
--- a/chrome/browser/resources/settings/search_settings.js
+++ b/chrome/browser/resources/settings/search_settings.js
@@ -103,11 +103,26 @@
             bubbles.push(bubble);
           }
 
-          // Don't highlight <select> nodes, yellow rectangles can't be
-          // displayed within an <option>.
-          // TODO(dpapad): highlight <select> controls with a search bubble
-          // instead.
-          if (node.parentNode.nodeName != 'OPTION') {
+          if (node.parentNode.nodeName === 'OPTION') {
+            const select = node.parentNode.parentNode;
+            assert(select.nodeName === 'SELECT');
+
+            // TODO(crbug.com/355446): support showing bubbles inside subpages.
+            // Currently, they're incorrectly positioned and there's no great
+            // signal at which to know when to reposition them (because every
+            // page asynchronously loads/renders things differently).
+            const isSubpage = n => n.nodeName === 'SETTINGS-SUBPAGE';
+            if (findAncestor(select, isSubpage, true)) {
+              return;
+            }
+
+            const bubble = cr.search_highlight_utils.highlightControlWithBubble(
+                select, textContent.match(request.regExp)[0],
+                /*horizontallyCenter=*/ true);
+            if (bubble) {
+              bubbles.push(bubble);
+            }
+          } else {
             request.addTextObserver(node);
             highlights.push(cr.search_highlight_utils.highlight(
                 node, textContent.split(request.regExp)));
diff --git a/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc
index 62b2fa7c..6badcd77 100644
--- a/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc
+++ b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc
@@ -54,48 +54,48 @@
 
 // Extension manifests to override settings.
 const char kManifestNoOverride[] =
-    "{"
-    "  'name': 'Safe Extension',"
-    "  'version': '1',"
-    "  'manifest_version': 2"
-    "}";
+    R"({
+         "name": "Safe Extension",
+         "version": "1",
+         "manifest_version": 2
+       })";
 
 const char kManifestToOverrideHomepage[] =
-    "{"
-    "  'name': 'Homepage Extension',"
-    "  'version': '1',"
-    "  'manifest_version': 2,"
-    "  'chrome_settings_overrides' : {"
-    "    'homepage': '%s'"
-    "  }"
-    "}";
+    R"({
+         "name": "Homepage Extension",
+         "version": "1",
+         "manifest_version": 2,
+         "chrome_settings_overrides" : {
+           "homepage": "%s"
+         }
+       })";
 
 const char kManifestToOverrideSearch[] =
-    "{"
-    "  'name': 'Search Extension',"
-    "  'version': '0.1',"
-    "  'manifest_version': 2,"
-    "  'chrome_settings_overrides': {"
-    "    'search_provider': {"
-    "        'name': 'name',"
-    "        'keyword': 'keyword',"
-    "        'search_url': '%s',"
-    "        'favicon_url': 'http://someplace.com/favicon.ico',"
-    "        'encoding': 'UTF-8',"
-    "        'is_default': true"
-    "    }"
-    "  }"
-    "}";
+    R"({
+         "name": "Search Extension",
+         "version": "0.1",
+         "manifest_version": 2,
+         "chrome_settings_overrides": {
+           "search_provider": {
+              "name": "name",
+              "keyword": "keyword",
+              "search_url": "%s",
+              "favicon_url": "http://someplace.com/favicon.ico",
+              "encoding": "UTF-8",
+              "is_default": true
+           }
+         }
+       })";
 
 const char kManifestToOverrideStartupUrls[] =
-    "{"
-    "  'name': 'Startup URLs Extension',"
-    "  'version': '1',"
-    "  'manifest_version': 2,"
-    "  'chrome_settings_overrides' : {"
-    "    'startup_pages': ['%s']"
-    "  }"
-    "}";
+    R"({
+         "name": "Startup URLs Extension",
+         "version": "1",
+         "manifest_version": 2,
+         "chrome_settings_overrides" : {
+           "startup_pages": ["%s"]
+         }
+       })";
 
 class SettingsResetPromptModelBrowserTest
     : public extensions::ExtensionBrowserTest {
@@ -183,7 +183,7 @@
   void LoadManifest(const std::string& manifest,
                     const Extension** out_extension) {
     extensions::TestExtensionDir extension_dir;
-    extension_dir.WriteManifestWithSingleQuotes(manifest);
+    extension_dir.WriteManifest(manifest);
     *out_extension = LoadExtension(extension_dir.UnpackedPath());
     ASSERT_TRUE(*out_extension);
   }
diff --git a/chrome/browser/signin/signin_ui_util_unittest.cc b/chrome/browser/signin/signin_ui_util_unittest.cc
index 45f2c90..d5477ab2 100644
--- a/chrome/browser/signin/signin_ui_util_unittest.cc
+++ b/chrome/browser/signin/signin_ui_util_unittest.cc
@@ -471,8 +471,8 @@
       active_contents->GetVisibleURL());
 }
 
-// TODO(https://crbug.com/1014790): Timeout on Mac10.12.
-#if defined(OS_MACOSX)
+// TODO(https://crbug.com/1014790): Timeout on Mac10.12 and Win7 x64.
+#if defined(OS_MACOSX) || defined(OS_WIN)
 #define MAYBE_GetAccountsForDicePromos DISABLED_GetAccountsForDicePromos
 #else
 #define MAYBE_GetAccountsForDicePromos GetAccountsForDicePromos
@@ -568,8 +568,8 @@
       *profile_manager()->profile_attributes_storage(), profile()));
 }
 
-// TODO(https://crbug.com/1014790): Timeout on Mac10.12.
-#if defined(OS_MACOSX)
+// TODO(https://crbug.com/1014790): Timeout on Mac10.12 and Win7 x64.
+#if defined(OS_MACOSX) || defined(OS_WIN)
 #define MAYBE_ShouldShowAnimatedIdentityOnOpeningWindow_ReturnsFalseForSingleProfileSingleSignin \
   DISABLED_ShouldShowAnimatedIdentityOnOpeningWindow_ReturnsFalseForSingleProfileSingleSignin
 #else
diff --git a/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc b/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
index b339dd3..9ba1e34 100644
--- a/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
+++ b/chrome/browser/ui/extensions/blocked_action_bubble_browsertest.cc
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/feature_list.h"
 #include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/ui/browser.h"
@@ -12,6 +14,7 @@
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/test_navigation_observer.h"
@@ -28,6 +31,7 @@
   void ShowUi(const std::string& name) override;
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   base::AutoReset<bool> disable_toolbar_animations_;
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionBlockedActionsBubbleTest);
@@ -36,7 +40,11 @@
 ExtensionBlockedActionsBubbleTest::ExtensionBlockedActionsBubbleTest()
     : disable_toolbar_animations_(
           &ToolbarActionsBar::disable_animations_for_testing_,
-          true) {}
+          true) {
+  // This code path only works for the old toolbar. The new toolbar is
+  // exercised in extensions_menu_view_browsertest.cc.
+  scoped_feature_list_.InitAndDisableFeature(features::kExtensionsToolbarMenu);
+}
 ExtensionBlockedActionsBubbleTest::~ExtensionBlockedActionsBubbleTest() =
     default;
 
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 87f066a..362af42c9 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -331,7 +331,7 @@
 }
 
 bool ExtensionActionViewController::GetExtensionCommand(
-    extensions::Command* command) {
+    extensions::Command* command) const {
   DCHECK(command);
   if (!ExtensionIsValid())
     return false;
@@ -345,6 +345,29 @@
       extension_->id(), CommandService::ACTIVE, command, NULL);
 }
 
+bool ExtensionActionViewController::CanHandleAccelerators() const {
+  if (!ExtensionIsValid())
+    return false;
+
+#if DCHECK_IS_ON()
+  {
+    extensions::Command command;
+    DCHECK(GetExtensionCommand(&command));
+  }
+#endif
+
+  // Page action accelerators are enabled if and only if the page action is
+  // enabled ("visible" in legacy terms) on the given tab. Other actions can
+  // always accept accelerators.
+  // TODO(devlin): Have all actions behave similarly; this should likely mean
+  // always checking IsEnabled(). It's weird to use a keyboard shortcut on a
+  // disabled action (in most cases, this will result in opening the context
+  // menu).
+  if (extension_action_->action_type() == extensions::ActionInfo::TYPE_PAGE)
+    return IsEnabled(view_delegate_->GetCurrentWebContents());
+  return true;
+}
+
 std::unique_ptr<IconWithBadgeImageSource>
 ExtensionActionViewController::GetIconImageSourceForTesting(
     content::WebContents* web_contents,
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index 73e8e39c..1750607 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -79,7 +79,14 @@
 
   // Populates |command| with the command associated with |extension|, if one
   // exists. Returns true if |command| was populated.
-  bool GetExtensionCommand(extensions::Command* command);
+  bool GetExtensionCommand(extensions::Command* command) const;
+
+  // Returns true if this controller can handle accelerators (i.e., keyboard
+  // commands) on the currently-active WebContents.
+  // This must only be called if the extension has an associated command.
+  // TODO(devlin): Move accelerator logic out of the platform delegate and into
+  // this class.
+  bool CanHandleAccelerators() const;
 
   const extensions::Extension* extension() const { return extension_.get(); }
   Browser* browser() { return browser_; }
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc b/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
index 2c0212a3..34e8695 100644
--- a/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
+++ b/chrome/browser/ui/extensions/extension_message_bubble_browsertest.cc
@@ -60,17 +60,18 @@
     const std::string& settings_override_value) {
   DCHECK(!custom_extension_dir_);
   custom_extension_dir_ = std::make_unique<extensions::TestExtensionDir>();
-  std::string manifest = base::StringPrintf(
-    "{\n"
-    "  'name': 'settings override',\n"
-    "  'version': '0.1',\n"
-    "  'manifest_version': 2,\n"
-    "  'description': 'controls settings',\n"
-    "  'chrome_settings_overrides': {\n"
-    "    %s\n"
-    "  }\n"
-    "}", settings_override_value.c_str());
-  custom_extension_dir_->WriteManifestWithSingleQuotes(manifest);
+  constexpr char kManifestTemplate[] =
+      R"({
+           "name": "settings override",
+           "version": "0.1",
+           "manifest_version": 2,
+           "description": "controls settings",
+           "chrome_settings_overrides": {
+             %s
+           }
+         })";
+  custom_extension_dir_->WriteManifest(
+      base::StringPrintf(kManifestTemplate, settings_override_value.c_str()));
   ASSERT_TRUE(LoadExtension(custom_extension_dir_->UnpackedPath()));
 }
 
@@ -279,7 +280,7 @@
 void ExtensionMessageBubbleBrowserTest::TestControlledHomeBubbleShown() {
   browser()->profile()->GetPrefs()->SetBoolean(prefs::kShowHomeButton, true);
 
-  const char kHomePage[] = "'homepage': 'https://www.google.com'\n";
+  const char kHomePage[] = R"("homepage": "https://www.google.com")";
   AddSettingsOverrideExtension(kHomePage);
 
   CheckBubbleIsNotPresent(browser(), false, false);
@@ -294,14 +295,14 @@
 
 void ExtensionMessageBubbleBrowserTest::TestControlledSearchBubbleShown() {
   const char kSearchProvider[] =
-      "'search_provider': {\n"
-      "  'search_url': 'https://www.google.com/search?q={searchTerms}',\n"
-      "  'is_default': true,\n"
-      "  'favicon_url': 'https://www.google.com/favicon.icon',\n"
-      "  'keyword': 'TheGoogs',\n"
-      "  'name': 'Google',\n"
-      "  'encoding': 'UTF-8'\n"
-      "}\n";
+      R"("search_provider": {
+           "search_url": "https://www.google.com/search?q={searchTerms}",
+           "is_default": true,
+           "favicon_url": "https://www.google.com/favicon.icon",
+           "keyword": "TheGoogs",
+           "name": "Google",
+           "encoding": "UTF-8"
+         })";
   AddSettingsOverrideExtension(kSearchProvider);
 
   CheckBubbleIsNotPresent(browser(), false, false);
diff --git a/chrome/browser/ui/messages/android/java/res/layout/snackbar.xml b/chrome/browser/ui/messages/android/java/res/layout/snackbar.xml
index 54a4a45..d77c874 100644
--- a/chrome/browser/ui/messages/android/java/res/layout/snackbar.xml
+++ b/chrome/browser/ui/messages/android/java/res/layout/snackbar.xml
@@ -20,30 +20,32 @@
         android:background="@drawable/infobar_shadow_top" />
 
     <View
-        android:id="@+id/snackbar_shadow_left"
+        android:id="@id/snackbar_shadow_left"
         android:layout_width="@dimen/snackbar_shadow_height"
         android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignTop="@id/snackbar_shadow_top"
+        android:layout_alignBottom="@+id/snackbar"
         android:background="@drawable/infobar_shadow_left"
         android:visibility="gone" />
 
     <View
-        android:id="@+id/snackbar_shadow_right"
+        android:id="@id/snackbar_shadow_right"
         android:layout_width="@dimen/snackbar_shadow_height"
         android:layout_height="wrap_content"
         android:layout_alignParentEnd="true"
         android:layout_alignTop="@id/snackbar_shadow_top"
+        android:layout_alignBottom="@+id/snackbar"
         android:background="@drawable/infobar_shadow_left"
         android:scaleX="-1"
         android:visibility="gone" />
 
     <LinearLayout
-        android:id="@+id/snackbar"
+        android:id="@id/snackbar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="@dimen/snackbar_min_height"
-        android:layout_below="@+id/snackbar_shadow_top"
+        android:layout_below="@id/snackbar_shadow_top"
         android:layout_toEndOf="@id/snackbar_shadow_left"
         android:layout_toStartOf="@id/snackbar_shadow_right"
         android:orientation="horizontal" >
diff --git a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
index 801fcb67..22d20bb4 100644
--- a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
@@ -82,6 +82,26 @@
   }
 }
 
+// A simple class to add a test failure if any console message comes in for
+// the given WebContents.
+class FailOnConsoleMessage : public content::WebContentsObserver {
+ public:
+  explicit FailOnConsoleMessage(content::WebContents* web_contents)
+      : WebContentsObserver(web_contents) {}
+  FailOnConsoleMessage(const FailOnConsoleMessage& other) = delete;
+  FailOnConsoleMessage& operator=(const FailOnConsoleMessage& other) = delete;
+  ~FailOnConsoleMessage() override = default;
+
+ private:
+  // content::WebContentsObserver:
+  void OnDidAddMessageToConsole(blink::mojom::ConsoleMessageLevel log_level,
+                                const base::string16& message,
+                                int32_t line_no,
+                                const base::string16& source_id) override {
+    ADD_FAILURE() << "Unexpected console message: " << message;
+  }
+};
+
 }  // namespace
 
 class LocalNTPDoodleTest : public InProcessBrowserTest {
@@ -218,8 +238,8 @@
   }
 
   void WaitForFadeIn(content::WebContents* tab, const std::string& id) {
-    content::ConsoleObserverDelegate console_observer(tab, "WaitForFadeIn");
-    tab->SetDelegate(&console_observer);
+    content::WebContentsConsoleObserver console_observer(tab);
+    console_observer.SetPattern("WaitForFadeIn");
 
     bool result = false;
     if (!instant_test_utils::GetBoolFromJS(
@@ -251,8 +271,8 @@
   }
 
   void WaitForLogoSwap(content::WebContents* tab, const std::string& id) {
-    content::ConsoleObserverDelegate console_observer(tab, "WaitForFadeIn");
-    tab->SetDelegate(&console_observer);
+    content::WebContentsConsoleObserver console_observer(tab);
+    console_observer.SetPattern("WaitForFadeIn");
 
     bool result = false;
     if (!instant_test_utils::GetBoolFromJS(
@@ -327,15 +347,14 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+
+  FailOnConsoleMessage console_observer(active_tab);
   base::HistogramTester histograms;
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   EXPECT_THAT(GetDimension(active_tab, "fakebox", "top"), Eq(kFakeboxTopPx));
   EXPECT_THAT(GetComputedOpacity(active_tab, "logo-default"), Eq(1.0));
   EXPECT_THAT(GetComputedOpacity(active_tab, "logo-doodle"), Eq(0.0));
-  EXPECT_THAT(console_observer.message(), IsEmpty());
 
   histograms.ExpectTotalCount("NewTabPage.LogoShown", 0);
   histograms.ExpectTotalCount("NewTabPage.LogoShown.FromCache", 0);
@@ -353,15 +372,13 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   base::HistogramTester histograms;
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   EXPECT_THAT(GetDimension(active_tab, "fakebox", "top"), Eq(kFakeboxTopPx));
   EXPECT_THAT(GetComputedOpacity(active_tab, "logo-default"), Eq(1.0));
   EXPECT_THAT(GetComputedOpacity(active_tab, "logo-doodle"), Eq(0.0));
-  EXPECT_THAT(console_observer.message(), IsEmpty());
 
   histograms.ExpectTotalCount("NewTabPage.LogoShown", 0);
   histograms.ExpectTotalCount("NewTabPage.LogoShown.FromCache", 0);
@@ -384,8 +401,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   base::HistogramTester histograms;
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
@@ -399,7 +415,6 @@
   EXPECT_THAT(GetElementProperty(active_tab, "logo-doodle-image", "title"),
               Eq<std::string>("Chromium"));
   // TODO(sfiera): check href by clicking on button.
-  EXPECT_THAT(console_observer.message(), IsEmpty());
 
   histograms.ExpectTotalCount("NewTabPage.LogoShown", 1);
   histograms.ExpectBucketCount("NewTabPage.LogoShown", kLogoImpressionStatic,
@@ -763,8 +778,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   base::HistogramTester histograms;
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
@@ -802,8 +816,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   EXPECT_FALSE(ElementExists(active_tab, "ddlsb"));
@@ -832,8 +845,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   EXPECT_FALSE(ElementExists(active_tab, "ddlsb"));
@@ -862,8 +874,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   EXPECT_FALSE(DialogIsOpen(active_tab, "ddlsd"));
@@ -1249,9 +1260,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::WebContentsDelegate* original_delegate = active_tab->GetDelegate();
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   ASSERT_THAT(GetComputedOpacity(active_tab, "logo-doodle"), Eq(1.0));
@@ -1260,9 +1269,6 @@
   std::string response = WaitForDdllogResponse(active_tab, 1);
   EXPECT_EQ("target_url_params a=b&c=d", response);
 
-  // Before clicking on the Doodle, re-attach the original WebContentsDelegate,
-  // otherwise setting 'window.location' doesn't have any effect.
-  active_tab->SetDelegate(original_delegate);
   content::TestNavigationObserver nav_observer(active_tab);
   ASSERT_TRUE(content::ExecuteScript(
       active_tab, "document.getElementById('logo-doodle-button').click();"));
@@ -1273,8 +1279,6 @@
   ASSERT_TRUE(instant_test_utils::GetStringFromJS(
       active_tab, "document.location.href", &target_url));
   EXPECT_EQ(on_click_url.spec() + "?a=b&c=d", target_url);
-
-  EXPECT_THAT(console_observer.message(), IsEmpty());
 }
 
 IN_PROC_BROWSER_TEST_F(LocalNTPDoodleTest, ShouldLogForAnimatedDoodle) {
@@ -1305,9 +1309,7 @@
   // Open a new blank tab, then go to NTP and listen for console messages.
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
-  content::WebContentsDelegate* original_delegate = active_tab->GetDelegate();
-  content::ConsoleObserverDelegate console_observer(active_tab, "*");
-  active_tab->SetDelegate(&console_observer);
+  FailOnConsoleMessage console_observer(active_tab);
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
 
   ASSERT_THAT(GetComputedOpacity(active_tab, "logo-doodle"), Eq(1.0));
@@ -1328,10 +1330,6 @@
   std::string anim_response = WaitForDdllogResponse(active_tab, 2);
   EXPECT_EQ("target_url_params a=b&c=d", anim_response);
 
-  // Before clicking on the Doodle, re-attach the original WebContentsDelegate,
-  // otherwise setting 'window.location' doesn't seem to have any effect for
-  // some reason.
-  active_tab->SetDelegate(original_delegate);
   content::TestNavigationObserver nav_observer(active_tab);
   ASSERT_TRUE(content::ExecuteScript(
       active_tab, "document.getElementById('logo-doodle-button').click();"));
@@ -1342,8 +1340,6 @@
   ASSERT_TRUE(instant_test_utils::GetStringFromJS(
       active_tab, "document.location.href", &target_url));
   EXPECT_EQ(on_click_url.spec() + "?a=b&c=d", target_url);
-
-  EXPECT_THAT(console_observer.message(), IsEmpty());
 }
 
 IN_PROC_BROWSER_TEST_F(LocalNTPDoodleTest, ShouldNotMoveFakeboxForIframeSizes) {
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
index b8d73f5..a20d6252 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_unittest.cc
@@ -619,13 +619,13 @@
       extensions::ExtensionRegistry::Get(profile());
 
   extensions::TestExtensionDir ext_dir;
-  const char kManifest[] =
-      "{"
-      "  'name': 'Test',"
-      "  'version': '1',"
-      "  'manifest_version': 2"
-      "}";
-  ext_dir.WriteManifestWithSingleQuotes(kManifest);
+  constexpr char kManifest[] =
+      R"({
+           "name": "Test",
+           "version": "1",
+           "manifest_version": 2
+         })";
+  ext_dir.WriteManifest(kManifest);
 
   scoped_refptr<extensions::UnpackedInstaller> installer =
       extensions::UnpackedInstaller::Create(service);
@@ -657,15 +657,15 @@
   // Replace the extension's valid manifest with one containing errors. In this
   // case, the error is that both the 'browser_action' and 'page_action' keys
   // are specified instead of only one.
-  const char kManifestWithErrors[] =
-      "{"
-      "  'name': 'Test',"
-      "  'version': '1',"
-      "  'manifest_version': 2,"
-      "  'page_action' : {},"
-      "  'browser_action' : {}"
-      "}";
-  ext_dir.WriteManifestWithSingleQuotes(kManifestWithErrors);
+  constexpr char kManifestWithErrors[] =
+      R"({
+           "name": "Test",
+           "version": "1",
+           "manifest_version": 2,
+           "page_action" : {},
+           "browser_action" : {}
+         })";
+  ext_dir.WriteManifest(kManifestWithErrors);
 
   // Reload the extension again. Check that the updated extension cannot be
   // loaded due to the manifest errors.
diff --git a/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc b/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
index ecaa5c4..7d12a54 100644
--- a/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
+++ b/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
@@ -117,11 +117,7 @@
 
 bool ExtensionActionPlatformDelegateViews::AcceleratorPressed(
     const ui::Accelerator& accelerator) {
-  // We shouldn't be handling any accelerators if the view is hidden, unless
-  // this is a browser action.
-  DCHECK(controller_->extension_action()->action_type() ==
-             ActionInfo::TYPE_BROWSER ||
-         GetDelegateViews()->GetAsView()->GetVisible());
+  DCHECK(controller_->CanHandleAccelerators());
 
   // Normal priority shortcuts must be handled via standard browser commands to
   // be processed at the proper time.
@@ -137,12 +133,7 @@
 }
 
 bool ExtensionActionPlatformDelegateViews::CanHandleAccelerators() const {
-  // Page actions can only handle accelerators when they are visible.
-  // Browser actions can handle accelerators even when not visible, since they
-  // might be hidden in an overflow menu.
-  return controller_->extension_action()->action_type() == ActionInfo::TYPE_PAGE
-             ? GetDelegateViews()->GetAsView()->GetVisible()
-             : true;
+  return controller_->CanHandleAccelerators();
 }
 
 void ExtensionActionPlatformDelegateViews::UnregisterCommand(
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
index 7b82f42e..f861dd60 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
@@ -25,14 +25,18 @@
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
 #include "ui/views/layout/animating_layout_manager.h"
 #include "ui/views/test/widget_test.h"
 
 class ExtensionsMenuViewBrowserTest : public DialogBrowserTest {
  protected:
+  Profile* profile() { return browser()->profile(); }
+
   void LoadTestExtension(const std::string& extension) {
-    extensions::ChromeTestExtensionLoader loader(browser()->profile());
+    extensions::ChromeTestExtensionLoader loader(profile());
     base::FilePath test_data_dir;
     base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
     extensions_.push_back(
@@ -50,6 +54,34 @@
   }
 
   void ShowUi(const std::string& name) override {
+    ui_test_name_ = name;
+
+    ClickExtensionsMenuButton();
+
+    if (name == "ReloadPageBubble") {
+      TriggerSingleExtensionButton();
+    }
+  }
+
+  bool VerifyUi() override {
+    DialogBrowserTest::VerifyUi();
+
+    if (ui_test_name_ == "ReloadPageBubble") {
+      // Clicking the extension should close the extensions menu, pop out the
+      // extension, and display the "reload this page" bubble.
+      ExtensionsToolbarContainer* const container =
+          BrowserView::GetBrowserViewForBrowser(browser())
+              ->toolbar()
+              ->extensions_container();
+      EXPECT_TRUE(container->action_bubble_public_for_testing());
+      EXPECT_FALSE(container->GetPoppedOutAction());
+      EXPECT_FALSE(ExtensionsMenuView::IsShowing());
+    }
+
+    return true;
+  }
+
+  void ClickExtensionsMenuButton() {
     ui::MouseEvent click_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                                base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0);
     BrowserView::GetBrowserViewForBrowser(browser())
@@ -103,6 +135,7 @@
     loop.Run();
   }
 
+  std::string ui_test_name_;
   base::test::ScopedFeatureList scoped_feature_list_;
   std::vector<scoped_refptr<const extensions::Extension>> extensions_;
 };
@@ -118,6 +151,50 @@
   ShowAndVerifyUi();
 }
 
+// Invokes the UI shown when a user has to reload a page in order to run an
+// extension.
+IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewBrowserTest,
+                       InvokeUi_ReloadPageBubble) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  extensions::TestExtensionDir test_dir;
+  // Load an extension that injects scripts at "document_start", which requires
+  // reloading the page to inject if permissions are withheld.
+  test_dir.WriteManifest(
+      R"({
+           "name": "Runs Script Everywhere",
+           "description": "An extension that runs script everywhere",
+           "manifest_version": 2,
+           "version": "0.1",
+           "content_scripts": [{
+             "matches": ["*://*/*"],
+             "js": ["script.js"],
+             "run_at": "document_start"
+           }]
+         })");
+  test_dir.WriteFile(FILE_PATH_LITERAL("script.js"),
+                     "console.log('injected!');");
+
+  scoped_refptr<const extensions::Extension> extension =
+      extensions::ChromeTestExtensionLoader(profile()).LoadExtension(
+          test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  extensions::ScriptingPermissionsModifier(profile(), extension)
+      .SetWithholdHostPermissions(true);
+
+  // Navigate to a page the extension wants to run on.
+  content::WebContents* tab =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  {
+    content::TestNavigationObserver observer(tab);
+    GURL url = embedded_test_server()->GetURL("example.com", "/title1.html");
+    ui_test_utils::NavigateToURL(browser(), url);
+    EXPECT_TRUE(observer.last_navigation_succeeded());
+  }
+
+  ShowAndVerifyUi();
+}
+
 IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewBrowserTest, TriggerPopup) {
   LoadTestExtension("extensions/simple_with_popup");
   ShowUi("");
@@ -150,7 +227,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewBrowserTest,
-                       ActivationWithReloadNeeded_Accept) {
+                       TriggeringExtensionClosesMenu) {
   LoadTestExtension("extensions/trigger_actions/browser_action");
   ShowUi("");
   VerifyUi();
@@ -215,8 +292,7 @@
   ASSERT_TRUE(embedded_test_server()->Start());
   LoadTestExtension("extensions/blocked_actions/content_scripts");
   auto extension = extensions_.back();
-  extensions::ScriptingPermissionsModifier modifier(browser()->profile(),
-                                                    extension);
+  extensions::ScriptingPermissionsModifier modifier(profile(), extension);
   modifier.SetWithholdHostPermissions(true);
 
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/vr/service/browser_xr_runtime.cc b/chrome/browser/vr/service/browser_xr_runtime.cc
index aacde2a9..bafaf3e 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.cc
+++ b/chrome/browser/vr/service/browser_xr_runtime.cc
@@ -246,7 +246,7 @@
       // Only support DOM overlay if the feature flag is enabled.
       if (feature ==
           device::mojom::XRSessionFeature::DOM_OVERLAY_FOR_HANDHELD_AR) {
-        return base::FeatureList::IsEnabled(features::kWebXrArDOMOverlay);
+        return base::FeatureList::IsEnabled(features::kWebXrIncubations);
       }
       return ContainsFeature(kARCoreDeviceFeatures, feature);
     case device::mojom::XRDeviceId::ORIENTATION_DEVICE_ID:
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 9bd2d50..f90857b 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -88,6 +88,8 @@
 const char kCarolUsername[] = "Carol";
 const char kCarolPassword[] = "test";
 const char kCarolAlternateUsername[] = "RealCarolUsername";
+const char kEmptyUsername[] = "";
+const char kEmptyUsernamePassword[] = "empty";
 
 const char kFormHTML[] =
     "<FORM id='LoginTestForm' action='http://www.bidule.com'>"
@@ -348,6 +350,8 @@
     username3_ = ASCIIToUTF16(kCarolUsername);
     password3_ = ASCIIToUTF16(kCarolPassword);
     alternate_username3_ = ASCIIToUTF16(kCarolAlternateUsername);
+    username4_ = ASCIIToUTF16(kEmptyUsername);
+    password4_ = ASCIIToUTF16(kEmptyUsernamePassword);
 
     FormFieldData username_field;
     username_field.name = ASCIIToUTF16(kUsernameName);
@@ -366,6 +370,9 @@
     PasswordAndMetadata password3;
     password3.password = password3_;
     fill_data_.additional_logins[username3_] = password3;
+    PasswordAndMetadata password4;
+    password3.password = password4_;
+    fill_data_.additional_logins[username4_] = password4;
 
     // We need to set the origin so it matches the frame URL and the action so
     // it matches the form action, otherwise we won't autocomplete.
@@ -615,7 +622,8 @@
   // suggestions, or only those starting with |username|.
   void CheckSuggestions(const std::string& username, bool show_all) {
     auto show_all_matches = [show_all](int options) {
-      return show_all == !!(options & autofill::SHOW_ALL);
+      return (show_all == ((options & autofill::SHOW_ALL) != 0)) ||
+             (show_all == ((options & autofill::IS_PASSWORD_FIELD) != 0));
     };
 
     EXPECT_CALL(fake_driver_,
@@ -775,10 +783,12 @@
   base::string16 username1_;
   base::string16 username2_;
   base::string16 username3_;
+  base::string16 username4_;
   base::string16 password1_;
   base::string16 password2_;
   base::string16 password3_;
   base::string16 alternate_username3_;
+  base::string16 password4_;
   PasswordFormFillData fill_data_;
 
   WebInputElement username_element_;
@@ -2364,7 +2374,7 @@
 
   SimulateSuggestionChoiceOfUsernameAndPassword(
       password_element_, base::string16(), ASCIIToUTF16(kAlicePassword));
-  CheckSuggestions(std::string(), false);
+  CheckSuggestions(std::string(), true);
   EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.Value().Utf16());
   EXPECT_TRUE(password_element_.IsAutofilled());
 }
@@ -2389,7 +2399,7 @@
 
   SimulateSuggestionChoiceOfUsernameAndPassword(
       password_element_, base::string16(), ASCIIToUTF16(kAlicePassword));
-  CheckSuggestions(std::string(), false);
+  CheckSuggestions(std::string(), true);
   EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.Value().Utf16());
   EXPECT_TRUE(password_element_.IsAutofilled());
 }
@@ -2647,7 +2657,7 @@
   // Simulate a user clicking on the password element. This should produce a
   // message.
   autofill_agent_->FormControlElementClicked(password_element_, true);
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 }
 
 // Tests that only the password field is autocompleted when the browser sends
@@ -3152,9 +3162,9 @@
   SimulateOnFillPasswordForm(fill_data_);
 
   SimulateElementClick("password1");
-  CheckSuggestions(std::string(), false);
+  CheckSuggestions(std::string(), true);
   SimulateElementClick("password2");
-  CheckSuggestions(std::string(), false);
+  CheckSuggestions(std::string(), true);
 }
 
 // Tests that password manager sees both autofill assisted and user entered
@@ -3255,7 +3265,7 @@
   // Simulate a user clicking on the password element. This should produce a
   // dropdown with suggestion of all available usernames.
   autofill_agent_->FormControlElementClicked(password_element_, false);
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 }
 
 // Tests that a suggestion dropdown is shown on each password field. But when a
@@ -3278,13 +3288,13 @@
   // Simulate a user clicking on the password elements. This should produce
   // dropdowns with suggestion of all available usernames.
   SimulateElementClick("password");
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 
   SimulateElementClick("newpassword");
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 
   SimulateElementClick("confirmpassword");
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 
   // The user chooses to autofill the current password field.
   EXPECT_TRUE(password_autofill_agent_->FillSuggestion(
@@ -3301,7 +3311,7 @@
   // But when the user clicks on the autofilled password field again it should
   // still produce a suggestion dropdown.
   SimulateElementClick("password");
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 }
 
 TEST_F(PasswordAutofillAgentTest, ShowAutofillSignaturesFlag) {
@@ -3345,7 +3355,7 @@
   // Simulate a user clicking on the password element. This should produce a
   // dropdown with suggestion of all available usernames.
   autofill_agent_->FormControlElementClicked(password_element_, false);
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 }
 
 // Checks that a same-document navigation form submission could have an empty
@@ -3577,7 +3587,7 @@
   password_autofill_agent_->FillPasswordForm(fill_data_);
   SimulateElementClick(kPasswordName);
   // Empty value because nothing was typed into the field.
-  CheckSuggestions("", false);
+  CheckSuggestions("", true);
 }
 
 // Tests that PSL matched password is not autofilled even when there is
@@ -3823,6 +3833,26 @@
   CheckUsernameSelection(3, 3);
 }
 
+// Fill on account select for credentials with empty usernames:
+// Do not refill usernames if non-empty username is already selected.
+TEST_F(PasswordAutofillAgentTest, NoUsernameCredential) {
+  SimulateOnFillPasswordForm(fill_data_);
+  ClearUsernameAndPasswordFields();
+  EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+  SimulateSuggestionChoiceOfUsernameAndPassword(password_element_,
+                                                ASCIIToUTF16(kAliceUsername),
+                                                ASCIIToUTF16(kAlicePassword));
+  CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_CALL(fake_driver_, ShowPasswordSuggestions);
+  SimulateSuggestionChoiceOfUsernameAndPassword(
+      password_element_, ASCIIToUTF16(kEmptyUsername),
+      ASCIIToUTF16(kEmptyUsernamePassword));
+
+  CheckTextFieldsDOMState(kAliceUsername, true, kEmptyUsernamePassword, true);
+}
+
 // Tests that any fields that have user input are not refilled on the next
 // call of FillPasswordForm.
 TEST_F(PasswordAutofillAgentTest, NoRefillOfUserInput) {
diff --git a/chrome/test/data/extensions/api_test/browser_action/close_background/background.js b/chrome/test/data/extensions/api_test/browser_action/close_background/background.js
index a0dc78a..368f82b 100644
--- a/chrome/test/data/extensions/api_test/browser_action/close_background/background.js
+++ b/chrome/test/data/extensions/api_test/browser_action/close_background/background.js
@@ -7,3 +7,5 @@
     window.close();
   });
 });
+
+chrome.test.sendMessage('ready');
diff --git a/chrome/test/data/prerender/prerender_alert_after_onload.html b/chrome/test/data/prerender/prerender_alert_after_onload.html
deleted file mode 100644
index dd54cdbe..0000000
--- a/chrome/test/data/prerender/prerender_alert_after_onload.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<html>
-  <!--
-  This test checks to make sure an alert after onLoad causes
-  prerendering to fail.
-  -->
-  <head>
-    <title>Prerender alert after onload cancellation</title>
-
-    <script language="javascript" type="text/javascript">
-      window.onload = function() {
-        // Delay the alert by an event loop iteration so the alert
-        // happens after the page has loaded, rather than just before.
-        setTimeout(alert, 0, "Testing prerender");
-      };
-    </script>
-  </head>
-  <body></body>
-</html>
diff --git a/chrome/test/data/prerender/prerender_alert_before_onload.html b/chrome/test/data/prerender/prerender_alert_before_onload.html
deleted file mode 100644
index a251f53..0000000
--- a/chrome/test/data/prerender/prerender_alert_before_onload.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<html>
-  <!--
-  This test checks to make sure an alert before onLoad causes
-  prerendering to fail.
-  -->
-  <head>
-    <title>Prerender alert before onload cancellation</title>
-
-    <script language="javascript" type="text/javascript">
-      alert("Testing prerender");
-    </script>
-  </head>
-  <body></body>
-</html>
diff --git a/chrome/test/data/webui/settings/search_settings_test.js b/chrome/test/data/webui/settings/search_settings_test.js
index 5fab6c8..bb0dfa1 100644
--- a/chrome/test/data/webui/settings/search_settings_test.js
+++ b/chrome/test/data/webui/settings/search_settings_test.js
@@ -66,8 +66,8 @@
 
     /**
      * Tests that a search hit within a <select> node causes the parent
-     * settings-section to be shown, but the DOM of the <select> is not
-     * modified.
+     * settings-section to be shown and the <select> to be highlighted by a
+     * bubble.
      */
     test('<select> highlighting', function() {
       document.body.innerHTML = `<settings-section hidden-by-search>
@@ -87,8 +87,8 @@
             assertFalse(section.hiddenBySearch);
 
             const highlightWrapper =
-                select.querySelector('.search-highlight-wrapper');
-            assertFalse(!!highlightWrapper);
+                section.querySelector('.search-highlight-wrapper');
+            assertTrue(!!highlightWrapper);
 
             // Check that original DOM structure is present even after search
             // highlights are cleared.
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index 0b06ad2..12307c8 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.4005.0-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-airmont-81-3987.0-1577100150-benchmark-81.0.4008.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 2942390..7e384c3d 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-81-3987.0-1576497268-benchmark-81.0.4001.0-r1.orderfile.xz
\ No newline at end of file
+chromeos-chrome-orderfile-field-81-3987.0-1577099071-benchmark-81.0.4005.0-r1.orderfile.xz
\ No newline at end of file
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 3dc9820..b3c6777 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -142,16 +142,17 @@
 // |current_username| as a prefix.
 bool CanShowSuggestion(const PasswordFormFillData& fill_data,
                        const base::string16& current_username,
-                       bool show_all) {
+                       bool show_all,
+                       bool is_password_field) {
   base::string16 current_username_lower = base::i18n::ToLower(current_username);
-  if (show_all ||
+  if (show_all || is_password_field ||
       base::StartsWith(base::i18n::ToLower(fill_data.username_field.value),
                        current_username_lower, base::CompareCase::SENSITIVE)) {
     return true;
   }
 
   for (const auto& login : fill_data.additional_logins) {
-    if (show_all ||
+    if (show_all || is_password_field ||
         base::StartsWith(base::i18n::ToLower(login.first),
                          current_username_lower,
                          base::CompareCase::SENSITIVE)) {
@@ -602,6 +603,7 @@
 
   if (IsUsernameAmendable(username_element,
                           element->IsPasswordFieldForAutofill()) &&
+      !(username.empty() && element->IsPasswordFieldForAutofill()) &&
       username_element.Value().Utf16() != username) {
     FillField(&username_element, username);
   }
@@ -880,12 +882,7 @@
   if (touch_to_fill_state_ == TouchToFillState::kIsShowing)
     return true;
 
-  // Chrome should never show more than one account for a password element since
-  // this implies that the username element cannot be modified. Thus even if
-  // |show_all| is true, check if the element in question is a password element
-  // for the call to ShowSuggestionPopup.
-  return ShowSuggestionPopup(*password_info, element,
-                             show_all && !element.IsPasswordFieldForAutofill(),
+  return ShowSuggestionPopup(*password_info, element, show_all,
                              element.IsPasswordFieldForAutofill());
 }
 
@@ -1443,11 +1440,15 @@
                                      ? base::string16()
                                      : user_input.Value().Utf16());
 
+  username_query_prefix_ = username_string;
+  if (!CanShowSuggestion(password_info.fill_data, username_string, show_all,
+                         show_on_password_field)) {
+    return false;
+  }
   GetPasswordManagerDriver()->ShowPasswordSuggestions(
       field.text_direction, username_string, options,
       render_frame()->ElementBoundsInWindow(user_input));
-  username_query_prefix_ = username_string;
-  return CanShowSuggestion(password_info.fill_data, username_string, show_all);
+  return true;
 }
 
 void PasswordAutofillAgent::CleanupOnDocumentShutdown() {
diff --git a/components/exo/wayland/zcr_color_space.cc b/components/exo/wayland/zcr_color_space.cc
index 2b54086..2c5f855 100644
--- a/components/exo/wayland/zcr_color_space.cc
+++ b/components/exo/wayland/zcr_color_space.cc
@@ -85,7 +85,7 @@
     // ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_ARIB_STD_B67
     gfx::ColorSpace::TransferID::ARIB_STD_B67,
     // ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_SMPTEST2084_NON_HDR
-    gfx::ColorSpace::TransferID::SMPTEST2084_NON_HDR,
+    gfx::ColorSpace::TransferID::SMPTEST2084,
     // ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_IEC61966_2_1_HDR
     gfx::ColorSpace::TransferID::IEC61966_2_1_HDR,
     // ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_LINEAR_HDR
diff --git a/components/viz/common/gl_scaler.cc b/components/viz/common/gl_scaler.cc
index 78b5725..c75e71f3 100644
--- a/components/viz/common/gl_scaler.cc
+++ b/components/viz/common/gl_scaler.cc
@@ -210,12 +210,6 @@
     transform = gfx::ColorTransform::NewColorTransform(
         scaling_color_space_, params_.output_color_space,
         gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
-    if (!transform->CanGetShaderSource()) {
-      NOTIMPLEMENTED() << "color transform from "
-                       << scaling_color_space_.ToString() << " to "
-                       << params_.output_color_space.ToString();
-      return false;
-    }
   }
   ScalerStage* const final_stage = chain.get();
   final_stage->set_shader_program(
@@ -247,12 +241,6 @@
     transform = gfx::ColorTransform::NewColorTransform(
         params_.source_color_space, scaling_color_space_,
         gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
-    if (!transform->CanGetShaderSource()) {
-      NOTIMPLEMENTED() << "color transform from "
-                       << params_.source_color_space.ToString() << " to "
-                       << scaling_color_space_.ToString();
-      return false;
-    }
     input_stage->set_shader_program(
         GetShaderProgram(input_stage->shader(), intermediate_texture_type,
                          transform.get(), kNoSwizzle));
diff --git a/components/viz/common/quads/render_pass_io.cc b/components/viz/common/quads/render_pass_io.cc
index 445b328..9266b8f 100644
--- a/components/viz/common/quads/render_pass_io.cc
+++ b/components/viz/common/quads/render_pass_io.cc
@@ -610,7 +610,6 @@
     MATCH_ENUM_CASE(TransferID, SMPTEST2084)
     MATCH_ENUM_CASE(TransferID, SMPTEST428_1)
     MATCH_ENUM_CASE(TransferID, ARIB_STD_B67)
-    MATCH_ENUM_CASE(TransferID, SMPTEST2084_NON_HDR)
     MATCH_ENUM_CASE(TransferID, IEC61966_2_1_HDR)
     MATCH_ENUM_CASE(TransferID, LINEAR_HDR)
     MATCH_ENUM_CASE(TransferID, CUSTOM)
@@ -689,7 +688,6 @@
   MATCH_ENUM_CASE(TransferID, SMPTEST2084)
   MATCH_ENUM_CASE(TransferID, SMPTEST428_1)
   MATCH_ENUM_CASE(TransferID, ARIB_STD_B67)
-  MATCH_ENUM_CASE(TransferID, SMPTEST2084_NON_HDR)
   MATCH_ENUM_CASE(TransferID, IEC61966_2_1_HDR)
   MATCH_ENUM_CASE(TransferID, LINEAR_HDR)
   MATCH_ENUM_CASE(TransferID, CUSTOM)
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 9188155..135c8bd4 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -18,8 +18,6 @@
     "display/bsp_tree.h",
     "display/bsp_walk_action.cc",
     "display/bsp_walk_action.h",
-    "display/color_lut_cache.cc",
-    "display/color_lut_cache.h",
     "display/damage_frame_annotator.cc",
     "display/damage_frame_annotator.h",
     "display/direct_renderer.cc",
diff --git a/components/viz/service/display/color_lut_cache.cc b/components/viz/service/display/color_lut_cache.cc
deleted file mode 100644
index e1c1312d6..0000000
--- a/components/viz/service/display/color_lut_cache.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/viz/service/display/color_lut_cache.h"
-
-#include <stdint.h>
-#include <cmath>
-#include <vector>
-
-#include "base/stl_util.h"
-#include "gpu/command_buffer/client/gles2_interface.h"
-#include "third_party/khronos/GLES2/gl2ext.h"
-#include "ui/gfx/color_transform.h"
-#include "ui/gfx/half_float.h"
-
-// After a LUT has not been used for this many frames, we release it.
-const uint32_t kMaxFramesUnused = 10;
-
-ColorLUTCache::ColorLUTCache(gpu::gles2::GLES2Interface* gl,
-                             bool texture_half_float_linear)
-    : lut_cache_(0),
-      gl_(gl),
-      texture_half_float_linear_(texture_half_float_linear) {}
-
-ColorLUTCache::~ColorLUTCache() {
-  GLuint textures[10];
-  size_t n = 0;
-  for (const auto& cache_entry : lut_cache_) {
-    textures[n++] = cache_entry.second.lut.texture;
-    if (n == base::size(textures)) {
-      gl_->DeleteTextures(n, textures);
-      n = 0;
-    }
-  }
-  if (n)
-    gl_->DeleteTextures(n, textures);
-}
-
-namespace {
-
-void FloatToLUT(const float* f, gfx::HalfFloat* out, size_t num) {
-  gfx::FloatToHalfFloat(f, out, num);
-}
-
-void FloatToLUT(float* f, unsigned char* out, size_t num) {
-  for (size_t i = 0; i < num; i++) {
-    out[i] = std::min<int>(255, std::max<int>(0, floorf(f[i] * 255.0f + 0.5f)));
-  }
-}
-
-}  // namespace
-
-template <typename T>
-unsigned int ColorLUTCache::MakeLUT(const gfx::ColorTransform* transform,
-                                    int lut_samples) {
-  int lut_entries = lut_samples * lut_samples * lut_samples;
-  float inverse = 1.0f / (lut_samples - 1);
-  std::vector<T> lut(lut_entries * 4);
-  std::vector<gfx::ColorTransform::TriStim> samples(lut_samples);
-  T* lutp = lut.data();
-  float one = 1.0f;
-  T alpha;
-  FloatToLUT(&one, &alpha, 1);
-  for (int v = 0; v < lut_samples; v++) {
-    for (int u = 0; u < lut_samples; u++) {
-      for (int y = 0; y < lut_samples; y++) {
-        samples[y].set_x(y * inverse);
-        samples[y].set_y(u * inverse);
-        samples[y].set_z(v * inverse);
-      }
-      transform->Transform(samples.data(), samples.size());
-      T* lutp2 = lutp + lut_samples;
-      FloatToLUT(reinterpret_cast<float*>(samples.data()), lutp2,
-                 lut_samples * 3);
-      for (int i = 0; i < lut_samples; i++) {
-        *(lutp++) = *(lutp2++);
-        *(lutp++) = *(lutp2++);
-        *(lutp++) = *(lutp2++);
-        *(lutp++) = alpha;
-      }
-    }
-  }
-
-  GLuint previously_bound_texture = 0;
-  GLuint lut_texture = 0;
-  gl_->GetIntegerv(GL_TEXTURE_BINDING_2D,
-                   reinterpret_cast<GLint*>(&previously_bound_texture));
-  gl_->GenTextures(1, &lut_texture);
-  gl_->BindTexture(GL_TEXTURE_2D, lut_texture);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-  gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lut_samples,
-                  lut_samples * lut_samples, 0, GL_RGBA,
-                  sizeof(T) == 1 ? GL_UNSIGNED_BYTE : GL_HALF_FLOAT_OES,
-                  lut.data());
-  gl_->BindTexture(GL_TEXTURE_2D, previously_bound_texture);
-  return lut_texture;
-}
-
-ColorLUTCache::LUT ColorLUTCache::GetLUT(const gfx::ColorTransform* transform) {
-  auto iter = lut_cache_.Get(transform);
-  if (iter != lut_cache_.end()) {
-    iter->second.last_used_frame = current_frame_;
-    return iter->second.lut;
-  }
-
-  LUT lut;
-  // If input is HDR, and the output is full range, we're going to need
-  // to produce values outside of 0-1, so we'll need to make a half-float
-  // LUT. Also, we'll need to build a larger lut to maintain accuracy.
-  // All LUT sizes should be odd as some transforms have a knee at 0.5.
-  if (transform->GetDstColorSpace().FullRangeEncodedValues() &&
-      transform->GetSrcColorSpace().IsHDR() && texture_half_float_linear_) {
-    lut.size = 37;
-    lut.texture = MakeLUT<uint16_t>(transform, lut.size);
-  } else {
-    lut.size = 17;
-    lut.texture = MakeLUT<unsigned char>(transform, lut.size);
-  }
-  lut_cache_.Put(transform, CacheVal(lut, current_frame_));
-  return lut;
-}
-
-void ColorLUTCache::Swap() {
-  current_frame_++;
-  while (!lut_cache_.empty() &&
-         current_frame_ - lut_cache_.rbegin()->second.last_used_frame >
-             kMaxFramesUnused) {
-    gl_->DeleteTextures(1, &lut_cache_.rbegin()->second.lut.texture);
-    lut_cache_.ShrinkToSize(lut_cache_.size() - 1);
-  }
-}
diff --git a/components/viz/service/display/color_lut_cache.h b/components/viz/service/display/color_lut_cache.h
deleted file mode 100644
index b437d2b..0000000
--- a/components/viz/service/display/color_lut_cache.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_COLOR_LUT_CACHE_H_
-#define COMPONENTS_VIZ_SERVICE_DISPLAY_COLOR_LUT_CACHE_H_
-
-#include <map>
-
-#include "base/containers/mru_cache.h"
-#include "base/macros.h"
-#include "components/viz/service/viz_service_export.h"
-#include "ui/gfx/color_space.h"
-
-namespace gfx {
-class ColorTransform;
-}
-
-namespace gpu {
-namespace gles2 {
-class GLES2Interface;
-}
-}  // namespace gpu
-
-class VIZ_SERVICE_EXPORT ColorLUTCache {
- public:
-  explicit ColorLUTCache(gpu::gles2::GLES2Interface* gl,
-                         bool texture_half_float_linear);
-  ~ColorLUTCache();
-
-  struct LUT {
-    unsigned int texture;
-    int size;
-  };
-
-  LUT GetLUT(const gfx::ColorTransform* transform);
-
-  // End of frame, assume all LUTs handed out are no longer used.
-  void Swap();
-
- private:
-  template <typename T>
-  unsigned int MakeLUT(const gfx::ColorTransform* transform, int lut_samples);
-
-  typedef const gfx::ColorTransform* CacheKey;
-
-  struct CacheVal {
-    CacheVal(LUT lut, uint32_t last_used_frame)
-        : lut(lut), last_used_frame(last_used_frame) {}
-    LUT lut;
-    uint32_t last_used_frame;
-  };
-
-  base::MRUCache<CacheKey, CacheVal> lut_cache_;
-  uint32_t current_frame_;
-  gpu::gles2::GLES2Interface* gl_;
-  bool texture_half_float_linear_;
-  DISALLOW_COPY_AND_ASSIGN(ColorLUTCache);
-};
-
-#endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_COLOR_LUT_CACHE_H_
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index f4b2fc3..43c5e25 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -350,10 +350,6 @@
       copier_(output_surface->context_provider(), &texture_deleter_),
       sync_queries_(gl_),
       bound_geometry_(NO_BINDING),
-      color_lut_cache_(gl_,
-                       output_surface_->context_provider()
-                           ->ContextCapabilities()
-                           .texture_half_float_linear),
       current_task_runner_(std::move(current_task_runner)) {
   DCHECK(gl_);
   DCHECK(context_support_);
@@ -3128,7 +3124,6 @@
       awaiting_release_overlay_textures_.erase(it);
     }
   }
-  color_lut_cache_.Swap();
 }
 
 void GLRenderer::BindFramebufferToOutputSurface() {
@@ -3294,14 +3289,6 @@
     };
     gl_->Uniform4fv(current_program_->viewport_location(), 1, viewport);
   }
-  if (current_program_->lut_texture_location() != -1) {
-    ColorLUTCache::LUT lut = color_lut_cache_.GetLUT(color_transform);
-    gl_->ActiveTexture(GL_TEXTURE5);
-    gl_->BindTexture(GL_TEXTURE_2D, lut.texture);
-    gl_->Uniform1i(current_program_->lut_texture_location(), 5);
-    gl_->Uniform1f(current_program_->lut_size_location(), lut.size);
-    gl_->ActiveTexture(GL_TEXTURE0);
-  }
 
   if (has_output_color_matrix) {
     DCHECK_NE(current_program_->output_color_matrix_location(), -1);
diff --git a/components/viz/service/display/gl_renderer.h b/components/viz/service/display/gl_renderer.h
index 7c4d7b5a..4f5e911 100644
--- a/components/viz/service/display/gl_renderer.h
+++ b/components/viz/service/display/gl_renderer.h
@@ -20,7 +20,6 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/tile_draw_quad.h"
 #include "components/viz/common/quads/yuv_video_draw_quad.h"
-#include "components/viz/service/display/color_lut_cache.h"
 #include "components/viz/service/display/direct_renderer.h"
 #include "components/viz/service/display/gl_renderer_copier.h"
 #include "components/viz/service/display/gl_renderer_draw_cache.h"
@@ -442,7 +441,6 @@
   bool force_drawing_frame_framebuffer_unflipped_ = false;
 
   BoundGeometry bound_geometry_;
-  ColorLUTCache color_lut_cache_;
 
   unsigned offscreen_stencil_renderbuffer_id_ = 0;
   gfx::Size offscreen_stencil_renderbuffer_size_;
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index 94bd87f..7dc43111 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -375,7 +375,7 @@
                                 NON_PREMULTIPLIED_ALPHA, true, true, false,
                                 false));
 
-    // Iterate over alpha plane, nv12, and color_lut parameters.
+    // Iterate over alpha plane and nv12 parameters.
     UVTextureMode uv_modes[2] = {UV_TEXTURE_MODE_UV, UV_TEXTURE_MODE_U_V};
     YUVAlphaTextureMode a_modes[2] = {YUV_NO_ALPHA_TEXTURE,
                                       YUV_HAS_ALPHA_TEXTURE};
diff --git a/components/viz/service/display/program_binding.cc b/components/viz/service/display/program_binding.cc
index c0d5c66f..0c246b05 100644
--- a/components/viz/service/display/program_binding.cc
+++ b/components/viz/service/display/program_binding.cc
@@ -164,11 +164,9 @@
   color_transform_ = nullptr;
   if (transform->IsIdentity()) {
     color_conversion_mode_ = COLOR_CONVERSION_MODE_NONE;
-  } else if (transform->CanGetShaderSource()) {
+  } else {
     color_conversion_mode_ = COLOR_CONVERSION_MODE_SHADER;
     color_transform_ = transform;
-  } else {
-    color_conversion_mode_ = COLOR_CONVERSION_MODE_LUT;
   }
 }
 
diff --git a/components/viz/service/display/program_binding.h b/components/viz/service/display/program_binding.h
index e3ae7eb..19bf651d 100644
--- a/components/viz/service/display/program_binding.h
+++ b/components/viz/service/display/program_binding.h
@@ -305,10 +305,6 @@
   int a_texture_location() const {
     return fragment_shader_.a_texture_location_;
   }
-  int lut_texture_location() const {
-    return fragment_shader_.lut_texture_location_;
-  }
-  int lut_size_location() const { return fragment_shader_.lut_size_location_; }
   int resource_multiplier_location() const {
     return fragment_shader_.resource_multiplier_location_;
   }
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 0edb550..5a0932d 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -4899,8 +4899,6 @@
     // Allow a difference of 2 bytes in comparison for shader-based transforms,
     // and 4 bytes for LUT-based transforms (determined empirically).
     cc::FuzzyPixelComparator comparator(false, 100.f, 0.f, 2.f, 2, 0);
-    if (!transform->CanGetShaderSource())
-      comparator = cc::FuzzyPixelComparator(false, 100.f, 0.f, 6.f, 6, 0);
     EXPECT_TRUE(
         this->RunPixelTest(&pass_list, &expected_output_colors, comparator));
   }
diff --git a/components/viz/service/display/shader.cc b/components/viz/service/display/shader.cc
index a86ed5b..879cb8f 100644
--- a/components/viz/service/display/shader.cc
+++ b/components/viz/service/display/shader.cc
@@ -450,10 +450,6 @@
       uniforms.push_back("color");
       break;
   }
-  if (color_conversion_mode_ == COLOR_CONVERSION_MODE_LUT) {
-    uniforms.push_back("lut_texture");
-    uniforms.push_back("lut_size");
-  }
   if (has_output_color_matrix_)
     uniforms.emplace_back("output_color_matrix");
 
@@ -516,10 +512,6 @@
       color_location_ = locations[index++];
       break;
   }
-  if (color_conversion_mode_ == COLOR_CONVERSION_MODE_LUT) {
-    lut_texture_location_ = locations[index++];
-    lut_size_location_ = locations[index++];
-  }
 
   if (has_output_color_matrix_)
     output_color_matrix_location_ = locations[index++];
@@ -1038,29 +1030,6 @@
 
   // Apply color conversion.
   switch (color_conversion_mode_) {
-    case COLOR_CONVERSION_MODE_LUT:
-      HDR("uniform sampler2D lut_texture;");
-      HDR("uniform float lut_size;");
-      HDR("vec4 LUT(sampler2D sampler, vec3 pos, float size) {");
-      HDR("  pos *= size - 1.0;");
-      HDR("  // Select layer");
-      HDR("  float layer = min(floor(pos.z), size - 2.0);");
-      HDR("  // Compress the xy coordinates so they stay within");
-      HDR("  // [0.5 .. 31.5] / N (assuming a LUT size of 17^3)");
-      HDR("  pos.xy = (pos.xy + vec2(0.5)) / size;");
-      HDR("  pos.y = (pos.y + layer) / size;");
-      HDR("  return mix(LutLookup(sampler, pos.xy),");
-      HDR("             LutLookup(sampler, pos.xy + vec2(0, 1.0 / size)),");
-      HDR("             pos.z - layer);");
-      HDR("}");
-      // Un-premultiply by alpha.
-      if (premultiply_alpha_mode_ != NON_PREMULTIPLIED_ALPHA) {
-        SRC("// un-premultiply alpha");
-        SRC("if (texColor.a > 0.0) texColor.rgb /= texColor.a;");
-      }
-      SRC("texColor.rgb = LUT(lut_texture, texColor.xyz, lut_size).xyz;");
-      SRC("texColor.rgb *= texColor.a;");
-      break;
     case COLOR_CONVERSION_MODE_SHADER:
       header += color_transform_->GetShaderSource();
       // Un-premultiply by alpha.
diff --git a/components/viz/service/display/shader.h b/components/viz/service/display/shader.h
index 9638bb1..6d154b62 100644
--- a/components/viz/service/display/shader.h
+++ b/components/viz/service/display/shader.h
@@ -130,10 +130,6 @@
 enum ColorConversionMode {
   // No color conversion is performed.
   COLOR_CONVERSION_MODE_NONE,
-  // Conversion is done directly from input RGB space (or YUV space if
-  // applicable) to output RGB space, via a 3D texture represented as a 2D
-  // texture.
-  COLOR_CONVERSION_MODE_LUT,
   // Conversion is done analytically in the shader.
   COLOR_CONVERSION_MODE_SHADER,
 };
@@ -312,10 +308,6 @@
   int resource_multiplier_location_ = -1;
   int resource_offset_location_ = -1;
 
-  // LUT YUV to color-converted RGB.
-  int lut_texture_location_ = -1;
-  int lut_size_location_ = -1;
-
  private:
   friend class Program;
 
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 91712c7..6d6857d8 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -2157,10 +2157,6 @@
     std::unique_ptr<gfx::ColorTransform> transform =
         gfx::ColorTransform::NewColorTransform(
             adjusted_src, dst, gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
-    // TODO(backer): Support lookup table transforms (e.g.
-    // COLOR_CONVERSION_MODE_LUT).
-    if (!transform->CanGetShaderSource())
-      return nullptr;
 
     const char* hdr = R"(
 uniform half offset;
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index fdb6d40..b57c047 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -2383,7 +2383,7 @@
   std::unique_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1));
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL(
-                   content::SlowDownloadHttpResponse::kFinishDownloadUrl)));
+                   content::SlowDownloadHttpResponse::kFinishSlowResponseUrl)));
   observer2->WaitForFinished();  // Wait for the third request.
   EXPECT_EQ(
       1u, observer2->NumDownloadsSeenInState(download::DownloadItem::COMPLETE));
@@ -2397,8 +2397,9 @@
   // source file.
   base::FilePath file1(download1->GetTargetFilePath());
   ASSERT_EQ(file1.DirName().MaybeAsASCII(), download1_path);
-  size_t file_size1 = content::SlowDownloadHttpResponse::kFirstDownloadSize +
-                      content::SlowDownloadHttpResponse::kSecondDownloadSize;
+  size_t file_size1 =
+      content::SlowDownloadHttpResponse::kFirstResponsePartSize +
+      content::SlowDownloadHttpResponse::kSecondResponsePartSize;
   std::string expected_contents(file_size1, '*');
   ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1));
 
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 0a0bc8a..fd4ea2b93 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -879,7 +879,7 @@
     host_resolver()->AddRule(kOriginOne, real_host);
     host_resolver()->AddRule(kOriginTwo, real_host);
     host_resolver()->AddRule(kOriginThree, real_host);
-    host_resolver()->AddRule(SlowDownloadHttpResponse::kSlowDownloadHostName,
+    host_resolver()->AddRule(SlowDownloadHttpResponse::kSlowResponseHostName,
                              real_host);
     host_resolver()->AddRule(TestDownloadHttpResponse::kTestDownloadHostName,
                              real_host);
@@ -1344,7 +1344,7 @@
   // we're in the expected state.
   download::DownloadItem* download = StartDownloadAndReturnItem(
       shell(), embedded_test_server()->GetURL(
-                   SlowDownloadHttpResponse::kSlowDownloadHostName,
+                   SlowDownloadHttpResponse::kSlowResponseHostName,
                    SlowDownloadHttpResponse::kUnknownSizeUrl));
   ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download->GetState());
 
@@ -1366,7 +1366,7 @@
   // we're in the expected state.
   download::DownloadItem* download1 = StartDownloadAndReturnItem(
       shell(), embedded_test_server()->GetURL(
-                   SlowDownloadHttpResponse::kSlowDownloadHostName,
+                   SlowDownloadHttpResponse::kSlowResponseHostName,
                    SlowDownloadHttpResponse::kUnknownSizeUrl));
   ASSERT_EQ(download::DownloadItem::IN_PROGRESS, download1->GetState());
 
@@ -1380,10 +1380,10 @@
 
   // Allow the first request to finish.
   std::unique_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1));
-  EXPECT_TRUE(NavigateToURL(shell(),
-                            embedded_test_server()->GetURL(
-                                SlowDownloadHttpResponse::kSlowDownloadHostName,
-                                SlowDownloadHttpResponse::kFinishDownloadUrl)));
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL(
+                   SlowDownloadHttpResponse::kSlowResponseHostName,
+                   SlowDownloadHttpResponse::kFinishSlowResponseUrl)));
 
   observer2->WaitForFinished();  // Wait for the third request.
 
@@ -1398,8 +1398,8 @@
   // |file1| should be full of '*'s, and |file2| should be the same as the
   // source file.
   base::FilePath file1(download1->GetTargetFilePath());
-  size_t file_size1 = SlowDownloadHttpResponse::kFirstDownloadSize +
-                      SlowDownloadHttpResponse::kSecondDownloadSize;
+  size_t file_size1 = SlowDownloadHttpResponse::kFirstResponsePartSize +
+                      SlowDownloadHttpResponse::kSecondResponsePartSize;
   std::string expected_contents(file_size1, '*');
   ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1));
 
@@ -1619,7 +1619,7 @@
   // Create a download that won't complete.
   download::DownloadItem* download = StartDownloadAndReturnItem(
       shell(), embedded_test_server()->GetURL(
-                   SlowDownloadHttpResponse::kSlowDownloadHostName,
+                   SlowDownloadHttpResponse::kSlowResponseHostName,
                    SlowDownloadHttpResponse::kUnknownSizeUrl));
 
   EXPECT_EQ(download::DownloadItem::IN_PROGRESS, download->GetState());
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 582ae65..82de7a3 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -182,12 +182,9 @@
           {wf::EnablePeriodicBackgroundSync, features::kPeriodicBackgroundSync,
            kEnableOnly},
           {wf::EnableWebXR, features::kWebXr, kUseFeatureState},
-          {wf::EnableWebXRARDOMOverlay, features::kWebXrArDOMOverlay,
-           kEnableOnly},
           {wf::EnableWebXRARModule, features::kWebXrArModule, kEnableOnly},
           {wf::EnableWebXRHitTest, features::kWebXrHitTest, kEnableOnly},
-          {wf::EnableWebXRAnchors, features::kWebXrAnchors, kEnableOnly},
-          {wf::EnableWebXRPlaneDetection, features::kWebXrPlaneDetection,
+          {wf::EnableWebXRIncubations, features::kWebXrIncubations,
            kEnableOnly},
           {wf::EnableFetchMetadata, network::features::kFetchMetadata,
            kUseFeatureState},
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index fe9fd904..c59d115 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -715,21 +715,13 @@
 const base::Feature kWebXrArModule{"WebXRARModule",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables access to anchors via WebXR API.
-const base::Feature kWebXrAnchors{"WebXRAnchors",
-                                  base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables access to raycasting against estimated XR scene geometry.
 const base::Feature kWebXrHitTest{"WebXRHitTest",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables access to planes detected in the user's environment.
-const base::Feature kWebXrPlaneDetection{"WebXRPlaneDetection",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Enables access to planes detected in the user's environment.
-const base::Feature kWebXrArDOMOverlay{"WebXRARDOMOverlay",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
+// Enables access to experimental WebXR features.
+const base::Feature kWebXrIncubations{"WebXRIncubations",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Start streaming scripts on script preload.
 const base::Feature kScriptStreamingOnPreload{"ScriptStreamingOnPreload",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 23a29843..6928c60 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -148,10 +148,8 @@
 CONTENT_EXPORT extern const base::Feature kWebUsb;
 CONTENT_EXPORT extern const base::Feature kWebXr;
 CONTENT_EXPORT extern const base::Feature kWebXrArModule;
-CONTENT_EXPORT extern const base::Feature kWebXrAnchors;
-CONTENT_EXPORT extern const base::Feature kWebXrArDOMOverlay;
 CONTENT_EXPORT extern const base::Feature kWebXrHitTest;
-CONTENT_EXPORT extern const base::Feature kWebXrPlaneDetection;
+CONTENT_EXPORT extern const base::Feature kWebXrIncubations;
 CONTENT_EXPORT extern const base::Feature kScriptStreamingOnPreload;
 CONTENT_EXPORT extern const base::Feature kTrustedDOMTypes;
 CONTENT_EXPORT extern const base::Feature kBrowserUseDisplayThreadPriority;
diff --git a/content/public/test/slow_download_http_response.cc b/content/public/test/slow_download_http_response.cc
index 31c297c..b28d395e 100644
--- a/content/public/test/slow_download_http_response.cc
+++ b/content/public/test/slow_download_http_response.cc
@@ -4,107 +4,46 @@
 
 #include "content/public/test/slow_download_http_response.h"
 
-#include <utility>
-
-#include "base/bind.h"
 #include "base/strings/stringprintf.h"
-#include "base/threading/thread_task_runner_handle.h"
 
 namespace content {
 
-namespace {
-
-static bool g_should_finish_download = false;
-
-void SendResponseBodyDone(const net::test_server::SendBytesCallback& send,
-                          net::test_server::SendCompleteCallback done);
-
-// Sends the response body with the given size.
-void SendResponseBody(const net::test_server::SendBytesCallback& send,
-                      net::test_server::SendCompleteCallback done,
-                      bool finish_download) {
-  int data_size = finish_download
-                      ? SlowDownloadHttpResponse::kSecondDownloadSize
-                      : SlowDownloadHttpResponse::kFirstDownloadSize;
-  std::string response(data_size, '*');
-
-  if (finish_download) {
-    send.Run(response, std::move(done));
-  } else {
-    send.Run(response,
-             base::BindOnce(&SendResponseBodyDone, send, std::move(done)));
-  }
-}
-
-// Called when the response body was sucessfully sent.
-void SendResponseBodyDone(const net::test_server::SendBytesCallback& send,
-                          net::test_server::SendCompleteCallback done) {
-  if (g_should_finish_download) {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&SendResponseBody, send, std::move(done), true),
-        base::TimeDelta::FromMilliseconds(100));
-  } else {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE, base::BindOnce(&SendResponseBodyDone, send, std::move(done)),
-        base::TimeDelta::FromMilliseconds(100));
-  }
-}
-
-}  // namespace
-
 // static
-const char SlowDownloadHttpResponse::kSlowDownloadHostName[] =
-    "url.handled.by.slow.download";
 const char SlowDownloadHttpResponse::kUnknownSizeUrl[] =
     "/download-unknown-size";
 const char SlowDownloadHttpResponse::kKnownSizeUrl[] = "/download-known-size";
-const char SlowDownloadHttpResponse::kFinishDownloadUrl[] = "/download-finish";
-
-const int SlowDownloadHttpResponse::kFirstDownloadSize = 1024 * 35;
-const int SlowDownloadHttpResponse::kSecondDownloadSize = 1024 * 10;
 
 // static
 std::unique_ptr<net::test_server::HttpResponse>
 SlowDownloadHttpResponse::HandleSlowDownloadRequest(
     const net::test_server::HttpRequest& request) {
-  if (request.relative_url != kUnknownSizeUrl &&
-      request.relative_url != kKnownSizeUrl &&
-      request.relative_url != kFinishDownloadUrl) {
-    return nullptr;
-  }
   auto response =
       std::make_unique<SlowDownloadHttpResponse>(request.relative_url);
+  if (!response->IsHandledUrl()) {
+    return nullptr;
+  }
   return std::move(response);
 }
 
 SlowDownloadHttpResponse::SlowDownloadHttpResponse(const std::string& url)
-    : url_(url) {}
+    : SlowHttpResponse(url) {}
 
 SlowDownloadHttpResponse::~SlowDownloadHttpResponse() = default;
 
-void SlowDownloadHttpResponse::SendResponse(
-    const net::test_server::SendBytesCallback& send,
-    net::test_server::SendCompleteCallback done) {
-  std::string response;
-  response.append("HTTP/1.1 200 OK\r\n");
-  if (base::LowerCaseEqualsASCII(kFinishDownloadUrl, url_)) {
-    response.append("Content-type: text/plain\r\n");
-    response.append("\r\n");
+bool SlowDownloadHttpResponse::IsHandledUrl() {
+  return (url() == SlowDownloadHttpResponse::kUnknownSizeUrl ||
+          url() == SlowDownloadHttpResponse::kKnownSizeUrl ||
+          SlowHttpResponse::IsHandledUrl());
+}
 
-    g_should_finish_download = true;
-    send.Run(response, std::move(done));
-  } else {
-    response.append("Content-type: application/octet-stream\r\n");
-    response.append("Cache-Control: max-age=0\r\n");
+void SlowDownloadHttpResponse::AddResponseHeaders(std::string* response) {
+  response->append("Content-type: application/octet-stream\r\n");
+  response->append("Cache-Control: max-age=0\r\n");
 
-    if (base::LowerCaseEqualsASCII(kKnownSizeUrl, url_)) {
-      response.append(base::StringPrintf(
-          "Content-Length: %d\r\n", kFirstDownloadSize + kSecondDownloadSize));
-    }
-    response.append("\r\n");
-    send.Run(response,
-             base::BindOnce(&SendResponseBody, send, std::move(done), false));
+  if (base::LowerCaseEqualsASCII(kKnownSizeUrl, url())) {
+    response->append(
+        base::StringPrintf("Content-Length: %d\r\n",
+                           kFirstResponsePartSize + kSecondResponsePartSize));
   }
 }
 
diff --git a/content/public/test/slow_download_http_response.h b/content/public/test/slow_download_http_response.h
index 8aaad8c..34e95c4 100644
--- a/content/public/test/slow_download_http_response.h
+++ b/content/public/test/slow_download_http_response.h
@@ -5,27 +5,16 @@
 #ifndef CONTENT_PUBLIC_TEST_SLOW_DOWNLOAD_HTTP_RESPONSE_H_
 #define CONTENT_PUBLIC_TEST_SLOW_DOWNLOAD_HTTP_RESPONSE_H_
 
-#include <set>
-#include <string>
-
-#include "net/test/embedded_test_server/http_request.h"
-#include "net/test/embedded_test_server/http_response.h"
+#include "content/public/test/slow_http_response.h"
 
 namespace content {
 
-/*
- * Download response that won't complete until |kFinishDownloadUrl| request is
- * received.
- */
-class SlowDownloadHttpResponse : public net::test_server::HttpResponse {
+// A subclass of SlowHttpResponse that serves a download.
+class SlowDownloadHttpResponse : public SlowHttpResponse {
  public:
   // Test URLs.
-  static const char kSlowDownloadHostName[];
   static const char kUnknownSizeUrl[];
   static const char kKnownSizeUrl[];
-  static const char kFinishDownloadUrl[];
-  static const int kFirstDownloadSize;
-  static const int kSecondDownloadSize;
 
   static std::unique_ptr<net::test_server::HttpResponse>
   HandleSlowDownloadRequest(const net::test_server::HttpRequest& request);
@@ -33,14 +22,12 @@
   SlowDownloadHttpResponse(const std::string& url);
   ~SlowDownloadHttpResponse() override;
 
-  // net::test_server::HttpResponse implementations.
-  void SendResponse(const net::test_server::SendBytesCallback& send,
-                    net::test_server::SendCompleteCallback done) override;
+  SlowDownloadHttpResponse(const SlowDownloadHttpResponse&) = delete;
+  SlowDownloadHttpResponse& operator=(const SlowDownloadHttpResponse&) = delete;
 
- private:
-  std::string url_;
-
-  DISALLOW_COPY_AND_ASSIGN(SlowDownloadHttpResponse);
+  // SlowHttpResponse:
+  bool IsHandledUrl() override;
+  void AddResponseHeaders(std::string* response) override;
 };
 
 }  // namespace content
diff --git a/content/public/test/slow_http_response.cc b/content/public/test/slow_http_response.cc
new file mode 100644
index 0000000..af548af
--- /dev/null
+++ b/content/public/test/slow_http_response.cc
@@ -0,0 +1,100 @@
+// 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 "content/public/test/slow_http_response.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace content {
+
+namespace {
+
+static bool g_should_finish_response = false;
+
+void SendResponseBodyDone(const net::test_server::SendBytesCallback& send,
+                          net::test_server::SendCompleteCallback done);
+
+// The response body is sent in two parts, of size |kFirstResponsePartSize| and
+// |kSecondResponsePartSize| respectively.
+void SendResponseBody(const net::test_server::SendBytesCallback& send,
+                      net::test_server::SendCompleteCallback done,
+                      bool finish_response) {
+  int data_size = finish_response ? SlowHttpResponse::kSecondResponsePartSize
+                                  : SlowHttpResponse::kFirstResponsePartSize;
+  std::string response(data_size, '*');
+
+  if (finish_response) {
+    send.Run(response, std::move(done));
+  } else {
+    send.Run(response,
+             base::BindOnce(&SendResponseBodyDone, send, std::move(done)));
+  }
+}
+
+// Called when the response body was successfully sent.
+void SendResponseBodyDone(const net::test_server::SendBytesCallback& send,
+                          net::test_server::SendCompleteCallback done) {
+  if (g_should_finish_response) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&SendResponseBody, send, std::move(done), true),
+        base::TimeDelta::FromMilliseconds(100));
+  } else {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, base::BindOnce(&SendResponseBodyDone, send, std::move(done)),
+        base::TimeDelta::FromMilliseconds(100));
+  }
+}
+
+}  // namespace
+
+// static
+const char SlowHttpResponse::kSlowResponseHostName[] =
+    "url.handled.by.slow.response";
+const char SlowHttpResponse::kSlowResponseUrl[] = "/slow-response";
+const char SlowHttpResponse::kFinishSlowResponseUrl[] = "/slow-response-finish";
+
+const int SlowHttpResponse::kFirstResponsePartSize = 1024 * 35;
+const int SlowHttpResponse::kSecondResponsePartSize = 1024 * 10;
+
+SlowHttpResponse::SlowHttpResponse(const std::string& url) : url_(url) {}
+
+SlowHttpResponse::~SlowHttpResponse() = default;
+
+bool SlowHttpResponse::IsHandledUrl() {
+  return url_ == kSlowResponseUrl || url_ == kFinishSlowResponseUrl;
+}
+
+void SlowHttpResponse::AddResponseHeaders(std::string* response) {
+  response->append("Content-type: text/html\r\n");
+}
+
+void SlowHttpResponse::SetStatusLine(std::string* response) {
+  response->append("HTTP/1.1 200 OK\r\n");
+}
+
+void SlowHttpResponse::SendResponse(
+    const net::test_server::SendBytesCallback& send,
+    net::test_server::SendCompleteCallback done) {
+  std::string response;
+  SetStatusLine(&response);
+  if (base::LowerCaseEqualsASCII(kFinishSlowResponseUrl, url_)) {
+    response.append("Content-type: text/plain\r\n");
+    response.append("\r\n");
+
+    g_should_finish_response = true;
+    send.Run(response, std::move(done));
+  } else {
+    AddResponseHeaders(&response);
+    response.append("Cache-Control: max-age=0\r\n");
+    response.append("\r\n");
+    send.Run(response,
+             base::BindOnce(&SendResponseBody, send, std::move(done), false));
+  }
+}
+
+}  // namespace content
diff --git a/content/public/test/slow_http_response.h b/content/public/test/slow_http_response.h
new file mode 100644
index 0000000..e2f2b867
--- /dev/null
+++ b/content/public/test/slow_http_response.h
@@ -0,0 +1,58 @@
+// 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 CONTENT_PUBLIC_TEST_SLOW_HTTP_RESPONSE_H_
+#define CONTENT_PUBLIC_TEST_SLOW_HTTP_RESPONSE_H_
+
+#include <string>
+
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace content {
+
+// An HTTP response that won't complete until a |kFinishSlowResponseUrl| request
+// is received, used to simulate a slow response.
+class SlowHttpResponse : public net::test_server::HttpResponse {
+ public:
+  // Test URLs.
+  static const char kSlowResponseHostName[];
+  static const char kSlowResponseUrl[];
+  static const char kFinishSlowResponseUrl[];
+  static const int kFirstResponsePartSize;
+  static const int kSecondResponsePartSize;
+
+  explicit SlowHttpResponse(const std::string& url);
+  ~SlowHttpResponse() override;
+
+  SlowHttpResponse(const SlowHttpResponse&) = delete;
+  SlowHttpResponse& operator=(const SlowHttpResponse&) = delete;
+
+  virtual bool IsHandledUrl();
+
+  // Subclasses can override this method to add custom HTTP response headers.
+  // These headers are only applied to the slow response itself, not the
+  // response to |kFinishSlowResponseUrl|.
+  virtual void AddResponseHeaders(std::string* response);
+
+  // Subclasses can override this method to write a custom status line; the
+  // default implementation sets a 200 OK response. This status code is applied
+  // only to the slow response itself, not the response to
+  // |kFinishSlowResponseUrl|.
+  virtual void SetStatusLine(std::string* response);
+
+  // net::test_server::HttpResponse implementations.
+  void SendResponse(const net::test_server::SendBytesCallback& send,
+                    net::test_server::SendCompleteCallback done) override;
+
+ protected:
+  const std::string& url() { return url_; }
+
+ private:
+  std::string url_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_TEST_SLOW_HTTP_RESPONSE_H_
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 14547768..6cb8f312 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -173,6 +173,8 @@
     "../public/test/simple_url_loader_test_helper.h",
     "../public/test/slow_download_http_response.cc",
     "../public/test/slow_download_http_response.h",
+    "../public/test/slow_http_response.cc",
+    "../public/test/slow_http_response.h",
     "../public/test/test_browser_context.cc",
     "../public/test/test_browser_context.h",
     "../public/test/test_content_client_initializer.cc",
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index f3abb38..1291d3d 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -244,3 +244,5 @@
 crbug.com/1037949 [ use-vulkan android android-chromium skia-renderer ] Pixel_Video_MP4_FourColors_Rot_270 [ Failure ]
 crbug.com/1037949 [ use-vulkan android android-chromium skia-renderer ] Pixel_Video_MP4_FourColors_Rot_90 [ Failure ]
 crbug.com/1037949 [ use-vulkan android android-chromium skia-renderer ] Pixel_Video_VP9 [ Failure ]
+
+crbug.com/1037654 [ use-vulkan android android-chromium skia-renderer ] Pixel_WebGLCopyImage [ Failure ]
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn
index a70f417..7803b5e 100644
--- a/device/fido/BUILD.gn
+++ b/device/fido/BUILD.gn
@@ -202,7 +202,6 @@
       "mac/make_credential_operation.h",
       "mac/make_credential_operation.mm",
       "mac/operation.h",
-      "mac/operation_base.h",
       "mac/touch_id_context.h",
       "mac/touch_id_context.mm",
       "mac/util.h",
diff --git a/device/fido/mac/get_assertion_operation.h b/device/fido/mac/get_assertion_operation.h
index 6a0ff4b..c05260e 100644
--- a/device/fido/mac/get_assertion_operation.h
+++ b/device/fido/mac/get_assertion_operation.h
@@ -5,13 +5,15 @@
 #ifndef DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_
 #define DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_
 
+#include "base/callback.h"
 #include "base/component_export.h"
 #include "base/mac/availability.h"
 #include "base/macros.h"
 #include "device/fido/authenticator_get_assertion_response.h"
 #include "device/fido/ctap_get_assertion_request.h"
 #include "device/fido/mac/keychain.h"
-#include "device/fido/mac/operation_base.h"
+#include "device/fido/mac/operation.h"
+#include "device/fido/mac/touch_id_context.h"
 
 namespace device {
 namespace fido {
@@ -27,17 +29,19 @@
 // For documentation on the keychain item metadata, see
 // |MakeCredentialOperation|.
 class API_AVAILABLE(macosx(10.12.2))
-    COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionOperation
-    : public OperationBase<CtapGetAssertionRequest,
-                           AuthenticatorGetAssertionResponse> {
+    COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionOperation : public Operation {
  public:
+  using Callback = base::OnceCallback<void(
+      CtapDeviceResponseCode,
+      base::Optional<AuthenticatorGetAssertionResponse>)>;
+
   GetAssertionOperation(CtapGetAssertionRequest request,
                         std::string metadata_secret,
                         std::string keychain_access_group,
                         Callback callback);
   ~GetAssertionOperation() override;
 
-  // OperationBase:
+  // Operation:
   void Run() override;
 
   // GetNextAssertion() may be called for a request with an empty allowList
@@ -45,11 +49,20 @@
   void GetNextAssertion(Callback callback);
 
  private:
-  const std::string& RpId() const override;
-  void PromptTouchIdDone(bool success) override;
+  void PromptTouchIdDone(bool success);
   base::Optional<AuthenticatorGetAssertionResponse> ResponseForCredential(
       const Credential& credential);
 
+  // The secret parameter passed to |CredentialMetadata| operations to encrypt
+  // or encode credential metadata for storage in the macOS keychain.
+  const std::string metadata_secret_;
+  const std::string keychain_access_group_;
+
+  const std::unique_ptr<TouchIdContext> touch_id_context_ =
+      TouchIdContext::Create();
+
+  const CtapGetAssertionRequest request_;
+  Callback callback_;
   std::list<Credential> matching_credentials_;
 
   DISALLOW_COPY_AND_ASSIGN(GetAssertionOperation);
diff --git a/device/fido/mac/get_assertion_operation.mm b/device/fido/mac/get_assertion_operation.mm
index 2867098..4cd7758 100644
--- a/device/fido/mac/get_assertion_operation.mm
+++ b/device/fido/mac/get_assertion_operation.mm
@@ -9,6 +9,7 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/bind.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
 #include "base/mac/scoped_cftyperef.h"
@@ -17,6 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "components/device_event_log/device_event_log.h"
 #include "device/fido/fido_constants.h"
+#include "device/fido/mac/credential_metadata.h"
 #include "device/fido/mac/keychain.h"
 #include "device/fido/mac/util.h"
 #include "device/fido/public_key_credential_descriptor.h"
@@ -34,65 +36,64 @@
                                              std::string metadata_secret,
                                              std::string keychain_access_group,
                                              Callback callback)
-    : OperationBase<CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>(
-          std::move(request),
-          std::move(metadata_secret),
-          std::move(keychain_access_group),
-          std::move(callback)) {}
+    : metadata_secret_(std::move(metadata_secret)),
+      keychain_access_group_(std::move(keychain_access_group)),
+      request_(std::move(request)),
+      callback_(std::move(callback)) {}
 GetAssertionOperation::~GetAssertionOperation() = default;
 
-const std::string& GetAssertionOperation::RpId() const {
-  return request().rp_id;
-}
-
 void GetAssertionOperation::Run() {
-  Init();
   // Display the macOS Touch ID prompt.
-  PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
-                                           base::UTF8ToUTF16(RpId())));
+  touch_id_context_->PromptTouchId(
+      l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
+                                 base::UTF8ToUTF16(request_.rp_id)),
+      base::BindOnce(&GetAssertionOperation::PromptTouchIdDone,
+                     base::Unretained(this)));
 }
 
 void GetAssertionOperation::PromptTouchIdDone(bool success) {
   if (!success) {
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied,
+                             base::nullopt);
     return;
   }
   std::set<std::vector<uint8_t>> allowed_credential_ids =
-      FilterInapplicableEntriesFromAllowList(request());
-  if (allowed_credential_ids.empty() && !request().allow_list.empty()) {
+      FilterInapplicableEntriesFromAllowList(request_);
+  if (allowed_credential_ids.empty() && !request_.allow_list.empty()) {
     // The caller checking
     // TouchIdAuthenticator::HasCredentialForGetAssertionRequest() should have
     // caught this.
     NOTREACHED();
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
+                             base::nullopt);
     return;
   }
-  const bool empty_allow_list = request().allow_list.empty();
+  const bool empty_allow_list = request_.allow_list.empty();
 
   std::list<Credential> credentials =
-      empty_allow_list ? FindResidentCredentialsInKeychain(
-                             keychain_access_group(), metadata_secret(), RpId(),
-                             authentication_context())
-                       : FindCredentialsInKeychain(
-                             keychain_access_group(), metadata_secret(), RpId(),
-                             allowed_credential_ids, authentication_context());
+      empty_allow_list
+          ? FindResidentCredentialsInKeychain(
+                keychain_access_group_, metadata_secret_, request_.rp_id,
+                touch_id_context_->authentication_context())
+          : FindCredentialsInKeychain(
+                keychain_access_group_, metadata_secret_, request_.rp_id,
+                allowed_credential_ids,
+                touch_id_context_->authentication_context());
 
   if (credentials.empty()) {
-    // TouchIdAuthenticator::HasCredentialForGetAssertionRequest() is invoked
-    // first to ensure this doesn't occur.
+    // TouchIdAuthenticator::HasCredentialForGetAssertionRequest() is
+    // invoked first to ensure this doesn't occur.
     NOTREACHED();
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
+                             base::nullopt);
     return;
   }
 
   base::Optional<AuthenticatorGetAssertionResponse> response =
       ResponseForCredential(credentials.front());
   if (!response) {
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
+                             base::nullopt);
     return;
   }
 
@@ -102,8 +103,8 @@
     matching_credentials_ = std::move(credentials);
   }
 
-  std::move(callback())
-      .Run(CtapDeviceResponseCode::kSuccess, std::move(*response));
+  std::move(callback_).Run(CtapDeviceResponseCode::kSuccess,
+                           std::move(*response));
 }
 
 void GetAssertionOperation::GetNextAssertion(Callback callback) {
@@ -123,8 +124,8 @@
 
 base::Optional<AuthenticatorGetAssertionResponse>
 GetAssertionOperation::ResponseForCredential(const Credential& credential) {
-  base::Optional<CredentialMetadata> metadata =
-      UnsealCredentialId(metadata_secret(), RpId(), credential.credential_id);
+  base::Optional<CredentialMetadata> metadata = UnsealCredentialId(
+      metadata_secret_, request_.rp_id, credential.credential_id);
   if (!metadata) {
     // The keychain query already filtered for the RP ID encoded under this
     // operation's metadata secret, so the credential id really should have
@@ -133,10 +134,10 @@
     return base::nullopt;
   }
 
-  AuthenticatorData authenticator_data =
-      MakeAuthenticatorData(RpId(), /*attested_credential_data=*/base::nullopt);
+  AuthenticatorData authenticator_data = MakeAuthenticatorData(
+      request_.rp_id, /*attested_credential_data=*/base::nullopt);
   base::Optional<std::vector<uint8_t>> signature = GenerateSignature(
-      authenticator_data, request().client_data_hash, credential.private_key);
+      authenticator_data, request_.client_data_hash, credential.private_key);
   if (!signature) {
     FIDO_LOG(ERROR) << "GenerateSignature failed";
     return base::nullopt;
diff --git a/device/fido/mac/make_credential_operation.h b/device/fido/mac/make_credential_operation.h
index 85fd88c..5495b6371 100644
--- a/device/fido/mac/make_credential_operation.h
+++ b/device/fido/mac/make_credential_operation.h
@@ -5,12 +5,14 @@
 #ifndef DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_
 #define DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_
 
+#include "base/callback.h"
 #include "base/component_export.h"
 #include "base/mac/availability.h"
 #include "base/macros.h"
 #include "device/fido/authenticator_make_credential_response.h"
 #include "device/fido/ctap_make_credential_request.h"
-#include "device/fido/mac/operation_base.h"
+#include "device/fido/mac/operation.h"
+#include "device/fido/mac/touch_id_context.h"
 
 namespace device {
 namespace fido {
@@ -43,22 +45,42 @@
 //  separate from any other data that Chrome may store in the keychain in
 //  the future.
 class API_AVAILABLE(macosx(10.12.2))
-    COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialOperation
-    : public OperationBase<CtapMakeCredentialRequest,
-                           AuthenticatorMakeCredentialResponse> {
+    COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialOperation : public Operation {
  public:
+  using Callback = base::OnceCallback<void(
+      CtapDeviceResponseCode,
+      base::Optional<AuthenticatorMakeCredentialResponse>)>;
+
   MakeCredentialOperation(CtapMakeCredentialRequest request,
                           std::string profile_id,
                           std::string keychain_access_group,
                           Callback callback);
   ~MakeCredentialOperation() override;
 
+  // Operation:
   void Run() override;
 
  private:
-  // OperationBase:
-  const std::string& RpId() const override;
-  void PromptTouchIdDone(bool success) override;
+  void PromptTouchIdDone(bool success);
+
+  // DefaultKeychainQuery returns a default keychain query dictionary that has
+  // the keychain item class, keychain access group and RP ID filled out (but
+  // not the credential ID). More fields can be set on the return value to
+  // refine the query.
+  base::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery() const;
+
+  // The secret parameter passed to |CredentialMetadata| operations to encrypt
+  // or encode credential metadata for storage in the macOS keychain.
+  const std::string metadata_secret_;
+  const std::string keychain_access_group_;
+
+  const std::unique_ptr<TouchIdContext> touch_id_context_ =
+      TouchIdContext::Create();
+
+  const CtapMakeCredentialRequest request_;
+  Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(MakeCredentialOperation);
 };
 
 }  // namespace mac
diff --git a/device/fido/mac/make_credential_operation.mm b/device/fido/mac/make_credential_operation.mm
index e0e8788..0e2ead1 100644
--- a/device/fido/mac/make_credential_operation.mm
+++ b/device/fido/mac/make_credential_operation.mm
@@ -8,9 +8,11 @@
 
 #import <Foundation/Foundation.h>
 
+#include "base/bind.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
 #include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/device_event_log/device_event_log.h"
 #include "device/fido/attestation_statement_formats.h"
@@ -36,20 +38,13 @@
     std::string metadata_secret,
     std::string keychain_access_group,
     Callback callback)
-    : OperationBase<CtapMakeCredentialRequest,
-                    AuthenticatorMakeCredentialResponse>(
-          std::move(request),
-          std::move(metadata_secret),
-          std::move(keychain_access_group),
-          std::move(callback)) {}
+    : metadata_secret_(std::move(metadata_secret)),
+      keychain_access_group_(std::move(keychain_access_group)),
+      request_(std::move(request)),
+      callback_(std::move(callback)) {}
 MakeCredentialOperation::~MakeCredentialOperation() = default;
 
-const std::string& MakeCredentialOperation::RpId() const {
-  return request().rp.id;
-}
-
 void MakeCredentialOperation::Run() {
-  Init();
   // Verify pubKeyCredParams contains ES-256, which is the only algorithm we
   // support.
   auto is_es256 =
@@ -58,30 +53,32 @@
                static_cast<int>(CoseAlgorithmIdentifier::kCoseEs256);
       };
   const auto& key_params =
-      request().public_key_credential_params.public_key_credential_params();
+      request_.public_key_credential_params.public_key_credential_params();
   if (!std::any_of(key_params.begin(), key_params.end(), is_es256)) {
     DVLOG(1) << "No supported algorithm found.";
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm,
-             base::nullopt);
+    std::move(callback_).Run(
+        CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm, base::nullopt);
     return;
   }
 
   // Display the macOS Touch ID prompt.
-  PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
-                                           base::UTF8ToUTF16(RpId())));
+  touch_id_context_->PromptTouchId(
+      l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
+                                 base::UTF8ToUTF16(request_.rp.id)),
+      base::BindOnce(&MakeCredentialOperation::PromptTouchIdDone,
+                     base::Unretained(this)));
 }
 
 void MakeCredentialOperation::PromptTouchIdDone(bool success) {
   if (!success) {
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied,
+                             base::nullopt);
     return;
   }
 
   // Evaluate that excludeList does not contain any credentials stored by this
   // authenticator.
-  for (auto& credential : request().exclude_list) {
+  for (auto& credential : request_.exclude_list) {
     ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery();
     CFDictionarySetValue(query, kSecAttrApplicationLabel,
                          [NSData dataWithBytes:credential.id().data()
@@ -90,16 +87,15 @@
     if (status == errSecSuccess) {
       // Excluded item found.
       DVLOG(1) << "credential from excludeList found";
-      std::move(callback())
-          .Run(CtapDeviceResponseCode::kCtap2ErrCredentialExcluded,
-               base::nullopt);
+      std::move(callback_).Run(
+          CtapDeviceResponseCode::kCtap2ErrCredentialExcluded, base::nullopt);
       return;
     }
     if (status != errSecItemNotFound) {
       // Unexpected keychain error.
       OSSTATUS_DLOG(ERROR, status) << "failed to check for excluded credential";
-      std::move(callback())
-          .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+      std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                               base::nullopt);
       return;
     }
   }
@@ -109,7 +105,7 @@
   // Note that because the rk bit is not encoded here, a resident credential
   // may overwrite a non-resident credential and vice versa.
   const std::string encoded_rp_id_user_id =
-      EncodeRpIdAndUserId(metadata_secret(), RpId(), request().user.id);
+      EncodeRpIdAndUserId(metadata_secret_, request_.rp.id, request_.user.id);
   {
     ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery();
     CFDictionarySetValue(query, kSecAttrApplicationTag,
@@ -117,17 +113,17 @@
     OSStatus status = Keychain::GetInstance().ItemDelete(query);
     if (status != errSecSuccess && status != errSecItemNotFound) {
       OSSTATUS_DLOG(ERROR, status) << "SecItemDelete failed";
-      std::move(callback())
-          .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+      std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                               base::nullopt);
       return;
     }
   }
 
   // Generate the new key pair.
   const std::vector<uint8_t> credential_id =
-      SealCredentialId(metadata_secret(), RpId(),
+      SealCredentialId(metadata_secret_, request_.rp.id,
                        CredentialMetadata::FromPublicKeyCredentialUserEntity(
-                           request().user, request().resident_key_required));
+                           request_.user, request_.resident_key_required));
 
   ScopedCFTypeRef<CFMutableDictionaryRef> params(
       CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
@@ -142,9 +138,9 @@
   CFDictionarySetValue(params, kSecPrivateKeyAttrs, private_key_params);
   CFDictionarySetValue(private_key_params, kSecAttrIsPermanent, @YES);
   CFDictionarySetValue(private_key_params, kSecAttrAccessControl,
-                       access_control());
+                       touch_id_context_->access_control());
   CFDictionarySetValue(private_key_params, kSecUseAuthenticationContext,
-                       authentication_context());
+                       touch_id_context_->authentication_context());
   CFDictionarySetValue(private_key_params, kSecAttrApplicationTag,
                        base::SysUTF8ToNSString(encoded_rp_id_user_id));
   CFDictionarySetValue(private_key_params, kSecAttrApplicationLabel,
@@ -157,16 +153,16 @@
                                                  cferr.InitializeInto()));
   if (!private_key) {
     FIDO_LOG(ERROR) << "SecKeyCreateRandomKey failed: " << cferr;
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                             base::nullopt);
     return;
   }
   ScopedCFTypeRef<SecKeyRef> public_key(
       Keychain::GetInstance().KeyCopyPublicKey(private_key));
   if (!public_key) {
     FIDO_LOG(ERROR) << "SecKeyCopyPublicKey failed";
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                             base::nullopt);
     return;
   }
 
@@ -177,18 +173,18 @@
                                  SecKeyRefToECPublicKey(public_key));
   if (!attested_credential_data) {
     FIDO_LOG(ERROR) << "MakeAttestedCredentialData failed";
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                             base::nullopt);
     return;
   }
-  AuthenticatorData authenticator_data =
-      MakeAuthenticatorData(RpId(), std::move(*attested_credential_data));
+  AuthenticatorData authenticator_data = MakeAuthenticatorData(
+      request_.rp.id, std::move(*attested_credential_data));
   base::Optional<std::vector<uint8_t>> signature = GenerateSignature(
-      authenticator_data, request().client_data_hash, private_key);
+      authenticator_data, request_.client_data_hash, private_key);
   if (!signature) {
     FIDO_LOG(ERROR) << "MakeSignature failed";
-    std::move(callback())
-        .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
+    std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
+                             base::nullopt);
     return;
   }
   AuthenticatorMakeCredentialResponse response(
@@ -198,10 +194,22 @@
           std::make_unique<PackedAttestationStatement>(
               CoseAlgorithmIdentifier::kCoseEs256, std::move(*signature),
               /*x509_certificates=*/std::vector<std::vector<uint8_t>>())));
-  std::move(callback())
-      .Run(CtapDeviceResponseCode::kSuccess, std::move(response));
+  std::move(callback_).Run(CtapDeviceResponseCode::kSuccess,
+                           std::move(response));
 }
 
+base::ScopedCFTypeRef<CFMutableDictionaryRef>
+MakeCredentialOperation::DefaultKeychainQuery() const {
+  base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
+      CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+  CFDictionarySetValue(query, kSecClass, kSecClassKey);
+  CFDictionarySetValue(query, kSecAttrAccessGroup,
+                       base::SysUTF8ToNSString(keychain_access_group_));
+  CFDictionarySetValue(
+      query, kSecAttrLabel,
+      base::SysUTF8ToNSString(EncodeRpId(metadata_secret_, request_.rp.id)));
+  return query;
+}
 }  // namespace mac
 }  // namespace fido
 }  // namespace device
diff --git a/device/fido/mac/operation.h b/device/fido/mac/operation.h
index 191a2dbe..c62a2c9 100644
--- a/device/fido/mac/operation.h
+++ b/device/fido/mac/operation.h
@@ -11,15 +11,12 @@
 namespace fido {
 namespace mac {
 
-// Operation is the interface to OperationBase.
 class Operation {
  public:
+  Operation() = default;
   virtual ~Operation() = default;
   virtual void Run() = 0;
 
- protected:
-  Operation() = default;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(Operation);
 };
diff --git a/device/fido/mac/operation_base.h b/device/fido/mac/operation_base.h
deleted file mode 100644
index be25331..0000000
--- a/device/fido/mac/operation_base.h
+++ /dev/null
@@ -1,113 +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 DEVICE_FIDO_MAC_OPERATION_BASE_H_
-#define DEVICE_FIDO_MAC_OPERATION_BASE_H_
-
-#import <Foundation/Foundation.h>
-#import <Security/Security.h>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/mac/scoped_cftyperef.h"
-#include "base/macros.h"
-#include "base/strings/sys_string_conversions.h"
-#include "device/fido/mac/credential_metadata.h"
-#include "device/fido/mac/operation.h"
-#include "device/fido/mac/touch_id_context.h"
-
-namespace device {
-namespace fido {
-namespace mac {
-
-// OperationBase abstracts behavior common to both concrete Operations,
-// |MakeCredentialOperation| and |GetAssertionOperation|.
-template <class Request, class Response>
-class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation {
- public:
-  using Callback = base::OnceCallback<void(CtapDeviceResponseCode,
-                                           base::Optional<Response>)>;
-
-  OperationBase(Request request,
-                std::string metadata_secret,
-                std::string keychain_access_group,
-                Callback callback)
-      : request_(std::move(request)),
-        metadata_secret_(std::move(metadata_secret)),
-        keychain_access_group_(std::move(keychain_access_group)),
-        callback_(std::move(callback)),
-        touch_id_context_(TouchIdContext::Create()) {}
-
-  ~OperationBase() override = default;
-
- protected:
-  // Subclasses must call Init() at the beginning of Run().
-  void Init() { encoded_rp_id_ = EncodeRpId(metadata_secret(), RpId()); }
-
-  // PromptTouchId triggers a Touch ID consent dialog with the given reason
-  // string. Subclasses implement the PromptTouchIdDone callback to receive the
-  // result.
-  void PromptTouchId(const base::string16& reason) {
-    // The callback passed to TouchIdContext::Prompt will not fire if the
-    // TouchIdContext itself has been deleted. Since that it is owned by this
-    // class, there is no need to bind the callback to a weak ref here.
-    touch_id_context_->PromptTouchId(
-        reason, base::BindOnce(&OperationBase::PromptTouchIdDone,
-                               base::Unretained(this)));
-  }
-
-  // Callback for |PromptTouchId|.
-  virtual void PromptTouchIdDone(bool success) = 0;
-
-  // Subclasses override RpId to return the RP ID from the type-specific
-  // request.
-  virtual const std::string& RpId() const = 0;
-
-  LAContext* authentication_context() const {
-    return touch_id_context_->authentication_context();
-  }
-  SecAccessControlRef access_control() const {
-    return touch_id_context_->access_control();
-  }
-
-  // DefaultKeychainQuery returns a default keychain query dictionary that has
-  // the keychain item class, keychain access group and RP ID filled out (but
-  // not the credential ID). More fields can be set on the return value to
-  // refine the query.
-  base::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery() const {
-    base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-        CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
-    CFDictionarySetValue(query, kSecClass, kSecClassKey);
-    CFDictionarySetValue(query, kSecAttrAccessGroup,
-                         base::SysUTF8ToNSString(keychain_access_group_));
-    DCHECK(!encoded_rp_id_.empty());
-    CFDictionarySetValue(query, kSecAttrLabel,
-                         base::SysUTF8ToNSString(encoded_rp_id_));
-    return query;
-  }
-
-  const std::string& metadata_secret() const { return metadata_secret_; }
-  const std::string& keychain_access_group() const {
-    return keychain_access_group_;
-  }
-  const Request& request() const { return request_; }
-  Callback& callback() { return callback_; }
-
- private:
-  Request request_;
-  // The secret parameter passed to |CredentialMetadata| operations to encrypt
-  // or encode credential metadata for storage in the macOS keychain.
-  std::string metadata_secret_;
-  std::string keychain_access_group_;
-  std::string encoded_rp_id_ = "";
-  Callback callback_;
-
-  std::unique_ptr<TouchIdContext> touch_id_context_;
-};
-
-}  // namespace mac
-}  // namespace fido
-}  // namespace device
-
-#endif  // DEVICE_FIDO_MAC_OPERATION_BASE_H_
diff --git a/extensions/common/api/_behavior_features.json b/extensions/common/api/_behavior_features.json
index 1a34c0ad..6c6e5b7 100644
--- a/extensions/common/api/_behavior_features.json
+++ b/extensions/common/api/_behavior_features.json
@@ -69,7 +69,6 @@
       "2F47B526FA71F44816618C41EC55E5EE9543FDCC",  // Braille Keyboard
       "86672C8D7A04E24EFB244BF96FE518C4C4809F73",  // Speech synthesis
       "1CF709D51B2B96CF79D00447300BD3BFBE401D21",  // Mobile activation
-      "D519188F86D9ACCEE0412007B227D9936EB9676B",  // Gaia auth extension
       "40FF1103292F40C34066E023B8BE8CAE18306EAE",  // Chromeos help
       "3C654B3B6682CA194E75AD044CEDE927675DDEE8",  // Easy unlock
       "2FCBCE08B34CCA1728A85F1EFBD9A34DD2558B2E",  // ChromeVox
diff --git a/extensions/test/test_extension_dir.cc b/extensions/test/test_extension_dir.cc
index 8e1e94f0..0941ae2e 100644
--- a/extensions/test/test_extension_dir.cc
+++ b/extensions/test/test_extension_dir.cc
@@ -28,13 +28,6 @@
   WriteFile(FILE_PATH_LITERAL("manifest.json"), manifest);
 }
 
-void TestExtensionDir::WriteManifestWithSingleQuotes(
-    base::StringPiece manifest) {
-  std::string double_quotes;
-  base::ReplaceChars(manifest.data(), "'", "\"", &double_quotes);
-  WriteManifest(double_quotes);
-}
-
 void TestExtensionDir::WriteFile(const base::FilePath::StringType& filename,
                                  base::StringPiece contents) {
   base::ScopedAllowBlockingForTesting allow_blocking;
diff --git a/extensions/test/test_extension_dir.h b/extensions/test/test_extension_dir.h
index d9d30d2..1b0fa73 100644
--- a/extensions/test/test_extension_dir.h
+++ b/extensions/test/test_extension_dir.h
@@ -23,12 +23,6 @@
   // is performed. If desired this should be done on extension installation.
   void WriteManifest(base::StringPiece manifest);
 
-  // Like WriteManifest, but where the |manifest| is given in single-quotes
-  // rather than double-quotes. This is for convenience to avoid escaping.
-  //
-  // E.g. |manifest| can be {'name': 'me'} rather than {\"name\": \"me\"}.
-  void WriteManifestWithSingleQuotes(base::StringPiece manifest);
-
   // Writes |contents| to |filename| within the unpacked dir, overwriting
   // anything that was already there.
   void WriteFile(const base::FilePath::StringType& filename,
diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc
index 1300753..fe66742 100644
--- a/google_apis/gaia/fake_gaia.cc
+++ b/google_apis/gaia/fake_gaia.cc
@@ -61,9 +61,6 @@
 
 const char kDefaultGaiaId[] = "12345";
 
-const base::FilePath::CharType kServiceLogin[] =
-    FILE_PATH_LITERAL("google_apis/test/service_login.html");
-
 const base::FilePath::CharType kEmbeddedSetupChromeos[] =
     FILE_PATH_LITERAL("google_apis/test/embedded_setup_chromeos.html");
 
@@ -143,9 +140,6 @@
   base::FilePath source_root_dir;
   base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
   CHECK(base::ReadFileToString(
-      source_root_dir.Append(base::FilePath(kServiceLogin)),
-      &service_login_response_));
-  CHECK(base::ReadFileToString(
       source_root_dir.Append(base::FilePath(kEmbeddedSetupChromeos)),
       &embedded_setup_chromeos_response_));
 }
@@ -212,10 +206,6 @@
   REGISTER_RESPONSE_HANDLER(
       gaia_urls->merge_session_url(), HandleMergeSession);
 
-  // Handles /ServiceLogin GAIA call.
-  REGISTER_RESPONSE_HANDLER(
-      gaia_urls->service_login_url(), HandleServiceLogin);
-
   // Handles /embedded/setup/v2/chromeos GAIA call.
   REGISTER_RESPONSE_HANDLER(gaia_urls->embedded_setup_chromeos_url(2),
                             HandleEmbeddedSetupChromeos);
@@ -445,13 +435,6 @@
   return nullptr;
 }
 
-void FakeGaia::HandleServiceLogin(const HttpRequest& request,
-                                  BasicHttpResponse* http_response) {
-  http_response->set_code(net::HTTP_OK);
-  http_response->set_content(service_login_response_);
-  http_response->set_content_type("text/html");
-}
-
 void FakeGaia::HandleEmbeddedSetupChromeos(const HttpRequest& request,
                                            BasicHttpResponse* http_response) {
   GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
diff --git a/google_apis/gaia/fake_gaia.h b/google_apis/gaia/fake_gaia.h
index ee6e367..3b2a025f9 100644
--- a/google_apis/gaia/fake_gaia.h
+++ b/google_apis/gaia/fake_gaia.h
@@ -270,7 +270,6 @@
   EmailToGaiaIdMap email_to_gaia_id_map_;
   AccessTokenInfoMap access_token_info_map_;
   RequestHandlerMap request_handlers_;
-  std::string service_login_response_;
   std::string embedded_setup_chromeos_response_;
   SamlAccountIdpMap saml_account_idp_map_;
   SamlDomainRedirectUrlMap saml_domain_url_map_;
diff --git a/google_apis/test/service_login.html b/google_apis/test/service_login.html
deleted file mode 100644
index 332be01..0000000
--- a/google_apis/test/service_login.html
+++ /dev/null
@@ -1,64 +0,0 @@
-<HTML>
-<HEAD>
-<SCRIPT>
-var gaia = gaia || {};
-gaia.chromeOSLogin = {};
-
-gaia.chromeOSLogin.parent_page_url_ =
-'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html';
-
-gaia.chromeOSLogin.attemptLogin = function(email, password, attemptToken) {
-  var msg = {
-    'method': 'attemptLogin',
-    'email': email,
-    'password': password,
-    'attemptToken': attemptToken
-  };
-  window.parent.postMessage(msg, gaia.chromeOSLogin.parent_page_url_);
-};
-
-gaia.chromeOSLogin.onAttemptedLogin = function(emailFormElement,
-                                               passwordFormElement,
-                                               continueUrlElement) {
-    var email = emailFormElement.value;
-    var passwd = passwordFormElement.value;
-    var attemptToken = new Date().getTime();
-
-    gaia.chromeOSLogin.attemptLogin(email, passwd, attemptToken);
-
-    if (continueUrlElement) {
-      var prevAttemptIndex = continueUrlElement.value.indexOf('?attemptToken');
-      if (prevAttemptIndex != -1) {
-        continueUrlElement.value =
-            continueUrlElement.value.substr(0, prevAttemptIndex);
-      }
-      continueUrlElement.value += '?attemptToken=' + attemptToken;
-    }
-};
-
-function submitAndGo() {
-  gaia.chromeOSLogin.onAttemptedLogin(document.getElementById("Email"),
-                                      document.getElementById("Passwd"),
-                                      document.getElementById("continue"));
-  return true;
-}
-
-function onAuthError() {
-  if (window.domAutomationController) {
-    window.domAutomationController.send('loginfail');
-  }
-}
-
-</SCRIPT>
-</HEAD>
-<BODY>
-  Local Auth Server:<BR>
-  <FORM action='/ServiceLoginAuth' method=POST onsubmit='submitAndGo()'>
-    <INPUT TYPE=text id="Email" name="Email">
-    <INPUT TYPE=text id="Passwd" name="Passwd">
-    <INPUT TYPE=hidden id="continue" name="continue"
-        value="chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html">
-    <INPUT TYPE=Submit id="signIn">
-  </FORM>
-</BODY>
-</HTML>
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 38d551b..5ac1d3e4 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -424,7 +424,8 @@
       break;
   }
 
-  transfer_cache_->PurgeMemory(memory_pressure_level);
+  if (transfer_cache_)
+    transfer_cache_->PurgeMemory(memory_pressure_level);
 }
 
 uint64_t SharedContextState::GetMemoryUsage() {
diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc
index 2de537a..84a8833 100644
--- a/headless/lib/browser/headless_web_contents_impl.cc
+++ b/headless/lib/browser/headless_web_contents_impl.cc
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
 #include "base/trace_event/trace_event.h"
 #include "base/values.h"
 #include "components/security_state/content/content_utils.h"
@@ -304,6 +305,10 @@
   agent_host_->RemoveObserver(this);
   if (render_process_host_)
     render_process_host_->RemoveObserver(this);
+  // Defer destruction of WindowTreeHost, as it does sync mojo calls
+  // in the destructor of ui::Compositor.
+  base::SequencedTaskRunnerHandle::Get()->DeleteSoon(
+      FROM_HERE, std::move(window_tree_host_));
 }
 
 void HeadlessWebContentsImpl::RenderFrameCreated(
diff --git a/headless/test/headless_browser_test.cc b/headless/test/headless_browser_test.cc
index f0d0459..b0d1eec 100644
--- a/headless/test/headless_browser_test.cc
+++ b/headless/test/headless_browser_test.cc
@@ -295,6 +295,9 @@
   browser()->GetDevToolsTarget()->DetachClient(browser_devtools_client_.get());
   browser_context_->Close();
   browser_context_ = nullptr;
+  // Let the tasks that might have beein scheduled during web contents
+  // being closed run (see https://crbug.com/1036627 for details).
+  base::RunLoop().RunUntilIdle();
 }
 
 bool HeadlessAsyncDevTooledBrowserTest::GetEnableBeginFrameControl() {
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index 5c118e1..b51eb860 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -152,6 +152,7 @@
     "CoreSpotlight.framework",
     "UIKit.framework",
   ]
+  allow_circular_includes_from = [ "//ios/chrome/browser/url_loading" ]
 }
 
 source_set("test_support") {
diff --git a/ios/chrome/browser/overlays/aggregate_overlay_request_support_unittest.cc b/ios/chrome/browser/overlays/aggregate_overlay_request_support_unittest.cc
index c22476ea..a3ca9129 100644
--- a/ios/chrome/browser/overlays/aggregate_overlay_request_support_unittest.cc
+++ b/ios/chrome/browser/overlays/aggregate_overlay_request_support_unittest.cc
@@ -6,27 +6,14 @@
 
 #include "ios/chrome/browser/overlays/public/overlay_request.h"
 #include "ios/chrome/browser/overlays/public/overlay_request_config.h"
+#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
 #include "testing/platform_test.h"
 
 namespace {
 // Fake request config types.
-class FirstConfig : public OverlayRequestConfig<FirstConfig> {
- private:
-  OVERLAY_USER_DATA_SETUP(FirstConfig);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(FirstConfig);
-
-class SecondConfig : public OverlayRequestConfig<SecondConfig> {
- private:
-  OVERLAY_USER_DATA_SETUP(SecondConfig);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(SecondConfig);
-
-class ThirdConfig : public OverlayRequestConfig<ThirdConfig> {
- private:
-  OVERLAY_USER_DATA_SETUP(ThirdConfig);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(ThirdConfig);
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(FirstConfig);
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(SecondConfig);
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ThirdConfig);
 }  // namespace
 
 using AggregateOverlayRequestSupportTest = PlatformTest;
diff --git a/ios/chrome/browser/overlays/default_overlay_request_cancel_handler_unittest.mm b/ios/chrome/browser/overlays/default_overlay_request_cancel_handler_unittest.mm
index a8c42d5..63d7ecb 100644
--- a/ios/chrome/browser/overlays/default_overlay_request_cancel_handler_unittest.mm
+++ b/ios/chrome/browser/overlays/default_overlay_request_cancel_handler_unittest.mm
@@ -21,7 +21,7 @@
  public:
   DefaultOverlayRequestCancelHandlerTest() : PlatformTest() {
     std::unique_ptr<OverlayRequest> request =
-        OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+        OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
     queue()->AddRequest(std::move(request));
   }
 
diff --git a/ios/chrome/browser/overlays/overlay_callback_manager_impl_unittest.cc b/ios/chrome/browser/overlays/overlay_callback_manager_impl_unittest.cc
index 433b009e..d3548742 100644
--- a/ios/chrome/browser/overlays/overlay_callback_manager_impl_unittest.cc
+++ b/ios/chrome/browser/overlays/overlay_callback_manager_impl_unittest.cc
@@ -7,20 +7,13 @@
 #include "base/bind.h"
 #include "ios/chrome/browser/overlays/public/overlay_response.h"
 #include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
 #include "testing/platform_test.h"
 
 namespace {
 // Fake dispatch response info types.
-class FirstResponseInfo : public OverlayUserData<FirstResponseInfo> {
- private:
-  OVERLAY_USER_DATA_SETUP(FirstResponseInfo);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(FirstResponseInfo);
-class SecondResponseInfo : public OverlayUserData<SecondResponseInfo> {
- private:
-  OVERLAY_USER_DATA_SETUP(SecondResponseInfo);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(SecondResponseInfo);
+DEFINE_TEST_OVERLAY_RESPONSE_INFO(FirstResponseInfo);
+DEFINE_TEST_OVERLAY_RESPONSE_INFO(SecondResponseInfo);
 }  // namespace
 
 using OverlayCallbackManagerImplTest = PlatformTest;
diff --git a/ios/chrome/browser/overlays/overlay_presenter_impl_unittest.mm b/ios/chrome/browser/overlays/overlay_presenter_impl_unittest.mm
index 2a2b901..c17e62f 100644
--- a/ios/chrome/browser/overlays/overlay_presenter_impl_unittest.mm
+++ b/ios/chrome/browser/overlays/overlay_presenter_impl_unittest.mm
@@ -92,7 +92,7 @@
     if (!queue)
       return nullptr;
     std::unique_ptr<OverlayRequest> inserted_request =
-        OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+        OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
     OverlayRequest* request = inserted_request.get();
     if (expect_presentation)
       EXPECT_CALL(observer(), WillShowOverlay(&presenter(), request));
diff --git a/ios/chrome/browser/overlays/overlay_request_impl_unittest.cc b/ios/chrome/browser/overlays/overlay_request_impl_unittest.cc
index 5a8fe1fd..e7c626d 100644
--- a/ios/chrome/browser/overlays/overlay_request_impl_unittest.cc
+++ b/ios/chrome/browser/overlays/overlay_request_impl_unittest.cc
@@ -16,7 +16,7 @@
 TEST_F(OverlayRequestImplTest, ExecuteCallback) {
   void* kResponseData = &kResponseData;
   std::unique_ptr<OverlayRequest> request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   __block bool callback_executed = false;
   request->GetCallbackManager()->AddCompletionCallback(
       base::BindOnce(base::RetainBlock(^(OverlayResponse* response) {
diff --git a/ios/chrome/browser/overlays/overlay_request_queue_impl_unittest.mm b/ios/chrome/browser/overlays/overlay_request_queue_impl_unittest.mm
index ea165b8..05c4215 100644
--- a/ios/chrome/browser/overlays/overlay_request_queue_impl_unittest.mm
+++ b/ios/chrome/browser/overlays/overlay_request_queue_impl_unittest.mm
@@ -60,7 +60,7 @@
 
   OverlayRequest* AddRequest() {
     std::unique_ptr<OverlayRequest> passed_request =
-        OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+        OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
     OverlayRequest* request = passed_request.get();
     EXPECT_CALL(observer(),
                 RequestAddedToQueue(queue(), request, queue()->size()));
@@ -165,7 +165,7 @@
 // when cancelling a request with a custom cancel handler.
 TEST_F(OverlayRequestQueueImplTest, CustomCancelHandler) {
   std::unique_ptr<OverlayRequest> passed_request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   OverlayRequest* request = passed_request.get();
   std::unique_ptr<FakeCancelHandler> passed_cancel_handler =
       std::make_unique<FakeCancelHandler>(request, queue());
diff --git a/ios/chrome/browser/overlays/overlay_request_support_unittest.cc b/ios/chrome/browser/overlays/overlay_request_support_unittest.cc
index b8cf8cd..2908ad04 100644
--- a/ios/chrome/browser/overlays/overlay_request_support_unittest.cc
+++ b/ios/chrome/browser/overlays/overlay_request_support_unittest.cc
@@ -5,15 +5,12 @@
 #include "ios/chrome/browser/overlays/public/overlay_request_support.h"
 
 #include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
 #include "testing/platform_test.h"
 
 namespace {
 // Fake request config type for use in tests.
-class FakeConfig : public OverlayUserData<FakeConfig> {
- private:
-  OVERLAY_USER_DATA_SETUP(FakeConfig);
-};
-OVERLAY_USER_DATA_SETUP_IMPL(FakeConfig);
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(FakeConfig);
 }  // namespace
 
 using SupportsOverlayRequestTest = PlatformTest;
@@ -21,7 +18,7 @@
 // Tests that OverlayRequestSupport::All() supports arbitrary config types.
 TEST_F(SupportsOverlayRequestTest, SupportAll) {
   std::unique_ptr<OverlayRequest> first_request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   std::unique_ptr<OverlayRequest> second_request =
       OverlayRequest::CreateWithConfig<FakeConfig>();
 
@@ -33,7 +30,7 @@
 // Tests that OverlayRequestSupport::None() does not support config types.
 TEST_F(SupportsOverlayRequestTest, SupportNone) {
   std::unique_ptr<OverlayRequest> first_request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   std::unique_ptr<OverlayRequest> second_request =
       OverlayRequest::CreateWithConfig<FakeConfig>();
 
@@ -51,7 +48,7 @@
 
   // Verify that FakeOverlayUserData requests aren't supported.
   std::unique_ptr<OverlayRequest> unsupported_request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   EXPECT_FALSE(support->IsRequestSupported(unsupported_request.get()));
 
   // Verify that FakeConfig requests are supported.
diff --git a/ios/chrome/browser/overlays/test/BUILD.gn b/ios/chrome/browser/overlays/test/BUILD.gn
index ab2ca63..00d4182 100644
--- a/ios/chrome/browser/overlays/test/BUILD.gn
+++ b/ios/chrome/browser/overlays/test/BUILD.gn
@@ -9,6 +9,7 @@
     "fake_overlay_presentation_context.h",
     "fake_overlay_user_data.cc",
     "fake_overlay_user_data.h",
+    "overlay_test_macros.h",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/ios/chrome/browser/overlays/test/fake_overlay_user_data.h b/ios/chrome/browser/overlays/test/fake_overlay_user_data.h
index 7e0b256c..fe9b507 100644
--- a/ios/chrome/browser/overlays/test/fake_overlay_user_data.h
+++ b/ios/chrome/browser/overlays/test/fake_overlay_user_data.h
@@ -16,7 +16,7 @@
 
  private:
   OVERLAY_USER_DATA_SETUP(FakeOverlayUserData);
-  FakeOverlayUserData(void* value);
+  FakeOverlayUserData(void* value = nullptr);
 
   void* value_ = nullptr;
 };
diff --git a/ios/chrome/browser/overlays/test/overlay_test_macros.h b/ios/chrome/browser/overlays/test/overlay_test_macros.h
new file mode 100644
index 0000000..121c757
--- /dev/null
+++ b/ios/chrome/browser/overlays/test/overlay_test_macros.h
@@ -0,0 +1,28 @@
+// 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_TEST_OVERLAY_TEST_MACROS_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_TEST_OVERLAY_TEST_MACROS_H_
+
+#include "ios/chrome/browser/overlays/public/overlay_request_config.h"
+
+// Macro used to define an OverlayRequestConfig that holds no data.  Can be used
+// in tests for functionality specific to config types.
+#define DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ConfigType)         \
+  class ConfigType : public OverlayRequestConfig<ConfigType> { \
+   private:                                                    \
+    OVERLAY_USER_DATA_SETUP(ConfigType);                       \
+  };                                                           \
+  OVERLAY_USER_DATA_SETUP_IMPL(ConfigType)
+
+// Macro used to define a response info that holds no data.  Can be used
+// in tests for functionality specific to info types.
+#define DEFINE_TEST_OVERLAY_RESPONSE_INFO(InfoType)   \
+  class InfoType : public OverlayUserData<InfoType> { \
+   private:                                           \
+    OVERLAY_USER_DATA_SETUP(InfoType);                \
+  };                                                  \
+  OVERLAY_USER_DATA_SETUP_IMPL(InfoType)
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_TEST_OVERLAY_TEST_MACROS_H_
diff --git a/ios/chrome/browser/ui/overlays/common/alerts/alert_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/common/alerts/alert_overlay_mediator_unittest.mm
index 594b965f..c8d5128 100644
--- a/ios/chrome/browser/ui/overlays/common/alerts/alert_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/common/alerts/alert_overlay_mediator_unittest.mm
@@ -36,7 +36,7 @@
 // applied to the consumer.
 TEST_F(AlertOverlayMediatorTest, SetUpConsumer) {
   std::unique_ptr<OverlayRequest> request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   FakeAlertOverlayMediator* mediator =
       [[FakeAlertOverlayMediator alloc] initWithRequest:request.get()];
   mediator.alertTitle = @"Title";
diff --git a/ios/chrome/browser/ui/overlays/overlay_request_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/overlay_request_mediator_unittest.mm
index 8229cb4..24835f1 100644
--- a/ios/chrome/browser/ui/overlays/overlay_request_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/overlay_request_mediator_unittest.mm
@@ -18,7 +18,7 @@
 // Tests that the mediator's request is reset after destruction.
 TEST_F(OverlayRequestMediatorTest, ResetRequestAfterDestruction) {
   std::unique_ptr<OverlayRequest> request =
-      OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr);
+      OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
   OverlayRequestMediator* mediator =
       [[OverlayRequestMediator alloc] initWithRequest:request.get()];
   EXPECT_EQ(request.get(), mediator.request);
diff --git a/ios/chrome/browser/ui/overlays/overlay_request_ui_state_unittest.mm b/ios/chrome/browser/ui/overlays/overlay_request_ui_state_unittest.mm
index dad5aa0..4ccedf31 100644
--- a/ios/chrome/browser/ui/overlays/overlay_request_ui_state_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/overlay_request_ui_state_unittest.mm
@@ -22,8 +22,7 @@
  public:
   OverlayRequestUIStateTest()
       : PlatformTest(),
-        request_(
-            OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr)),
+        request_(OverlayRequest::CreateWithConfig<FakeOverlayUserData>()),
         state_(request_.get()) {}
 
   OverlayRequest* request() { return request_.get(); }
diff --git a/ios/chrome/browser/url_loading/app_url_loading_service.h b/ios/chrome/browser/url_loading/app_url_loading_service.h
index 7ed27d8..3b67cb1 100644
--- a/ios/chrome/browser/url_loading/app_url_loading_service.h
+++ b/ios/chrome/browser/url_loading/app_url_loading_service.h
@@ -9,6 +9,7 @@
 #import <UIKit/UIKit.h>
 
 #include "base/ios/block_types.h"
+#import "ios/chrome/app/application_delegate/tab_opening.h"
 #include "ios/chrome/app/application_mode.h"
 #include "ui/base/page_transition_types.h"
 
@@ -37,7 +38,7 @@
 // If the current tab in |targetMode| is a NTP, it can be reused to open URL.
 // |completion| is executed after the tab is opened. After Tab is open the
 // virtual URL is set to the pending navigation item.
-- (void)openSelectedTabInMode:(ApplicationMode)targetMode
+- (void)openSelectedTabInMode:(ApplicationModeForTabOpening)targetMode
             withUrlLoadParams:(const UrlLoadParams&)urlLoadParams
                    completion:(ProceduralBlock)completion;
 
diff --git a/ios/chrome/browser/url_loading/app_url_loading_service.mm b/ios/chrome/browser/url_loading/app_url_loading_service.mm
index 6cb7aff..5f7c8026 100644
--- a/ios/chrome/browser/url_loading/app_url_loading_service.mm
+++ b/ios/chrome/browser/url_loading/app_url_loading_service.mm
@@ -34,9 +34,10 @@
     if (params.from_chrome) {
       [delegate_
           dismissModalDialogsWithCompletion:^{
-            [delegate_ openSelectedTabInMode:ApplicationMode::NORMAL
-                           withUrlLoadParams:saved_params
-                                  completion:nil];
+            [delegate_
+                openSelectedTabInMode:ApplicationModeForTabOpening::NORMAL
+                    withUrlLoadParams:saved_params
+                           completion:nil];
           }
                              dismissOmnibox:YES];
     } else {
diff --git a/net/base/io_buffer.cc b/net/base/io_buffer.cc
index 6e2ea7a7..429dbc09 100644
--- a/net/base/io_buffer.cc
+++ b/net/base/io_buffer.cc
@@ -9,23 +9,19 @@
 
 namespace net {
 
-namespace {
-
 // TODO(eroman): IOBuffer is being converted to require buffer sizes and offsets
 // be specified as "size_t" rather than "int" (crbug.com/488553). To facilitate
 // this move (since LOTS of code needs to be updated), both "size_t" and "int
 // are being accepted. When using "size_t" this function ensures that it can be
 // safely converted to an "int" without truncation.
-void AssertValidBufferSize(size_t size) {
+void IOBuffer::AssertValidBufferSize(size_t size) {
   base::CheckedNumeric<int>(size).ValueOrDie();
 }
 
-void AssertValidBufferSize(int size) {
+void IOBuffer::AssertValidBufferSize(int size) {
   CHECK_GE(size, 0);
 }
 
-}  // namespace
-
 IOBuffer::IOBuffer() : data_(nullptr) {}
 
 IOBuffer::IOBuffer(int buffer_size) {
diff --git a/net/base/io_buffer.h b/net/base/io_buffer.h
index 4064047..a2944ea5 100644
--- a/net/base/io_buffer.h
+++ b/net/base/io_buffer.h
@@ -86,6 +86,9 @@
  protected:
   friend class base::RefCountedThreadSafe<IOBuffer>;
 
+  static void AssertValidBufferSize(size_t size);
+  static void AssertValidBufferSize(int size);
+
   // Only allow derived classes to specify data_.
   // In all other cases, we own data_, and must delete it at destruction time.
   explicit IOBuffer(char* data);
diff --git a/net/quic/platform/impl/quic_mem_slice_impl.cc b/net/quic/platform/impl/quic_mem_slice_impl.cc
index 0abf31a..99b42078 100644
--- a/net/quic/platform/impl/quic_mem_slice_impl.cc
+++ b/net/quic/platform/impl/quic_mem_slice_impl.cc
@@ -8,11 +8,28 @@
 
 namespace quic {
 
+namespace {
+
+class QuicIOBuffer : public net::IOBuffer {
+ public:
+  QuicIOBuffer(QuicUniqueBufferPtr buffer, size_t size)
+      : buffer_(std::move(buffer)) {
+    AssertValidBufferSize(size);
+    data_ = buffer_.get();
+  }
+
+ private:
+  ~QuicIOBuffer() override { data_ = nullptr; }
+
+  QuicUniqueBufferPtr buffer_;
+};
+
+}  // namespace
+
 QuicMemSliceImpl::QuicMemSliceImpl() = default;
 
-QuicMemSliceImpl::QuicMemSliceImpl(QuicBufferAllocator* /*allocator*/,
-                                   size_t length) {
-  io_buffer_ = base::MakeRefCounted<net::IOBuffer>(length);
+QuicMemSliceImpl::QuicMemSliceImpl(QuicUniqueBufferPtr buffer, size_t length) {
+  io_buffer_ = base::MakeRefCounted<QuicIOBuffer>(std::move(buffer), length);
   length_ = length;
 }
 
diff --git a/net/quic/platform/impl/quic_mem_slice_impl.h b/net/quic/platform/impl/quic_mem_slice_impl.h
index 95cddf3a..bf550d4 100644
--- a/net/quic/platform/impl/quic_mem_slice_impl.h
+++ b/net/quic/platform/impl/quic_mem_slice_impl.h
@@ -7,12 +7,11 @@
 
 #include "base/memory/ref_counted.h"
 #include "net/base/io_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 
 namespace quic {
 
-class QuicBufferAllocator;
-
 // QuicMemSliceImpl TODO(fayang)
 class QUIC_EXPORT_PRIVATE QuicMemSliceImpl {
  public:
@@ -20,7 +19,7 @@
   QuicMemSliceImpl();
   // Constructs a QuicMemSliceImp by let |allocator| allocate a data buffer of
   // |length|.
-  QuicMemSliceImpl(QuicBufferAllocator* allocator, size_t length);
+  QuicMemSliceImpl(QuicUniqueBufferPtr buffer, size_t length);
 
   QuicMemSliceImpl(scoped_refptr<net::IOBuffer> io_buffer, size_t length);
 
diff --git a/net/quic/quic_chromium_connection_helper.cc b/net/quic/quic_chromium_connection_helper.cc
index 70f8fd2..b5d1208 100644
--- a/net/quic/quic_chromium_connection_helper.cc
+++ b/net/quic/quic_chromium_connection_helper.cc
@@ -3,9 +3,17 @@
 // found in the LICENSE file.
 
 #include "net/quic/quic_chromium_connection_helper.h"
+#include "base/no_destructor.h"
 
 namespace net {
 
+namespace {
+quic::QuicBufferAllocator* GetBufferAllocator() {
+  static base::NoDestructor<quic::SimpleBufferAllocator> allocator;
+  return &*allocator;
+}
+}  // namespace
+
 QuicChromiumConnectionHelper::QuicChromiumConnectionHelper(
     const quic::QuicClock* clock,
     quic::QuicRandom* random_generator)
@@ -23,7 +31,7 @@
 
 quic::QuicBufferAllocator*
 QuicChromiumConnectionHelper::GetStreamSendBufferAllocator() {
-  return &buffer_allocator_;
+  return GetBufferAllocator();
 }
 
 }  // namespace net
diff --git a/net/quic/quic_chromium_connection_helper.h b/net/quic/quic_chromium_connection_helper.h
index 2417ee0e..e3b4460 100644
--- a/net/quic/quic_chromium_connection_helper.h
+++ b/net/quic/quic_chromium_connection_helper.h
@@ -39,7 +39,6 @@
  private:
   const quic::QuicClock* clock_;
   quic::QuicRandom* random_generator_;
-  quic::SimpleBufferAllocator buffer_allocator_;
 
   DISALLOW_COPY_AND_ASSIGN(QuicChromiumConnectionHelper);
 };
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index d050be8b..696fef0 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -433,3 +433,9 @@
     bool,
     FLAGS_quic_reloadable_flag_quic_create_server_handshaker_in_constructor,
     false)
+
+// If true, the frequency of stream frame coalescing will be logged as
+// QuicSession.CoalesceStreamFrameStatus.
+QUIC_FLAG(bool,
+          FLAGS_quic_reloadable_flag_quic_log_coalesce_stream_frame_frequency,
+          false)
diff --git a/services/device/hid/hid_service_linux.cc b/services/device/hid/hid_service_linux.cc
index 61157bfe..74aca99 100644
--- a/services/device/hid/hid_service_linux.cc
+++ b/services/device/hid/hid_service_linux.cc
@@ -222,19 +222,20 @@
   }
   scoped_refptr<HidDeviceInfo> device_info = map_entry->second;
 
-  auto params =
-      std::make_unique<ConnectParams>(device_info, std::move(callback));
-
 #if defined(OS_CHROMEOS)
-  chromeos::PermissionBrokerClient::ErrorCallback error_callback =
-      base::BindOnce(&HidServiceLinux::OnPathOpenError,
-                     params->device_info->device_node(),
-                     std::move(params->callback));
+  // Adapt |callback| to a repeating callback because the implementation below
+  // requires separate callbacks for success and error. Only one will be called.
+  auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
   chromeos::PermissionBrokerClient::Get()->OpenPath(
       device_info->device_node(),
-      base::BindOnce(&HidServiceLinux::OnPathOpenComplete, std::move(params)),
-      std::move(error_callback));
+      base::BindOnce(
+          &HidServiceLinux::OnPathOpenComplete,
+          std::make_unique<ConnectParams>(device_info, copyable_callback)),
+      base::BindOnce(&HidServiceLinux::OnPathOpenError,
+                     device_info->device_node(), copyable_callback));
 #else
+  auto params =
+      std::make_unique<ConnectParams>(device_info, std::move(callback));
   scoped_refptr<base::SequencedTaskRunner> blocking_task_runner =
       params->blocking_task_runner;
   blocking_task_runner->PostTask(
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 77525216..bff4403c 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -123,6 +123,7 @@
   """
 
   def __init__(self, isolated_out_dir, perf_test_name):
+    self.name = perf_test_name
     self.benchmark_path = os.path.join(isolated_out_dir, perf_test_name)
 
   def SetUp(self):
@@ -161,8 +162,10 @@
 
 
 class GtestCommandGenerator(object):
-  def __init__(self, options):
+  def __init__(self, options, override_executable=None, additional_flags=None):
     self._options = options
+    self._override_executable = override_executable
+    self._additional_flags = additional_flags or []
 
   def generate(self, output_dir):
     """Generate the command to run to start the gtest perf test.
@@ -178,20 +181,20 @@
             self._get_passthrough_args()
            )
 
+  @property
+  def executable_name(self):
+    """Gets the platform-independent name of the executable."""
+    return self._override_executable or self._options.executable
+
   def _get_executable(self):
-    executable = self._options.executable
+    executable = self.executable_name
     if IsWindows():
       return r'.\%s.exe' % executable
     else:
       return './%s' % executable
 
-  @property
-  def executable_name(self):
-    """Gets the platform-independent name of the executable."""
-    return self._options.executable
-
   def _get_passthrough_args(self):
-    return self._options.passthrough_args
+    return self._options.passthrough_args + self._additional_flags
 
   def _generate_filter_args(self):
     if self._options.isolated_script_test_filter:
@@ -214,22 +217,33 @@
     if self._options.use_gtest_benchmark_script:
       output_args.append('--output-dir=' + output_dir)
     # These flags are to make sure that test output perf metrics in the log.
-    if not '--verbose' in self._options.passthrough_args:
+    if not '--verbose' in self._get_passthrough_args():
       output_args.append('--verbose')
     if (not '--test-launcher-print-test-stdio=always'
-        in self._options.passthrough_args):
+        in self._get_passthrough_args()):
       output_args.append('--test-launcher-print-test-stdio=always')
     return output_args
 
 
-def write_legacy_test_results(return_code, output_filepath):
+def write_simple_test_results(return_code, output_filepath, benchmark_name):
   # TODO(crbug.com/920002): Fix to output
   # https://chromium.googlesource.com/chromium/src/+/master/docs/testing/json_test_results_format.md
-  valid = (return_code == 0)
-  failures = [] if valid else ['(entire test suite)']
+  # for each test rather than this summary.
   output_json = {
-      'valid': valid,
-      'failures': failures,
+      'tests': {
+          benchmark_name: {
+              'expected': 'PASS',
+              'actual': 'FAIL' if return_code else 'PASS',
+          },
+      },
+      'interrupted': False,
+      'path_delimiter': '/',
+      'version': 3,
+      'seconds_since_epoch': time.time(),
+      'num_failures_by_type': {
+          'FAIL': 1 if return_code else 0,
+          'PASS': 0 if return_code else 1,
+      },
   }
   with open(output_filepath, 'w') as fh:
     json.dump(output_json, fh)
@@ -268,13 +282,14 @@
   except Exception:
     traceback.print_exc()
     return_code = 1
-  write_legacy_test_results(return_code, output_paths.test_results)
   if command_generator.executable_name in GTEST_CONVERSION_WHITELIST:
     with path_util.SysPath(path_util.GetTracingDir()):
       # pylint: disable=no-name-in-module
       from tracing.value import gtest_json_converter
       # pylint: enable=no-name-in-module
     gtest_json_converter.ConvertGtestJsonFile(output_paths.perf_results)
+  write_simple_test_results(return_code, output_paths.test_results,
+                            output_paths.name)
   return return_code
 
 
@@ -559,33 +574,53 @@
         raise Exception(
             'Sharded Telemetry perf tests must either specify --benchmarks '
             'list or have GTEST_SHARD_INDEX environment variable present.')
-      benchmarks_and_configs = shard_map[shard_index]['benchmarks']
-
-      for (benchmark, story_selection_config
-           ) in benchmarks_and_configs.iteritems():
-        # Need to run the benchmark on both latest browser and reference build.
-        output_paths = OutputFilePaths(isolated_out_dir, benchmark).SetUp()
-        command_generator = TelemetryCommandGenerator(
-            benchmark, options, story_selection_config=story_selection_config)
-        print('\n### {folder} ###'.format(folder=benchmark))
-        return_code = execute_telemetry_benchmark(
-            command_generator, output_paths, options.xvfb)
-        overall_return_code = return_code or overall_return_code
-        test_results_files.append(output_paths.test_results)
-        if options.run_ref_build:
-          reference_benchmark_foldername = benchmark + '.reference'
-          reference_output_paths = OutputFilePaths(
-              isolated_out_dir, reference_benchmark_foldername).SetUp()
-          reference_command_generator = TelemetryCommandGenerator(
+      shard_configuration = shard_map[shard_index]
+      assert ('benchmarks' in shard_configuration or
+              'executables' in shard_configuration), (
+                  'Every shard must have benchmarks or executables associated '
+                  'with it.')
+      if 'benchmarks' in shard_configuration:
+        benchmarks_and_configs = shard_configuration['benchmarks']
+        for (benchmark, story_selection_config
+             ) in benchmarks_and_configs.iteritems():
+          # Need to run the benchmark on both latest browser and reference
+          # build.
+          output_paths = OutputFilePaths(isolated_out_dir, benchmark).SetUp()
+          command_generator = TelemetryCommandGenerator(
               benchmark, options,
-              story_selection_config=story_selection_config, is_reference=True)
-          print('\n### {folder} ###'.format(
-              folder=reference_benchmark_foldername))
-          # We intentionally ignore the return code and test results of the
-          # reference build.
-          execute_telemetry_benchmark(
-              reference_command_generator, reference_output_paths,
-              options.xvfb)
+              story_selection_config=story_selection_config)
+          print('\n### {folder} ###'.format(folder=benchmark))
+          return_code = execute_telemetry_benchmark(
+              command_generator, output_paths, options.xvfb)
+          overall_return_code = return_code or overall_return_code
+          test_results_files.append(output_paths.test_results)
+          if options.run_ref_build:
+            reference_benchmark_foldername = benchmark + '.reference'
+            reference_output_paths = OutputFilePaths(
+                isolated_out_dir, reference_benchmark_foldername).SetUp()
+            reference_command_generator = TelemetryCommandGenerator(
+                benchmark, options,
+                story_selection_config=story_selection_config,
+                is_reference=True)
+            print('\n### {folder} ###'.format(
+                folder=reference_benchmark_foldername))
+            # We intentionally ignore the return code and test results of the
+            # reference build.
+            execute_telemetry_benchmark(
+                reference_command_generator, reference_output_paths,
+                options.xvfb)
+      if 'executables' in shard_configuration:
+        names_and_configs = shard_configuration['executables']
+        for (name, configuration
+             ) in names_and_configs.iteritems():
+          command_generator = GtestCommandGenerator(
+              options, override_executable=configuration['path'],
+              additional_flags=configuration['arguments'])
+          output_paths = OutputFilePaths(isolated_out_dir, name).SetUp()
+          print('\n### {folder} ###'.format(folder=name))
+          overall_return_code = execute_gtest_perf_test(
+              command_generator, output_paths, options.xvfb)
+          test_results_files.append(output_paths.test_results)
     else:
       raise Exception('Telemetry tests must provide either a shard map or a '
                       '--benchmarks list so that we know which stories to run.')
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 1e5d370..51fcd3f3 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -396,10 +396,6 @@
 const base::Feature kHtmlImportsRequestInitiatorLock{
     "HtmlImportsRequestInitiatorLock", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// When 'enabled', directly compositing images is turned off.
-const base::Feature kDisableDirectlyCompositedImages{
-    "DisableDirectlyCompositedImages", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables redirecting subresources in the page to better compressed and
 // optimized versions to provide data savings.
 const base::Feature kSubresourceRedirect{"SubresourceRedirect",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index c6a0a73..a8159c1 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -127,7 +127,6 @@
 
 BLINK_COMMON_EXPORT extern const base::Feature kARIAAnnotations;
 
-BLINK_COMMON_EXPORT extern const base::Feature kDisableDirectlyCompositedImages;
 BLINK_COMMON_EXPORT extern const base::Feature kCompositeCrossOriginIframes;
 BLINK_COMMON_EXPORT extern const base::Feature kVizHitTestOcclusionCheck;
 
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index f82bb2a5..c6141238d 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -193,10 +193,8 @@
   BLINK_PLATFORM_EXPORT static void EnableWebUsb(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXR(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRARModule(bool);
-  BLINK_PLATFORM_EXPORT static void EnableWebXRARDOMOverlay(bool);
-  BLINK_PLATFORM_EXPORT static void EnableWebXRAnchors(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRHitTest(bool);
-  BLINK_PLATFORM_EXPORT static void EnableWebXRPlaneDetection(bool);
+  BLINK_PLATFORM_EXPORT static void EnableWebXRIncubations(bool);
   BLINK_PLATFORM_EXPORT static void EnableXSLT(bool);
   BLINK_PLATFORM_EXPORT static void ForceOverlayFullscreenVideo(bool);
   BLINK_PLATFORM_EXPORT static void EnableTimerThrottlingForBackgroundTabs(
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
index 5dd315c..8b2b7a48 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
@@ -432,25 +432,40 @@
     except that addition and removal of None have no effect.
     """
 
-    def __init__(self, code_nodes=None, separator="\n", separator_last=""):
+    def __init__(self, code_nodes=None, separator="\n", head="", tail=""):
+        """
+        Args:
+            code_nodes: A list of CodeNode to be rendered.
+            separator: A str inserted between code nodes.
+            head:
+            tail: The head and tail sections that will be rendered iff the
+                content list is not empty.
+        """
         assert isinstance(separator, str)
-        assert isinstance(separator_last, str)
+        assert isinstance(head, str)
+        assert isinstance(tail, str)
 
         element_nodes_gensym = CodeNode.gensym()
         element_nodes = []
         template_text = format_template(
             """\
+% if {element_nodes}:
+{head}\\
+% endif
 % for node in {element_nodes}:
 ${node}\\
 % if not loop.last:
 {separator}\\
 % endif
 % endfor
-{separator_last}\
+% if {element_nodes}:
+{tail}\\
+% endif\
 """,
             element_nodes=element_nodes_gensym,
             separator=separator,
-            separator_last=separator_last)
+            head=head,
+            tail=tail)
         template_vars = {element_nodes_gensym: element_nodes}
 
         CodeNode.__init__(
@@ -530,12 +545,13 @@
     and provides the points where SymbolDefinitionNodes can be inserted.
     """
 
-    def __init__(self, code_nodes=None, separator="\n", separator_last=""):
+    def __init__(self, code_nodes=None, separator="\n", head="", tail=""):
         ListNode.__init__(
             self,
             code_nodes=code_nodes,
             separator=separator,
-            separator_last=separator_last)
+            head=head,
+            tail=tail)
 
     def _render(self, renderer, last_render_state):
         duplicates = []
@@ -558,12 +574,13 @@
     insert corresponding SymbolDefinitionNodes appropriately.
     """
 
-    def __init__(self, code_nodes=None, separator="\n", separator_last=""):
+    def __init__(self, code_nodes=None, separator="\n", head="", tail=""):
         SequenceNode.__init__(
             self,
             code_nodes=code_nodes,
             separator=separator,
-            separator_last=separator_last)
+            head=head,
+            tail=tail)
 
         self._likeliness = Likeliness.ALWAYS
         self._registered_code_symbols = set()
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx.py b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx.py
index 08f30a1..e911960 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx.py
@@ -152,7 +152,7 @@
                  delete=False):
         """
         Args:
-            name: Function name node, which may include nested-name-specifier
+            name: Function name, which may include nested-name-specifier
                 (i.e. 'namespace_name::' and/or 'class_name::').
             arg_decls: List of argument declarations.
             return_type: Return type.
@@ -185,10 +185,10 @@
         CompositeNode.__init__(
             self,
             template_format,
-            name=name,
+            name=_to_maybe_text_node(name),
             arg_decls=ListNode(
-                _to_node_list(arg_decls, TextNode), separator=", "),
-            return_type=return_type,
+                map(_to_maybe_text_node, arg_decls), separator=", "),
+            return_type=_to_maybe_text_node(return_type),
             const=const,
             override=override,
             default_or_delete=default_or_delete)
@@ -204,7 +204,7 @@
                  member_initializer_list=None):
         """
         Args:
-            name: Function name node, which may include nested-name-specifier
+            name: Function name, which may include nested-name-specifier
                 (i.e. 'namespace_name::' and/or 'class_name::').
             arg_decls: List of argument declarations.
             return_type: Return type.
@@ -228,21 +228,20 @@
         if member_initializer_list is None:
             member_initializer_list = ""
         else:
-            initializers = ListNode(
-                _to_node_list(member_initializer_list, TextNode),
-                separator=", ")
-            member_initializer_list = ListNode([TextNode(" : "), initializers],
-                                               separator="")
+            member_initializer_list = ListNode(
+                map(_to_maybe_text_node, member_initializer_list),
+                separator=", ",
+                head=" : ")
 
         self._body_node = SymbolScopeNode()
 
         CompositeNode.__init__(
             self,
             template_format,
-            name=name,
+            name=_to_maybe_text_node(name),
             arg_decls=ListNode(
-                _to_node_list(arg_decls, TextNode), separator=", "),
-            return_type=return_type,
+                map(_to_maybe_text_node, arg_decls), separator=", "),
+            return_type=_to_maybe_text_node(return_type),
             const=const,
             override=override,
             member_initializer_list=member_initializer_list,
@@ -253,20 +252,117 @@
         return self._body_node
 
 
+class CxxClassDefNode(CompositeNode):
+    def __init__(self, name, base_class_names=None, final=False):
+        """
+        Args:
+            name: The class name to be defined.
+            base_class_names: The list of base class names.
+            final: True makes this a final class.
+        """
+        assert isinstance(final, bool)
+
+        template_format = ("class {name}{final}{base_clause} {{\n"
+                           "  {top_section}\n"
+                           "  {public_section}\n"
+                           "  {protected_section}\n"
+                           "  {private_section}\n"
+                           "}};")
+
+        final = " final" if final else ""
+
+        if base_class_names is None:
+            base_clause = ""
+        else:
+            base_specifier_list = [
+                CompositeNode(
+                    "public {base_class_name}",
+                    base_class_name=_to_maybe_text_node(base_class_name))
+                for base_class_name in base_class_names
+            ]
+            base_clause = ListNode(
+                base_specifier_list, separator=", ", head=" : ")
+
+        self._top_section = ListNode(tail="\n")
+        self._public_section = ListNode(head="public:\n", tail="\n")
+        self._protected_section = ListNode(head="protected:\n", tail="\n")
+        self._private_section = ListNode(head="private:\n", tail="\n")
+
+        CompositeNode.__init__(
+            self,
+            template_format,
+            name=_to_maybe_text_node(name),
+            final=final,
+            base_clause=base_clause,
+            top_section=self._top_section,
+            public_section=self._public_section,
+            protected_section=self._protected_section,
+            private_section=self._private_section)
+
+    @property
+    def top_section(self):
+        return self._top_section
+
+    @property
+    def public_section(self):
+        return self._public_section
+
+    @property
+    def protected_section(self):
+        return self._protected_section
+
+    @property
+    def private_section(self):
+        return self._private_section
+
+
+class CxxNamespaceNode(CompositeNode):
+    def __init__(self, name="", body=None):
+        template_format = ("namespace {name} {{\n"
+                           "\n"
+                           "{body}\n"
+                           "\n"
+                           "}}  // namespace {name}")
+
+        if body is None:
+            self._body = ListNode()
+        else:
+            self._body = _to_list_node(body)
+
+        CompositeNode.__init__(
+            self, template_format, name=name, body=self._body)
+
+    @property
+    def body(self):
+        return self._body
+
+
 def _to_conditional_node(cond):
     if isinstance(cond, CodeNode):
         return cond
-    elif isinstance(cond, CodeGenExpr):
+    if isinstance(cond, CodeGenExpr):
         return TextNode(cond.to_text())
-    elif isinstance(cond, str):
+    if isinstance(cond, str):
         return TextNode(cond)
-    else:
-        assert False
+    assert False
 
 
-def _to_node_list(iterable, constructor):
-    return map(lambda x: x if isinstance(x, CodeNode) else constructor(x),
-               iterable)
+def _to_list_node(node):
+    if isinstance(node, ListNode):
+        return node
+    if isinstance(node, CodeNode):
+        return ListNode([node])
+    if isinstance(node, (list, tuple)):
+        return ListNode(node)
+    assert False
+
+
+def _to_maybe_text_node(node):
+    if isinstance(node, CodeNode):
+        return node
+    if isinstance(node, str):
+        return TextNode(node)
+    assert False
 
 
 def _to_symbol_scope_node(node, likeliness):
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx_test.py b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx_test.py
index e7dd51a..1210b58 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx_test.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_cxx_test.py
@@ -7,6 +7,7 @@
 from .code_node import SymbolNode
 from .code_node import SymbolScopeNode
 from .code_node import TextNode
+from .code_node_cxx import CxxClassDefNode
 from .code_node_cxx import CxxFuncDefNode
 from .code_node_cxx import CxxLikelyIfNode
 from .code_node_cxx import CxxUnlikelyIfNode
@@ -33,7 +34,7 @@
         self.assertEqual(actual, expected)
 
     def test_symbol_definition_with_branches(self):
-        root = SymbolScopeNode(separator_last="\n")
+        root = SymbolScopeNode()
 
         root.register_code_symbols([
             SymbolNode("var1", "int ${var1} = 1;"),
@@ -75,11 +76,11 @@
   int var6 = 6;
   return var6;
 }
-var3;
+var3;\
 """)
 
     def test_symbol_definition_with_nested_branches(self):
-        root = SymbolScopeNode(separator_last="\n")
+        root = SymbolScopeNode()
 
         root.register_code_symbols([
             SymbolNode("var1", "int ${var1} = 1;"),
@@ -128,26 +129,21 @@
     return var2;
   }
   return;
-}
+}\
 """)
 
     def test_function_definition_minimum(self):
-        root = SymbolScopeNode(separator_last="\n")
-        root.append(
-            CxxFuncDefNode(
-                name="blink::bindings::func", arg_decls=[],
-                return_type="void"))
+        root = CxxFuncDefNode(
+            name="blink::bindings::func", arg_decls=[], return_type="void")
 
         self.assertRenderResult(root, """\
 void blink::bindings::func() {
 
-}
+}\
 """)
 
     def test_function_definition_full(self):
-        root = SymbolScopeNode(separator_last="\n")
-
-        func_def = CxxFuncDefNode(
+        root = CxxFuncDefNode(
             name="blink::bindings::func",
             arg_decls=["int arg1", "int arg2"],
             return_type="void",
@@ -158,19 +154,17 @@
                 "member2(\"str\")",
             ])
 
-        func_def.body.register_code_symbols([
+        root.body.register_code_symbols([
             SymbolNode("var1", "int ${var1} = 1;"),
             SymbolNode("var2", "int ${var2} = 2;"),
         ])
 
-        func_def.body.extend([
+        root.body.extend([
             CxxUnlikelyIfNode(
                 cond=TextNode("${var1}"), body=[TextNode("return ${var1};")]),
             TextNode("return ${var2};"),
         ])
 
-        root.append(func_def)
-
         self.assertRenderResult(
             root, """\
 void blink::bindings::func(int arg1, int arg2) const override\
@@ -181,5 +175,33 @@
   }
   int var2 = 2;
   return var2;
-}
+}\
+""")
+
+    def test_class_definition(self):
+        root = CxxClassDefNode("X", ["A", "B"], final=True)
+
+        root.public_section.extend([
+            TextNode("void m1();"),
+            TextNode("void m2();"),
+        ])
+        root.private_section.extend([
+            TextNode("int m1_;"),
+            TextNode("int m2_;"),
+        ])
+
+        self.assertRenderResult(
+            root, """\
+class X final : public A, public B {
+
+ public:
+  void m1();
+  void m2();
+
+
+ private:
+  int m1_;
+  int m2_;
+
+};\
 """)
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_test.py b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_test.py
index bee9252..a5ea6bb 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_test.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node_test.py
@@ -72,6 +72,14 @@
         root.remove(root[-1])
         self.assertRenderResult(root, "2,3,5")
 
+    def test_list_node_head_and_tail(self):
+        self.assertRenderResult(ListNode(), "")
+        self.assertRenderResult(ListNode(head="head"), "")
+        self.assertRenderResult(ListNode(tail="tail"), "")
+        self.assertRenderResult(
+            ListNode([TextNode("-content-")], head="head", tail="tail"),
+            "head-content-tail")
+
     def test_nested_sequence(self):
         """Tests nested ListNodes."""
         root = ListNode(separator=",")
@@ -93,7 +101,7 @@
         Tests that use of SymbolNode inserts necessary SymbolDefinitionNode
         appropriately.
         """
-        root = SymbolScopeNode(separator_last="\n")
+        root = SymbolScopeNode(tail="\n")
 
         root.register_code_symbols([
             SymbolNode("var1", "int ${var1} = ${var2} + ${var3};"),
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py b/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
index c52af687..fc13087b 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
@@ -637,7 +637,7 @@
             make_dict_member_set_def(cg_context.make_copy(dict_member=member)),
         ])
 
-    root_node = SymbolScopeNode(separator_last="\n")
+    root_node = ListNode(tail="\n")
     root_node.set_accumulator(CodeGenAccumulator())
     root_node.set_renderer(MakoRenderer())
 
@@ -681,7 +681,7 @@
         make_dict_class_def(cg_context),
     ])
 
-    root_node = SymbolScopeNode(separator_last="\n")
+    root_node = ListNode(tail="\n")
     root_node.set_accumulator(CodeGenAccumulator())
     root_node.set_renderer(MakoRenderer())
 
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index bee6053..91b3120 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3260,7 +3260,7 @@
     return;
 
   if (val != is_immersive_ar_overlay_) {
-    DCHECK(RuntimeEnabledFeatures::WebXRARDOMOverlayEnabled(this));
+    DCHECK(RuntimeEnabledFeatures::WebXRIncubationsEnabled(this));
     is_immersive_ar_overlay_ = val;
 
     // If the property has changed, apply the pseudo-style change to the root
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index f7679fc7..3b6cac6 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -2060,14 +2060,9 @@
       container->SetNeedsOverflowRecalc();
   }
 
-  if (diff.NeedsRecomputeOverflow() && !NeedsLayout()) {
-    // TODO(rhogan): Make inlines capable of recomputing overflow too.
-    if (IsLayoutBlock()) {
-      SetNeedsOverflowRecalc();
-    } else {
-      SetNeedsLayoutAndPrefWidthsRecalc(
-          layout_invalidation_reason::kStyleChange);
-    }
+  if (diff.NeedsRecomputeVisualOverflow()) {
+    PaintingLayer()->SetNeedsVisualOverflowRecalc();
+    SetShouldCheckForPaintInvalidation();
   }
 
   if (diff.NeedsPaintInvalidationSubtree() ||
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index b99cf22..7a3652c 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -28,7 +28,6 @@
 #include <memory>
 
 #include "cc/layers/picture_layer.h"
-#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/core/accessibility/apply_dark_mode.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
@@ -1576,9 +1575,6 @@
 bool CompositedLayerMapping::IsDirectlyCompositedImage() const {
   DCHECK(GetLayoutObject().IsImage());
 
-  if (base::FeatureList::IsEnabled(features::kDisableDirectlyCompositedImages))
-    return false;
-
   LayoutImage& image_layout_object = ToLayoutImage(GetLayoutObject());
 
   if (owning_layer_.HasBoxDecorationsOrBackground() ||
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index b20a297..9b7525f 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -903,8 +903,9 @@
     diff.SetFilterChanged();
 
   if (ComputedStyleBase::
-          UpdatePropertySpecificDifferencesNeedsRecomputeOverflow(*this, other))
-    diff.SetNeedsRecomputeOverflow();
+          UpdatePropertySpecificDifferencesNeedsRecomputeVisualOverflow(*this,
+                                                                        other))
+    diff.SetNeedsRecomputeVisualOverflow();
 
   if (ComputedStyleBase::UpdatePropertySpecificDifferencesBackdropFilter(*this,
                                                                          other))
diff --git a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5 b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
index eb09609..21c836e 100644
--- a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
+++ b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
@@ -388,7 +388,7 @@
         fields_to_diff: ["Mask", "MaskBoxImage"],
     },
     {
-        name: "UpdatePropertySpecificDifferencesNeedsRecomputeOverflow",
+        name: "UpdatePropertySpecificDifferencesNeedsRecomputeVisualOverflow",
         predicates_to_test: [
           {
             predicate: "a.BoxShadowDataEquivalent(b)",
diff --git a/third_party/blink/renderer/core/style/style_difference.cc b/third_party/blink/renderer/core/style/style_difference.cc
index 49ef5c4..92a12377 100644
--- a/third_party/blink/renderer/core/style/style_difference.cc
+++ b/third_party/blink/renderer/core/style/style_difference.cc
@@ -43,7 +43,7 @@
       break;
   }
 
-  out << ", recomputeOverflow=" << diff.recompute_overflow_;
+  out << ", recomputeVisualOverflow=" << diff.recompute_visual_overflow_;
   out << ", visualRectUpdate=" << diff.visual_rect_update_;
 
   out << ", propertySpecificDifferences=";
diff --git a/third_party/blink/renderer/core/style/style_difference.h b/third_party/blink/renderer/core/style/style_difference.h
index 517fbb8..437fad93 100644
--- a/third_party/blink/renderer/core/style/style_difference.h
+++ b/third_party/blink/renderer/core/style/style_difference.h
@@ -40,7 +40,7 @@
         layout_type_(kNoLayout),
         needs_collect_inlines_(false),
         needs_reshape_(false),
-        recompute_overflow_(false),
+        recompute_visual_overflow_(false),
         visual_rect_update_(false),
         property_specific_differences_(0),
         scroll_anchor_disabling_property_changed_(false),
@@ -52,7 +52,7 @@
     layout_type_ = std::max(layout_type_, other.layout_type_);
     needs_collect_inlines_ |= other.needs_collect_inlines_;
     needs_reshape_ |= other.needs_reshape_;
-    recompute_overflow_ |= other.recompute_overflow_;
+    recompute_visual_overflow_ |= other.recompute_visual_overflow_;
     visual_rect_update_ |= other.visual_rect_update_;
     property_specific_differences_ |= other.property_specific_differences_;
     scroll_anchor_disabling_property_changed_ |=
@@ -63,7 +63,7 @@
   bool HasDifference() const {
     return paint_invalidation_type_ || layout_type_ || needs_collect_inlines_ ||
            needs_reshape_ || property_specific_differences_ ||
-           recompute_overflow_ || visual_rect_update_ ||
+           recompute_visual_overflow_ || visual_rect_update_ ||
            scroll_anchor_disabling_property_changed_ ||
            compositing_reasons_changed_;
   }
@@ -116,8 +116,10 @@
   bool NeedsReshape() const { return needs_reshape_; }
   void SetNeedsReshape() { needs_reshape_ = true; }
 
-  bool NeedsRecomputeOverflow() const { return recompute_overflow_; }
-  void SetNeedsRecomputeOverflow() { recompute_overflow_ = true; }
+  bool NeedsRecomputeVisualOverflow() const {
+    return recompute_visual_overflow_;
+  }
+  void SetNeedsRecomputeVisualOverflow() { recompute_visual_overflow_ = true; }
 
   bool NeedsVisualRectUpdate() const { return visual_rect_update_; }
   void SetNeedsVisualRectUpdate() { visual_rect_update_ = true; }
@@ -214,7 +216,7 @@
   unsigned layout_type_ : 2;
   unsigned needs_collect_inlines_ : 1;
   unsigned needs_reshape_ : 1;
-  unsigned recompute_overflow_ : 1;
+  unsigned recompute_visual_overflow_ : 1;
   unsigned visual_rect_update_ : 1;
   unsigned property_specific_differences_ : kPropertyDifferenceCount;
   unsigned scroll_anchor_disabling_property_changed_ : 1;
diff --git a/third_party/blink/renderer/core/style/style_difference_test.cc b/third_party/blink/renderer/core/style/style_difference_test.cc
index f45ae9b..9cffd2f 100644
--- a/third_party/blink/renderer/core/style/style_difference_test.cc
+++ b/third_party/blink/renderer/core/style/style_difference_test.cc
@@ -16,7 +16,7 @@
   EXPECT_EQ(
       "StyleDifference{layoutType=NoLayout, "
       "collectInlines=0, reshape=0, "
-      "paintInvalidationType=NoPaintInvalidation, recomputeOverflow=0, "
+      "paintInvalidationType=NoPaintInvalidation, recomputeVisualOverflow=0, "
       "visualRectUpdate=0, propertySpecificDifferences=, "
       "scrollAnchorDisablingPropertyChanged=0}",
       string_stream.str());
@@ -29,7 +29,7 @@
   diff.SetNeedsPositionedMovementLayout();
   diff.SetNeedsReshape();
   diff.SetNeedsCollectInlines();
-  diff.SetNeedsRecomputeOverflow();
+  diff.SetNeedsRecomputeVisualOverflow();
   diff.SetNeedsVisualRectUpdate();
   diff.SetTransformChanged();
   diff.SetScrollAnchorDisablingPropertyChanged();
@@ -37,8 +37,9 @@
   EXPECT_EQ(
       "StyleDifference{layoutType=PositionedMovement, "
       "collectInlines=1, reshape=1, "
-      "paintInvalidationType=PaintInvalidationObject, recomputeOverflow=1, "
-      "visualRectUpdate=1, propertySpecificDifferences=TransformChanged, "
+      "paintInvalidationType=PaintInvalidationObject, "
+      "recomputeVisualOverflow=1, visualRectUpdate=1, "
+      "propertySpecificDifferences=TransformChanged, "
       "scrollAnchorDisablingPropertyChanged=1}",
       string_stream.str());
 }
@@ -58,7 +59,7 @@
   EXPECT_EQ(
       "StyleDifference{layoutType=NoLayout, "
       "collectInlines=0, reshape=0, "
-      "paintInvalidationType=NoPaintInvalidation, recomputeOverflow=0, "
+      "paintInvalidationType=NoPaintInvalidation, recomputeVisualOverflow=0, "
       "visualRectUpdate=0, "
       "propertySpecificDifferences=TransformChanged|OpacityChanged|"
       "ZIndexChanged|FilterChanged|BackdropFilterChanged|CSSClipChanged|"
diff --git a/third_party/blink/renderer/modules/remote_objects/remote_object.cc b/third_party/blink/renderer/modules/remote_objects/remote_object.cc
index 95674b2..2cffcbd 100644
--- a/third_party/blink/renderer/modules/remote_objects/remote_object.cc
+++ b/third_party/blink/renderer/modules/remote_objects/remote_object.cc
@@ -31,8 +31,16 @@
 
 std::vector<std::string> RemoteObject::EnumerateNamedProperties(
     v8::Isolate* isolate) {
-  // TODO(crbug.com/794320): implement this.
-  return std::vector<std::string>();
+  if (!object_.is_bound()) {
+    gateway_->BindRemoteObjectReceiver(object_id_,
+                                       object_.BindNewPipeAndPassReceiver());
+  }
+  WTF::Vector<WTF::String> methods;
+  object_->GetMethods(&methods);
+  std::vector<std::string> result;
+  for (const auto& method : methods)
+    result.push_back(method.Utf8());
+  return result;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/remote_objects/remote_object.h b/third_party/blink/renderer/modules/remote_objects/remote_object.h
index e48b712f..c9636d4b 100644
--- a/third_party/blink/renderer/modules/remote_objects/remote_object.h
+++ b/third_party/blink/renderer/modules/remote_objects/remote_object.h
@@ -39,6 +39,7 @@
 
  private:
   WeakPersistent<RemoteObjectGatewayImpl> gateway_{nullptr};
+  mojo::Remote<mojom::blink::RemoteObject> object_;
   int32_t object_id_;
 };
 
diff --git a/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.cc b/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.cc
index bc9acdc0..6d308bec 100644
--- a/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.cc
+++ b/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.cc
@@ -9,6 +9,8 @@
 #include "third_party/blink/renderer/platform/bindings/v8_binding.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 
+#undef GetObject
+
 namespace blink {
 
 // static
@@ -94,6 +96,12 @@
   named_objects_.erase(iter);
 }
 
+void RemoteObjectGatewayImpl::BindRemoteObjectReceiver(
+    int32_t object_id,
+    mojo::PendingReceiver<mojom::blink::RemoteObject> receiver) {
+  object_host_->GetObject(object_id, std::move(receiver));
+}
+
 // static
 void RemoteObjectGatewayFactoryImpl::Create(
     LocalFrame* frame,
diff --git a/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h b/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h
index 6797675..32097c5 100644
--- a/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h
+++ b/third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h
@@ -54,6 +54,10 @@
     Supplement<LocalFrame>::Trace(visitor);
   }
 
+  void BindRemoteObjectReceiver(
+      int32_t object_id,
+      mojo::PendingReceiver<mojom::blink::RemoteObject>);
+
  private:
   // mojom::blink::RemoteObjectGateway
   void AddNamedObject(const WTF::String& name, int32_t id) override;
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index 8cef770..478d187 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -117,7 +117,7 @@
     return device::mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR;
   } else if (feature_string == "unbounded") {
     return device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED;
-  } else if (RuntimeEnabledFeatures::WebXRARDOMOverlayEnabled(doc) &&
+  } else if (RuntimeEnabledFeatures::WebXRIncubationsEnabled(doc) &&
              feature_string == "dom-overlay-for-handheld-ar") {
     return device::mojom::XRSessionFeature::DOM_OVERLAY_FOR_HANDHELD_AR;
   }
diff --git a/third_party/blink/renderer/modules/xr/xr_anchor.idl b/third_party/blink/renderer/modules/xr/xr_anchor.idl
index 337c56e..2728ce5 100644
--- a/third_party/blink/renderer/modules/xr/xr_anchor.idl
+++ b/third_party/blink/renderer/modules/xr/xr_anchor.idl
@@ -5,7 +5,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRAnchors
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRAnchor {
     readonly attribute XRSpace? anchorSpace;
diff --git a/third_party/blink/renderer/modules/xr/xr_anchor_set.idl b/third_party/blink/renderer/modules/xr/xr_anchor_set.idl
index aee2cb2..ddb69dd 100644
--- a/third_party/blink/renderer/modules/xr/xr_anchor_set.idl
+++ b/third_party/blink/renderer/modules/xr/xr_anchor_set.idl
@@ -5,7 +5,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRAnchors
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRAnchorSet {
   readonly setlike<XRAnchor>;
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.idl b/third_party/blink/renderer/modules/xr/xr_frame.idl
index 4c0cdfa6..e76a5b4f 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.idl
+++ b/third_party/blink/renderer/modules/xr/xr_frame.idl
@@ -12,9 +12,9 @@
 
   // More details about the real-world understanding APIs can be found here:
   // https://github.com/immersive-web/real-world-geometry/blob/master/plane-detection-explainer.md
-  [RuntimeEnabled=WebXRPlaneDetection] readonly attribute XRWorldInformation worldInformation;
+  [RuntimeEnabled=WebXRIncubations] readonly attribute XRWorldInformation worldInformation;
 
-  [RuntimeEnabled=WebXRAnchors] readonly attribute XRAnchorSet trackedAnchors;
+  [RuntimeEnabled=WebXRIncubations] readonly attribute XRAnchorSet trackedAnchors;
 
   [RaisesException] XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace);
   [RaisesException] XRPose? getPose(XRSpace space, XRSpace relativeTo);
diff --git a/third_party/blink/renderer/modules/xr/xr_plane.idl b/third_party/blink/renderer/modules/xr/xr_plane.idl
index 2c2f6820..e448c0793b 100644
--- a/third_party/blink/renderer/modules/xr/xr_plane.idl
+++ b/third_party/blink/renderer/modules/xr/xr_plane.idl
@@ -12,7 +12,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRPlaneDetection
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRPlane {
     readonly attribute XRSpace planeSpace;
@@ -20,5 +20,5 @@
     readonly attribute XRPlaneOrientation? orientation;
     readonly attribute DOMHighResTimeStamp lastChangedTime;
 
-    [RuntimeEnabled=WebXRAnchors, CallWith=ScriptState, RaisesException] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
+    [CallWith=ScriptState, RaisesException] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.idl b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.idl
index 94573244..8b41426a 100644
--- a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.idl
+++ b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.idl
@@ -7,7 +7,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRPlaneDetection
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRPlaneDetectionState {
    readonly attribute boolean enabled;
diff --git a/third_party/blink/renderer/modules/xr/xr_plane_set.idl b/third_party/blink/renderer/modules/xr/xr_plane_set.idl
index 8e04e6bd..f0d3a0d4 100644
--- a/third_party/blink/renderer/modules/xr/xr_plane_set.idl
+++ b/third_party/blink/renderer/modules/xr/xr_plane_set.idl
@@ -7,7 +7,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRPlaneDetection
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRPlaneSet {
   readonly setlike<XRPlane>;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl
index cb85a643d..4638ba0 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -50,11 +50,11 @@
   [RuntimeEnabled=WebXRHitTest, CallWith=ScriptState, RaisesException] Promise<FrozenArray<XRHitResult>> requestHitTest(XRRay ray, XRSpace space);
 
   // https://github.com/immersive-web/real-world-geometry/blob/master/plane-detection-explainer.md
-  [RuntimeEnabled=WebXRPlaneDetection] readonly attribute XRWorldTrackingState worldTrackingState;
-  [RuntimeEnabled=WebXRPlaneDetection, RaisesException] void updateWorldTrackingState(optional XRWorldTrackingStateInit state);
+  [RuntimeEnabled=WebXRIncubations] readonly attribute XRWorldTrackingState worldTrackingState;
+  [RuntimeEnabled=WebXRIncubations, RaisesException] void updateWorldTrackingState(optional XRWorldTrackingStateInit state);
 
-  [RuntimeEnabled=WebXRAnchors] readonly attribute XRAnchorSet trackedAnchors;
-  [RuntimeEnabled=WebXRAnchors, CallWith=ScriptState, RaisesException] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
+  [RuntimeEnabled=WebXRIncubations] readonly attribute XRAnchorSet trackedAnchors;
+  [RuntimeEnabled=WebXRIncubations, CallWith=ScriptState, RaisesException] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
 
   [CallWith=ScriptState, Measure, RaisesException] Promise<void> end();
 
diff --git a/third_party/blink/renderer/modules/xr/xr_world_information.idl b/third_party/blink/renderer/modules/xr/xr_world_information.idl
index 7aa6950..b60addc 100644
--- a/third_party/blink/renderer/modules/xr/xr_world_information.idl
+++ b/third_party/blink/renderer/modules/xr/xr_world_information.idl
@@ -7,7 +7,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRPlaneDetection
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRWorldInformation {
    readonly attribute XRPlaneSet? detectedPlanes;
diff --git a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.idl b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.idl
index 070ee06..ee714c3 100644
--- a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.idl
+++ b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.idl
@@ -7,7 +7,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXRPlaneDetection
+    RuntimeEnabled=WebXRIncubations
 ]
 interface XRWorldTrackingState {
    readonly attribute XRPlaneDetectionState planeDetectionState;
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 5821aa7..57a9102 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -476,20 +476,12 @@
   RuntimeEnabledFeatures::SetWebXRARModuleEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableWebXRARDOMOverlay(bool enable) {
-  RuntimeEnabledFeatures::SetWebXRARDOMOverlayEnabled(enable);
-}
-
-void WebRuntimeFeatures::EnableWebXRAnchors(bool enable) {
-  RuntimeEnabledFeatures::SetWebXRAnchorsEnabled(enable);
-}
-
 void WebRuntimeFeatures::EnableWebXRHitTest(bool enable) {
   RuntimeEnabledFeatures::SetWebXRHitTestEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableWebXRPlaneDetection(bool enable) {
-  RuntimeEnabledFeatures::SetWebXRPlaneDetectionEnabled(enable);
+void WebRuntimeFeatures::EnableWebXRIncubations(bool enable) {
+  RuntimeEnabledFeatures::SetWebXRIncubationsEnabled(enable);
 }
 
 void WebRuntimeFeatures::EnablePresentationAPI(bool enable) {
diff --git a/third_party/blink/renderer/platform/heap/heap_stats_collector.cc b/third_party/blink/renderer/platform/heap/heap_stats_collector.cc
index 6b8dc5c..da31a21d 100644
--- a/third_party/blink/renderer/platform/heap/heap_stats_collector.cc
+++ b/third_party/blink/renderer/platform/heap/heap_stats_collector.cc
@@ -170,6 +170,10 @@
   return base::TimeDelta::FromSecondsD(estimated_marking_time_in_seconds());
 }
 
+base::TimeDelta ThreadHeapStatsCollector::Event::roots_marking_time() const {
+  return scope_data[kVisitRoots];
+}
+
 base::TimeDelta ThreadHeapStatsCollector::Event::incremental_marking_time()
     const {
   return scope_data[kIncrementalMarkingStartMarking] +
diff --git a/third_party/blink/renderer/platform/heap/heap_stats_collector.h b/third_party/blink/renderer/platform/heap/heap_stats_collector.h
index e5e7207f..4e8a2bd 100644
--- a/third_party/blink/renderer/platform/heap/heap_stats_collector.h
+++ b/third_party/blink/renderer/platform/heap/heap_stats_collector.h
@@ -63,6 +63,7 @@
   V(VisitDOMWrappers)                 \
   V(VisitPersistentRoots)             \
   V(VisitPersistents)                 \
+  V(VisitRoots)                       \
   V(VisitStackRoots)
 
 #define FOR_ALL_CONCURRENT_SCOPES(V) \
@@ -250,6 +251,9 @@
     // Time spent in the final atomic pause in sweeping and compacting the heap.
     base::TimeDelta atomic_sweep_and_compact_time() const;
 
+    // Time spent marking the roots.
+    base::TimeDelta roots_marking_time() const;
+
     // Time spent incrementally marking the heap.
     base::TimeDelta incremental_marking_time() const;
 
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 9afaaa7..4d1078c9 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -888,6 +888,8 @@
   UMA_HISTOGRAM_TIMES("BlinkGC.TimeForAtomicPhaseMarking",
                       event.atomic_marking_time());
   UMA_HISTOGRAM_TIMES("BlinkGC.TimeForGCCycle", event.gc_cycle_time());
+  UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarkingRoots",
+                      event.roots_marking_time());
   UMA_HISTOGRAM_TIMES("BlinkGC.TimeForIncrementalMarking",
                       event.incremental_marking_time());
   UMA_HISTOGRAM_TIMES("BlinkGC.TimeForMarking.Foreground",
@@ -1618,6 +1620,9 @@
 }
 
 void ThreadState::MarkPhaseVisitRoots() {
+  ThreadHeapStatsCollector::Scope stats_scope(
+      Heap().stats_collector(), ThreadHeapStatsCollector::kVisitRoots);
+
   Visitor* visitor = current_gc_data_.visitor.get();
 
   VisitPersistents(visitor);
@@ -1639,7 +1644,7 @@
   }
 
   if (current_gc_data_.stack_state == BlinkGC::kHeapPointersOnStack) {
-    ThreadHeapStatsCollector::Scope stats_scope(
+    ThreadHeapStatsCollector::Scope stack_stats_scope(
         Heap().stats_collector(), ThreadHeapStatsCollector::kVisitStackRoots);
     PushRegistersAndVisitStack();
   }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
index 4a07d2eb..0a75aaf2 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
@@ -97,6 +97,7 @@
   std::unique_ptr<ResourceRequest> request =
       std::make_unique<ResourceRequest>(new_url);
   request->SetRequestorOrigin(RequestorOrigin());
+  request->SetIsolatedWorldOrigin(IsolatedWorldOrigin());
   request->SetHttpMethod(new_method);
   request->SetSiteForCookies(new_site_for_cookies);
   String referrer =
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 9420a1b..870fc43 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1871,15 +1871,6 @@
       status: "stable",
     },
     {
-      name: "WebXRAnchors",
-      // depends_on: ["WebXRARModule"],  // TODO(https://crbug.com/954679): uncomment once bug is fixed
-    },
-    {
-      name: "WebXRARDOMOverlay",
-      // depends_on: ["WebXRARModule"],  // TODO(https://crbug.com/954679): uncomment once bug is fixed
-      status: "experimental",
-    },
-    {
       name: "WebXRARModule",
       depends_on: ["WebXR"],
       status: "experimental",
@@ -1890,8 +1881,9 @@
       status: "experimental"
     },
     {
-      name: "WebXRPlaneDetection",
+      name: "WebXRIncubations",
       // depends_on: ["WebXRARModule"],  // TODO(https://crbug.com/954679): uncomment once bug is fixed
+      status: "experimental",
     },
     // Extends window placement functionality for multi-screen devices.
     {
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index bab9f08b..4540782 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -362,3 +362,7 @@
 # Note to sheriffs: Timeouts about "idlharness" or "interfaces" are usually crbug.com/856601.
 crbug.com/856601 [ Linux ] external/wpt/secure-contexts/idlharness.any.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/compression/compression-stream.tentative.any.serviceworker.html [ Pass Timeout ]
+
+# Sheriff 2019-12-27
+crbug.com/856601 [ Linux ] external/wpt/battery-status/idlharness.https.window.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] external/wpt/permissions/idlharness.any.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index dfe34d6..9b47a6b3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1470,7 +1470,7 @@
 crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-015.html [ Failure ]
 crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-1.html [ Failure ]
 crbug.com/6606 external/wpt/mathml/relations/css-styling/displaystyle-2.html [ Failure ]
-crbug.com/6606 external/wpt/mathml/relations/css-styling/ignored-properties-001.html [ Failure ]
+crbug.com/6606 external/wpt/mathml/relations/css-styling/ignored-properties-001.html [ Failure Timeout ]
 crbug.com/6606 external/wpt/mathml/relations/css-styling/lengths-1.html [ Failure ]
 crbug.com/6606 external/wpt/mathml/relations/css-styling/lengths-2.html [ Failure ]
 crbug.com/6606 external/wpt/mathml/relations/css-styling/mathvariant-auto.html [ Failure ]
@@ -6145,4 +6145,10 @@
 crbug.com/1036626 virtual/threaded/http/tests/devtools/tracing/tracing-record-input-events.js [ Pass Failure ]
 crbug.com/1036054 [ Mac10.13 ] virtual/threaded/fast/scroll-snap/snap-to-area-with-fractional-offset.html [ Pass Failure ]
 crbug.com/960944 animations/web-animations/animation-state-changes-positive-playback-rate.html [ Pass Failure ]
-crbug.com/1033381 synthetic_gestures/smooth-scroll-tiny-delta.html [ Pass Crash ]
\ No newline at end of file
+crbug.com/1033381 synthetic_gestures/smooth-scroll-tiny-delta.html [ Pass Crash ]
+
+# Sheriff 2019-12-27
+crbug.com/1038037 [ Win ] virtual/gpu-rasterization/images/exif-orientation-css.html [ Pass Failure ]
+crbug.com/1038037 [ Win ] virtual/gpu-rasterization/images/exif-orientation-none-css.html [ Pass Failure ]
+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 ]
diff --git a/third_party/blink/web_tests/animations/composition/column-gap-composition.html b/third_party/blink/web_tests/animations/composition/column-gap-composition.html
deleted file mode 100644
index 6c8512d..0000000
--- a/third_party/blink/web_tests/animations/composition/column-gap-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: 'column-gap',
-  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: 'column-gap',
-  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: 'column-gap',
-  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'},
-]);
-
-assertComposition({
-  property: 'column-gap',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'normal',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'normal'},
-  {at: 1, is: 'normal'},
-  {at: 1.5, is: 'normal'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/height-composition.html b/third_party/blink/web_tests/animations/composition/height-composition.html
deleted file mode 100644
index d1224586..0000000
--- a/third_party/blink/web_tests/animations/composition/height-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'height',
-  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: 'height',
-  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: 'height',
-  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: 'height',
-  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'},
-]);
-
-assertComposition({
-  property: 'height',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/margin-bottom-composition.html b/third_party/blink/web_tests/animations/composition/margin-bottom-composition.html
deleted file mode 100644
index a2c6e5b..0000000
--- a/third_party/blink/web_tests/animations/composition/margin-bottom-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'margin-bottom',
-  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: 'margin-bottom',
-  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: 'margin-bottom',
-  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: 'margin-bottom',
-  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'},
-]);
-
-assertComposition({
-  property: 'margin-bottom',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/margin-left-composition.html b/third_party/blink/web_tests/animations/composition/margin-left-composition.html
deleted file mode 100644
index 7db9096f..0000000
--- a/third_party/blink/web_tests/animations/composition/margin-left-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'margin-left',
-  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: 'margin-left',
-  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: 'margin-left',
-  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: 'margin-left',
-  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'},
-]);
-
-assertComposition({
-  property: 'margin-left',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/margin-right-composition.html b/third_party/blink/web_tests/animations/composition/margin-right-composition.html
deleted file mode 100644
index 00762fa..0000000
--- a/third_party/blink/web_tests/animations/composition/margin-right-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'margin-right',
-  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: 'margin-right',
-  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: 'margin-right',
-  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: 'margin-right',
-  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'},
-]);
-
-assertComposition({
-  property: 'margin-right',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/margin-top-composition.html b/third_party/blink/web_tests/animations/composition/margin-top-composition.html
deleted file mode 100644
index 21774b61..0000000
--- a/third_party/blink/web_tests/animations/composition/margin-top-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'margin-top',
-  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: 'margin-top',
-  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: 'margin-top',
-  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: 'margin-top',
-  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'},
-]);
-
-assertComposition({
-  property: 'margin-top',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/max-height-composition.html b/third_party/blink/web_tests/animations/composition/max-height-composition.html
deleted file mode 100644
index 64d7057..0000000
--- a/third_party/blink/web_tests/animations/composition/max-height-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'max-height',
-  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: 'max-height',
-  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: 'max-height',
-  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: 'max-height',
-  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'},
-]);
-
-assertComposition({
-  property: 'max-height',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'none',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'none'},
-  {at: 1, is: 'none'},
-  {at: 1.5, is: 'none'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/max-width-composition.html b/third_party/blink/web_tests/animations/composition/max-width-composition.html
deleted file mode 100644
index 4d8ddb04..0000000
--- a/third_party/blink/web_tests/animations/composition/max-width-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'max-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: 'max-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: 'max-width',
-  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: 'max-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'},
-]);
-
-assertComposition({
-  property: 'max-width',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'none',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'none'},
-  {at: 1, is: 'none'},
-  {at: 1.5, is: 'none'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/min-height-composition.html b/third_party/blink/web_tests/animations/composition/min-height-composition.html
deleted file mode 100644
index efe3d874..0000000
--- a/third_party/blink/web_tests/animations/composition/min-height-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'min-height',
-  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: 'min-height',
-  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: 'min-height',
-  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: 'min-height',
-  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'},
-]);
-
-assertComposition({
-  property: 'min-height',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/min-width-composition.html b/third_party/blink/web_tests/animations/composition/min-width-composition.html
deleted file mode 100644
index 55e1e75..0000000
--- a/third_party/blink/web_tests/animations/composition/min-width-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'min-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: 'min-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: 'min-width',
-  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: 'min-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'},
-]);
-
-assertComposition({
-  property: 'min-width',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/padding-bottom-composition.html b/third_party/blink/web_tests/animations/composition/padding-bottom-composition.html
deleted file mode 100644
index 4a4ff24..0000000
--- a/third_party/blink/web_tests/animations/composition/padding-bottom-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: 'padding-bottom',
-  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: 'padding-bottom',
-  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: 'padding-bottom',
-  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: 'padding-bottom',
-  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/padding-left-composition.html b/third_party/blink/web_tests/animations/composition/padding-left-composition.html
deleted file mode 100644
index 4b265105..0000000
--- a/third_party/blink/web_tests/animations/composition/padding-left-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: 'padding-left',
-  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: 'padding-left',
-  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: 'padding-left',
-  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: 'padding-left',
-  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/padding-right-composition.html b/third_party/blink/web_tests/animations/composition/padding-right-composition.html
deleted file mode 100644
index 4bbef1e..0000000
--- a/third_party/blink/web_tests/animations/composition/padding-right-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: 'padding-right',
-  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: 'padding-right',
-  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: 'padding-right',
-  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: 'padding-right',
-  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/padding-top-composition.html b/third_party/blink/web_tests/animations/composition/padding-top-composition.html
deleted file mode 100644
index 3b88e53..0000000
--- a/third_party/blink/web_tests/animations/composition/padding-top-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: 'padding-top',
-  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: 'padding-top',
-  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: 'padding-top',
-  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: 'padding-top',
-  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/row-gap-composition.html b/third_party/blink/web_tests/animations/composition/row-gap-composition.html
deleted file mode 100644
index 055d5cb..0000000
--- a/third_party/blink/web_tests/animations/composition/row-gap-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: 'row-gap',
-  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: 'row-gap',
-  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: 'row-gap',
-  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'},
-]);
-
-assertComposition({
-  property: 'row-gap',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'normal',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'normal'},
-  {at: 1, is: 'normal'},
-  {at: 1.5, is: 'normal'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/shape-margin-composition.html b/third_party/blink/web_tests/animations/composition/shape-margin-composition.html
deleted file mode 100644
index 62ae2dd..0000000
--- a/third_party/blink/web_tests/animations/composition/shape-margin-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: 'shape-margin',
-  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: 'shape-margin',
-  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: 'shape-margin',
-  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: 'shape-margin',
-  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/shape-outside-composition.html b/third_party/blink/web_tests/animations/composition/shape-outside-composition.html
deleted file mode 100644
index 700ac0d..0000000
--- a/third_party/blink/web_tests/animations/composition/shape-outside-composition.html
+++ /dev/null
@@ -1,182 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'circle(100px at 25px 25%)',
-  addFrom: 'circle(10px at 25px 75%)',
-  addTo: 'circle(50px at 50px center)',
-}, [
-  {at: -0.3, is: 'circle(98px at 42.5px 107.5%)'},
-  {at: 0, is: 'circle(110px at 50px 100%)'},
-  {at: 0.3, is: 'circle(122px at 57.5px 92.5%)'},
-  {at: 0.6, is: 'circle(134px at 65px 85%)'},
-  {at: 1, is: 'circle(150px at 75px 75%)'},
-  {at: 1.5, is: 'circle(170px at 87.5px 62.5%)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'circle(100px at 20px 20%)',
-  addFrom: 'circle(50px at 50px 50%)',
-  replaceTo: 'circle(50% at 150px 150%)',
-}, [
-  {at: -0.3, is: 'circle(calc(195px + -15%) at 46px 46%)'},
-  {at: 0, is: 'circle(150px at 70px 70%)'},
-  {at: 0.3, is: 'circle(calc(105px + 15%) at 94px 94%)'},
-  {at: 0.6, is: 'circle(calc(60px + 30%) at 118px 118%)'},
-  {at: 1, is: 'circle(50% at 150px 150%)'},
-  {at: 1.5, is: 'circle(calc(-75px + 75%) at 190px 190%)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'circle(farthest-side at 25px 75%)',
-  addFrom: 'circle(farthest-side at 25px 75%)',
-  addTo: 'circle(farthest-side at 50px center)',
-}, [
-  {at: 0.25, is: 'circle(farthest-side at 25px 75%)'},
-  {at: 0.75, is: 'circle(farthest-side at 50px 50%)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'circle(50px at 10px 20px)',
-  addFrom: 'circle(50px at 10px 20px)',
-  addTo: 'circle(farthest-side at 30px 40px)',
-}, [
-  {at: 0.25, is: 'circle(100px at 20px 40px)'},
-  {at: 0.75, is: 'circle(farthest-side at 30px 40px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'ellipse(10px 20px at 30px 40px)',
-  addFrom: 'ellipse(40px 30px at 20px 10px)',
-  addTo: 'ellipse(140px 130px at 120px 110px)',
-}, [
-  {at: -0.3, is: 'ellipse(20px 20px at 20px 20px)'},
-  {at: 0, is: 'ellipse(50px 50px at 50px 50px)'},
-  {at: 0.3, is: 'ellipse(80px 80px at 80px 80px)'},
-  {at: 0.6, is: 'ellipse(110px 110px at 110px 110px)'},
-  {at: 1, is: 'ellipse(150px 150px at 150px 150px)'},
-  {at: 1.5, is: 'ellipse(200px 200px at 200px 200px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'ellipse(10px 20px at 30px 40px)',
-  replaceFrom: 'ellipse(40px 30px at 20px 10px)',
-  addTo: 'ellipse(40px 30px at 20px 10px)',
-}, [
-  {at: -0.3, is: 'ellipse(37px 24px at 11px -2px)'},
-  {at: 0, is: 'ellipse(40px 30px at 20px 10px)'},
-  {at: 0.3, is: 'ellipse(43px 36px at 29px 22px)'},
-  {at: 0.6, is: 'ellipse(46px 42px at 38px 34px)'},
-  {at: 1, is: 'ellipse(50px 50px at 50px 50px)'},
-  {at: 1.5, is: 'ellipse(55px 60px at 65px 70px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'ellipse(25px 75%)',
-  addFrom: 'ellipse()',
-  addTo: 'ellipse(closest-side farthest-side)',
-}, [
-  {at: 0.25, is: 'ellipse(at 50% 50%)'},
-  {at: 0.75, is: 'ellipse(closest-side farthest-side at 50% 50%)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'inset(20px)',
-  addFrom: 'inset(20px)',
-  addTo: 'inset(40%)',
-}, [
-  {at: -0.3, is: 'inset(calc(46px + -12%))'},
-  {at: 0, is: 'inset(40px)'},
-  {at: 0.3, is: 'inset(calc(34px + 12%))'},
-  {at: 0.6, is: 'inset(calc(28px + 24%))'},
-  {at: 1, is: 'inset(calc(20px + 40%))'},
-  {at: 1.5, is: 'inset(calc(10px + 60%))'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'inset(1px 2px 3px 4px round 10px 20px 30px 40px / 50px 60px 70px 80px)',
-  addFrom: 'inset(1px 2px 3px 4px round 10px 20px 30px 40px / 50px 60px 70px 80px)',
-  replaceTo: 'inset(102px 104px 106px 108px round 120px 140px 160px 180px / 200px 220px 240px 260px)',
-}, [
-  {at: -0.3, is: 'inset(-28px -26px -24px -22px round 0px 10px 30px 50px / 70px 90px 110px 130px)'},
-  {at: 0, is: 'inset(2px 4px 6px 8px round 20px 40px 60px 80px / 100px 120px 140px 160px)'},
-  {at: 0.25, is: 'inset(27px 29px 31px 33px round 45px 65px 85px 105px / 125px 145px 165px 185px)'},
-  {at: 0.75, is: 'inset(77px 79px 81px 83px round 95px 115px 135px 155px / 175px 195px 215px 235px)'},
-  {at: 1, is: 'inset(102px 104px 106px 108px round 120px 140px 160px 180px / 200px 220px 240px 260px)'},
-  {at: 1.5, is: 'inset(152px 154px 156px 158px round 170px 190px 210px 230px / 250px 270px 290px 310px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'inset(1px 2px round 100px 200px)',
-  addFrom: 'inset(1px 2px round 100px 200px)',
-  addTo: 'inset(101px 102px 101px 102px)',
-}, [
-  {at: -0.3, is: 'inset(-28px -26px round 230px 460px)'},
-  {at: 0, is: 'inset(2px 4px round 200px 400px)'},
-  {at: 0.3, is: 'inset(32px 34px round 170px 340px)'},
-  {at: 0.6, is: 'inset(62px 64px round 140px 280px)'},
-  {at: 1, is: 'inset(102px 104px round 100px 200px)'},
-  {at: 1.5, is: 'inset(152px 154px round 50px 100px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'polygon(10px 20%, 30px 40%)',
-  addFrom: 'polygon(10px 20%, 30px 40%)',
-  addTo: 'polygon(110px 120%, 130px 140%)',
-}, [
-  {at: -0.3, is: 'polygon(-10px 10%, 30px 50%)'},
-  {at: 0, is: 'polygon(20px 40%, 60px 80%)'},
-  {at: 0.3, is: 'polygon(50px 70%, 90px 110%)'},
-  {at: 0.6, is: 'polygon(80px 100%, 120px 140%)'},
-  {at: 1, is: 'polygon(120px 140%, 160px 180%)'},
-  {at: 1.5, is: 'polygon(170px 190%, 210px 230%)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'polygon(evenodd, 10px 20px)',
-  addFrom: 'polygon(evenodd, 10px 20px)',
-  addTo: 'polygon(evenodd, 110px 120px)',
-}, [
-  {at: -0.3, is: 'polygon(evenodd, -10px 10px)'},
-  {at: 0, is: 'polygon(evenodd, 20px 40px)'},
-  {at: 0.3, is: 'polygon(evenodd, 50px 70px)'},
-  {at: 0.6, is: 'polygon(evenodd, 80px 100px)'},
-  {at: 1, is: 'polygon(evenodd, 120px 140px)'},
-  {at: 1.5, is: 'polygon(evenodd, 170px 190px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'polygon(evenodd, 10px 20px)',
-  addFrom: 'polygon(evenodd, 10px 20px)',
-  addTo: 'polygon(nonzero, 30px 40px)',
-}, [
-  {at: 0.25, is: 'polygon(evenodd, 20px 40px)'},
-  {at: 0.75, is: 'polygon(30px 40px)'},
-]);
-
-assertComposition({
-  property: 'shape-outside',
-  underlying: 'polygon(10px 20px, 30px 40px)',
-  addFrom: 'polygon(10px 20px, 30px 40px)',
-  addTo: 'polygon(30px 40px)',
-}, [
-  {at: 0.25, is: 'polygon(20px 40px, 60px 80px)'},
-  {at: 0.75, is: 'polygon(30px 40px)'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/animations/composition/width-composition.html b/third_party/blink/web_tests/animations/composition/width-composition.html
deleted file mode 100644
index 8508886..0000000
--- a/third_party/blink/web_tests/animations/composition/width-composition.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<meta charset="UTF-8">
-<body>
-<script src="../interpolation/resources/interpolation-test.js"></script>
-<script>
-assertComposition({
-  property: '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: '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: 'width',
-  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: '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'},
-]);
-
-assertComposition({
-  property: 'width',
-  underlying: '100px',
-  addFrom: '100px',
-  addTo: 'auto',
-}, [
-  {at: -0.3, is: '200px'},
-  {at: 0, is: '200px'},
-  {at: 0.5, is: 'auto'},
-  {at: 1, is: 'auto'},
-  {at: 1.5, is: 'auto'},
-]);
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/animation/column-gap-composition.html b/third_party/blink/web_tests/external/wpt/css/css-align/animation/column-gap-composition.html
new file mode 100644
index 0000000..0054206c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/animation/column-gap-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>column-gap composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-column-gap">
+<meta name="assert" content="column-gap 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: 'column-gap',
+  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: 'column-gap',
+  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: 'column-gap',
+  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'},
+]);
+
+test_composition({
+  property: 'column-gap',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'normal',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'normal'},
+  {at: 1, expect: 'normal'},
+  {at: 1.5, expect: 'normal'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/animation/row-gap-composition.html b/third_party/blink/web_tests/external/wpt/css/css-align/animation/row-gap-composition.html
new file mode 100644
index 0000000..238253a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/animation/row-gap-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>row-gap composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-row-gap">
+<meta name="assert" content="row-gap 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: 'row-gap',
+  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: 'row-gap',
+  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: 'row-gap',
+  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'},
+]);
+
+test_composition({
+  property: 'row-gap',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'normal',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'normal'},
+  {at: 1, expect: 'normal'},
+  {at: 1.5, expect: 'normal'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-bottom-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-bottom-composition.html
new file mode 100644
index 0000000..c95f8de2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-bottom-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>margin-bottom composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#margin-properties">
+<meta name="assert" content="margin-bottom 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: 'margin-bottom',
+  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: 'margin-bottom',
+  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: 'margin-bottom',
+  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: 'margin-bottom',
+  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'},
+]);
+
+test_composition({
+  property: 'margin-bottom',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-left-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-left-composition.html
new file mode 100644
index 0000000..8f3c646
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-left-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>margin-left composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#margin-properties">
+<meta name="assert" content="margin-left 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: 'margin-left',
+  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: 'margin-left',
+  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: 'margin-left',
+  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: 'margin-left',
+  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'},
+]);
+
+test_composition({
+  property: 'margin-left',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-right-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-right-composition.html
new file mode 100644
index 0000000..c9033033
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-right-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>margin-right composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#margin-properties">
+<meta name="assert" content="margin-right 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: 'margin-right',
+  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: 'margin-right',
+  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: 'margin-right',
+  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: 'margin-right',
+  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'},
+]);
+
+test_composition({
+  property: 'margin-right',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-top-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-top-composition.html
new file mode 100644
index 0000000..5f050bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/margin-top-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>margin-top composition</title>
+<link rel="help" href="https://www.w3.org/TR/CSS2/box.html#margin-properties">
+<meta name="assert" content="margin-top 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: 'margin-top',
+  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: 'margin-top',
+  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: 'margin-top',
+  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: 'margin-top',
+  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'},
+]);
+
+test_composition({
+  property: 'margin-top',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-bottom-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-bottom-composition.html
new file mode 100644
index 0000000..855b5d3d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-bottom-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>padding-bottom composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-box-3/#padding-shorthand">
+<meta name="assert" content="padding-bottom supports animation as a list of lengths">
+
+<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: 'padding-bottom',
+  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: 'padding-bottom',
+  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: 'padding-bottom',
+  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: 'padding-bottom',
+  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-box/animation/padding-left-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-left-composition.html
new file mode 100644
index 0000000..417777ae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-left-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>padding-left composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-box-3/#padding-shorthand">
+<meta name="assert" content="padding-left supports animation as a list of lengths">
+
+<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: 'padding-left',
+  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: 'padding-left',
+  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: 'padding-left',
+  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: 'padding-left',
+  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-box/animation/padding-right-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-right-composition.html
new file mode 100644
index 0000000..3c80849b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-right-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>padding-right composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-box-3/#padding-shorthand">
+<meta name="assert" content="padding-right supports animation as a list of lengths">
+
+<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: 'padding-right',
+  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: 'padding-right',
+  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: 'padding-right',
+  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: 'padding-right',
+  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-box/animation/padding-top-composition.html b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-top-composition.html
new file mode 100644
index 0000000..b5083ae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-box/animation/padding-top-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>padding-top composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-box-3/#padding-shorthand">
+<meta name="assert" content="padding-top supports animation as a list of lengths">
+
+<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: 'padding-top',
+  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: 'padding-top',
+  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: 'padding-top',
+  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: 'padding-top',
+  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-shapes/animation/shape-margin-composition.html b/third_party/blink/web_tests/external/wpt/css/css-shapes/animation/shape-margin-composition.html
new file mode 100644
index 0000000..395bad0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-shapes/animation/shape-margin-composition.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>shape-margin composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-margin-property">
+<meta name="assert" content="shape-margin 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: 'shape-margin',
+  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: 'shape-margin',
+  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: 'shape-margin',
+  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: 'shape-margin',
+  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-shapes/animation/shape-outside-composition.html b/third_party/blink/web_tests/external/wpt/css/css-shapes/animation/shape-outside-composition.html
new file mode 100644
index 0000000..0115148e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-shapes/animation/shape-outside-composition.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>shape-outside composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property">
+<meta name="assert" content="shape-outside supports animation as <basic-shape> or discrete">
+
+<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: 'shape-outside',
+  underlying: 'circle(100px at 25px 25%)',
+  addFrom: 'circle(10px at 25px 75%)',
+  addTo: 'circle(50px at 50px center)',
+}, [
+  {at: -0.3, expect: 'circle(98px at 42.5px 107.5%)'},
+  {at: 0, expect: 'circle(110px at 50px 100%)'},
+  {at: 0.3, expect: 'circle(122px at 57.5px 92.5%)'},
+  {at: 0.6, expect: 'circle(134px at 65px 85%)'},
+  {at: 1, expect: 'circle(150px at 75px 75%)'},
+  {at: 1.5, expect: 'circle(170px at 87.5px 62.5%)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'circle(100px at 20px 20%)',
+  addFrom: 'circle(50px at 50px 50%)',
+  replaceTo: 'circle(50% at 150px 150%)',
+}, [
+  {at: -0.3, expect: 'circle(calc(195px + -15%) at 46px 46%)'},
+  {at: 0, expect: 'circle(calc(150px + 0%) at 70px 70%)'},
+  {at: 0.3, expect: 'circle(calc(105px + 15%) at 94px 94%)'},
+  {at: 0.6, expect: 'circle(calc(60px + 30%) at 118px 118%)'},
+  {at: 1, expect: 'circle(50% at 150px 150%)'},
+  {at: 1.5, expect: 'circle(calc(-75px + 75%) at 190px 190%)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'circle(farthest-side at 25px 75%)',
+  addFrom: 'circle(farthest-side at 25px 75%)',
+  addTo: 'circle(farthest-side at 50px center)',
+}, [
+  {at: 0.25, expect: 'circle(farthest-side at 25px 75%)'},
+  {at: 0.75, expect: 'circle(farthest-side at 50px 50%)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'circle(50px at 10px 20px)',
+  addFrom: 'circle(50px at 10px 20px)',
+  addTo: 'circle(farthest-side at 30px 40px)',
+}, [
+  {at: 0.25, expect: 'circle(100px at 20px 40px)'},
+  {at: 0.75, expect: 'circle(farthest-side at 30px 40px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'ellipse(10px 20px at 30px 40px)',
+  addFrom: 'ellipse(40px 30px at 20px 10px)',
+  addTo: 'ellipse(140px 130px at 120px 110px)',
+}, [
+  {at: -0.3, expect: 'ellipse(20px 20px at 20px 20px)'},
+  {at: 0, expect: 'ellipse(50px 50px at 50px 50px)'},
+  {at: 0.3, expect: 'ellipse(80px 80px at 80px 80px)'},
+  {at: 0.6, expect: 'ellipse(110px 110px at 110px 110px)'},
+  {at: 1, expect: 'ellipse(150px 150px at 150px 150px)'},
+  {at: 1.5, expect: 'ellipse(200px 200px at 200px 200px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'ellipse(10px 20px at 30px 40px)',
+  replaceFrom: 'ellipse(40px 30px at 20px 10px)',
+  addTo: 'ellipse(40px 30px at 20px 10px)',
+}, [
+  {at: -0.3, expect: 'ellipse(37px 24px at 11px -2px)'},
+  {at: 0, expect: 'ellipse(40px 30px at 20px 10px)'},
+  {at: 0.3, expect: 'ellipse(43px 36px at 29px 22px)'},
+  {at: 0.6, expect: 'ellipse(46px 42px at 38px 34px)'},
+  {at: 1, expect: 'ellipse(50px 50px at 50px 50px)'},
+  {at: 1.5, expect: 'ellipse(55px 60px at 65px 70px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'ellipse(25px 75%)',
+  addFrom: 'ellipse()',
+  addTo: 'ellipse(closest-side farthest-side)',
+}, [
+  {at: 0.25, expect: 'ellipse(at 50% 50%)'},
+  {at: 0.75, expect: 'ellipse(closest-side farthest-side at 50% 50%)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'inset(20px)',
+  addFrom: 'inset(20px)',
+  addTo: 'inset(40%)',
+}, [
+  {at: -0.3, expect: 'inset(calc(46px + -12%))'},
+  {at: 0, expect: 'inset(calc(40px + 0%))'},
+  {at: 0.3, expect: 'inset(calc(34px + 12%))'},
+  {at: 0.6, expect: 'inset(calc(28px + 24%))'},
+  {at: 1, expect: 'inset(calc(20px + 40%))'},
+  {at: 1.5, expect: 'inset(calc(10px + 60%))'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'inset(1px 2px 3px 4px round 10px 20px 30px 40px / 50px 60px 70px 80px)',
+  addFrom: 'inset(1px 2px 3px 4px round 10px 20px 30px 40px / 50px 60px 70px 80px)',
+  replaceTo: 'inset(102px 104px 106px 108px round 120px 140px 160px 180px / 200px 220px 240px 260px)',
+}, [
+  {at: -0.3, expect: 'inset(-28px -26px -24px -22px round 0px 10px 30px 50px / 70px 90px 110px 130px)'},
+  {at: 0, expect: 'inset(2px 4px 6px 8px round 20px 40px 60px 80px / 100px 120px 140px 160px)'},
+  {at: 0.25, expect: 'inset(27px 29px 31px 33px round 45px 65px 85px 105px / 125px 145px 165px 185px)'},
+  {at: 0.75, expect: 'inset(77px 79px 81px 83px round 95px 115px 135px 155px / 175px 195px 215px 235px)'},
+  {at: 1, expect: 'inset(102px 104px 106px 108px round 120px 140px 160px 180px / 200px 220px 240px 260px)'},
+  {at: 1.5, expect: 'inset(152px 154px 156px 158px round 170px 190px 210px 230px / 250px 270px 290px 310px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'inset(1px 2px round 100px 200px)',
+  addFrom: 'inset(1px 2px round 100px 200px)',
+  addTo: 'inset(101px 102px 101px 102px)',
+}, [
+  {at: -0.3, expect: 'inset(-28px -26px round 230px 460px)'},
+  {at: 0, expect: 'inset(2px 4px round 200px 400px)'},
+  {at: 0.3, expect: 'inset(32px 34px round 170px 340px)'},
+  {at: 0.6, expect: 'inset(62px 64px round 140px 280px)'},
+  {at: 1, expect: 'inset(102px 104px round 100px 200px)'},
+  {at: 1.5, expect: 'inset(152px 154px round 50px 100px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'polygon(10px 20%, 30px 40%)',
+  addFrom: 'polygon(10px 20%, 30px 40%)',
+  addTo: 'polygon(110px 120%, 130px 140%)',
+}, [
+  {at: -0.3, expect: 'polygon(-10px 10%, 30px 50%)'},
+  {at: 0, expect: 'polygon(20px 40%, 60px 80%)'},
+  {at: 0.3, expect: 'polygon(50px 70%, 90px 110%)'},
+  {at: 0.6, expect: 'polygon(80px 100%, 120px 140%)'},
+  {at: 1, expect: 'polygon(120px 140%, 160px 180%)'},
+  {at: 1.5, expect: 'polygon(170px 190%, 210px 230%)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'polygon(evenodd, 10px 20px)',
+  addFrom: 'polygon(evenodd, 10px 20px)',
+  addTo: 'polygon(evenodd, 110px 120px)',
+}, [
+  {at: -0.3, expect: 'polygon(evenodd, -10px 10px)'},
+  {at: 0, expect: 'polygon(evenodd, 20px 40px)'},
+  {at: 0.3, expect: 'polygon(evenodd, 50px 70px)'},
+  {at: 0.6, expect: 'polygon(evenodd, 80px 100px)'},
+  {at: 1, expect: 'polygon(evenodd, 120px 140px)'},
+  {at: 1.5, expect: 'polygon(evenodd, 170px 190px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'polygon(evenodd, 10px 20px)',
+  addFrom: 'polygon(evenodd, 10px 20px)',
+  addTo: 'polygon(nonzero, 30px 40px)',
+}, [
+  {at: 0.25, expect: 'polygon(evenodd, 20px 40px)'},
+  {at: 0.75, expect: 'polygon(30px 40px)'},
+]);
+
+test_composition({
+  property: 'shape-outside',
+  underlying: 'polygon(10px 20px, 30px 40px)',
+  addFrom: 'polygon(10px 20px, 30px 40px)',
+  addTo: 'polygon(30px 40px)',
+}, [
+  {at: 0.25, expect: 'polygon(20px 40px, 60px 80px)'},
+  {at: 0.75, expect: 'polygon(30px 40px)'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/height-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/height-composition.html
new file mode 100644
index 0000000..094e247
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/height-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>height composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-height">
+<meta name="assert" content="height 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: 'height',
+  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: 'height',
+  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: 'height',
+  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: 'height',
+  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'},
+]);
+
+test_composition({
+  property: 'height',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-height-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-height-composition.html
new file mode 100644
index 0000000..fb5b241
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-height-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>max-height composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-max-height">
+<meta name="assert" content="max-height 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: 'max-height',
+  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: 'max-height',
+  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: 'max-height',
+  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: 'max-height',
+  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'},
+]);
+
+test_composition({
+  property: 'max-height',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'none',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'none'},
+  {at: 1, expect: 'none'},
+  {at: 1.5, expect: 'none'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-width-composition.html
new file mode 100644
index 0000000..8b6d8b7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/max-width-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>max-width composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-max-width">
+<meta name="assert" content="max-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: 'max-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: 'max-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: 'max-width',
+  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: 'max-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'},
+]);
+
+test_composition({
+  property: 'max-width',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'none',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'none'},
+  {at: 1, expect: 'none'},
+  {at: 1.5, expect: 'none'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-height-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-height-composition.html
new file mode 100644
index 0000000..1e92b0e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-height-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>min-height composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-min-height">
+<meta name="assert" content="min-height 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: 'min-height',
+  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: 'min-height',
+  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: 'min-height',
+  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: 'min-height',
+  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'},
+]);
+
+test_composition({
+  property: 'min-height',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-width-composition.html
new file mode 100644
index 0000000..e8bd410
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/min-width-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>min-width composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-min-width">
+<meta name="assert" content="min-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: 'min-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: 'min-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: 'min-width',
+  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: 'min-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'},
+]);
+
+test_composition({
+  property: 'min-width',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/width-composition.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/width-composition.html
new file mode 100644
index 0000000..bfe45cb3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/animation/width-composition.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>width composition</title>
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#propdef-width">
+<meta name="assert" content="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: '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: '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: 'width',
+  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: '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'},
+]);
+
+test_composition({
+  property: 'width',
+  underlying: '100px',
+  addFrom: '100px',
+  addTo: 'auto',
+}, [
+  {at: -0.3, expect: '200px'},
+  {at: 0, expect: '200px'},
+  {at: 0.5, expect: 'auto'},
+  {at: 1, expect: 'auto'},
+  {at: 1.5, expect: 'auto'},
+]);
+</script>
+</body>
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-change-invalidation-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-change-invalidation-expected.txt
index 5f92e7d..511002e 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-change-invalidation-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-change-invalidation-expected.txt
@@ -10,11 +10,6 @@
           "object": "LayoutBlockFlow A id='link'",
           "rect": [43, 83, 754, 30],
           "reason": "appeared"
-        },
-        {
-          "object": "LayoutListMarker (anonymous)",
-          "rect": [30, 88, 7, 19],
-          "reason": "style change"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-containing-image-in-non-standard-mode-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-containing-image-in-non-standard-mode-expected.txt
index 8b5d067..acf093c 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-containing-image-in-non-standard-mode-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/outline/outline-containing-image-in-non-standard-mode-expected.txt
@@ -10,11 +10,6 @@
           "object": "LayoutInline SPAN id='target'",
           "rect": [6, 6, 204, 58],
           "reason": "style change"
-        },
-        {
-          "object": "LayoutImage IMG",
-          "rect": [8, 8, 200, 50],
-          "reason": "geometry"
         }
       ]
     }
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index ce3fd626..e51a216 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -11086,6 +11086,22 @@
     method requestSession
     method supportsSession
     setter ondevicechange
+interface XRAnchor
+    attribute @@toStringTag
+    getter anchorSpace
+    getter lastChangedTime
+    method constructor
+    method detach
+interface XRAnchorSet
+    attribute @@toStringTag
+    getter size
+    method @@iterator
+    method constructor
+    method entries
+    method forEach
+    method has
+    method keys
+    method values
 interface XRBoundedReferenceSpace : XRReferenceSpace
     attribute @@toStringTag
     getter boundsGeometry
@@ -11093,6 +11109,8 @@
 interface XRFrame
     attribute @@toStringTag
     getter session
+    getter trackedAnchors
+    getter worldInformation
     method constructor
     method getHitTestResults
     method getHitTestResultsForTransientInput
@@ -11139,6 +11157,28 @@
     getter removed
     getter session
     method constructor
+interface XRPlane
+    attribute @@toStringTag
+    getter lastChangedTime
+    getter orientation
+    getter planeSpace
+    getter polygon
+    method constructor
+    method createAnchor
+interface XRPlaneDetectionState
+    attribute @@toStringTag
+    getter enabled
+    method constructor
+interface XRPlaneSet
+    attribute @@toStringTag
+    getter size
+    method @@iterator
+    method constructor
+    method entries
+    method forEach
+    method has
+    method keys
+    method values
 interface XRPose
     attribute @@toStringTag
     getter emulatedPosition
@@ -11186,9 +11226,12 @@
     getter onselectstart
     getter onvisibilitychange
     getter renderState
+    getter trackedAnchors
     getter visibilityState
+    getter worldTrackingState
     method cancelAnimationFrame
     method constructor
+    method createAnchor
     method end
     method requestAnimationFrame
     method requestHitTest
@@ -11196,6 +11239,7 @@
     method requestHitTestSourceForTransientInput
     method requestReferenceSpace
     method updateRenderState
+    method updateWorldTrackingState
     setter onend
     setter oninputsourceschange
     setter onselect
@@ -11245,6 +11289,14 @@
     getter ignoreDepthValues
     method constructor
     method getViewport
+interface XRWorldInformation
+    attribute @@toStringTag
+    getter detectedPlanes
+    method constructor
+interface XRWorldTrackingState
+    attribute @@toStringTag
+    getter planeDetectionState
+    method constructor
 interface XSLTProcessor
     attribute @@toStringTag
     method clearParameters
diff --git a/tools/binary_size/generate_milestone_reports.py b/tools/binary_size/generate_milestone_reports.py
index 22f07663..0322a253 100755
--- a/tools/binary_size/generate_milestone_reports.py
+++ b/tools/binary_size/generate_milestone_reports.py
@@ -231,6 +231,10 @@
 
   if args.sync:
     subprocess.check_call(cmd)
+    subprocess.check_call([
+        'gsutil.py', 'setmeta', '-h', 'Cache-Control:no-cache',
+        _PUSH_URL + 'milestones.json'
+    ])
   else:
     print()
     print('Sync files by running:')
diff --git a/tools/binary_size/generate_official_build_report.py b/tools/binary_size/generate_official_build_report.py
index 44649c40..b08ccce 100755
--- a/tools/binary_size/generate_official_build_report.py
+++ b/tools/binary_size/generate_official_build_report.py
@@ -11,45 +11,11 @@
 import os
 import re
 import subprocess
-import sys
-import urllib2
-
-_SRC_ROOT = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
-
-sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android', 'gyp'))
-from util import build_utils
+import tempfile
 
 _REPORTS_BASE_URL = 'gs://chrome-supersize/official_builds'
-_REPORTS_JSON_GS_URL = os.path.join(_REPORTS_BASE_URL, 'reports.json')
+_REPORTS_JSON_GS_URL = os.path.join(_REPORTS_BASE_URL, 'canary_reports.json')
 _REPORTS_GS_URL = os.path.join(_REPORTS_BASE_URL, 'reports')
-_SUPERSIZE_PATH = os.path.join(_SRC_ROOT, 'tools', 'binary_size', 'supersize')
-
-
-def _FetchReferenceVersion(new_version_str, platform):
-  all_versions = json.loads(
-      urllib2.urlopen('http://omahaproxy.appspot.com/history.json').read())
-
-  # Filter out versions newer than the last branch point.
-  new_version_major = new_version_str.split('.')[0]
-  versions = (e['version'] for e in all_versions if e['os'] == platform)
-  prev_versions = (e for e in versions if not e.startswith(new_version_major))
-
-  return max(prev_versions, key=lambda x: tuple(int(y) for y in x.split('.')))
-
-
-def _FetchSizeFileForVersion(gs_url, version, gs_size_path, output_path):
-  gs_path = '{}/{}/{}'.format(gs_url, version, gs_size_path)
-  cmd = ['gsutil.py', 'cp', gs_path, output_path]
-  subprocess.check_call(cmd)
-
-
-def _CreateReports(report_path, diff_report_path, ref_size_path, size_path):
-  subprocess.check_call(
-      [_SUPERSIZE_PATH, 'html_report', size_path, report_path])
-  subprocess.check_call([
-      _SUPERSIZE_PATH, 'html_report', '--diff-with', ref_size_path, size_path,
-      diff_report_path
-  ])
 
 
 def _WriteReportsJson(out):
@@ -57,47 +23,18 @@
 
   reports = []
   report_re = re.compile(
-      _REPORTS_GS_URL +
-      r'/(?P<cpu>\S+)/(?P<apk>\S+)/(?P<path>report_(?P<version>[^_]+)\.ndjson)')
+      re.escape(_REPORTS_GS_URL) +
+      r'/(?P<version>.+?)/(?P<apk>.+?)/(?P<cpu>.+?)\.size')
   for line in output.splitlines():
     m = report_re.search(line)
     if m:
-      meta = {
+      reports.append({
           'cpu': m.group('cpu'),
           'version': m.group('version'),
           'apk': m.group('apk'),
-          'path': m.group('path')
-      }
-      diff_re = re.compile(
-          r'{}/{}/(?P<path>report_(?P<version>\S+)_{}.ndjson)'.format(
-              meta['cpu'], meta['apk'], meta['version']))
-      m = diff_re.search(output)
-      if not m:
-        raise Exception('Missing diff report for {}'.format(str(meta)))
+      })
 
-      meta['diff_path'] = m.group('path')
-      meta['reference_version'] = m.group('version')
-      reports.append(meta)
-
-  return json.dump({'pushed': reports}, out)
-
-
-def _UploadReports(reports_json_path, base_url, *ndjson_paths):
-  for path in ndjson_paths:
-    dst = os.path.join(base_url, os.path.basename(path))
-    cmd = ['gsutil.py', 'cp', '-a', 'public-read', path, dst]
-    logging.warning(' '.join(cmd))
-    subprocess.check_call(cmd)
-
-  with open(reports_json_path, 'w') as f:
-    _WriteReportsJson(f)
-
-  cmd = [
-      'gsutil.py', 'cp', '-a', 'public-read', reports_json_path,
-      _REPORTS_JSON_GS_URL
-  ]
-  logging.warning(' '.join(cmd))
-  subprocess.check_call(cmd)
+  json.dump({'pushed': reports}, out)
 
 
 def main():
@@ -110,15 +47,8 @@
       '--size-path',
       required=True,
       help='Path to .size file for the given version.')
-  parser.add_argument(
-      '--gs-size-url',
-      required=True,
-      help='Bucket url that contains the .size files.')
-  parser.add_argument(
-      '--gs-size-path',
-      required=True,
-      help='Path within bucket to a .size file (full path looks like '
-      'GS_SIZE_URL/VERSION/GS_SIZE_PATH) used to locate reference .size file.')
+  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(
@@ -129,25 +59,24 @@
 
   args = parser.parse_args()
 
-  with build_utils.TempDir() as tmp_dir:
-    ref_version = _FetchReferenceVersion(args.version, args.platform)
-    logging.warning('Found reference version name: %s', ref_version)
+  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')
 
-    ref_size_path = os.path.join(tmp_dir, ref_version) + '.size'
-    report_path = os.path.join(tmp_dir, 'report_{}.ndjson'.format(args.version))
-    diff_report_path = os.path.join(
-        tmp_dir, 'report_{}_{}.ndjson'.format(ref_version, args.version))
-    reports_json_path = os.path.join(tmp_dir, 'reports.json')
-    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')
-    reports_base_url = os.path.join(_REPORTS_GS_URL, args.arch, report_basename)
+  cmd = ['gsutil.py', 'cp', '-a', 'public-read', args.size_path, dst_url]
+  logging.warning(' '.join(cmd))
+  subprocess.check_call(cmd)
 
-    _FetchSizeFileForVersion(args.gs_size_url, ref_version, args.gs_size_path,
-                             ref_size_path)
-    _CreateReports(report_path, diff_report_path, ref_size_path, args.size_path)
-    _UploadReports(reports_json_path, reports_base_url, report_path,
-                   diff_report_path)
+  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)
 
 
 if __name__ == '__main__':
diff --git a/tools/binary_size/libsupersize/static/index.html b/tools/binary_size/libsupersize/static/index.html
index 5174dbd..12c2c22 100644
--- a/tools/binary_size/libsupersize/static/index.html
+++ b/tools/binary_size/libsupersize/static/index.html
@@ -106,217 +106,7 @@
 }
   </style>
   <link rel="icon" href="favicon.ico" sizes="16x16 32x32 256x256" type="image/x-icon">
-  <script>
-const DO_NOT_DIFF = "Don't diff";
-
-/**
- * @param {string[]} options
- * @param {string} key
- */
-function buildOptions(options, key) {
-  const fragment = document.createDocumentFragment();
-  if (key) {
-    options = options
-      .map((item, i, self) => { return item[key] })
-      .filter((item, i, self) => { return i === self.indexOf(item) })
-      .sort();
-  }
-  for (let option of options) {
-    const optionEl = document.createElement('option');
-    optionEl.value = option;
-    optionEl.textContent = option;
-    fragment.appendChild(optionEl);
-  }
-  return fragment;
-}
-
-/**
- * Is `v1` a larger version than `v2`?
- * @param {string} v1
- * @param {string} v2
- */
-function isGreaterOrEqual(v1, v2) {
-  const [version1] = v1.split('.', 1).map(n => parseInt(n, 10));
-  const [version2] = v2.split('.', 1).map(n => parseInt(n, 10));
-  return version1 >= version2;
-}
-
-function selectOption(optList, index) {
-  const n = optList.length;
-  if (n > 0) optList[((index % n) + n) % n].selected = true;
-}
-
-function setSubmitListener(form, fetchDataUrl) {
-  form.addEventListener('submit', event => {
-    event.preventDefault();
-    const dataUrl = fetchDataUrl();
-    // Exclude unwind_cfi via a filter as a work-around for it being included
-    // in the size data. It's a file that exists in dev but not beta/stable.
-    window.open(`viewer.html?load_url=${dataUrl}&exclude=assets%2Funwind_cfi`);
-  });
-}
-
-// Milestones.
-(async () => {
-  // Milestones.
-  const milestoneResponse = await fetch('milestones/milestones.json');
-  const milestonesPushed = (await milestoneResponse.json())['pushed'];
-
-  // Official Builds
-  const officialBuildsResponse = await fetch('official_builds/reports.json');
-  const officialBuildsPushed = (await officialBuildsResponse.json())['pushed'];
-
-  if (document.readyState === 'loading') {
-    await new Promise(resolve => {
-      document.onreadystatechange = () => {
-        if (document.readyState !== 'loading') {
-          resolve();
-          document.onreadystatechange = null;
-        }
-      }
-    });
-  }
-
-  /** @type {HTMLFormElement} */
-  const submitButton = document.getElementById('submit-button');
-  const form = document.getElementById('select-form');
-  const selApk = form.elements.namedItem('apk');
-  const selVersion1 = form.elements.namedItem('version1');
-  const selVersion2 = form.elements.namedItem('version2');
-  const btnOpen = form.querySelector('button[type="submit"]');
-
-  function channelIsMilestone() {
-    // Returns: Whether channel is set to stable/beta (i.e. is a milestone)
-    const channel = document.querySelector('input[name="mode"]:checked').value;
-    return channel == 'stable/beta';
-  }
-
-  function fmtCpuApk(cpu, apk) {
-    return cpu + '/' + apk;
-  }
-
-  function cpuApkPairs(cpus, apks) {
-    let out = [];
-    for (let cpu of cpus) {
-      for (let apk of apks) {
-        // Chrome.apk not available for arm_64
-        if(!(cpu == 'arm_64' && apk == 'Chrome.apk')) {
-          out.push(fmtCpuApk(cpu, apk));
-        }
-      }
-    }
-    return out;
-  }
-
-  function updateApk() {
-    // Overwrites the apk selector with entries of format {cpu}/{apk}
-    let apks = [];
-    if (channelIsMilestone()) {
-      apks = cpuApkPairs(milestonesPushed.cpu, milestonesPushed.apk);
-    } else {
-      apks = [...new Set(officialBuildsPushed.map(a => {
-        let apk = a.apk;
-        // Can safely discard minimal apks - as of 16-Sep-2019 there was only
-        // one entry with an attached Monochrome.minimal apk, assumed bug
-        if (apk === 'Monochrome.minimal.apks') apk = 'Monochrome.apk';
-        return fmtCpuApk(a.cpu, a.apk);
-      }
-      ))];
-    }
-    selApk.innerHTML = '';
-    selApk.appendChild(buildOptions(apks));
-  }
-
-  function updateSubmitButton() {
-    if (channelIsMilestone()) {
-      submitButton.textContent = 'Open Milestone Report';
-    } else {
-      submitButton.textContent = 'Open Canary Report';
-    }
-  }
-
-  function updateVersions() {
-    const prev = selVersion1.value;
-    let versions = [];
-    if (channelIsMilestone()) {
-      // For the selected APK
-      versions = milestonesPushed.version;
-    } else {
-      versions = (officialBuildsPushed
-        .filter(a => fmtCpuApk(a.cpu, a.apk) == selApk.value)
-        .map(a => a.version));
-    }
-    selVersion1.innerHTML = '';
-    selVersion1.appendChild(buildOptions(versions));
-    // Selects latest version (index -1) if previous option not still in list.
-    selectOption(selVersion1.querySelectorAll('option'),
-                 versions.indexOf(prev));
-  }
-
-  function updateDiffVersions() {
-    // Filter diff-against versions that are newer
-    // Preserve current options if possible
-    let diffVersions = [];
-    if (channelIsMilestone()) {
-      diffVersions = [...milestonesPushed.version];
-      diffVersions = diffVersions.filter(v2 => !isGreaterOrEqual(v2, selVersion1.value));
-    } else {
-      diffVersions = ['Diff with Previous Branch'];
-    }
-    diffVersions.push(DO_NOT_DIFF);
-    selVersion2.innerHTML = '';
-    selVersion2.appendChild(buildOptions(diffVersions));
-    selectOption(selVersion2.querySelectorAll('option'), -1);
-  }
-
-  updateApk();
-  updateVersions();
-  updateDiffVersions();
-
-  selApk.addEventListener('change', () => {
-    updateVersions();
-  });
-
-  const selChannels = document.getElementsByName('mode');
-  for(let i=0; i < selChannels.length; i++) {
-    selChannels[i].addEventListener('click', () => {
-      updateSubmitButton();
-      updateApk();
-      updateVersions();
-      selectOption(selVersion1.querySelectorAll('option'), -1);
-      updateDiffVersions();
-    });
-  }
-
-  selVersion1.addEventListener('change', () => {
-    updateDiffVersions();
-  });
-
-  function getDataUrl() {
-    if (channelIsMilestone()) {
-      let ret = `milestones/${selVersion1.value}/${apk.value}.size`;
-      if (selVersion2.value !== DO_NOT_DIFF) {
-        ret += `&before_url=milestones/${selVersion2.value}/${apk.value}.size`;
-      }
-      return ret;
-    } else {
-      let path = `official_builds/reports/${selApk.value}/`;
-      if (selVersion2.value === DO_NOT_DIFF) {
-        return path + `report_${selVersion1.value}.ndjson`;
-      } else { // Diff with previous version
-        for (const item of officialBuildsPushed) {
-          if (item['version'] == selVersion1.value) {
-            const refVersion = item['reference_version'];
-            return path + `report_${refVersion}_${selVersion1.value}.ndjson`;
-          }
-        }
-      }
-    }
-  }
-
-  setSubmitListener(form, getDataUrl);
-})();
-  </script>
+  <script src="index.js"></script>
 </head>
 
 <body>
@@ -329,10 +119,6 @@
     <tr>
       <td style="width: 40%">
         <form id="select-form">
-          <h2 style="font-size: 16px">Channel:
-            <label><input type="radio" name="mode" value="stable/beta" checked> Stable/Beta</label>
-            <label><input type="radio" name="mode" value="canary"> Canary</label>
-          </h2>
           <p class="select-wrapper">
             <select id="apk" class="sel-big" name="apk"></select>
             <label class="select-label" for="apk">APK</label>
@@ -347,15 +133,15 @@
               Diff Against
             </label>
           </p>
+          <p class="select-wrapper" style="font-size: 16px">
+            <label><input type="checkbox" name="showall"> Show all Canary versions</label>
+          </p>
 
           <p class="select-wrapper">
             <button type="submit" class="text-button" id="submit-button">
-              Open Milestone Report
+              Open Size Report
             </button>
           </p>
-          <div style="margin:20pt 0 10pt">
-            <b>Note:</b> AndroidWebview.apk size information exists only for M71 and above.<br>
-          </div>
         </form>
       </td>
     </tr>
diff --git a/tools/binary_size/libsupersize/static/index.js b/tools/binary_size/libsupersize/static/index.js
new file mode 100644
index 0000000..e6611fc
--- /dev/null
+++ b/tools/binary_size/libsupersize/static/index.js
@@ -0,0 +1,187 @@
+// 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.
+
+const DO_NOT_DIFF = 'Don\'t diff';
+
+function buildOptions(options) {
+  const fragment = document.createDocumentFragment();
+  for (let option of options) {
+    const optionEl = document.createElement('option');
+    optionEl.value = option;
+    optionEl.textContent = option;
+    fragment.appendChild(optionEl);
+  }
+  return fragment;
+}
+
+function selectOption(optList, index) {
+  const n = optList.length;
+  if (n > 0)
+    optList[((index % n) + n) % n].selected = true;
+}
+
+function setSubmitListener(form, fetchDataUrl) {
+  form.addEventListener('submit', event => {
+    event.preventDefault();
+    const dataUrl = fetchDataUrl();
+    // Exclude unwind_cfi via a filter as a work-around for it being included
+    // in the size data. It's a file that exists in dev but not beta/stable.
+    window.open(`viewer.html?load_url=${dataUrl}&exclude=assets%2Funwind_cfi`);
+  });
+}
+
+// Milestones.
+(async () => {
+  // Milestones.
+  const milestoneResponse = await fetch('milestones/milestones.json');
+  const milestonesPushed = (await milestoneResponse.json())['pushed'];
+
+  // Official Builds
+  const officialBuildsResponse =
+      await fetch('official_builds/canary_reports.json');
+  const officialBuildsPushed = (await officialBuildsResponse.json())['pushed'];
+
+  if (document.readyState === 'loading') {
+    await new Promise(resolve => {
+      document.onreadystatechange =
+          () => {
+            if (document.readyState !== 'loading') {
+              resolve();
+              document.onreadystatechange = null;
+            }
+          }
+    });
+  }
+
+  /** @type {HTMLFormElement} */
+  const submitButton = document.getElementById('submit-button');
+  const form = document.getElementById('select-form');
+  const selApk = form.elements.namedItem('apk');
+  const selVersion1 = form.elements.namedItem('version1');
+  const selVersion2 = form.elements.namedItem('version2');
+  const showAll = document.getElementsByName('showall')[0];
+  const btnOpen = form.querySelector('button[type="submit"]');
+
+  let activeVersions = [];
+
+  function fmtCpuApk(cpu, apk) {
+    return cpu + '/' + apk;
+  }
+
+  function cpuApkPairs(cpus, apks) {
+    let out = [];
+    for (let cpu of cpus) {
+      for (let apk of apks) {
+        // Chrome.apk not available for arm_64
+        if (!(cpu == 'arm_64' && apk == 'Chrome.apk')) {
+          out.push(fmtCpuApk(cpu, apk));
+        }
+      }
+    }
+    return out;
+  }
+
+  function updateApk() {
+    // Overwrites the apk selector with entries of format {cpu}/{apk}
+    let mainApks = cpuApkPairs(milestonesPushed.cpu, milestonesPushed.apk);
+    let canaryApks = officialBuildsPushed.map(a => {
+      return fmtCpuApk(a.cpu, a.apk);
+    });
+    selApk.innerHTML = '';
+    selApk.appendChild(
+        buildOptions([...new Set([...mainApks, ...canaryApks])]));
+  }
+
+  function compareVersions(v1, v2) {
+    function toNumber(s) {
+      return (
+          s.split('.').map(x => parseInt(x)).reduce((x, y) => x * 1000 + y));
+    }
+    return toNumber(v1) - toNumber(v2);
+  }
+
+  function updateVersions() {
+    const prev = selVersion1.value;
+    // For the selected APK
+    let mainVersions = milestonesPushed.version;
+    let canaryVersions =
+        (officialBuildsPushed
+             .filter(a => fmtCpuApk(a.cpu, a.apk) == selApk.value)
+             .map(a => a.version + ' (canary)'));
+
+    if (selApk.value.indexOf('AndroidWebview.apk') != -1) {
+      // AndroidWebview.apk size information exists only for M71 and above.
+      mainVersions =
+          mainVersions.filter(v2 => compareVersions(v2, '71.0.0.0') > 0);
+    }
+
+    if (showAll.value) {
+      activeVersions = [...mainVersions, ...canaryVersions];
+      activeVersions.sort(compareVersions);
+    } else {
+      canaryVersions.sort(compareVersions);
+      activeVersions = [...mainVersions];
+      if (canaryVersions.length) {
+        activeVersions.push(canaryVersions[canaryVersions.length - 1]);
+      }
+    }
+    selVersion1.innerHTML = '';
+    selVersion1.appendChild(buildOptions(activeVersions));
+    // Selects latest version (index -1) if previous option not still in list.
+    selectOption(
+        selVersion1.querySelectorAll('option'), activeVersions.indexOf(prev));
+  }
+
+  function updateDiffVersions() {
+    // Filter diff-against versions that are newer
+    // Preserve current options if possible
+    const prev = selVersion2.value;
+    selVersion2.innerHTML = '';
+    let v1 = selVersion1.value;
+    if (v1) {
+      let diffVersions =
+          activeVersions.filter(v2 => compareVersions(v2, v1) < 0);
+      diffVersions.push(DO_NOT_DIFF);
+      selVersion2.appendChild(buildOptions(diffVersions));
+      selectOption(
+          selVersion2.querySelectorAll('option'), diffVersions.indexOf(prev));
+    }
+  }
+
+  updateApk();
+  updateVersions();
+  updateDiffVersions();
+
+  selApk.addEventListener('change', () => {
+    updateVersions();
+  });
+
+  selVersion1.addEventListener('change', () => {
+    updateDiffVersions();
+  });
+
+  showAll.addEventListener('click', () => {
+    updateApk();
+    updateVersions();
+    updateDiffVersions();
+  });
+
+  function getDataUrl() {
+    function sizeUrlFor(value) {
+      if (value.indexOf('canary') != -1) {
+        const strippedVersion = value.replace(/[^\d.]/g, '');
+        return `official_builds/reports/${strippedVersion}/${
+            selApk.value}.size`;
+      }
+      return `milestones/${value}/${selApk.value}.size`;
+    }
+    let ret = sizeUrlFor(selVersion1.value);
+    if (selVersion2.value !== DO_NOT_DIFF) {
+      ret += '&before_url=' + sizeUrlFor(selVersion2.value);
+    }
+    return ret;
+  }
+
+  setSubmitListener(form, getDataUrl);
+})();
diff --git a/tools/binary_size/libsupersize/upload_html_viewer.py b/tools/binary_size/libsupersize/upload_html_viewer.py
index 3f1e986a..e92162e 100755
--- a/tools/binary_size/libsupersize/upload_html_viewer.py
+++ b/tools/binary_size/libsupersize/upload_html_viewer.py
@@ -18,7 +18,8 @@
   """Upload static files from the static directory."""
   static_files = os.path.join(os.path.dirname(__file__), 'static')
   subprocess.check_call([
-    'gsutil.py', '-m', 'rsync', '-r', static_files, GS_BUCKET
+      'gsutil.py', '--', '-m', '-h'
+      'Cache-Control:no-cache', 'rsync', '-r', static_files, GS_BUCKET
   ])
 
 
@@ -28,8 +29,10 @@
   cache_hash = uuid.uuid4().hex
 
   p = subprocess.Popen([
-    'gsutil.py', 'cp', '-p', '-', '%s/sw.js' % GS_BUCKET
-  ], stdin=subprocess.PIPE)
+      'gsutil.py', '--', '-h'
+      'Cache-Control:no-cache', 'cp', '-p', '-', GS_BUCKET + '/sw.js'
+  ],
+                       stdin=subprocess.PIPE)
   with open(template_file, 'r') as in_file:
     p.communicate(in_file.read().replace('{{cache_hash}}', cache_hash))
 
@@ -42,9 +45,8 @@
   ])
 
   # All files in the root of the bucket are user readable
-  subprocess.check_call([
-    'gsutil.py', '-m', 'acl', 'ch', '-u', 'AllUsers:R', '%s/*' % GS_BUCKET
-  ])
+  subprocess.check_call(
+      ['gsutil.py', '-m', 'acl', 'ch', '-u', 'AllUsers:R', GS_BUCKET + '/*'])
 
 
 def main():
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 09c331d1..9a58d9f5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -37061,6 +37061,7 @@
   <int value="-977770313" label="AndroidSurfaceControl:enabled"/>
   <int value="-977476498" label="disable-eol-notification"/>
   <int value="-975787829" label="AutofillProfileServerValidation:enabled"/>
+  <int value="-974882918" label="WebXRIncubations:disabled"/>
   <int value="-974378602" label="WebAuthenticationCable:disabled"/>
   <int value="-973509424" label="NewTabPageIcons:disabled"/>
   <int value="-972945109" label="NtpRealbox:disabled"/>
@@ -37693,6 +37694,7 @@
   <int value="-143819280" label="google-password-manager:disabled"/>
   <int value="-143394485" label="SplitSettingsSync:enabled"/>
   <int value="-143382681" label="InstantTethering:enabled"/>
+  <int value="-143121333" label="WebXRIncubations:enabled"/>
   <int value="-141516902" label="UseModernMediaControls:enabled"/>
   <int value="-140168253" label="ProfileMenuRevamp:enabled"/>
   <int value="-138983372" label="DesktopPWAWindowing:disabled"/>
@@ -50988,54 +50990,54 @@
 <enum name="PrerenderFinalStatus">
   <int value="0" label="USED"/>
   <int value="1" label="TIMED_OUT"/>
-  <int value="2" label="EVICTED"/>
-  <int value="3" label="MANAGER_SHUTDOWN"/>
+  <int value="2" label="EVICTED (Obsolete)"/>
+  <int value="3" label="MANAGER_SHUTDOWN (Obsolete)"/>
   <int value="4" label="CLOSED"/>
   <int value="5" label="CREATE_NEW_WINDOW"/>
   <int value="6" label="PROFILE_DESTROYED"/>
   <int value="7" label="APP_TERMINATING"/>
-  <int value="8" label="JAVASCRIPT_ALERT"/>
+  <int value="8" label="JAVASCRIPT_ALERT (Obsolete)"/>
   <int value="9" label="AUTH_NEEDED"/>
-  <int value="10" label="HTTPS"/>
+  <int value="10" label="HTTPS (Obsolete)"/>
   <int value="11" label="DOWNLOAD"/>
   <int value="12" label="MEMORY_LIMIT_EXCEEDED"/>
-  <int value="13" label="JS_OUT_OF_MEMORY"/>
-  <int value="14" label="RENDERER_UNRESPONSIVE"/>
+  <int value="13" label="JS_OUT_OF_MEMORY (Obsolete)"/>
+  <int value="14" label="RENDERER_UNRESPONSIVE (Obsolete)"/>
   <int value="15" label="TOO_MANY_PROCESSES"/>
   <int value="16" label="RATE_LIMIT_EXCEEDED"/>
-  <int value="17" label="PENDING_SKIPPED"/>
-  <int value="18" label="CONTROL_GROUP"/>
-  <int value="19" label="HTML5_MEDIA"/>
-  <int value="20" label="SOURCE_RENDER_VIEW_CLOSED"/>
+  <int value="17" label="PENDING_SKIPPED (Obsolete)"/>
+  <int value="18" label="CONTROL_GROUP (Obsolete)"/>
+  <int value="19" label="HTML5_MEDIA (Obsolete)"/>
+  <int value="20" label="SOURCE_RENDER_VIEW_CLOSED (Obsolete)"/>
   <int value="21" label="RENDERER_CRASHED"/>
   <int value="22" label="UNSUPPORTED_SCHEME"/>
   <int value="23" label="INVALID_HTTP_METHOD"/>
   <int value="24" label="WINDOW_PRINT (Obsolete)"/>
   <int value="25" label="RECENTLY_VISITED"/>
   <int value="26" label="WINDOW_OPENER"/>
-  <int value="27" label="PAGE_ID_CONFLICT"/>
+  <int value="27" label="PAGE_ID_CONFLICT (Obsolete)"/>
   <int value="28" label="SAFE_BROWSING"/>
-  <int value="29" label="FRAGMENT_MISMATCH"/>
+  <int value="29" label="FRAGMENT_MISMATCH (Obsolete)"/>
   <int value="30" label="SSL_CLIENT_CERTIFICATE_REQUESTED"/>
   <int value="31" label="CACHE_OR_HISTORY_CLEARED"/>
   <int value="32" label="CANCELLED"/>
   <int value="33" label="SSL_ERROR"/>
-  <int value="34" label="CROSS_SITE_NAVIGATION_PENDING"/>
-  <int value="35" label="DEVTOOLS_ATTACHED"/>
-  <int value="36" label="SESSION_STORAGE_NAMESPACE_MISMATCH"/>
-  <int value="37" label="NO_USE_GROUP"/>
-  <int value="38" label="MATCH_COMPLETE_DUMMY"/>
+  <int value="34" label="CROSS_SITE_NAVIGATION_PENDING (Obsolete)"/>
+  <int value="35" label="DEVTOOLS_ATTACHED (Obsolete)"/>
+  <int value="36" label="SESSION_STORAGE_NAMESPACE_MISMATCH (Obsolete)"/>
+  <int value="37" label="NO_USE_GROUP (Obsolete)"/>
+  <int value="38" label="MATCH_COMPLETE_DUMMY (Obsolete)"/>
   <int value="39" label="DUPLICATE"/>
   <int value="40" label="OPEN_URL"/>
-  <int value="41" label="WOULD_HAVE_BEEN_USED"/>
+  <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="45" label="BAD_DEFERRED_REDIRECT"/>
   <int value="46" label="NAVIGATION_UNCOMMITTED"/>
   <int value="47" label="NEW_NAVIGATION_ENTRY"/>
-  <int value="48" label="COOKIE_STORE_NOT_LOADED"/>
-  <int value="49" label="COOKIE_CONFLICT"/>
+  <int value="48" label="COOKIE_STORE_NOT_LOADED (Obsolete)"/>
+  <int value="49" label="COOKIE_CONFLICT (Obsolete)"/>
   <int value="50" label="NON_EMPTY_BROWSING_INSTANCE"/>
   <int value="51" label="NAVIGATION_INTERCEPTED"/>
   <int value="52" label="PRERENDERING_DISABLED"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 9b2ae55..ec662ca 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -118809,6 +118809,9 @@
 
 <histogram name="Prerender.TabContentsDeleterSuppressedDialog"
     enum="BooleanSuppressed">
+  <obsolete>
+    Obsoleted in Dec 2019.
+  </obsolete>
   <owner>pasko@chromium.org</owner>
   <summary>
     A boolean that indicates how often we suppress a dialog from a tab when
diff --git a/tools/perf/benchmarks/benchmark_smoke_unittest.py b/tools/perf/benchmarks/benchmark_smoke_unittest.py
index 7f5e0c7..9dd73b1 100644
--- a/tools/perf/benchmarks/benchmark_smoke_unittest.py
+++ b/tools/perf/benchmarks/benchmark_smoke_unittest.py
@@ -71,6 +71,8 @@
       options.pageset_repeat = 1  # For smoke testing only run the page once.
       options.output_formats = ['histograms']
       options.max_values_per_test_case = MAX_VALUES_PER_TEST_CASE
+      results_processor.ProcessOptions(options)
+
       return_code = benchmark_class().Run(options)
       # TODO(crbug.com/1019139): Make 111 be the exit code that means
       # "no stories were run.".
diff --git a/tools/perf/benchmarks/system_health_smoke_test.py b/tools/perf/benchmarks/system_health_smoke_test.py
index a76b1aa6..5e7cd37 100644
--- a/tools/perf/benchmarks/system_health_smoke_test.py
+++ b/tools/perf/benchmarks/system_health_smoke_test.py
@@ -305,6 +305,7 @@
   options.browser_options.logging_verbosity = 'non-verbose'
   options.target_platforms = benchmark_cls.GetSupportedPlatformNames(
       benchmark_cls.SUPPORTED_PLATFORMS)
+  results_processor.ProcessOptions(options)
   return options
 
 
diff --git a/tools/perf/core/benchmark_runner_test.py b/tools/perf/core/benchmark_runner_test.py
index 3e66a155..a47b023 100644
--- a/tools/perf/core/benchmark_runner_test.py
+++ b/tools/perf/core/benchmark_runner_test.py
@@ -56,6 +56,7 @@
     self.options = testing.GetRunOptions(
         output_dir=tempfile.mkdtemp())
     self.options.output_formats = ['histograms']
+    results_processor.ProcessOptions(self.options)
 
   def tearDown(self):
     shutil.rmtree(self.options.output_dir)
diff --git a/tools/perf/core/minidump_unittest.py b/tools/perf/core/minidump_unittest.py
index 1e7784c2..5bb282d 100644
--- a/tools/perf/core/minidump_unittest.py
+++ b/tools/perf/core/minidump_unittest.py
@@ -18,7 +18,8 @@
   @decorators.Isolated
   # Minidump symbolization doesn't work in ChromeOS local mode if the rootfs is
   # still read-only, so skip the test in that case.
-  @decorators.Disabled('chromeos-local')
+  # TODO(crbug.com/1038043): Test is failing on chromeos-betty-chrome.
+  @decorators.Disabled('chromeos-local', 'chromeos')
   def testSymbolizeMinidump(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var sam = "car";', 'sam')
@@ -58,7 +59,8 @@
   @decorators.Isolated
   # Minidump symbolization doesn't work in ChromeOS local mode if the rootfs is
   # still read-only, so skip the test in that case.
-  @decorators.Disabled('chromeos-local')
+  # TODO(crbug.com/1038043): Test is failing on chromeos-betty-chrome.
+  @decorators.Disabled('chromeos-local', 'chromeos')
   def testMultipleCrashMinidumps(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var cat = "dog";', 'cat')
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 1524b68..992dd83 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,6 +4,14 @@
             "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 418e992..83152de 100644
--- a/tools/perf/scripts_smoke_unittest.py
+++ b/tools/perf/scripts_smoke_unittest.py
@@ -162,8 +162,7 @@
 
   # Android: crbug.com/932301
   # ChromeOS: crbug.com/754913
-  # Linux: crbug.com/1024767
-  @decorators.Disabled('chromeos', 'android', 'linux')
+  @decorators.Disabled('chromeos', 'android')
   def testRunPerformanceTestsTelemetrySharded_end2end(self):
     tempdir = tempfile.mkdtemp()
     env = os.environ.copy()
@@ -188,22 +187,26 @@
       self.assertEquals(return_code, 0)
       expected_benchmark_folders = (
           'dummy_benchmark.stable_benchmark_1',
-          'dummy_benchmark.stable_benchmark_1.reference')
+          'dummy_benchmark.stable_benchmark_1.reference',
+          'dummy_gtest')
       with open(os.path.join(tempdir, 'output.json')) as f:
         test_results = json.load(f)
       self.assertIsNotNone(
           test_results, 'json_test_results should be populated.')
-      test_repeats = test_results['num_failures_by_type']['PASS']
+      test_runs = test_results['num_failures_by_type']['PASS']
+      # 1 gtest runs (since --isolated-script-test-repeat doesn't work for gtest
+      # yet) plus 2 dummy_benchmark runs = 3 runs.
       self.assertEqual(
-          test_repeats, 2, '--isolated-script-test-repeat=2 should work.')
+          test_runs, 3, '--isolated-script-test-repeat=2 should work.')
       for folder in expected_benchmark_folders:
         with open(os.path.join(tempdir, folder, 'test_results.json')) as f:
           test_results = json.load(f)
         self.assertIsNotNone(
             test_results, 'json test results should be populated.')
         test_repeats = test_results['num_failures_by_type']['PASS']
-        self.assertEqual(
-            test_repeats, 2, '--isolated-script-test-repeat=2 should work.')
+        if 'dummy_gtest' not in folder:  # Repeats don't work for gtest yet.
+          self.assertEqual(
+              test_repeats, 2, '--isolated-script-test-repeat=2 should work.')
         with open(os.path.join(tempdir, folder, 'perf_results.json')) as f:
           perf_results = json.load(f)
         self.assertIsNotNone(
@@ -231,11 +234,22 @@
              if generate_trace else '') +
         ' --non-telemetry=true '
         '--this-arg=passthrough '
+        '--argument-to-check-that-arguments-work '
         '--gtest-benchmark-name dummy_gtest '
         '--isolated-script-test-output=%s' % (
             os.path.join(tempdir, 'output.json')
         ))
-    self.assertEquals(return_code, 0, stdout)
+    try:
+      self.assertEquals(return_code, 0, stdout)
+    except AssertionError:
+      try:
+        with open(os.path.join(tempdir, benchmark, 'benchmark_log.txt')) as fh:
+          print fh.read()
+      # pylint: disable=bare-except
+      except:
+      # pylint: enable=bare-except
+        pass
+      raise
     try:
       with open(os.path.join(tempdir, 'output.json')) as f:
         test_results = json.load(f)
@@ -266,7 +280,7 @@
   def testRunPerformanceTestsGtestTrace_end2end(self):
     self.RunGtest(generate_trace=True)
 
-  def testRunPerformanceTestsTelemetryArgsParser(self):
+  def testRunPerformanceTestsShardedArgsParser(self):
     options = run_performance_tests.parse_arguments([
         '../../tools/perf/run_benchmark', '-v', '--browser=release_x64',
         '--upload-results', '--run-ref-build',
diff --git a/tools/perf/testdata/dummy_gtest b/tools/perf/testdata/dummy_gtest
index 84c61aa..6baa671 100755
--- a/tools/perf/testdata/dummy_gtest
+++ b/tools/perf/testdata/dummy_gtest
@@ -18,6 +18,8 @@
 # Simulate trace file generation when "--trace-dir" switch is present.
 parser = argparse.ArgumentParser()
 parser.add_argument(
+    "--argument-to-check-that-arguments-work", action='store_true')
+parser.add_argument(
     "--trace-dir",
     type=str,
     help="Simulate trace file generation to the given path.")
@@ -37,3 +39,8 @@
   with open(
       os.path.join(options.trace_dir, 'dummy_luci_test_result.json'), 'w') as f:
     f.write(test_result)
+if options.argument_to_check_that_arguments_work:
+  print 'Arguments worked!'
+else:
+  print '--argument-to-check-that-arguments-work was not passed. Failing.'
+  sys.exit(1)
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index a51cbdce..9470d3ab 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -62,6 +62,8 @@
     "ax_export.h",
     "ax_language_detection.cc",
     "ax_language_detection.h",
+    "ax_live_region_tracker.cc",
+    "ax_live_region_tracker.h",
     "ax_mode.cc",
     "ax_mode.h",
     "ax_mode_observer.h",
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 106364a..b0c41170 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -8,6 +8,7 @@
 
 #include "base/stl_util.h"
 #include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_live_region_tracker.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_role_properties.h"
 
@@ -100,8 +101,10 @@
 AXEventGenerator::AXEventGenerator() = default;
 
 AXEventGenerator::AXEventGenerator(AXTree* tree) : tree_(tree) {
-  if (tree_)
+  if (tree_) {
     tree_->AddObserver(this);
+    live_region_tracker_ = std::make_unique<AXLiveRegionTracker>(tree_);
+  }
 }
 
 AXEventGenerator::~AXEventGenerator() {
@@ -110,11 +113,15 @@
 }
 
 void AXEventGenerator::SetTree(AXTree* new_tree) {
-  if (tree_)
+  if (tree_) {
     tree_->RemoveObserver(this);
+    live_region_tracker_.reset();
+  }
   tree_ = new_tree;
-  if (tree_)
+  if (tree_) {
     tree_->AddObserver(this);
+    live_region_tracker_ = std::make_unique<AXLiveRegionTracker>(tree_);
+  }
 }
 
 void AXEventGenerator::ReleaseTree() {
@@ -475,6 +482,12 @@
 }
 
 void AXEventGenerator::OnNodeWillBeDeleted(AXTree* tree, AXNode* node) {
+  if (node->data().HasStringAttribute(
+          ax::mojom::StringAttribute::kContainerLiveStatus)) {
+    FireLiveRegionEvents(node);
+    live_region_tracker_->OnNodeWillBeDeleted(node);
+  }
+
   DCHECK_EQ(tree_, tree);
   tree_events_.erase(node);
 }
@@ -506,6 +519,14 @@
   }
 
   for (const auto& change : changes) {
+    if ((change.type == NODE_CREATED || change.type == SUBTREE_CREATED ||
+         change.type == NODE_REPARENTED || change.type == SUBTREE_REPARENTED)) {
+      if (change.node->data().HasStringAttribute(
+              ax::mojom::StringAttribute::kContainerLiveStatus)) {
+        live_region_tracker_->TrackNode(change.node);
+      }
+    }
+
     if (change.type == SUBTREE_CREATED) {
       AddEvent(change.node, Event::SUBTREE_CREATED);
     } else if (change.type != NODE_CREATED) {
@@ -525,23 +546,29 @@
 }
 
 void AXEventGenerator::FireLiveRegionEvents(AXNode* node) {
-  AXNode* live_root = node;
-  while (live_root && !live_root->data().HasStringAttribute(
-                          ax::mojom::StringAttribute::kLiveStatus))
-    live_root = live_root->parent();
+  AXNode* live_root = live_region_tracker_->GetLiveRoot(node);
 
-  if (live_root &&
-      !live_root->data().GetBoolAttribute(ax::mojom::BoolAttribute::kBusy) &&
-      live_root->data().GetStringAttribute(
-          ax::mojom::StringAttribute::kLiveStatus) != "off") {
-    // Fire LIVE_REGION_NODE_CHANGED on each node that changed.
-    if (!node->data()
-             .GetStringAttribute(ax::mojom::StringAttribute::kName)
-             .empty())
-      AddEvent(node, Event::LIVE_REGION_NODE_CHANGED);
-    // Fire LIVE_REGION_CHANGED on the root of the live region.
-    AddEvent(live_root, Event::LIVE_REGION_CHANGED);
+  // Note that |live_root| might be nullptr if a live region was just added.
+  if (!live_root)
+    return;
+
+  if (live_root->data().GetBoolAttribute(ax::mojom::BoolAttribute::kBusy))
+    return;
+
+  std::string live_status = live_root->data().GetStringAttribute(
+      ax::mojom::StringAttribute::kLiveStatus);
+  if (live_status != "polite" && live_status != "assertive")
+    return;
+
+  // Fire LIVE_REGION_NODE_CHANGED on each node that changed.
+  if (!node->data()
+           .GetStringAttribute(ax::mojom::StringAttribute::kName)
+           .empty()) {
+    AddEvent(node, Event::LIVE_REGION_NODE_CHANGED);
   }
+
+  // Fire LIVE_REGION_CHANGED on the root of the live region.
+  AddEvent(live_root, Event::LIVE_REGION_CHANGED);
 }
 
 void AXEventGenerator::FireActiveDescendantEvents() {
diff --git a/ui/accessibility/ax_event_generator.h b/ui/accessibility/ax_event_generator.h
index 4016e91..aa58667 100644
--- a/ui/accessibility/ax_event_generator.h
+++ b/ui/accessibility/ax_event_generator.h
@@ -6,8 +6,10 @@
 #define UI_ACCESSIBILITY_AX_EVENT_GENERATOR_H_
 
 #include <map>
+#include <memory>
 #include <ostream>
 #include <set>
+#include <string>
 #include <vector>
 
 #include "ui/accessibility/ax_export.h"
@@ -16,6 +18,8 @@
 
 namespace ui {
 
+class AXLiveRegionTracker;
+
 // Subclass of AXTreeObserver that automatically generates AXEvents to fire
 // based on changes to an accessibility tree.  Every platform
 // tends to want different events, so this class lets each platform
@@ -232,6 +236,9 @@
   // Valid between the call to OnIntAttributeChanged and the call to
   // OnAtomicUpdateFinished. List of nodes whose active descendant changed.
   std::vector<AXNode*> active_descendant_changed_;
+
+  // Helper that tracks live regions.
+  std::unique_ptr<AXLiveRegionTracker> live_region_tracker_;
 };
 
 AX_EXPORT std::ostream& operator<<(std::ostream& os,
diff --git a/ui/accessibility/ax_event_generator_unittest.cc b/ui/accessibility/ax_event_generator_unittest.cc
index 32a0768..fb3d0b4 100644
--- a/ui/accessibility/ax_event_generator_unittest.cc
+++ b/ui/accessibility/ax_event_generator_unittest.cc
@@ -1370,4 +1370,41 @@
           HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 1)));
 }
 
+TEST(AXEventGeneratorTest, LiveRegionNodeRemoved) {
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(3);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].AddStringAttribute(
+      ax::mojom::StringAttribute::kLiveStatus, "polite");
+  initial_state.nodes[0].AddStringAttribute(
+      ax::mojom::StringAttribute::kContainerLiveStatus, "polite");
+  initial_state.nodes[0].child_ids = {2, 3};
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[1].AddStringAttribute(
+      ax::mojom::StringAttribute::kContainerLiveStatus, "polite");
+  initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName,
+                                            "Before 1");
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
+  initial_state.nodes[2].AddStringAttribute(
+      ax::mojom::StringAttribute::kContainerLiveStatus, "polite");
+  initial_state.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName,
+                                            "Before 2");
+  AXTree tree(initial_state);
+
+  AXEventGenerator event_generator(&tree);
+  AXTreeUpdate update = initial_state;
+  update.nodes.resize(1);
+  update.nodes[0].child_ids = {2};
+
+  EXPECT_TRUE(tree.Unserialize(update));
+  EXPECT_THAT(
+      event_generator,
+      UnorderedElementsAre(
+          HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1),
+          HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_CHANGED, 1)));
+}
+
 }  // namespace ui
diff --git a/ui/accessibility/ax_live_region_tracker.cc b/ui/accessibility/ax_live_region_tracker.cc
new file mode 100644
index 0000000..a2cf7f195
--- /dev/null
+++ b/ui/accessibility/ax_live_region_tracker.cc
@@ -0,0 +1,59 @@
+// 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 "ui/accessibility/ax_live_region_tracker.h"
+
+#include "base/stl_util.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_role_properties.h"
+
+namespace ui {
+
+AXLiveRegionTracker::AXLiveRegionTracker(AXTree* tree) : tree_(tree) {
+  InitializeLiveRegionNodeToRoot(tree_->root(), nullptr);
+}
+
+AXLiveRegionTracker::~AXLiveRegionTracker() {}
+
+void AXLiveRegionTracker::TrackNode(AXNode* node) {
+  LOG(ERROR) << "TrackNode " << node->data().ToString();
+  AXNode* live_root = node;
+  while (live_root && !live_root->HasStringAttribute(
+                          ax::mojom::StringAttribute::kLiveStatus))
+    live_root = live_root->parent();
+  if (live_root) {
+    LOG(ERROR) << "  Live root: " << live_root->data().ToString();
+    live_region_node_to_root_[node] = live_root;
+  } else
+    LOG(ERROR) << "  No live root";
+}
+
+void AXLiveRegionTracker::OnNodeWillBeDeleted(AXNode* node) {
+  LOG(ERROR) << "OnNodeWillBeDeleted " << node->data().ToString();
+  live_region_node_to_root_.erase(node);
+}
+
+AXNode* AXLiveRegionTracker::GetLiveRoot(AXNode* node) {
+  LOG(ERROR) << "GetLiveRoot for " << node->data().ToString();
+  auto iter = live_region_node_to_root_.find(node);
+  if (iter != live_region_node_to_root_.end())
+    return iter->second;
+  return nullptr;
+}
+
+void AXLiveRegionTracker::InitializeLiveRegionNodeToRoot(AXNode* node,
+                                                         AXNode* current_root) {
+  if (!current_root &&
+      node->HasStringAttribute(ax::mojom::StringAttribute::kLiveStatus)) {
+    current_root = node;
+  }
+
+  if (current_root)
+    live_region_node_to_root_[node] = current_root;
+
+  for (size_t i = 0; i < node->children().size(); i++)
+    InitializeLiveRegionNodeToRoot(node->children()[i], current_root);
+}
+
+}  // namespace ui
diff --git a/ui/accessibility/ax_live_region_tracker.h b/ui/accessibility/ax_live_region_tracker.h
new file mode 100644
index 0000000..b530fbab
--- /dev/null
+++ b/ui/accessibility/ax_live_region_tracker.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 UI_ACCESSIBILITY_AX_LIVE_REGION_TRACKER_H_
+#define UI_ACCESSIBILITY_AX_LIVE_REGION_TRACKER_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "ui/accessibility/ax_tree.h"
+
+namespace ui {
+
+// Class that works with AXEventGenerator to track live regions in
+// an AXTree.
+class AXLiveRegionTracker {
+ public:
+  explicit AXLiveRegionTracker(AXTree* tree);
+
+  ~AXLiveRegionTracker();
+
+  void TrackNode(AXNode* node);
+  void OnNodeWillBeDeleted(AXNode* node);
+  AXNode* GetLiveRoot(AXNode* node);
+
+ private:
+  void InitializeLiveRegionNodeToRoot(AXNode* node, AXNode* current_root);
+
+  AXTree* tree_;  // Not owned.
+
+  // Map from live region node to its live region root.
+  std::map<AXNode*, AXNode*> live_region_node_to_root_;
+
+  DISALLOW_COPY_AND_ASSIGN(AXLiveRegionTracker);
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCESSIBILITY_AX_LIVE_REGION_TRACKER_H_
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index eb2acb8a..ea49b09e 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -605,6 +605,124 @@
   EXPECT_EQ(AXNodePosition::INVALID_INDEX, copy_position->child_index());
 }
 
+TEST_F(AXPositionTest, ToString) {
+  AXNodeData root_data;
+  root_data.id = 1;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+
+  AXNodeData static_text_data_1;
+  static_text_data_1.id = 2;
+  static_text_data_1.role = ax::mojom::Role::kStaticText;
+  static_text_data_1.SetName("some text");
+
+  AXNodeData static_text_data_2;
+  static_text_data_2.id = 3;
+  static_text_data_2.role = ax::mojom::Role::kStaticText;
+  static_text_data_2.SetName(base::WideToUTF16(L"\xfffc"));
+
+  AXNodeData static_text_data_3;
+  static_text_data_3.id = 4;
+  static_text_data_3.role = ax::mojom::Role::kStaticText;
+  static_text_data_3.SetName("more text");
+
+  root_data.child_ids = {static_text_data_1.id, static_text_data_2.id,
+                         static_text_data_3.id};
+
+  std::unique_ptr<AXTree> new_tree = CreateAXTree(
+      {root_data, static_text_data_1, static_text_data_2, static_text_data_3});
+  AXNodePosition::SetTree(new_tree.get());
+
+  TestPositionType text_position_1 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, root_data.id, 0 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_1->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=1 text_offset=0 affinity=downstream "
+      "annotated_text=<s>ome text\xEF\xBF\xBCmore text",
+      text_position_1->ToString());
+
+  TestPositionType text_position_2 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, root_data.id, 5 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_2->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=1 text_offset=5 affinity=downstream "
+      "annotated_text=some <t>ext\xEF\xBF\xBCmore text",
+      text_position_2->ToString());
+
+  TestPositionType text_position_3 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, root_data.id, 9 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_3->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=1 text_offset=9 affinity=downstream "
+      "annotated_text=some text<\xEF\xBF\xBC>more text",
+      text_position_3->ToString());
+
+  TestPositionType text_position_4 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, root_data.id, 10 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_4->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=1 text_offset=10 affinity=downstream "
+      "annotated_text=some text\xEF\xBF\xBC<m>ore text",
+      text_position_4->ToString());
+
+  TestPositionType text_position_5 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, root_data.id, 19 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_5->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=1 text_offset=19 affinity=downstream "
+      "annotated_text=some text\xEF\xBF\xBCmore text<>",
+      text_position_5->ToString());
+
+  TestPositionType text_position_6 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, static_text_data_2.id, 0 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_6->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=3 text_offset=0 affinity=downstream "
+      "annotated_text=<\xEF\xBF\xBC>",
+      text_position_6->ToString());
+
+  TestPositionType text_position_7 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, static_text_data_2.id, 1 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_7->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=3 text_offset=1 affinity=downstream "
+      "annotated_text=\xEF\xBF\xBC<>",
+      text_position_7->ToString());
+
+  TestPositionType text_position_8 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, static_text_data_3.id, 0 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_8->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=4 text_offset=0 affinity=downstream "
+      "annotated_text=<m>ore text",
+      text_position_8->ToString());
+
+  TestPositionType text_position_9 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, static_text_data_3.id, 5 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_9->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=4 text_offset=5 affinity=downstream "
+      "annotated_text=more <t>ext",
+      text_position_9->ToString());
+
+  TestPositionType text_position_10 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, static_text_data_3.id, 9 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_TRUE(text_position_10->IsTextPosition());
+  EXPECT_EQ(
+      "TextPosition anchor_id=4 text_offset=9 affinity=downstream "
+      "annotated_text=more text<>",
+      text_position_10->ToString());
+}
+
 TEST_F(AXPositionTest, IsIgnored) {
   EXPECT_FALSE(AXNodePosition::CreateNullPosition()->IsIgnored());
 
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 0f5935e..ed525a2 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -267,18 +267,19 @@
     if (!IsTextPosition() || text_offset_ > MaxTextOffset())
       return str;
 
-    std::string text = base::UTF16ToUTF8(GetText());
+    base::string16 text = GetText();
     DCHECK_GE(text_offset_, 0);
     DCHECK_LE(text_offset_, int{text.length()});
-    std::string annotated_text;
+    base::string16 annotated_text;
     if (text_offset_ == MaxTextOffset()) {
-      annotated_text = text + "<>";
+      annotated_text = text + base::WideToUTF16(L"<>");
     } else {
-      annotated_text = text.substr(0, text_offset_) + "<" + text[text_offset_] +
-                       ">" + text.substr(text_offset_ + 1);
+      annotated_text = text.substr(0, text_offset_) + base::WideToUTF16(L"<") +
+                       text[text_offset_] + base::WideToUTF16(L">") +
+                       text.substr(text_offset_ + 1);
     }
 
-    return str + " annotated_text=" + annotated_text;
+    return str + " annotated_text=" + base::UTF16ToUTF8(annotated_text);
   }
 
   AXTreeID tree_id() const { return tree_id_; }
diff --git a/ui/gfx/color_space.cc b/ui/gfx/color_space.cc
index 372652e..df187341 100644
--- a/ui/gfx/color_space.cc
+++ b/ui/gfx/color_space.cc
@@ -350,7 +350,6 @@
     PRINT_ENUM_CASE(TransferID, SMPTEST2084)
     PRINT_ENUM_CASE(TransferID, SMPTEST428_1)
     PRINT_ENUM_CASE(TransferID, ARIB_STD_B67)
-    PRINT_ENUM_CASE(TransferID, SMPTEST2084_NON_HDR)
     PRINT_ENUM_CASE(TransferID, IEC61966_2_1_HDR)
     PRINT_ENUM_CASE(TransferID, LINEAR_HDR)
     case TransferID::CUSTOM: {
@@ -786,7 +785,6 @@
     case ColorSpace::TransferID::LOG:
     case ColorSpace::TransferID::LOG_SQRT:
     case ColorSpace::TransferID::SMPTEST2084:
-    case ColorSpace::TransferID::SMPTEST2084_NON_HDR:
     case ColorSpace::TransferID::CUSTOM:
     case ColorSpace::TransferID::INVALID:
       break;
diff --git a/ui/gfx/color_space.h b/ui/gfx/color_space.h
index b909df38..ea9b97d 100644
--- a/ui/gfx/color_space.h
+++ b/ui/gfx/color_space.h
@@ -84,10 +84,6 @@
     SMPTEST2084,
     SMPTEST428_1,
     ARIB_STD_B67,  // AKA hybrid-log gamma, HLG.
-    // This is an ad-hoc transfer function that decodes SMPTE 2084 content
-    // into a [0, 1] range more or less suitable for viewing on a non-hdr
-    // display.
-    SMPTEST2084_NON_HDR,
     // The same as IEC61966_2_1 on the interval [0, 1], with the nonlinear
     // segment continuing beyond 1 and point symmetry defining values below 0.
     IEC61966_2_1_HDR,
diff --git a/ui/gfx/color_space_win.cc b/ui/gfx/color_space_win.cc
index cf3425f..45b97183 100644
--- a/ui/gfx/color_space_win.cc
+++ b/ui/gfx/color_space_win.cc
@@ -122,7 +122,6 @@
     case gfx::ColorSpace::TransferID::BT709_APPLE:
     case gfx::ColorSpace::TransferID::GAMMA18:
     case gfx::ColorSpace::TransferID::GAMMA24:
-    case gfx::ColorSpace::TransferID::SMPTEST2084_NON_HDR:
     case gfx::ColorSpace::TransferID::CUSTOM:
     case gfx::ColorSpace::TransferID::INVALID:
       // Not handled
diff --git a/ui/gfx/color_transform.cc b/ui/gfx/color_transform.cc
index 26fa712..1d908f6a 100644
--- a/ui/gfx/color_transform.cc
+++ b/ui/gfx/color_transform.cc
@@ -56,10 +56,6 @@
 
 float FromLinear(ColorSpace::TransferID id, float v) {
   switch (id) {
-    case ColorSpace::TransferID::SMPTEST2084_NON_HDR:
-      // Should already be handled.
-      break;
-
     case ColorSpace::TransferID::LOG:
       if (v < 0.01f)
         return 0.0f;
@@ -178,10 +174,6 @@
       return v;
     }
 
-    case ColorSpace::TransferID::SMPTEST2084_NON_HDR:
-      v = max(0.0f, v);
-      return min(2.3f * pow(v, 2.8f), v / 5.0f + 0.8f);
-
     // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf
     case ColorSpace::TransferID::ARIB_STD_B67: {
       v = max(0.0f, v);
@@ -224,7 +216,6 @@
 class ColorTransformMatrix;
 class ColorTransformSkTransferFn;
 class ColorTransformFromLinear;
-class ColorTransformToBT2020CL;
 class ColorTransformFromBT2020CL;
 class ColorTransformNull;
 
@@ -233,7 +224,6 @@
   ColorTransformStep() {}
   virtual ~ColorTransformStep() {}
   virtual ColorTransformFromLinear* GetFromLinear() { return nullptr; }
-  virtual ColorTransformToBT2020CL* GetToBT2020CL() { return nullptr; }
   virtual ColorTransformFromBT2020CL* GetFromBT2020CL() { return nullptr; }
   virtual ColorTransformSkTransferFn* GetSkTransferFn() { return nullptr; }
   virtual ColorTransformMatrix* GetMatrix() { return nullptr; }
@@ -247,19 +237,14 @@
   // Return true if this is a null transform.
   virtual bool IsNull() { return false; }
   virtual void Transform(ColorTransform::TriStim* color, size_t num) const = 0;
-  virtual bool CanAppendShaderSource() { return false; }
   // In the shader, |hdr| will appear before |src|, so any helper functions that
   // are created should be put in |hdr|. Any helper functions should have
   // |step_index| included in the function name, to ensure that there are no
   // naming conflicts.
   virtual void AppendShaderSource(std::stringstream* hdr,
                                   std::stringstream* src,
-                                  size_t step_index) const {
-    NOTREACHED();
-  }
-  virtual void AppendSkShaderSource(std::stringstream* src) const {
-    NOTREACHED();
-  }
+                                  size_t step_index) const = 0;
+  virtual void AppendSkShaderSource(std::stringstream* src) const = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ColorTransformStep);
@@ -280,16 +265,14 @@
       step->Transform(colors, num);
     }
   }
-  bool CanGetShaderSource() const override;
   std::string GetShaderSource() const override;
   std::string GetSkShaderSource() const override;
   bool IsIdentity() const override { return steps_.empty(); }
   size_t NumberOfStepsForTesting() const override { return steps_.size(); }
 
  private:
-  void AppendColorSpaceToColorSpaceTransform(ColorSpace src,
-                                             const ColorSpace& dst,
-                                             ColorTransform::Intent intent);
+  void AppendColorSpaceToColorSpaceTransform(const ColorSpace& src,
+                                             const ColorSpace& dst);
   void Simplify();
 
   std::list<std::unique_ptr<ColorTransformStep>> steps_;
@@ -302,7 +285,6 @@
   ColorTransformNull* GetNull() override { return this; }
   bool IsNull() override { return true; }
   void Transform(ColorTransform::TriStim* color, size_t num) const override {}
-  bool CanAppendShaderSource() override { return true; }
   void AppendShaderSource(std::stringstream* hdr,
                           std::stringstream* src,
                           size_t step_index) const override {}
@@ -333,7 +315,6 @@
       matrix_.TransformPoint(colors + i);
   }
 
-  bool CanAppendShaderSource() override { return true; }
 
   void AppendShaderSource(std::stringstream* hdr,
                           std::stringstream* src,
@@ -483,7 +464,6 @@
     }
     return false;
   }
-  bool CanAppendShaderSource() override { return true; }
   bool IsNull() override { return SkTransferFnIsApproximatelyIdentity(fn_); }
 
   // ColorTransformPerChannelTransferFn implementation:
@@ -534,7 +514,6 @@
   explicit ColorTransformFromLinear(ColorSpace::TransferID transfer)
       : ColorTransformPerChannelTransferFn(false), transfer_(transfer) {}
   ColorTransformFromLinear* GetFromLinear() override { return this; }
-  bool CanAppendShaderSource() override { return true; }
   bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; }
 
   // ColorTransformPerChannelTransferFn implementation:
@@ -625,7 +604,6 @@
     }
     return false;
   }
-  bool CanAppendShaderSource() override { return true; }
   bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; }
 
   // ColorTransformPerChannelTransferFn implementation:
@@ -690,10 +668,6 @@
                 "              (c2 - c3 * pow(v2, 1.0 / m2)), 1.0 / m1);\n"
                 "  v = v2 * 10000.0 / 80.0;\n";
         return;
-      case ColorSpace::TransferID::SMPTEST2084_NON_HDR:
-        *src << "  v = max(0.0, v);\n"
-                "  v = min(2.3 * pow(v, 2.8), v / 5.0 + 0.8);\n";
-        return;
       case ColorSpace::TransferID::ARIB_STD_B67:
         *src << "  v = max(0.0, v);\n"
              << "  " << scalar_type << " a = 0.17883277;\n"
@@ -714,43 +688,6 @@
   ColorSpace::TransferID transfer_;
 };
 
-class ColorTransformSMPTEST2048NonHdrToLinear : public ColorTransformStep {
- public:
-  // Assumes BT2020 primaries.
-  static float Luma(const ColorTransform::TriStim& c) {
-    return c.x() * 0.2627f + c.y() * 0.6780f + c.z() * 0.0593f;
-  }
-  static ColorTransform::TriStim ClipToWhite(ColorTransform::TriStim* c) {
-    float maximum = max(max(c->x(), c->y()), c->z());
-    if (maximum > 1.0f) {
-      float l = Luma(*c);
-      c->Scale(1.0f / maximum);
-      ColorTransform::TriStim white(1.0f, 1.0f, 1.0f);
-      white.Scale((1.0f - 1.0f / maximum) * l / Luma(white));
-      ColorTransform::TriStim black(0.0f, 0.0f, 0.0f);
-      *c += white - black;
-    }
-    return *c;
-  }
-  void Transform(ColorTransform::TriStim* colors, size_t num) const override {
-    for (size_t i = 0; i < num; i++) {
-      ColorTransform::TriStim ret(
-          ToLinear(ColorSpace::TransferID::SMPTEST2084_NON_HDR, colors[i].x()),
-          ToLinear(ColorSpace::TransferID::SMPTEST2084_NON_HDR, colors[i].y()),
-          ToLinear(ColorSpace::TransferID::SMPTEST2084_NON_HDR, colors[i].z()));
-      if (Luma(ret) > 0.0) {
-        ColorTransform::TriStim smpte2084(
-            ToLinear(ColorSpace::TransferID::SMPTEST2084, colors[i].x()),
-            ToLinear(ColorSpace::TransferID::SMPTEST2084, colors[i].y()),
-            ToLinear(ColorSpace::TransferID::SMPTEST2084, colors[i].z()));
-        smpte2084.Scale(Luma(ret) / Luma(smpte2084));
-        ret = ClipToWhite(&smpte2084);
-      }
-      colors[i] = ret;
-    }
-  }
-};
-
 // BT2020 Constant Luminance is different than most other
 // ways to encode RGB values as YUV. The basic idea is that
 // transfer functions are applied on the Y value instead of
@@ -764,61 +701,9 @@
 // Then we run the transfer function like normal, and finally
 // this class is inserted as an extra step which takes calculates
 // the U and V values.
-class ColorTransformToBT2020CL : public ColorTransformStep {
- public:
-  bool Join(ColorTransformStep* next_untyped) override {
-    ColorTransformFromBT2020CL* next = next_untyped->GetFromBT2020CL();
-    if (!next)
-      return false;
-    if (null_)
-      return false;
-    null_ = true;
-    return true;
-  }
-
-  bool IsNull() override { return null_; }
-
-  void Transform(ColorTransform::TriStim* RYB, size_t num) const override {
-    for (size_t i = 0; i < num; i++) {
-      float U, V;
-      float B_Y = RYB[i].z() - RYB[i].y();
-      if (B_Y <= 0) {
-        U = B_Y / (-2.0 * -0.9702);
-      } else {
-        U = B_Y / (2.0 * 0.7910);
-      }
-      float R_Y = RYB[i].x() - RYB[i].y();
-      if (R_Y <= 0) {
-        V = R_Y / (-2.0 * -0.8591);
-      } else {
-        V = R_Y / (2.0 * 0.4969);
-      }
-      RYB[i] = ColorTransform::TriStim(RYB[i].y(), U + 0.5, V + 0.5);
-    }
-  }
-
- private:
-  bool null_ = false;
-};
-
-// Inverse of ColorTransformToBT2020CL, see comment above for more info.
 class ColorTransformFromBT2020CL : public ColorTransformStep {
  public:
-  bool Join(ColorTransformStep* next_untyped) override {
-    ColorTransformToBT2020CL* next = next_untyped->GetToBT2020CL();
-    if (!next)
-      return false;
-    if (null_)
-      return false;
-    null_ = true;
-    return true;
-  }
-
-  bool IsNull() override { return null_; }
-
   void Transform(ColorTransform::TriStim* YUV, size_t num) const override {
-    if (null_)
-      return;
     for (size_t i = 0; i < num; i++) {
       float Y = YUV[i].x();
       float U = YUV[i].y() - 0.5;
@@ -838,7 +723,6 @@
       YUV[i] = ColorTransform::TriStim(R_Y + Y, Y, B_Y + Y);
     }
   }
-  bool CanAppendShaderSource() override { return true; }
   void AppendShaderSource(std::stringstream* hdr,
                           std::stringstream* src,
                           size_t step_index) const override {
@@ -866,14 +750,14 @@
          << "(color.rgb);" << endl;
   }
 
- private:
-  bool null_ = false;
+  void AppendSkShaderSource(std::stringstream* src) const override {
+    NOTREACHED();
+  }
 };
 
 void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform(
-    ColorSpace src,
-    const ColorSpace& dst,
-    ColorTransform::Intent intent) {
+    const ColorSpace& src,
+    const ColorSpace& dst) {
   steps_.push_back(
       std::make_unique<ColorTransformMatrix>(GetRangeAdjustMatrix(src)));
 
@@ -895,10 +779,6 @@
   if (src.GetTransferFunction(&src_to_linear_fn)) {
     steps_.push_back(std::make_unique<ColorTransformSkTransferFn>(
         src_to_linear_fn, src.HasExtendedSkTransferFn()));
-  } else if (src.GetTransferID() ==
-             ColorSpace::TransferID::SMPTEST2084_NON_HDR) {
-    steps_.push_back(
-        std::make_unique<ColorTransformSMPTEST2048NonHdrToLinear>());
   } else {
     steps_.push_back(
         std::make_unique<ColorTransformToLinear>(src.GetTransferID()));
@@ -930,7 +810,7 @@
   }
 
   if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
-    steps_.push_back(std::make_unique<ColorTransformToBT2020CL>());
+    NOTREACHED();
   } else {
     steps_.push_back(
         std::make_unique<ColorTransformMatrix>(GetTransferMatrix(dst)));
@@ -948,13 +828,7 @@
   // TODO(ccameron): We may want dst assume sRGB at some point in the future.
   if (!src_.IsValid())
     return;
-
-  // SMPTEST2084_NON_HDR is not a valid destination.
-  if (dst.GetTransferID() == ColorSpace::TransferID::SMPTEST2084_NON_HDR) {
-    DLOG(ERROR) << "Invalid dst transfer function, returning identity.";
-    return;
-  }
-  AppendColorSpaceToColorSpaceTransform(src_, dst_, intent);
+  AppendColorSpaceToColorSpaceTransform(src_, dst_);
   if (intent != Intent::TEST_NO_OPT)
     Simplify();
 }
@@ -981,14 +855,6 @@
   return src.str();
 }
 
-bool ColorTransformInternal::CanGetShaderSource() const {
-  for (const auto& step : steps_) {
-    if (!step->CanAppendShaderSource())
-      return false;
-  }
-  return true;
-}
-
 ColorTransformInternal::~ColorTransformInternal() {}
 
 void ColorTransformInternal::Simplify() {
diff --git a/ui/gfx/color_transform.h b/ui/gfx/color_transform.h
index 064ad1c..db7d75d8 100644
--- a/ui/gfx/color_transform.h
+++ b/ui/gfx/color_transform.h
@@ -33,7 +33,6 @@
 
   // Return GLSL shader source that defines a function DoColorConversion that
   // converts a vec3 according to this transform.
-  virtual bool CanGetShaderSource() const = 0;
   virtual std::string GetShaderSource() const = 0;
 
   // Return SKSL shader sources that modifies an "inout half4 color" according
diff --git a/ui/gfx/color_transform_unittest.cc b/ui/gfx/color_transform_unittest.cc
index e98a2f4..4139839 100644
--- a/ui/gfx/color_transform_unittest.cc
+++ b/ui/gfx/color_transform_unittest.cc
@@ -52,7 +52,6 @@
 // This one is weird as the non-linear numbers are not between 0 and 1.
 ColorSpace::TransferID noninvertible_transfers[] = {
     ColorSpace::TransferID::SMPTEST428_1,
-    ColorSpace::TransferID::SMPTEST2084_NON_HDR,
 };
 
 ColorSpace::TransferID extended_transfers[] = {
@@ -61,15 +60,18 @@
 };
 
 ColorSpace::MatrixID all_matrices[] = {
-    ColorSpace::MatrixID::RGB, ColorSpace::MatrixID::BT709,
-    ColorSpace::MatrixID::FCC, ColorSpace::MatrixID::BT470BG,
-    ColorSpace::MatrixID::SMPTE170M, ColorSpace::MatrixID::SMPTE240M,
+    ColorSpace::MatrixID::RGB,
+    ColorSpace::MatrixID::BT709,
+    ColorSpace::MatrixID::FCC,
+    ColorSpace::MatrixID::BT470BG,
+    ColorSpace::MatrixID::SMPTE170M,
+    ColorSpace::MatrixID::SMPTE240M,
 
     // YCOCG produces lots of negative values which isn't compatible with many
     // transfer functions.
     // TODO(hubbe): Test this separately.
     // ColorSpace::MatrixID::YCOCG,
-    ColorSpace::MatrixID::BT2020_NCL, ColorSpace::MatrixID::BT2020_CL,
+    ColorSpace::MatrixID::BT2020_NCL,
     ColorSpace::MatrixID::YDZDX,
 };
 
@@ -513,9 +515,6 @@
     for (const auto& dst : common_color_spaces) {
       auto transform = ColorTransform::NewColorTransform(
           src, dst, ColorTransform::Intent::INTENT_PERCEPTUAL);
-      if (!transform->CanGetShaderSource())
-        continue;
-
       std::string source = "void main(inout half4 color) {" +
                            transform->GetSkShaderSource() + "}";
       SkRuntimeColorFilterFactory factory(
diff --git a/ui/gfx/mojom/color_space.mojom b/ui/gfx/mojom/color_space.mojom
index 78891112..2af7bcc5 100644
--- a/ui/gfx/mojom/color_space.mojom
+++ b/ui/gfx/mojom/color_space.mojom
@@ -47,7 +47,6 @@
   SMPTEST2084,
   SMPTEST428_1,
   ARIB_STD_B67,
-  SMPTEST2084_NON_HDR,
   IEC61966_2_1_HDR,
   LINEAR_HDR,
   CUSTOM
diff --git a/ui/gfx/mojom/color_space_mojom_traits.h b/ui/gfx/mojom/color_space_mojom_traits.h
index 5621f95..9997436 100644
--- a/ui/gfx/mojom/color_space_mojom_traits.h
+++ b/ui/gfx/mojom/color_space_mojom_traits.h
@@ -156,8 +156,6 @@
         return gfx::mojom::ColorSpaceTransferID::SMPTEST428_1;
       case gfx::ColorSpace::TransferID::ARIB_STD_B67:
         return gfx::mojom::ColorSpaceTransferID::ARIB_STD_B67;
-      case gfx::ColorSpace::TransferID::SMPTEST2084_NON_HDR:
-        return gfx::mojom::ColorSpaceTransferID::SMPTEST2084_NON_HDR;
       case gfx::ColorSpace::TransferID::IEC61966_2_1_HDR:
         return gfx::mojom::ColorSpaceTransferID::IEC61966_2_1_HDR;
       case gfx::ColorSpace::TransferID::LINEAR_HDR:
@@ -232,9 +230,6 @@
       case gfx::mojom::ColorSpaceTransferID::ARIB_STD_B67:
         *out = gfx::ColorSpace::TransferID::ARIB_STD_B67;
         return true;
-      case gfx::mojom::ColorSpaceTransferID::SMPTEST2084_NON_HDR:
-        *out = gfx::ColorSpace::TransferID::SMPTEST2084_NON_HDR;
-        return true;
       case gfx::mojom::ColorSpaceTransferID::IEC61966_2_1_HDR:
         *out = gfx::ColorSpace::TransferID::IEC61966_2_1_HDR;
         return true;
diff --git a/ui/gl/yuv_to_rgb_converter.cc b/ui/gl/yuv_to_rgb_converter.cc
index e85c85dd..a5169cd7 100644
--- a/ui/gl/yuv_to_rgb_converter.cc
+++ b/ui/gl/yuv_to_rgb_converter.cc
@@ -98,7 +98,6 @@
       gfx::ColorTransform::NewColorTransform(
           color_space, color_space.GetAsFullRangeRGB(),
           gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
-  DCHECK(color_transform->CanGetShaderSource());
   std::string do_color_conversion = color_transform->GetShaderSource();
 
   const char* fragment_header = nullptr;
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 137d244..83dca0de 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -883,8 +883,11 @@
       NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
   if (!window_host)
     return nullptr;
-  while (window_host->parent())
+  while (window_host->parent()) {
+    if (window_host->native_widget_mac()->GetWidget()->is_top_level())
+      break;
     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 6735f70..ff72483 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -737,6 +737,7 @@
   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;
@@ -753,6 +754,7 @@
   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/widget/widget.cc b/ui/views/widget/widget.cc
index af72459..8ff27f1 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -460,11 +460,12 @@
 }
 
 void Widget::NotifyNativeViewHierarchyWillChange() {
-  FocusManager* focus_manager = GetFocusManager();
-  // We are being removed from a window hierarchy.  Treat this as
-  // the root_view_ being removed.
-  if (focus_manager)
-    focus_manager->ViewRemoved(root_view_.get());
+  // During tear-down the top-level focus manager becomes unavailable to
+  // GTK tabbed panes and their children, so normal deregistration via
+  // |FocusManager::ViewRemoved()| calls are fouled.  We clear focus here
+  // to avoid these redundant steps and to avoid accessing deleted views
+  // that may have been in focus.
+  ClearFocusFromWidget();
 }
 
 void Widget::NotifyNativeViewHierarchyChanged() {
@@ -613,14 +614,7 @@
   widget_closed_ = true;
   closed_reason_ = closed_reason;
   SaveWindowPlacement();
-
-  // During tear-down the top-level focus manager becomes unavailable to
-  // GTK tabbed panes and their children, so normal deregistration via
-  // |FocusManager::ViewRemoved()| calls are fouled.  We clear focus here
-  // to avoid these redundant steps and to avoid accessing deleted views
-  // that may have been in focus.
-  if (is_top_level() && focus_manager_)
-    focus_manager_->SetFocusedView(nullptr);
+  ClearFocusFromWidget();
 
   for (WidgetObserver& observer : observers_)
     observer.OnWidgetClosing(this);
@@ -1525,8 +1519,7 @@
 }
 
 void Widget::DestroyRootView() {
-  if (is_top_level() && focus_manager_)
-    focus_manager_->SetFocusedView(nullptr);
+  ClearFocusFromWidget();
   NotifyWillRemoveView(root_view_.get());
   non_client_view_ = nullptr;
   // Remove all children before the unique_ptr reset so that
@@ -1662,6 +1655,14 @@
     widget_delegate()->OnPaintAsActiveChanged(paint_as_active);
 }
 
+void Widget::ClearFocusFromWidget() {
+  FocusManager* focus_manager = GetFocusManager();
+  // We are being removed from a window hierarchy.  Treat this as
+  // the root_view_ being removed.
+  if (focus_manager)
+    focus_manager->ViewRemoved(root_view_.get());
+}
+
 namespace internal {
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index 88cb98ff..1c6330d 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -994,6 +994,9 @@
   // Notifies the window frame that the active rendering state has changed.
   void UpdatePaintAsActiveState(bool paint_as_active);
 
+  // If a descendent of |root_view_| is focused, then clear the focus.
+  void ClearFocusFromWidget();
+
   static bool g_disable_activation_change_handling_;
 
   internal::NativeWidgetPrivate* native_widget_ = nullptr;
diff --git a/ui/webui/resources/html/search_highlight_utils.html b/ui/webui/resources/html/search_highlight_utils.html
index 7bc5c69..933de81 100644
--- a/ui/webui/resources/html/search_highlight_utils.html
+++ b/ui/webui/resources/html/search_highlight_utils.html
@@ -1 +1,2 @@
+<link rel="import" href="assert.html">
 <script src="../js/search_highlight_utils.js"></script>
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index baebf22b..4099959 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -59,6 +59,7 @@
 
 js_library("search_highlight_utils") {
   deps = [
+    ":assert",
     ":cr",
   ]
 }
diff --git a/ui/webui/resources/js/search_highlight_utils.js b/ui/webui/resources/js/search_highlight_utils.js
index 11d7cc3d..1910528 100644
--- a/ui/webui/resources/js/search_highlight_utils.js
+++ b/ui/webui/resources/js/search_highlight_utils.js
@@ -95,11 +95,26 @@
    * should already be visible or the bubble will render incorrectly.
    * @param {!HTMLElement} element The element to be highlighted.
    * @param {string} rawQuery The search query.
+   * @param {boolean=} horizontallyCenter Whether or not to horizontally center
+   *     the shown search bubble (if any) based on |element|'s left and width.
    * @return {?Node} The search bubble that was added, or null if no new bubble
    *     was added.
    */
-  /* #export */ function highlightControlWithBubble(element, rawQuery) {
-    let searchBubble = element.querySelector(`.${SEARCH_BUBBLE_CSS_CLASS}`);
+  /* #export */ function highlightControlWithBubble(
+      element, rawQuery, horizontallyCenter) {
+    let anchor = element;
+    if (element.tagName === 'SELECT') {
+      anchor = element.parentNode;
+    }
+    // NOTE(dbeam): this is theoretically only possible in the select case, but
+    // callers are often fast and loose with regard to casting |element| from a
+    // parentNode (which can easily be a shadow root). So we leave this if for
+    // all branches (rather than solely inside the select branch).
+    if (anchor instanceof ShadowRoot) {
+      anchor = anchor.host.parentNode;
+    }
+
+    let searchBubble = anchor.querySelector(`.${SEARCH_BUBBLE_CSS_CLASS}`);
     // If the element has already been highlighted, there is no need to do
     // anything.
     if (searchBubble) {
@@ -112,13 +127,18 @@
     innards.classList.add('search-bubble-innards');
     innards.textContent = rawQuery;
     searchBubble.appendChild(innards);
-    element.appendChild(searchBubble);
+    anchor.appendChild(searchBubble);
 
     const updatePosition = function() {
+      assert(typeof element.offsetTop === 'number');
       searchBubble.style.top = element.offsetTop +
           (innards.classList.contains('above') ? -searchBubble.offsetHeight :
                                                  element.offsetHeight) +
           'px';
+      if (horizontallyCenter) {
+        const width = element.offsetWidth - searchBubble.offsetWidth;
+        searchBubble.style.left = element.offsetLeft + width / 2 + 'px';
+      }
     };
     updatePosition();
 
@@ -126,6 +146,9 @@
       innards.classList.toggle('above');
       updatePosition();
     });
+    // TODO(crbug.com/355446): create a way to programmatically update these
+    // bubbles (i.e. call updatePosition()) when outer scope knows they need to
+    // be repositioned.
     return searchBubble;
   }
 
diff --git a/ui/wm/test/testing_cursor_client_observer.h b/ui/wm/test/testing_cursor_client_observer.h
index 2698935..e6fbccc 100644
--- a/ui/wm/test/testing_cursor_client_observer.h
+++ b/ui/wm/test/testing_cursor_client_observer.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef UI_WM_TEST_TESTING_CURSOR_CLIENT_OBSERVER_H_
+#define UI_WM_TEST_TESTING_CURSOR_CLIENT_OBSERVER_H_
+
 #include "ui/aura/client/cursor_client_observer.h"
 #include "ui/wm/core/cursor_manager.h"
 
@@ -32,3 +35,5 @@
 };
 
 }  // namespace wm
+
+#endif  // UI_WM_TEST_TESTING_CURSOR_CLIENT_OBSERVER_H_
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index 9b1b82a7..83475eac 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -32,9 +32,12 @@
 #include "services/network/network_service.h"
 #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"
+#include "third_party/blink/public/mojom/installedapp/related_application.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 #include "weblayer/browser/browser_main_parts_impl.h"
@@ -105,6 +108,29 @@
       const std::string& serialized_report) override {}
 };
 
+#if defined(OS_ANDROID)
+// TODO(https://crbug.com/1037884): Remove this.
+class StubInstalledAppProvider : public blink::mojom::InstalledAppProvider {
+ public:
+  StubInstalledAppProvider() {}
+  ~StubInstalledAppProvider() override = default;
+
+  // InstalledAppProvider overrides:
+  void FilterInstalledApps(
+      std::vector<blink::mojom::RelatedApplicationPtr> related_apps,
+      FilterInstalledAppsCallback callback) override {
+    std::move(callback).Run(std::vector<blink::mojom::RelatedApplicationPtr>());
+  }
+
+  static void Create(
+      content::RenderFrameHost* rfh,
+      mojo::PendingReceiver<blink::mojom::InstalledAppProvider> receiver) {
+    mojo::MakeSelfOwnedReceiver(std::make_unique<StubInstalledAppProvider>(),
+                                std::move(receiver));
+  }
+};
+#endif
+
 }  // namespace
 
 namespace weblayer {
@@ -354,6 +380,16 @@
 #endif  // defined(OS_ANDROID)
 }
 
+void ContentBrowserClientImpl::RegisterBrowserInterfaceBindersForFrame(
+    content::RenderFrameHost* render_frame_host,
+    service_manager::BinderMapWithContext<content::RenderFrameHost*>* map) {
+#if defined(OS_ANDROID)
+  // TODO(https://crbug.com/1037884): Remove this.
+  map->Add<blink::mojom::InstalledAppProvider>(
+      base::BindRepeating(&StubInstalledAppProvider::Create));
+#endif
+}
+
 void ContentBrowserClientImpl::GetQuotaSettings(
     content::BrowserContext* context,
     content::StoragePartition* partition,
diff --git a/weblayer/browser/content_browser_client_impl.h b/weblayer/browser/content_browser_client_impl.h
index dbbee63..c869f91 100644
--- a/weblayer/browser/content_browser_client_impl.h
+++ b/weblayer/browser/content_browser_client_impl.h
@@ -74,6 +74,10 @@
       service_manager::BinderRegistry* registry,
       blink::AssociatedInterfaceRegistry* associated_registry,
       content::RenderProcessHost* render_process_host) override;
+  void RegisterBrowserInterfaceBindersForFrame(
+      content::RenderFrameHost* render_frame_host,
+      service_manager::BinderMapWithContext<content::RenderFrameHost*>* map)
+      override;
   void GetQuotaSettings(
       content::BrowserContext* context,
       content::StoragePartition* partition,
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
index dc2be0d..ed43222 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java
@@ -48,15 +48,7 @@
         static CrashReporterControllerImpl sInstance = new CrashReporterControllerImpl();
     }
 
-    private CrashReporterControllerImpl() {
-        ChildProcessCrashObserver.registerCrashCallback(
-                new ChildProcessCrashObserver.ChildCrashedCallback() {
-                    @Override
-                    public void childCrashed(int pid) {
-                        processNewMinidumps();
-                    }
-                });
-    }
+    private CrashReporterControllerImpl() {}
 
     public static CrashReporterControllerImpl getInstance() {
         return Holder.sInstance;
@@ -144,6 +136,15 @@
         if (mIsNativeInitialized) {
             processNewMinidumps();
         }
+
+        // Now that there is a client, register to observe child process crashes.
+        ChildProcessCrashObserver.registerCrashCallback(
+                new ChildProcessCrashObserver.ChildCrashedCallback() {
+                    @Override
+                    public void childCrashed(int pid) {
+                        processNewMinidumps();
+                    }
+                });
     }
 
     /** Start an async task to import crashes, and notify if any are found. */