diff --git a/DEPS b/DEPS
index 3601242c..25c6f5b 100644
--- a/DEPS
+++ b/DEPS
@@ -129,11 +129,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': 'e21616804a5af69a3e74748669c5d4f100d92102',
+  'skia_revision': '51874e3e371230e2cc02912ed0c9ddb569e48532',
   # 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': '5671f8b940b0fcdb550e318e449ded0f866e935a',
+  'v8_revision': '2f88b9b20539a240a74256f647f249f32b355e9e',
   # 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.
@@ -141,11 +141,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': '896e7ded5f25c36f802ed7dd839f8790d16aed23',
+  'angle_revision': '27f115aa0b8098feb21b85df8694b796be360c98',
   # 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': 'd39c96e96a31e2c9e386f5a6fc2278468c22d70e',
+  'swiftshader_revision': '0e3d328ac338c4c9474f5d967455ff501bd869cb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -180,7 +180,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': 'de8da4841bc3d1ed020d798d5a7fd6fb3d817fa1',
+  'nacl_revision': '2c300847c7700af6948ad78e79ccd21bd7ba0c14',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -196,7 +196,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': '144122958b2879a06cdc9098d63a3703be8b181a',
+  'catapult_revision': '810aaa1e18466131f6a75b1f631fb5b0dd81f344',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0b63029f96dd8e791b832212638d3356cd8def40',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c012fdb54ccb7b73fe5e83bce907fc8c62217737',
       'condition': 'checkout_linux',
   },
 
@@ -978,7 +978,7 @@
     Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + 'f7ce90e84f5aa9acfbc9b7ca04e567bf471e5bcd',
 
   'src/third_party/icu':
-    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'b10cc9f714e6da621c94de0f1e6090c176f876ae',
+    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '69c72a6dfe1d1ef5677db03920518638f535591f',
 
   'src/third_party/icu4j': {
       'packages': [
@@ -1172,7 +1172,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '9ea63e7d30e8f356cc8a4712eee859ce7bf059cf',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'e382e0ed7613c7aef564518f2e8b58efe6f7f7c1',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1343,7 +1343,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '688fbfe33779392aa210d67d4aa12cb012f112c2',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f75d458951d097e116e710fc70ead3344ad2c259',
+    Var('webrtc_git') + '/src.git' + '@' + '1c747f5717bcbde029a2d496832ee52388dbda05',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/WATCHLISTS b/WATCHLISTS
index 2aa65f0a..b1b0279 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -45,9 +45,6 @@
       'filepath': 'third_party/android_deps/' \
                   '|tools/android/roll/android_deps/'
     },
-    'android_features': {
-      'filepath': 'chrome/android/features/'
-    },
     'android_infobars': {
       'filepath': 'chrome/android/java/src/org/chromium/chrome/browser/infobar/'
     },
@@ -1825,7 +1822,6 @@
                                 'wnwen+watch@chromium.org'],
     'android_crazy_linker': ['johnmaguire+watch@google.com'],
     'android_deps': ['wnwen+watch@chromium.org'],
-    'android_features': ['tiborg+watch@chromium.org'],
     'android_infobars': ['dfalcantara+watch@chromium.org'],
     'android_infra': ['agrieve+watch@chromium.org',
                       'estevenson+watch@chromium.org',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 0a8e33ab..1231b076 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -454,14 +454,12 @@
   # differentiate only by ABI. Each library has an app-bundle ("_base")
   # variant.
   if (current_toolchain == android_secondary_abi_toolchain) {
-    if (enable_64_bit_browser) {
-      # These targets are the 32-bit WebView libraries that pair with a 64-bit
-      # browser. They are suffixed with _64 because their names must mach the
-      # 64-bit browser library.
-      webview_library("monochrome_64") {
-      }
-      webview_library("monochrome_64_base") {
-      }
+    # These targets are the 32-bit WebView libraries that pair with a 64-bit
+    # browser. They are suffixed with _64 because their names must mach the
+    # 64-bit browser library.
+    webview_library("monochrome_64") {
+    }
+    webview_library("monochrome_64_base") {
     }
   } else {
     # Inverse of the others above, for the original 32-bit case.
diff --git a/android_webview/docs/quick-start.md b/android_webview/docs/quick-start.md
index 7fe0e242..aee78cb 100644
--- a/android_webview/docs/quick-start.md
+++ b/android_webview/docs/quick-start.md
@@ -68,13 +68,8 @@
 # Install the APK
 $ out/Default/bin/system_webview_apk install
 
-# If you don't have `adb` in your path, you can source this file to use
-# the copy from chromium's Android SDK.
-$ source build/android/envsetup.sh
-
-# Tell Android platform to load a WebView implementation from this APK.
-# Use the package name from GN args.
-$ adb shell cmd webviewupdate set-webview-implementation com.google.android.apps.chrome
+# Tell Android platform to load a WebView implementation from this APK
+$ out/Default/bin/system_webview_apk set-webview-provider
 ```
 
 ## Start running an app
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc
index 88610f81..294c6f47 100644
--- a/ash/accessibility/accessibility_controller.cc
+++ b/ash/accessibility/accessibility_controller.cc
@@ -42,7 +42,6 @@
 #include "ui/aura/window.h"
 #include "ui/base/cursor/cursor_type.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/keyboard/keyboard_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
@@ -1257,23 +1256,7 @@
 
   NotifyAccessibilityStatusChanged();
 
-  const bool was_enabled = keyboard::IsKeyboardEnabled();
   keyboard::SetAccessibilityKeyboardEnabled(enabled);
-
-  if (::features::IsMultiProcessMash()) {
-    // TODO(mash): Support on-screen keyboard. See https://crbug.com/646565.
-    NOTIMPLEMENTED();
-    return;
-  }
-
-  // Note that there are two versions of the on-screen keyboard. A full layout
-  // is provided for accessibility, which includes sticky modifier keys to
-  // enable typing of hotkeys. A compact version is used in tablet mode to
-  // provide a layout with larger keys to facilitate touch typing. In the event
-  // that the a11y keyboard is being disabled, an on-screen keyboard might still
-  // be enabled and a forced reset is required to pick up the layout change.
-  if (was_enabled)
-    Shell::Get()->ash_keyboard_controller()->RebuildKeyboardIfEnabled();
 }
 
 void AccessibilityController::GetBatteryDescription(
diff --git a/ash/accessibility/accessibility_controller_unittest.cc b/ash/accessibility/accessibility_controller_unittest.cc
index ae3dc44..59210e2 100644
--- a/ash/accessibility/accessibility_controller_unittest.cc
+++ b/ash/accessibility/accessibility_controller_unittest.cc
@@ -21,9 +21,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "components/prefs/pref_service.h"
-#include "ui/keyboard/keyboard_controller.h"
 #include "ui/keyboard/keyboard_util.h"
-#include "ui/keyboard/test/test_keyboard_controller_observer.h"
 #include "ui/message_center/message_center.h"
 
 using message_center::MessageCenter;
@@ -295,29 +293,6 @@
   controller->RemoveObserver(&observer);
 }
 
-// See https://crbug.com/946358.
-TEST_F(AccessibilityControllerTest, RebuildsVirtualKeyboardWhenPrefChanges) {
-  AccessibilityController* controller =
-      Shell::Get()->accessibility_controller();
-  EXPECT_FALSE(controller->virtual_keyboard_enabled());
-
-  // Virtual keyboard enabled with compact layout.
-  keyboard::SetTouchKeyboardEnabled(true);
-
-  keyboard::TestKeyboardControllerObserver observer;
-  keyboard::KeyboardController::Get()->AddObserver(&observer);
-
-  // Virtual keyboard should rebuild to switch to a11y layout.
-  controller->SetVirtualKeyboardEnabled(true);
-  EXPECT_EQ(1, observer.enabled_count);
-
-  // Virtual keyboard should rebuild to switch back to compact layout.
-  controller->SetVirtualKeyboardEnabled(false);
-  EXPECT_EQ(2, observer.enabled_count);
-
-  keyboard::KeyboardController::Get()->RemoveObserver(&observer);
-}
-
 // Tests that ash's controller gets shutdown sound duration properly from
 // remote client.
 TEST_F(AccessibilityControllerTest, GetShutdownSoundDuration) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index cf5f543..a7433ab 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -133,7 +133,7 @@
         Back
       </message>
       <message name="IDS_ASH_SHELF_OVERFLOW_NAME" desc="The title used for the Ash overflow button in the shelf">
-        Overflow button
+        Overflow
       </message>
       <message name="IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE" desc="Title of the menu item in the context menu for auto-hiding the shelf when the current window is not maximized">
         Autohide shelf
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 947b945..52684f9a 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -840,6 +840,10 @@
     const TargetBounds& target_bounds,
     bool animate,
     ui::ImplicitAnimationObserver* observer) {
+  // Do not update the work area during overview animation.
+  if (suspend_visibility_update_)
+    return;
+
   StatusAreaWidget* status_widget = shelf_widget_->status_area_widget();
   base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);
   {
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 0a654f73..0bae2eaf 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -62,6 +62,7 @@
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/display.h"
 #include "ui/display/display_layout.h"
+#include "ui/display/display_observer.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
@@ -271,23 +272,25 @@
   DISALLOW_COPY_AND_ASSIGN(ShelfDragCallback);
 };
 
-class ShelfLayoutObserverTest : public ShelfLayoutManagerObserver {
+class TestDisplayObserver : public display::DisplayObserver {
  public:
-  ShelfLayoutObserverTest() : changed_auto_hide_state_(false) {}
+  TestDisplayObserver() { display::Screen::GetScreen()->AddObserver(this); }
+  ~TestDisplayObserver() override {
+    display::Screen::GetScreen()->RemoveObserver(this);
+  }
 
-  ~ShelfLayoutObserverTest() override = default;
-
-  bool changed_auto_hide_state() const { return changed_auto_hide_state_; }
+  int metrics_change_count() const { return metrics_change_count_; }
 
  private:
   // ShelfLayoutManagerObserver:
-  void OnAutoHideStateChanged(ShelfAutoHideState new_state) override {
-    changed_auto_hide_state_ = true;
+  void OnDisplayMetricsChanged(const display::Display& display,
+                               uint32_t changed_metrics) override {
+    metrics_change_count_++;
   }
 
-  bool changed_auto_hide_state_;
+  int metrics_change_count_ = 0;
 
-  DISALLOW_COPY_AND_ASSIGN(ShelfLayoutObserverTest);
+  DISALLOW_COPY_AND_ASSIGN(TestDisplayObserver);
 };
 
 class WallpaperShownWaiter : public WallpaperControllerObserver {
@@ -2972,4 +2975,70 @@
             display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
 }
 
+namespace {
+
+class OverviewAnimationWaiter : public OverviewObserver {
+ public:
+  OverviewAnimationWaiter() {
+    Shell::Get()->overview_controller()->AddObserver(this);
+  }
+
+  ~OverviewAnimationWaiter() override {
+    Shell::Get()->overview_controller()->RemoveObserver(this);
+  }
+
+  // Note this could only be called once because RunLoop would not run after
+  // Quit is called. Create a new instance if there's need to wait again.
+  void Wait() { run_loop_.Run(); }
+
+  // OverviewObserver:
+  void OnOverviewModeStartingAnimationComplete(bool cancel) override {
+    run_loop_.Quit();
+  }
+  void OnOverviewModeEndingAnimationComplete(bool cancel) override {
+    run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(OverviewAnimationWaiter);
+};
+
+}  // namespace
+
+// Make sure we don't update the work area during overview animation
+// (crbug.com/947343).
+TEST_F(ShelfLayoutManagerTest, NoShelfUpdateDuringOverviewAnimation) {
+  // Finish lid detection task.
+  base::RunLoop().RunUntilIdle();
+  TabletModeControllerTestApi().EnterTabletMode();
+  // Run overview animations.
+  ui::ScopedAnimationDurationScaleMode regular_animations(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  std::unique_ptr<aura::Window> window1(AshTestBase::CreateTestWindow());
+  std::unique_ptr<aura::Window> fullscreen(AshTestBase::CreateTestWindow());
+  fullscreen->SetProperty(aura::client::kShowStateKey,
+                          ui::SHOW_STATE_FULLSCREEN);
+  wm::ActivateWindow(fullscreen.get());
+
+  OverviewController* overview_controller = Shell::Get()->overview_controller();
+  TestDisplayObserver observer;
+  {
+    OverviewAnimationWaiter waiter;
+    overview_controller->ToggleOverview();
+    waiter.Wait();
+  }
+  ASSERT_TRUE(TabletModeControllerTestApi().IsTabletModeStarted());
+  EXPECT_EQ(0, observer.metrics_change_count());
+  {
+    OverviewAnimationWaiter waiter;
+    overview_controller->ToggleOverview();
+    waiter.Wait();
+  }
+  ASSERT_TRUE(TabletModeControllerTestApi().IsTabletModeStarted());
+  EXPECT_EQ(0, observer.metrics_change_count());
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/OWNERS b/ash/system/unified/OWNERS
new file mode 100644
index 0000000..430ce8a
--- /dev/null
+++ b/ash/system/unified/OWNERS
@@ -0,0 +1,2 @@
+tetsui@chromium.org
+tengs@chromium.org
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 619eaceb..fadacd7 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -626,6 +626,7 @@
     "process/process_win.cc",
     "profiler/frame.cc",
     "profiler/frame.h",
+    "profiler/native_unwinder.h",
     "profiler/native_unwinder_mac.cc",
     "profiler/native_unwinder_mac.h",
     "profiler/native_unwinder_win.cc",
@@ -645,7 +646,6 @@
     "profiler/thread_delegate_mac.h",
     "profiler/thread_delegate_win.cc",
     "profiler/thread_delegate_win.h",
-    "profiler/unwind_result.h",
     "profiler/unwinder.h",
     "rand_util.cc",
     "rand_util.h",
diff --git a/base/profiler/native_unwinder.h b/base/profiler/native_unwinder.h
new file mode 100644
index 0000000..f26c5ad2
--- /dev/null
+++ b/base/profiler/native_unwinder.h
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_NATIVE_UNWINDER_H_
+#define BASE_PROFILER_NATIVE_UNWINDER_H_
+
+#include <memory>
+
+namespace base {
+
+class ModuleCache;
+class Unwinder;
+
+// Creates the native unwinder for the platform.
+std::unique_ptr<Unwinder> CreateNativeUnwinder(ModuleCache* module_cache);
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_NATIVE_UNWINDER_H_
diff --git a/base/profiler/native_unwinder_mac.cc b/base/profiler/native_unwinder_mac.cc
index a91adb3..1cf2f6d1 100644
--- a/base/profiler/native_unwinder_mac.cc
+++ b/base/profiler/native_unwinder_mac.cc
@@ -11,6 +11,7 @@
 #include <sys/ptrace.h>
 
 #include "base/logging.h"
+#include "base/profiler/native_unwinder.h"
 #include "base/profiler/profile_builder.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 
@@ -138,7 +139,7 @@
                                           uintptr_t stack_top,
                                           ModuleCache* module_cache,
                                           std::vector<Frame>* stack) const {
-  // We expect the frame corresponding to the |thread_context| register state to
+  // We expect the frame correponding to the |thread_context| register state to
   // exist within |stack|.
   DCHECK_GT(stack->size(), 0u);
 
@@ -270,4 +271,8 @@
   return UnwindResult::COMPLETED;
 }
 
+std::unique_ptr<Unwinder> CreateNativeUnwinder(ModuleCache* module_cache) {
+  return std::make_unique<NativeUnwinderMac>(module_cache);
+}
+
 }  // namespace base
diff --git a/base/profiler/native_unwinder_mac.h b/base/profiler/native_unwinder_mac.h
index 00cc318c..af44a7e9 100644
--- a/base/profiler/native_unwinder_mac.h
+++ b/base/profiler/native_unwinder_mac.h
@@ -29,7 +29,7 @@
   // Cached pointer to the libsystem_kernel module.
   const ModuleCache::Module* const libsystem_kernel_module_;
 
-  // The address range of _sigtramp(), the signal trampoline function.
+  // The address range of |_sigtramp|, the signal trampoline function.
   uintptr_t sigtramp_start_;
   uintptr_t sigtramp_end_;
 };
diff --git a/base/profiler/native_unwinder_win.cc b/base/profiler/native_unwinder_win.cc
index 4736fd2..5b255c4 100644
--- a/base/profiler/native_unwinder_win.cc
+++ b/base/profiler/native_unwinder_win.cc
@@ -4,6 +4,7 @@
 
 #include "base/profiler/native_unwinder_win.h"
 
+#include "base/profiler/native_unwinder.h"
 #include "base/profiler/win32_stack_frame_unwinder.h"
 
 namespace base {
@@ -19,7 +20,7 @@
                                           uintptr_t stack_top,
                                           ModuleCache* module_cache,
                                           std::vector<Frame>* stack) const {
-  // We expect the frame corresponding to the |thread_context| register state to
+  // We expect the frame correponding to the |thread_context| register state to
   // exist within |stack|.
   DCHECK_GT(stack->size(), 0u);
 
@@ -64,4 +65,8 @@
   return UnwindResult::COMPLETED;
 }
 
+std::unique_ptr<Unwinder> CreateNativeUnwinder(ModuleCache* module_cache) {
+  return std::make_unique<NativeUnwinderWin>();
+}
+
 }  // namespace base
diff --git a/base/profiler/native_unwinder_win.h b/base/profiler/native_unwinder_win.h
index a32ad40..979c2c4 100644
--- a/base/profiler/native_unwinder_win.h
+++ b/base/profiler/native_unwinder_win.h
@@ -5,6 +5,7 @@
 #ifndef BASE_PROFILER_NATIVE_UNWINDER_WIN_H_
 #define BASE_PROFILER_NATIVE_UNWINDER_WIN_H_
 
+#include "base/macros.h"
 #include "base/profiler/unwinder.h"
 
 namespace base {
diff --git a/base/profiler/stack_sampler_impl.cc b/base/profiler/stack_sampler_impl.cc
index 8477bbc..a1153c3 100644
--- a/base/profiler/stack_sampler_impl.cc
+++ b/base/profiler/stack_sampler_impl.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "base/profiler/profile_builder.h"
 #include "base/profiler/thread_delegate.h"
+#include "base/profiler/unwinder.h"
 
 // IMPORTANT NOTE: Some functions within this implementation are invoked while
 // the target thread is suspended so it must not do any allocation from the
@@ -74,9 +75,11 @@
 
 StackSamplerImpl::StackSamplerImpl(
     std::unique_ptr<ThreadDelegate> thread_delegate,
+    std::unique_ptr<Unwinder> native_unwinder,
     ModuleCache* module_cache,
     StackSamplerTestDelegate* test_delegate)
     : thread_delegate_(std::move(thread_delegate)),
+      native_unwinder_(std::move(native_unwinder)),
       module_cache_(module_cache),
       test_delegate_(test_delegate) {}
 
@@ -168,8 +171,7 @@
                      module_cache_->GetModuleForAddress(
                          RegisterContextInstructionPointer(thread_context)));
 
-  thread_delegate_->WalkNativeFrames(thread_context, stack_top, module_cache_,
-                                     &stack);
+  native_unwinder_->TryUnwind(thread_context, stack_top, module_cache_, &stack);
 
   return stack;
 }
diff --git a/base/profiler/stack_sampler_impl.h b/base/profiler/stack_sampler_impl.h
index 28d9556..926c4a3 100644
--- a/base/profiler/stack_sampler_impl.h
+++ b/base/profiler/stack_sampler_impl.h
@@ -15,12 +15,14 @@
 namespace base {
 
 class ThreadDelegate;
+class Unwinder;
 
 // Cross-platform stack sampler implementation. Delegates to ThreadDelegate for
 // platform-specific implementation.
 class BASE_EXPORT StackSamplerImpl : public StackSampler {
  public:
   StackSamplerImpl(std::unique_ptr<ThreadDelegate> delegate,
+                   std::unique_ptr<Unwinder> native_unwinder,
                    ModuleCache* module_cache,
                    StackSamplerTestDelegate* test_delegate = nullptr);
   ~StackSamplerImpl() override;
@@ -42,6 +44,7 @@
                                uintptr_t stack_top);
 
   const std::unique_ptr<ThreadDelegate> thread_delegate_;
+  const std::unique_ptr<Unwinder> native_unwinder_;
   ModuleCache* const module_cache_;
   StackSamplerTestDelegate* const test_delegate_;
 };
diff --git a/base/profiler/stack_sampler_impl_unittest.cc b/base/profiler/stack_sampler_impl_unittest.cc
index 6b6b95b..2a2e808e 100644
--- a/base/profiler/stack_sampler_impl_unittest.cc
+++ b/base/profiler/stack_sampler_impl_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/profiler/profile_builder.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate.h"
+#include "base/profiler/unwinder.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -52,19 +53,10 @@
   };
 
   TestThreadDelegate(const std::vector<uintptr_t>& fake_stack,
-                     // Vector to fill in with the copied stack.
-                     std::vector<uintptr_t>* stack_copy = nullptr,
-                     // Variable to fill in with the bottom address of the
-                     // copied stack. This will be different than
-                     // &(*stack_copy)[0] because |stack_copy| is a copy of the
-                     // copy so does not share memory with the actual copy.
-                     uintptr_t* stack_copy_bottom = nullptr,
                      // The register context will be initialized to
                      // *|thread_context| if non-null.
                      RegisterContext* thread_context = nullptr)
       : fake_stack_(fake_stack),
-        stack_copy_(stack_copy),
-        stack_copy_bottom_(stack_copy_bottom),
         thread_context_(thread_context) {}
 
   TestThreadDelegate(const TestThreadDelegate&) = delete;
@@ -96,14 +88,37 @@
     return {&RegisterContextFramePointer(thread_context)};
   }
 
-  UnwindResult WalkNativeFrames(RegisterContext* thread_context,
-                                uintptr_t stack_top,
-                                ModuleCache* module_cache,
-                                std::vector<Frame>* stack) override {
+ private:
+  // Must be a reference to retain the underlying allocation from the vector
+  // passed to the constructor.
+  const std::vector<uintptr_t>& fake_stack_;
+  RegisterContext* thread_context_;
+};
+
+// Trivial unwinder implementation for testing.
+class TestUnwinder : public Unwinder {
+ public:
+  TestUnwinder(size_t stack_size = 0,
+               std::vector<uintptr_t>* stack_copy = nullptr,
+               // Variable to fill in with the bottom address of the
+               // copied stack. This will be different than
+               // &(*stack_copy)[0] because |stack_copy| is a copy of the
+               // copy so does not share memory with the actual copy.
+               uintptr_t* stack_copy_bottom = nullptr)
+      : stack_size_(stack_size),
+        stack_copy_(stack_copy),
+        stack_copy_bottom_(stack_copy_bottom) {}
+
+  bool CanUnwindFrom(const Frame* current_frame) const override { return true; }
+
+  UnwindResult TryUnwind(RegisterContext* thread_context,
+                         uintptr_t stack_top,
+                         ModuleCache* module_cache,
+                         std::vector<Frame>* stack) const override {
     if (stack_copy_) {
       auto* bottom = reinterpret_cast<uintptr_t*>(
           RegisterContextStackPointer(thread_context));
-      auto* top = bottom + fake_stack_.size();
+      auto* top = bottom + stack_size_;
       *stack_copy_ = std::vector<uintptr_t>(bottom, top);
     }
     if (stack_copy_bottom_)
@@ -112,12 +127,9 @@
   }
 
  private:
-  // Must be a reference to retain the underlying allocation from the vector
-  // passed to the constructor.
-  const std::vector<uintptr_t>& fake_stack_;
+  size_t stack_size_;
   std::vector<uintptr_t>* stack_copy_;
   uintptr_t* stack_copy_bottom_;
-  RegisterContext* thread_context_;
 };
 
 class TestModule : public ModuleCache::Module {
@@ -153,7 +165,8 @@
   InjectModuleForContextInstructionPointer(stack, &module_cache);
   std::vector<uintptr_t> stack_copy;
   StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack, &stack_copy), &module_cache);
+      std::make_unique<TestThreadDelegate>(stack),
+      std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
 
   std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
       std::make_unique<StackSampler::StackBuffer>(stack.size() *
@@ -170,7 +183,8 @@
   InjectModuleForContextInstructionPointer(stack, &module_cache);
   std::vector<uintptr_t> stack_copy;
   StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack, &stack_copy), &module_cache);
+      std::make_unique<TestThreadDelegate>(stack),
+      std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
 
   std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
       std::make_unique<StackSampler::StackBuffer>((stack.size() - 1) *
@@ -196,8 +210,9 @@
   std::vector<uintptr_t> stack_copy;
   uintptr_t stack_copy_bottom;
   StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack, &stack_copy,
-                                           &stack_copy_bottom),
+      std::make_unique<TestThreadDelegate>(stack),
+      std::make_unique<TestUnwinder>(stack.size(), &stack_copy,
+                                     &stack_copy_bottom),
       &module_cache);
 
   std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
@@ -220,8 +235,8 @@
   RegisterContextFramePointer(&thread_context) =
       reinterpret_cast<uintptr_t>(&stack[1]);
   StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack, nullptr, &stack_copy_bottom,
-                                           &thread_context),
+      std::make_unique<TestThreadDelegate>(stack, &thread_context),
+      std::make_unique<TestUnwinder>(stack.size(), nullptr, &stack_copy_bottom),
       &module_cache);
 
   std::unique_ptr<StackSampler::StackBuffer> stack_buffer =
diff --git a/base/profiler/stack_sampler_mac.cc b/base/profiler/stack_sampler_mac.cc
index 3bb8f9b4..54ca505e 100644
--- a/base/profiler/stack_sampler_mac.cc
+++ b/base/profiler/stack_sampler_mac.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <memory>
-
 #include "base/profiler/stack_sampler.h"
+
+#include "base/profiler/native_unwinder_mac.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate_mac.h"
 
@@ -16,8 +16,9 @@
     ModuleCache* module_cache,
     StackSamplerTestDelegate* test_delegate) {
   return std::make_unique<StackSamplerImpl>(
-      std::make_unique<ThreadDelegateMac>(thread_id, module_cache),
-      module_cache, test_delegate);
+      std::make_unique<ThreadDelegateMac>(thread_id),
+      std::make_unique<NativeUnwinderMac>(module_cache), module_cache,
+      test_delegate);
 }
 
 // static
diff --git a/base/profiler/stack_sampler_win.cc b/base/profiler/stack_sampler_win.cc
index 2edec59..214cf0d 100644
--- a/base/profiler/stack_sampler_win.cc
+++ b/base/profiler/stack_sampler_win.cc
@@ -4,6 +4,7 @@
 
 #include "base/profiler/stack_sampler.h"
 
+#include "base/profiler/native_unwinder_win.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate_win.h"
 #include "build/build_config.h"
@@ -17,8 +18,8 @@
     StackSamplerTestDelegate* test_delegate) {
 #if defined(ARCH_CPU_X86_64)
   return std::make_unique<StackSamplerImpl>(
-      std::make_unique<ThreadDelegateWin>(thread_id), module_cache,
-      test_delegate);
+      std::make_unique<ThreadDelegateWin>(thread_id),
+      std::make_unique<NativeUnwinderWin>(), module_cache, test_delegate);
 #else
   return nullptr;
 #endif
diff --git a/base/profiler/thread_delegate.h b/base/profiler/thread_delegate.h
index 35072a4a..d0f0f02 100644
--- a/base/profiler/thread_delegate.h
+++ b/base/profiler/thread_delegate.h
@@ -10,7 +10,6 @@
 #include "base/base_export.h"
 #include "base/profiler/frame.h"
 #include "base/profiler/register_context.h"
-#include "base/profiler/unwind_result.h"
 
 namespace base {
 
@@ -66,16 +65,6 @@
   // May heap allocate.
   virtual std::vector<uintptr_t*> GetRegistersToRewrite(
       RegisterContext* thread_context) = 0;
-
-  // Walks the native frames on the stack pointed to by the stack pointer in
-  // |thread_context|, appending the frames to |stack|. When invoked
-  // stack->back() contains the frame corresponding to the state in
-  // |thread_context|.
-  // TODO(wittman): Move the unwinding support into a separate UnwindDelegate.
-  virtual UnwindResult WalkNativeFrames(RegisterContext* thread_context,
-                                        uintptr_t stack_top,
-                                        ModuleCache* module_cache,
-                                        std::vector<Frame>* stack) = 0;
 };
 
 }  // namespace base
diff --git a/base/profiler/thread_delegate_mac.cc b/base/profiler/thread_delegate_mac.cc
index 4342316..7632c4ef 100644
--- a/base/profiler/thread_delegate_mac.cc
+++ b/base/profiler/thread_delegate_mac.cc
@@ -58,12 +58,10 @@
 
 // ThreadDelegateMac ----------------------------------------------------------
 
-ThreadDelegateMac::ThreadDelegateMac(mach_port_t thread_port,
-                                     ModuleCache* module_cache)
+ThreadDelegateMac::ThreadDelegateMac(mach_port_t thread_port)
     : thread_port_(thread_port),
       thread_stack_base_address_(reinterpret_cast<uintptr_t>(
-          pthread_get_stackaddr_np(pthread_from_mach_thread_np(thread_port)))),
-      native_unwinder_(module_cache) {
+          pthread_get_stackaddr_np(pthread_from_mach_thread_np(thread_port)))) {
   // This class suspends threads, and those threads might be suspended in dyld.
   // Therefore, for all the system functions that might be linked in dynamically
   // that are used while threads are suspended, make calls to them to make sure
@@ -103,13 +101,4 @@
       &AsUintPtr(&thread_context->__r15)};
 }
 
-UnwindResult ThreadDelegateMac::WalkNativeFrames(
-    x86_thread_state64_t* thread_context,
-    uintptr_t stack_top,
-    ModuleCache* module_cache,
-    std::vector<Frame>* stack) {
-  return native_unwinder_.TryUnwind(thread_context, stack_top, module_cache,
-                                    stack);
-}
-
 }  // namespace base
diff --git a/base/profiler/thread_delegate_mac.h b/base/profiler/thread_delegate_mac.h
index 098b2a90..1ff2497 100644
--- a/base/profiler/thread_delegate_mac.h
+++ b/base/profiler/thread_delegate_mac.h
@@ -33,7 +33,7 @@
     mach_port_t thread_port_;
   };
 
-  ThreadDelegateMac(mach_port_t thread_port, ModuleCache* module_cache);
+  ThreadDelegateMac(mach_port_t thread_port);
   ~ThreadDelegateMac() override;
 
   ThreadDelegateMac(const ThreadDelegateMac&) = delete;
@@ -48,20 +48,12 @@
   std::vector<uintptr_t*> GetRegistersToRewrite(
       x86_thread_state64_t* thread_context) override;
 
-  UnwindResult WalkNativeFrames(x86_thread_state64_t* thread_context,
-                                uintptr_t stack_top,
-                                ModuleCache* module_cache,
-                                std::vector<Frame>* stack) override;
-
  private:
   // Weak reference: Mach port for thread being profiled.
   mach_port_t thread_port_;
 
   // The stack base address corresponding to |thread_port_|.
   const uintptr_t thread_stack_base_address_;
-
-  // The unwinder for native stack frames.
-  NativeUnwinderMac native_unwinder_;
 };
 
 }  // namespace base
diff --git a/base/profiler/thread_delegate_win.cc b/base/profiler/thread_delegate_win.cc
index 50045cd..c249ad4 100644
--- a/base/profiler/thread_delegate_win.cc
+++ b/base/profiler/thread_delegate_win.cc
@@ -200,12 +200,4 @@
   };
 }
 
-UnwindResult ThreadDelegateWin::WalkNativeFrames(CONTEXT* thread_context,
-                                                 uintptr_t stack_top,
-                                                 ModuleCache* module_cache,
-                                                 std::vector<Frame>* stack) {
-  NativeUnwinderWin unwinder;
-  return unwinder.TryUnwind(thread_context, stack_top, module_cache, stack);
-}
-
 }  // namespace base
diff --git a/base/profiler/thread_delegate_win.h b/base/profiler/thread_delegate_win.h
index e4079e9..9e1d781 100644
--- a/base/profiler/thread_delegate_win.h
+++ b/base/profiler/thread_delegate_win.h
@@ -47,11 +47,6 @@
   std::vector<uintptr_t*> GetRegistersToRewrite(
       CONTEXT* thread_context) override;
 
-  UnwindResult WalkNativeFrames(CONTEXT* thread_context,
-                                uintptr_t stack_top,
-                                ModuleCache* module_cache,
-                                std::vector<Frame>* stack) override;
-
  private:
   win::ScopedHandle thread_handle_;
   const uintptr_t thread_stack_base_address_;
diff --git a/base/profiler/unwind_result.h b/base/profiler/unwind_result.h
deleted file mode 100644
index 9aeaec8..0000000
--- a/base/profiler/unwind_result.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 BASE_PROFILER_UNWIND_RESULT_H_
-#define BASE_PROFILER_UNWIND_RESULT_H_
-
-namespace base {
-
-// The result of attempting to unwind stack frames.
-enum class UnwindResult {
-  // The end of the stack was reached successfully.
-  COMPLETED,
-
-  // The walk reached a frame that it doesn't know how to unwind, but might be
-  // unwindable by the other native/aux unwinder.
-  UNRECOGNIZED_FRAME,
-
-  // The walk was aborted and is not resumable.
-  ABORTED,
-};
-
-}  // namespace base
-
-#endif  // BASE_PROFILER_UNWIND_RESULT_H_
diff --git a/base/profiler/unwinder.h b/base/profiler/unwinder.h
index c42a391..09b3efa 100644
--- a/base/profiler/unwinder.h
+++ b/base/profiler/unwinder.h
@@ -7,20 +7,32 @@
 
 #include <vector>
 
+#include "base/macros.h"
 #include "base/profiler/frame.h"
 #include "base/profiler/register_context.h"
-#include "base/profiler/unwind_result.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 
 namespace base {
 
+// The result of attempting to unwind stack frames.
+enum class UnwindResult {
+  // The end of the stack was reached successfully.
+  COMPLETED,
+
+  // The walk reached a frame that it doesn't know how to unwind, but might be
+  // unwindable by the other native/aux unwinder.
+  UNRECOGNIZED_FRAME,
+
+  // The walk was aborted and is not resumable.
+  ABORTED,
+};
+
 // Unwinder provides an interface for stack frame unwinder implementations for
 // use with the StackSamplingProfiler. The profiler is expected to call
 // CanUnwind() to determine if the Unwinder thinks it can unwind from the frame
 // represented by the context values, then TryUnwind() to attempt the
-// unwind. Implementations of CanUnwindFrom() and TryUnwind() should be
-// stateless because stack samples for all target threads use the same Unwinder
-// instance.
+// unwind. Note that the stack samples for multiple collection scenarios are
+// interleaved on a single Unwinder instance.
 class Unwinder {
  public:
   virtual ~Unwinder() = default;
@@ -33,9 +45,11 @@
   // doesn't have unwind information.
   virtual bool CanUnwindFrom(const Frame* current_frame) const = 0;
 
-  // Attempts to unwind the frame represented by the context values. If
-  // successful appends frames onto the stack and returns true. Otherwise
-  // returns false.
+  // Attempts to unwind the frame represented by the context values.
+  // Walks the native frames on the stack pointed to by the stack pointer in
+  // |thread_context|, appending the frames to |stack|. When invoked
+  // stack->back() contains the frame corresponding to the state in
+  // |thread_context|.
   virtual UnwindResult TryUnwind(RegisterContext* thread_context,
                                  uintptr_t stack_top,
                                  ModuleCache* module_cache,
diff --git a/base/supports_user_data.cc b/base/supports_user_data.cc
index 2eb93f4..b3b21bb0 100644
--- a/base/supports_user_data.cc
+++ b/base/supports_user_data.cc
@@ -6,6 +6,10 @@
 
 namespace base {
 
+std::unique_ptr<SupportsUserData::Data> SupportsUserData::Data::Clone() {
+  return nullptr;
+}
+
 SupportsUserData::SupportsUserData() {
   // Harmless to construct on a different execution sequence to subsequent
   // usage.
@@ -27,7 +31,10 @@
   DCHECK(sequence_checker_.CalledOnValidSequence());
   // Avoid null keys; they are too vulnerable to collision.
   DCHECK(key);
-  user_data_[key] = std::move(data);
+  if (data.get())
+    user_data_[key] = std::move(data);
+  else
+    RemoveUserData(key);
 }
 
 void SupportsUserData::RemoveUserData(const void* key) {
@@ -39,6 +46,14 @@
   sequence_checker_.DetachFromSequence();
 }
 
+void SupportsUserData::CloneDataFrom(const SupportsUserData& other) {
+  for (const auto& data_pair : other.user_data_) {
+    auto cloned_data = data_pair.second->Clone();
+    if (cloned_data)
+      SetUserData(data_pair.first, std::move(cloned_data));
+  }
+}
+
 SupportsUserData::~SupportsUserData() {
   DCHECK(sequence_checker_.CalledOnValidSequence() || user_data_.empty());
   DataMap local_user_data;
diff --git a/base/supports_user_data.h b/base/supports_user_data.h
index 1d14874..cb97a23 100644
--- a/base/supports_user_data.h
+++ b/base/supports_user_data.h
@@ -27,17 +27,24 @@
   class BASE_EXPORT Data {
    public:
     virtual ~Data() = default;
+
+    // Returns a copy of |this|; null if copy is not supported.
+    virtual std::unique_ptr<Data> Clone();
   };
 
   // The user data allows the clients to associate data with this object.
-  // Multiple user data values can be stored under different keys.
-  // This object will TAKE OWNERSHIP of the given data pointer, and will
-  // delete the object if it is changed or the object is destroyed.
   // |key| must not be null--that value is too vulnerable for collision.
+  // NOTE: SetUserData() with an empty unique_ptr behaves the same as
+  // RemoveUserData().
   Data* GetUserData(const void* key) const;
   void SetUserData(const void* key, std::unique_ptr<Data> data);
   void RemoveUserData(const void* key);
 
+  // Adds all data from |other|, that is clonable, to |this|. That is, this
+  // iterates over the data in |other|, and any data that returns non-null from
+  // Clone() is added to |this|.
+  void CloneDataFrom(const SupportsUserData& other);
+
   // SupportsUserData is not thread-safe, and on debug build will assert it is
   // only used on one execution sequence. Calling this method allows the caller
   // to hand the SupportsUserData instance across execution sequences. Use only
diff --git a/base/task/task_scheduler/platform_native_worker_pool.cc b/base/task/task_scheduler/platform_native_worker_pool.cc
index 243c413f..d32f278b 100644
--- a/base/task/task_scheduler/platform_native_worker_pool.cc
+++ b/base/task/task_scheduler/platform_native_worker_pool.cc
@@ -54,7 +54,9 @@
 #endif
 }
 
-void PlatformNativeWorkerPool::Start() {
+void PlatformNativeWorkerPool::Start(WorkerEnvironment worker_environment) {
+  worker_environment_ = worker_environment;
+
   StartImpl();
 
   ScopedWorkersExecutor executor(this);
diff --git a/base/task/task_scheduler/platform_native_worker_pool.h b/base/task/task_scheduler/platform_native_worker_pool.h
index ca6a639..79e680a4 100644
--- a/base/task/task_scheduler/platform_native_worker_pool.h
+++ b/base/task/task_scheduler/platform_native_worker_pool.h
@@ -20,7 +20,7 @@
   ~PlatformNativeWorkerPool() override;
 
   // Starts the worker pool and allows tasks to begin running.
-  void Start();
+  void Start(WorkerEnvironment worker_environment = WorkerEnvironment::NONE);
 
   // SchedulerWorkerPool:
   void JoinForTesting() override;
@@ -40,6 +40,9 @@
   virtual void StartImpl() = 0;
   virtual void SubmitWork() = 0;
 
+  // Used to control the worker environment. Supports COM MTA on Windows.
+  WorkerEnvironment worker_environment_ = WorkerEnvironment::NONE;
+
  private:
   class ScopedWorkersExecutor;
 
diff --git a/base/task/task_scheduler/platform_native_worker_pool_win.cc b/base/task/task_scheduler/platform_native_worker_pool_win.cc
index 4178f33109..1a85e06 100644
--- a/base/task/task_scheduler/platform_native_worker_pool_win.cc
+++ b/base/task/task_scheduler/platform_native_worker_pool_win.cc
@@ -4,11 +4,26 @@
 
 #include "base/task/task_scheduler/platform_native_worker_pool_win.h"
 
+#include "base/no_destructor.h"
 #include "base/task/task_scheduler/task_tracker.h"
+#include "base/threading/thread_local.h"
+#include "base/win/scoped_com_initializer.h"
 
 namespace base {
 namespace internal {
 
+namespace {
+
+// Used to enable COM MTA when creating threads via the Windows Thread Pool API.
+ThreadLocalOwnedPointer<win::ScopedCOMInitializer>&
+ScopedCOMInitializerForCurrentThread() {
+  static base::NoDestructor<ThreadLocalOwnedPointer<win::ScopedCOMInitializer>>
+      scoped_com_initializer;
+  return *scoped_com_initializer;
+}
+
+}  // namespace
+
 PlatformNativeWorkerPoolWin::PlatformNativeWorkerPoolWin(
     TrackedRef<TaskTracker> task_tracker,
     TrackedRef<Delegate> delegate,
@@ -53,6 +68,14 @@
     PTP_WORK) {
   auto* worker_pool = static_cast<PlatformNativeWorkerPoolWin*>(
       scheduler_worker_pool_windows_impl);
+
+  if (worker_pool->worker_environment_ == WorkerEnvironment::COM_MTA &&
+      !ScopedCOMInitializerForCurrentThread().Get()) {
+    ScopedCOMInitializerForCurrentThread().Set(
+        std::make_unique<win::ScopedCOMInitializer>(
+            win::ScopedCOMInitializer::kMTA));
+  }
+
   worker_pool->RunNextSequenceImpl();
 }
 
diff --git a/base/task/task_scheduler/scheduler_worker_pool.h b/base/task/task_scheduler/scheduler_worker_pool.h
index bb6cf27..9c14fe3 100644
--- a/base/task/task_scheduler/scheduler_worker_pool.h
+++ b/base/task/task_scheduler/scheduler_worker_pool.h
@@ -13,6 +13,7 @@
 #include "base/task/task_scheduler/sequence.h"
 #include "base/task/task_scheduler/task.h"
 #include "base/task/task_scheduler/tracked_ref.h"
+#include "build/build_config.h"
 
 namespace base {
 namespace internal {
@@ -34,6 +35,15 @@
         const TaskTraits& traits) = 0;
   };
 
+  enum class WorkerEnvironment {
+    // No special worker environment required.
+    NONE,
+#if defined(OS_WIN)
+    // Initialize a COM MTA on the worker.
+    COM_MTA,
+#endif  // defined(OS_WIN)
+  };
+
   ~SchedulerWorkerPool() override;
 
   // CanScheduleSequenceObserver:
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl.cc b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
index 280d9b86..1effafe 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -31,6 +31,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
 
 #if defined(OS_WIN)
 #include "base/win/scoped_com_initializer.h"
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl.h b/base/task/task_scheduler/scheduler_worker_pool_impl.h
index 96d5e21..6949e51 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl.h
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl.h
@@ -31,7 +31,6 @@
 #include "base/task/task_scheduler/tracked_ref.h"
 #include "base/task_runner.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
 
 namespace base {
 
@@ -51,15 +50,6 @@
 // This class is thread-safe.
 class BASE_EXPORT SchedulerWorkerPoolImpl : public SchedulerWorkerPool {
  public:
-  enum class WorkerEnvironment {
-    // No special worker environment required.
-    NONE,
-#if defined(OS_WIN)
-    // Initialize a COM MTA on the worker.
-    COM_MTA,
-#endif  // defined(OS_WIN)
-  };
-
   // Constructs a pool without workers.
   //
   // |histogram_label| is used to label the pool's histograms ("TaskScheduler."
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
index 9e55160fc..d3afa74 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -55,10 +55,6 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if defined(OS_WIN)
-#include "base/win/com_init_util.h"
-#endif  // defined(OS_WIN)
-
 namespace base {
 namespace internal {
 namespace {
@@ -110,7 +106,7 @@
         SchedulerWorkerPoolParams(max_tasks, suggested_reclaim_time),
         max_best_effort_tasks ? max_best_effort_tasks.value() : max_tasks,
         service_thread_.task_runner(), worker_observer,
-        SchedulerWorkerPoolImpl::WorkerEnvironment::NONE, may_block_threshold);
+        SchedulerWorkerPool::WorkerEnvironment::NONE, may_block_threshold);
   }
 
   void CreateAndStartWorkerPool(
@@ -301,28 +297,6 @@
   worker_pool_->WaitForAllWorkersIdleForTesting();
 }
 
-#if defined(OS_WIN)
-TEST_P(TaskSchedulerWorkerPoolImplTestParam, NoEnvironment) {
-  // Verify that COM is not initialized in a SchedulerWorkerPoolImpl initialized
-  // with SchedulerWorkerPoolImpl::WorkerEnvironment::NONE.
-  scoped_refptr<TaskRunner> task_runner = CreateTaskRunnerWithExecutionMode(
-      GetParam(), &mock_scheduler_task_runner_delegate_);
-
-  WaitableEvent task_running;
-  task_runner->PostTask(
-      FROM_HERE, BindOnce(
-                     [](WaitableEvent* task_running) {
-                       win::AssertComApartmentType(win::ComApartmentType::NONE);
-                       task_running->Signal();
-                     },
-                     &task_running));
-
-  task_running.Wait();
-
-  worker_pool_->WaitForAllWorkersIdleForTesting();
-}
-#endif  // defined(OS_WIN)
-
 INSTANTIATE_TEST_SUITE_P(Parallel,
                          TaskSchedulerWorkerPoolImplTestParam,
                          ::testing::Values(test::ExecutionMode::PARALLEL));
@@ -330,62 +304,6 @@
                          TaskSchedulerWorkerPoolImplTestParam,
                          ::testing::Values(test::ExecutionMode::SEQUENCED));
 
-#if defined(OS_WIN)
-
-namespace {
-
-class TaskSchedulerWorkerPoolImplTestCOMMTAParam
-    : public TaskSchedulerWorkerPoolImplTestBase,
-      public testing::TestWithParam<test::ExecutionMode> {
- protected:
-  TaskSchedulerWorkerPoolImplTestCOMMTAParam() = default;
-
-  void SetUp() override {
-    CreateWorkerPool();
-    ASSERT_TRUE(worker_pool_);
-    worker_pool_->Start(SchedulerWorkerPoolParams(kMaxTasks, TimeDelta::Max()),
-                        kMaxTasks, service_thread_.task_runner(), nullptr,
-                        SchedulerWorkerPoolImpl::WorkerEnvironment::COM_MTA);
-  }
-
-  void TearDown() override {
-    TaskSchedulerWorkerPoolImplTestBase::CommonTearDown();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolImplTestCOMMTAParam);
-};
-
-}  // namespace
-
-TEST_P(TaskSchedulerWorkerPoolImplTestCOMMTAParam, COMMTAInitialized) {
-  // Verify that SchedulerWorkerPoolImpl workers have a COM MTA available.
-  scoped_refptr<TaskRunner> task_runner = CreateTaskRunnerWithExecutionMode(
-      GetParam(), &mock_scheduler_task_runner_delegate_);
-
-  WaitableEvent task_running;
-  task_runner->PostTask(
-      FROM_HERE, BindOnce(
-                     [](WaitableEvent* task_running) {
-                       win::AssertComApartmentType(win::ComApartmentType::MTA);
-                       task_running->Signal();
-                     },
-                     &task_running));
-
-  task_running.Wait();
-
-  worker_pool_->WaitForAllWorkersIdleForTesting();
-}
-
-INSTANTIATE_TEST_SUITE_P(Parallel,
-                         TaskSchedulerWorkerPoolImplTestCOMMTAParam,
-                         ::testing::Values(test::ExecutionMode::PARALLEL));
-INSTANTIATE_TEST_SUITE_P(Sequenced,
-                         TaskSchedulerWorkerPoolImplTestCOMMTAParam,
-                         ::testing::Values(test::ExecutionMode::SEQUENCED));
-
-#endif  // defined(OS_WIN)
-
 namespace {
 
 class TaskSchedulerWorkerPoolImplStartInBodyTest
@@ -1751,7 +1669,7 @@
     worker_pool_->Start(
         SchedulerWorkerPoolParams(kMaxTasks, base::TimeDelta::Max()),
         kMaxBestEffortTasks, service_thread_.task_runner(), nullptr,
-        SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+        SchedulerWorkerPool::WorkerEnvironment::NONE);
   }
 
   void TearDown() override {
@@ -1841,7 +1759,7 @@
   worker_pool_->Start(
       SchedulerWorkerPoolParams(kLocalMaxTasks, kReclaimTimeForRacyCleanupTest),
       kLocalMaxTasks, service_thread_.task_runner(), nullptr,
-      SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+      SchedulerWorkerPool::WorkerEnvironment::NONE);
 
   scoped_refptr<TaskRunner> task_runner = test::CreateTaskRunnerWithTraits(
       {WithBaseSyncPrimitives()}, &mock_scheduler_task_runner_delegate_);
diff --git a/base/task/task_scheduler/scheduler_worker_pool_unittest.cc b/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
index 14f7a504..c3b236c 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_unittest.cc
@@ -32,6 +32,7 @@
 
 #if defined(OS_WIN)
 #include "base/task/task_scheduler/platform_native_worker_pool_win.h"
+#include "base/win/com_init_util.h"
 #elif defined(OS_MACOSX)
 #include "base/task/task_scheduler/platform_native_worker_pool_mac.h"
 #endif
@@ -140,7 +141,9 @@
     mock_scheduler_task_runner_delegate_.SetWorkerPool(worker_pool_.get());
   }
 
-  void StartWorkerPool() {
+  void StartWorkerPool(
+      SchedulerWorkerPool::WorkerEnvironment worker_environment =
+          SchedulerWorkerPool::WorkerEnvironment::NONE) {
     ASSERT_TRUE(worker_pool_);
     switch (GetParam().pool_type) {
       case test::PoolType::GENERIC: {
@@ -149,14 +152,14 @@
         scheduler_worker_pool_impl->Start(
             SchedulerWorkerPoolParams(kMaxTasks, TimeDelta::Max()),
             kMaxBestEffortTasks, service_thread_.task_runner(), nullptr,
-            SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+            worker_environment);
         break;
       }
 #if defined(OS_WIN) || defined(OS_MACOSX)
       case test::PoolType::NATIVE: {
         PlatformNativeWorkerPoolType* scheduler_worker_pool_native_impl =
             static_cast<PlatformNativeWorkerPoolType*>(worker_pool_.get());
-        scheduler_worker_pool_native_impl->Start();
+        scheduler_worker_pool_native_impl->Start(worker_environment);
         break;
       }
 #endif
@@ -404,6 +407,40 @@
   task_tracker_.FlushForTesting();
 }
 
+#if defined(OS_WIN)
+TEST_P(TaskSchedulerWorkerPoolTest, COMMTAWorkerEnvironment) {
+  StartWorkerPool(SchedulerWorkerPool::WorkerEnvironment::COM_MTA);
+  auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+      GetParam().execution_mode, &mock_scheduler_task_runner_delegate_);
+
+  WaitableEvent task_ran;
+  task_runner->PostTask(
+      FROM_HERE, BindOnce(
+                     [](WaitableEvent* task_ran) {
+                       win::AssertComApartmentType(win::ComApartmentType::MTA);
+                       task_ran->Signal();
+                     },
+                     Unretained(&task_ran)));
+  task_ran.Wait();
+}
+
+TEST_P(TaskSchedulerWorkerPoolTest, NoWorkerEnvironment) {
+  StartWorkerPool(SchedulerWorkerPool::WorkerEnvironment::NONE);
+  auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+      GetParam().execution_mode, &mock_scheduler_task_runner_delegate_);
+
+  WaitableEvent task_ran;
+  task_runner->PostTask(
+      FROM_HERE, BindOnce(
+                     [](WaitableEvent* task_ran) {
+                       win::AssertComApartmentType(win::ComApartmentType::NONE);
+                       task_ran->Signal();
+                     },
+                     Unretained(&task_ran)));
+  task_ran.Wait();
+}
+#endif
+
 INSTANTIATE_TEST_SUITE_P(GenericParallel,
                          TaskSchedulerWorkerPoolTest,
                          ::testing::Values(PoolExecutionType{
diff --git a/base/task/task_scheduler/task_scheduler_impl.cc b/base/task/task_scheduler/task_scheduler_impl.cc
index 3366fbf5..7acd6f68 100644
--- a/base/task/task_scheduler/task_scheduler_impl.cc
+++ b/base/task/task_scheduler/task_scheduler_impl.cc
@@ -134,19 +134,19 @@
 
   single_thread_task_runner_manager_.Start(scheduler_worker_observer);
 
-  const SchedulerWorkerPoolImpl::WorkerEnvironment worker_environment =
+  const SchedulerWorkerPool::WorkerEnvironment worker_environment =
 #if defined(OS_WIN)
       init_params.shared_worker_pool_environment ==
               InitParams::SharedWorkerPoolEnvironment::COM_MTA
-          ? SchedulerWorkerPoolImpl::WorkerEnvironment::COM_MTA
-          : SchedulerWorkerPoolImpl::WorkerEnvironment::NONE;
+          ? SchedulerWorkerPool::WorkerEnvironment::COM_MTA
+          : SchedulerWorkerPool::WorkerEnvironment::NONE;
 #else
-      SchedulerWorkerPoolImpl::WorkerEnvironment::NONE;
+      SchedulerWorkerPool::WorkerEnvironment::NONE;
 #endif
 
 #if defined(OS_WIN) || defined(OS_MACOSX)
   if (native_foreground_pool_) {
-    native_foreground_pool_->Start();
+    native_foreground_pool_->Start(worker_environment);
   } else
 #endif
   {
diff --git a/base/util/README.md b/base/util/README.md
index dc224b10..bd3f8199 100644
--- a/base/util/README.md
+++ b/base/util/README.md
@@ -78,12 +78,15 @@
 
 
 ## How does this differ from //components
-First, //base/util is a layer lower than //components so some code just cannot fit
-in components.
+Both //components and //base/util contain subdirectories that are (a) intended
+for reuse. In addition, //components imposes no global layering in Chromium, so
+a subdirectory placed in //components can be used from most-to-all layers in the
+codebase, subject to the dependencies that that subdirectory itself holds.
 
-Second, //components contains things are closer to full features or subsystems
-(eg autofill, heap profiler, cloud devices, visited link tracker) that are not
-really intended for large scale reuse.
+In spite of these similarities, there are *conceptual* differences: //components
+contains things are closer to full features or subsystems (eg autofill, heap
+profiler, cloud devices, visited link tracker) that are not really intended for
+large scale reuse.
 
 There is some overlap and at some point it will become a judgment call, but
 in general, //components are a better fit if the code in question is a feature,
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index 2f6b204..55e3e88 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -263,6 +263,24 @@
   device_utils.DeviceUtils.parallel(devices).pMap(uninstall)
 
 
+def _IsWebViewProvider(apk_helper_instance):
+  meta_data = apk_helper_instance.GetAllMetadata()
+  meta_data_keys = [pair[0] for pair in meta_data]
+  return 'com.android.webview.WebViewLibrary' in meta_data_keys
+
+
+def _SetWebViewProvider(devices, package_name):
+
+  def switch_provider(device):
+    if device.build_version_sdk < version_codes.NOUGAT:
+      logging.error('No need to switch provider on pre-Nougat devices (%s)',
+                    device.serial)
+    else:
+      device.SetWebViewImplementation(package_name)
+
+  device_utils.DeviceUtils.parallel(devices).pMap(switch_provider)
+
+
 def _NormalizeProcessName(debug_process_name, package_name):
   if not debug_process_name:
     debug_process_name = package_name
@@ -1155,6 +1173,24 @@
     _UninstallApk(self.devices, self.install_dict, self.args.package_name)
 
 
+class _SetWebViewProviderCommand(_Command):
+  name = 'set-webview-provider'
+  description = ("Sets the device's WebView provider to this APK's "
+                 "package name.")
+  needs_package_name = True
+
+  def Run(self):
+    if self.is_bundle:
+      # TODO(ntfschr): Support this by figuring out how to construct
+      # self.apk_helper for bundles.
+      raise Exception(
+          'Switching WebView providers not supported for bundles yet!')
+    if not _IsWebViewProvider(self.apk_helper):
+      raise Exception('This package does not have a WebViewLibrary meta-data '
+                      'tag. Are you sure it contains a WebView implementation?')
+    _SetWebViewProvider(self.devices, self.args.package_name)
+
+
 class _LaunchCommand(_Command):
   name = 'launch'
   description = ('Sends a launch intent for the APK or bundle after first '
@@ -1496,6 +1532,7 @@
     _DevicesCommand,
     _InstallCommand,
     _UninstallCommand,
+    _SetWebViewProviderCommand,
     _LaunchCommand,
     _StopCommand,
     _ClearDataCommand,
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index e5a6e3c..a4e230c7 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -226,9 +226,6 @@
     # Checks that proguard flags have not changed (!is_java_debug only).
     check_android_configuration = false
 
-    # Enables instantiation of 64-bit browser targets.
-    enable_64_bit_browser = true
-
     # Enable the chrome build for devices without touchscreens.
     notouch_build = false
 
diff --git a/cc/layers/layer_sticky_position_constraint.cc b/cc/layers/layer_sticky_position_constraint.cc
index 718d567a..4da9089 100644
--- a/cc/layers/layer_sticky_position_constraint.cc
+++ b/cc/layers/layer_sticky_position_constraint.cc
@@ -17,9 +17,7 @@
       left_offset(0.f),
       right_offset(0.f),
       top_offset(0.f),
-      bottom_offset(0.f),
-      nearest_element_shifting_sticky_box(kInvalidElementId),
-      nearest_element_shifting_containing_block(kInvalidElementId) {}
+      bottom_offset(0.f) {}
 
 LayerStickyPositionConstraint::LayerStickyPositionConstraint(
     const LayerStickyPositionConstraint& other) = default;
diff --git a/cc/layers/viewport.cc b/cc/layers/viewport.cc
index b99cee3..d58b9f0 100644
--- a/cc/layers/viewport.cc
+++ b/cc/layers/viewport.cc
@@ -49,21 +49,20 @@
 
   gfx::Vector2dF pending_content_delta = content_delta;
 
-  ScrollNode* inner_node = InnerScrollNode();
+  // Attempt to scroll inner viewport first.
   pending_content_delta -= host_impl_->ScrollSingleNode(
-      inner_node, pending_content_delta, viewport_point, is_direct_manipulation,
-      &scroll_tree());
+      InnerScrollNode(), pending_content_delta, viewport_point,
+      is_direct_manipulation, &scroll_tree());
 
-  ScrollResult result;
-
+  // Now attempt to scroll the outer viewport.
   if (scroll_outer_viewport) {
     pending_content_delta -= host_impl_->ScrollSingleNode(
         OuterScrollNode(), pending_content_delta, viewport_point,
         is_direct_manipulation, &scroll_tree());
   }
 
+  ScrollResult result;
   result.consumed_delta = delta - AdjustOverscroll(pending_content_delta);
-
   result.content_scrolled_delta = content_delta - pending_content_delta;
   return result;
 }
diff --git a/cc/layers/viewport.h b/cc/layers/viewport.h
index 5abe67d..0343ff0 100644
--- a/cc/layers/viewport.h
+++ b/cc/layers/viewport.h
@@ -55,9 +55,7 @@
 
   bool CanScroll(const ScrollState& scroll_state) const;
 
-  // Scrolls the viewport. Unlike the above method, scrolls the inner before
-  // the outer viewport. Doesn't affect browser controls or return a result
-  // since callers don't need it.
+  // TODO(bokan): Callers can now be replaced by ScrollBy.
   void ScrollByInnerFirst(const gfx::Vector2dF& delta);
 
   // Scrolls the viewport, bubbling the delta between the inner and outer
diff --git a/cc/tiles/gpu_image_decode_cache.cc b/cc/tiles/gpu_image_decode_cache.cc
index 96b32a6..eaff9df 100644
--- a/cc/tiles/gpu_image_decode_cache.cc
+++ b/cc/tiles/gpu_image_decode_cache.cc
@@ -1513,6 +1513,15 @@
       RemoveFromPersistentCache(found_persistent);
   }
 
+  // Don't keep discardable cpu memory for GPU backed images. The cache hit rate
+  // of the cpu fallback (in case we don't find this image in gpu memory) is
+  // too low to cache this data.
+  if (image_data->decode.ref_count == 0 &&
+      image_data->mode != DecodedDataMode::kCpu &&
+      image_data->HasUploadedData()) {
+    image_data->decode.ResetData();
+  }
+
   // If we have no refs on an uploaded image, it should be unlocked. Do this
   // before any attempts to delete the image.
   if (image_data->IsGpuOrTransferCache() && image_data->upload.ref_count == 0 &&
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index e8418bf..087f8b2 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -2429,12 +2429,6 @@
   EXPECT_TRUE(decoded_draw_image.image());
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
   cache->DrawWithImageFinished(draw_image, decoded_draw_image);
-  // For non-lazy images which are downscaled, the scaled image should be
-  // cached.
-  auto sw_image = cache->GetSWImageDecodeForTesting(draw_image);
-  EXPECT_TRUE(sw_image);
-  EXPECT_EQ(sw_image->width(), (GetNormalImageSize().width() + 1) / 2);
-  EXPECT_EQ(sw_image->height(), (GetNormalImageSize().height() + 1) / 2);
 }
 
 TEST_P(GpuImageDecodeCacheTest, KeepOnlyLast2ContentIds) {
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index ec3f35f..e32fa24 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -708,14 +708,12 @@
 
   DCHECK(to_target.Preserves2dAxisAlignment());
 
-  const gfx::Vector2dF& translate = to_target.To2dTranslation();
-  const gfx::Vector2dF& scale = to_target.Scale2d();
-
-  gfx::RRectF bounds = node->rounded_corner_bounds;
-  bounds.Scale(scale.x(), scale.y());
-  bounds.Offset(translate);
-
-  return std::make_pair(bounds, node->is_fast_rounded_corner);
+  SkRRect result;
+  if (!SkRRect(node->rounded_corner_bounds)
+           .transform(to_target.matrix(), &result)) {
+    return kEmptyRoundedCornerInfo;
+  }
+  return std::make_pair(gfx::RRectF(result), node->is_fast_rounded_corner);
 }
 
 static void UpdateRenderTarget(EffectTree* effect_tree) {
diff --git a/cc/trees/element_id.cc b/cc/trees/element_id.cc
index b460a2e..6690378 100644
--- a/cc/trees/element_id.cc
+++ b/cc/trees/element_id.cc
@@ -14,6 +14,8 @@
 
 namespace cc {
 
+const ElementIdType ElementId::kInvalidElementId = 0;
+
 ElementId LayerIdToElementIdForTesting(int layer_id) {
   return ElementId(std::numeric_limits<int>::max() - layer_id);
 }
diff --git a/cc/trees/element_id.h b/cc/trees/element_id.h
index b34782d9..e8275c5 100644
--- a/cc/trees/element_id.h
+++ b/cc/trees/element_id.h
@@ -26,8 +26,6 @@
 
 using ElementIdType = uint64_t;
 
-static const ElementIdType kInvalidElementId = 0;
-
 // Element ids are chosen by cc's clients and can be used as a stable identifier
 // across updates.
 //
@@ -47,14 +45,17 @@
 // targets. A Layer's element id can change over the Layer's lifetime because
 // non-default ElementIds are only set during an animation's lifetime.
 struct CC_EXPORT ElementId {
-  explicit ElementId(ElementIdType id) : id_(id) {}
-  ElementId() : ElementId(kInvalidElementId) {}
+  explicit ElementId(ElementIdType id) : id_(id) {
+    DCHECK_NE(id, kInvalidElementId);
+  }
+
+  ElementId() : id_(kInvalidElementId) {}
 
   bool operator==(const ElementId& o) const { return id_ == o.id_; }
   bool operator!=(const ElementId& o) const { return !(*this == o); }
   bool operator<(const ElementId& o) const { return id_ < o.id_; }
 
-  // An ElementId's conversion to a boolean value depends only on its primaryId.
+  // Returns true if the ElementId has been initialized with a valid id.
   explicit operator bool() const { return !!id_; }
 
   void AddToTracedValue(base::trace_event::TracedValue* res) const;
@@ -66,6 +67,7 @@
 
  private:
   friend struct ElementIdHash;
+  static const ElementIdType kInvalidElementId;
 
   // The compositor treats this as an opaque handle and should not know how to
   // interpret these bits. Non-blink cc clients typically operate in terms of
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 0dc635ae..89ae622 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -4578,7 +4578,15 @@
   TRACE_EVENT2("cc",
                "LayerTreeHostImpl::SetSynchronousInputHandlerRootScrollOffset",
                "offset_x", root_offset.x(), "offset_y", root_offset.y());
-  bool changed = active_tree_->DistributeRootScrollOffset(root_offset);
+
+  gfx::Vector2dF delta = root_offset.DeltaFrom(viewport()->TotalScrollOffset());
+  bool changed = !viewport()
+                      ->ScrollBy(delta,
+                                 /*viewport_point=*/gfx::Point(),
+                                 /*is_wheel_scroll=*/false,
+                                 /*affect_browser_controls=*/false,
+                                 /*scroll_outer_viewport=*/true)
+                      .consumed_delta.IsZero();
   if (!changed)
     return;
 
@@ -4965,7 +4973,7 @@
   if (!browser_controls_offset_manager_->has_animation())
     return false;
 
-  gfx::Vector2dF scroll = browser_controls_offset_manager_->Animate(time);
+  gfx::Vector2dF scroll_delta = browser_controls_offset_manager_->Animate(time);
 
   if (browser_controls_offset_manager_->has_animation())
     SetNeedsOneBeginImplFrame();
@@ -4973,11 +4981,15 @@
   if (active_tree_->TotalScrollOffset().y() == 0.f)
     return false;
 
-  if (scroll.IsZero())
+  if (scroll_delta.IsZero())
     return false;
 
   DCHECK(viewport());
-  viewport()->ScrollBy(scroll, gfx::Point(), false, false, true);
+  viewport()->ScrollBy(scroll_delta,
+                       /*viewport_point=*/gfx::Point(),
+                       /*is_wheel_scroll=*/false,
+                       /*affect_browser_controls=*/false,
+                       /*scroll_outer_viewport=*/true);
   client_->SetNeedsCommitOnImplThread();
   client_->RenewTreePriority();
   return true;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index a24b83e..74da659 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -7703,42 +7703,121 @@
                         gfx::ScrollOffsetToVector2dF(scroll_offset));
 }
 
-TEST_F(LayerTreeHostImplTest,
-       ExternalRootLayerScrollOffsetPreventedByUserNotScrollable) {
-  host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(10, 20));
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  LayerImpl* clip_layer =
-      scroll_layer->test_properties()->parent->test_properties()->parent;
-  clip_layer->SetBounds(gfx::Size(10, 20));
-  scroll_layer->SetScrollable(gfx::Size(10, 20));
-  scroll_layer->SetDrawsContent(true);
-  host_impl_->active_tree()
-      ->InnerViewportScrollLayer()
-      ->test_properties()
-      ->user_scrollable_vertical = false;
-  host_impl_->active_tree()
-      ->InnerViewportScrollLayer()
-      ->test_properties()
-      ->user_scrollable_horizontal = false;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+// Ensure that the SetSynchronousInputHandlerRootScrollOffset method used by
+// the WebView API correctly respects the user_scrollable bits on both of the
+// inner and outer viewport scroll nodes.
+TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) {
+  gfx::Size viewport_size(100, 100);
+  gfx::Size content_size(200, 200);
+  CreateBasicVirtualViewportLayers(viewport_size, content_size);
 
-  // Draw first frame to clear any pending draws and check scroll.
+  auto* outer_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
+  auto* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
+
+  ScrollTree& scroll_tree =
+      host_impl_->active_tree()->property_trees()->scroll_tree;
+  ElementId inner_element_id = inner_scroll->element_id();
+  ElementId outer_element_id = outer_scroll->element_id();
+
+  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
-  CheckLayerScrollDelta(scroll_layer, gfx::Vector2dF(0.f, 0.f));
-  EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());
 
-  // Set external scroll delta on delegate and notify LayerTreeHost.
-  gfx::ScrollOffset scroll_offset(10.f, 10.f);
-  host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  float page_scale_factor = 2.f;
+  host_impl_->active_tree()->PushPageScaleFromMainThread(
+      page_scale_factor, page_scale_factor, page_scale_factor);
 
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
-  EXPECT_TRUE(frame.has_no_damage);
-  CheckLayerScrollDelta(scroll_layer,
-                        gfx::ScrollOffsetToVector2dF(gfx::ScrollOffset()));
+  // Disable scrolling the inner viewport. Only the outer should scroll.
+  {
+    ASSERT_FALSE(did_request_redraw_);
+    inner_scroll->test_properties()->user_scrollable_vertical = false;
+    inner_scroll->test_properties()->user_scrollable_horizontal = false;
+    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+
+    gfx::ScrollOffset scroll_offset(25.f, 30.f);
+    host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(),
+                     scroll_tree.current_scroll_offset(inner_element_id));
+    scroll_offset.Scale(1.f / page_scale_factor);
+    EXPECT_VECTOR_EQ(scroll_offset,
+                     scroll_tree.current_scroll_offset(outer_element_id));
+    EXPECT_TRUE(did_request_redraw_);
+
+    // Reset
+    did_request_redraw_ = false;
+    inner_scroll->test_properties()->user_scrollable_vertical = true;
+    inner_scroll->test_properties()->user_scrollable_horizontal = true;
+    outer_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(0, 0));
+  }
+
+  // Disable scrolling the outer viewport. The inner should scroll to its
+  // extent but there should be no bubbling over to the outer viewport.
+  {
+    ASSERT_FALSE(did_request_redraw_);
+    outer_scroll->test_properties()->user_scrollable_vertical = false;
+    outer_scroll->test_properties()->user_scrollable_horizontal = false;
+    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+
+    gfx::ScrollOffset scroll_offset(120.f, 140.f);
+    host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(50.f, 50.f),
+                     scroll_tree.current_scroll_offset(inner_element_id));
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(),
+                     scroll_tree.current_scroll_offset(outer_element_id));
+    EXPECT_TRUE(did_request_redraw_);
+
+    // Reset
+    did_request_redraw_ = false;
+    inner_scroll->test_properties()->user_scrollable_vertical = true;
+    inner_scroll->test_properties()->user_scrollable_horizontal = true;
+    inner_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(0, 0));
+  }
+
+  // Disable both viewports. No scrolling should take place, no redraw should
+  // be requested.
+  {
+    ASSERT_FALSE(did_request_redraw_);
+    outer_scroll->test_properties()->user_scrollable_vertical = false;
+    outer_scroll->test_properties()->user_scrollable_horizontal = false;
+    inner_scroll->test_properties()->user_scrollable_vertical = false;
+    inner_scroll->test_properties()->user_scrollable_horizontal = false;
+    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+
+    gfx::ScrollOffset scroll_offset(60.f, 70.f);
+    host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(),
+                     scroll_tree.current_scroll_offset(inner_element_id));
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(),
+                     scroll_tree.current_scroll_offset(outer_element_id));
+    EXPECT_FALSE(did_request_redraw_);
+
+    // Reset
+    inner_scroll->test_properties()->user_scrollable_vertical = true;
+    inner_scroll->test_properties()->user_scrollable_horizontal = true;
+    outer_scroll->test_properties()->user_scrollable_vertical = true;
+    outer_scroll->test_properties()->user_scrollable_horizontal = true;
+  }
+
+  // If the inner is at its extent but the outer cannot scroll, we shouldn't
+  // request a redraw.
+  {
+    ASSERT_FALSE(did_request_redraw_);
+    outer_scroll->test_properties()->user_scrollable_vertical = false;
+    outer_scroll->test_properties()->user_scrollable_horizontal = false;
+    inner_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(50.f, 50.f));
+    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+
+    gfx::ScrollOffset scroll_offset(60.f, 70.f);
+    host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(50.f, 50.f),
+                     scroll_tree.current_scroll_offset(inner_element_id));
+    EXPECT_VECTOR_EQ(gfx::ScrollOffset(),
+                     scroll_tree.current_scroll_offset(outer_element_id));
+    EXPECT_FALSE(did_request_redraw_);
+
+    // Reset
+    outer_scroll->test_properties()->user_scrollable_vertical = true;
+    outer_scroll->test_properties()->user_scrollable_horizontal = true;
+  }
 }
 
 TEST_F(LayerTreeHostImplTest, OverscrollRoot) {
@@ -11246,7 +11325,7 @@
   }
 };
 
-TEST_F(LayerTreeHostImplVirtualViewportTest, ScrollBothInnerAndOuterLayer) {
+TEST_F(LayerTreeHostImplVirtualViewportTest, RootScrollBothInnerAndOuterLayer) {
   gfx::Size content_size = gfx::Size(100, 160);
   gfx::Size outer_viewport = gfx::Size(50, 80);
   gfx::Size inner_viewport = gfx::Size(25, 40);
@@ -11269,11 +11348,11 @@
     EXPECT_EQ(gfx::ScrollOffset(25.f, 40.f), inner_scroll->MaxScrollOffset());
     EXPECT_EQ(gfx::ScrollOffset(50.f, 80.f), outer_scroll->MaxScrollOffset());
 
-    // Outer viewport scrolls first. Then the rest is applied to the inner
+    // Inner viewport scrolls first. Then the rest is applied to the outer
     // viewport.
-    EXPECT_EQ(gfx::ScrollOffset(20.f, 20.f),
+    EXPECT_EQ(gfx::ScrollOffset(25.f, 40.f),
               inner_scroll->CurrentScrollOffset());
-    EXPECT_EQ(gfx::ScrollOffset(50.f, 80.f),
+    EXPECT_EQ(gfx::ScrollOffset(45.f, 60.f),
               outer_scroll->CurrentScrollOffset());
   }
 }
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 8a8fc92..76d41d91 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -1740,49 +1740,6 @@
   state->EndArray();
 }
 
-bool LayerTreeImpl::DistributeRootScrollOffset(
-    const gfx::ScrollOffset& desired_root_offset) {
-  if (!InnerViewportScrollNode() || !OuterViewportScrollLayer())
-    return false;
-
-  gfx::ScrollOffset root_offset = desired_root_offset;
-  ScrollTree& scroll_tree = property_trees()->scroll_tree;
-
-  // If we get here, we have both inner/outer viewports, and need to distribute
-  // the scroll offset between them.
-  gfx::ScrollOffset inner_viewport_offset =
-      scroll_tree.current_scroll_offset(InnerViewportScrollNode()->element_id);
-  gfx::ScrollOffset outer_viewport_offset =
-      OuterViewportScrollLayer()->CurrentScrollOffset();
-  DCHECK(inner_viewport_offset + outer_viewport_offset == TotalScrollOffset());
-
-  // Setting the root scroll offset is driven by user actions so prevent
-  // it if it is not user scrollable in certain directions.
-  if (!InnerViewportScrollNode()->user_scrollable_horizontal)
-    root_offset.set_x(inner_viewport_offset.x() + outer_viewport_offset.x());
-
-  if (!InnerViewportScrollNode()->user_scrollable_vertical)
-    root_offset.set_y(inner_viewport_offset.y() + outer_viewport_offset.y());
-
-  // It may be nothing has changed.
-  if (inner_viewport_offset + outer_viewport_offset == root_offset)
-    return false;
-
-  gfx::ScrollOffset max_outer_viewport_scroll_offset =
-      OuterViewportScrollLayer()->MaxScrollOffset();
-
-  outer_viewport_offset = root_offset - inner_viewport_offset;
-  outer_viewport_offset.SetToMin(max_outer_viewport_scroll_offset);
-  outer_viewport_offset.SetToMax(gfx::ScrollOffset());
-
-  OuterViewportScrollLayer()->SetCurrentScrollOffset(outer_viewport_offset);
-  inner_viewport_offset = root_offset - outer_viewport_offset;
-  if (scroll_tree.SetScrollOffset(InnerViewportScrollNode()->element_id,
-                                  inner_viewport_offset))
-    DidUpdateScrollOffset(InnerViewportScrollNode()->element_id);
-  return true;
-}
-
 void LayerTreeImpl::QueueSwapPromise(
     std::unique_ptr<SwapPromise> swap_promise) {
   DCHECK(swap_promise);
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index 5d1097b..88d798bc 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -491,10 +491,6 @@
   // Used for accessing the task runner and debug assertions.
   TaskRunnerProvider* task_runner_provider() const;
 
-  // Distribute the root scroll between outer and inner viewport scroll layer.
-  // The outer viewport scroll layer scrolls first.
-  bool DistributeRootScrollOffset(const gfx::ScrollOffset& root_offset);
-
   void ApplyScroll(ScrollNode* scroll_node, ScrollState* scroll_state) {
     host_impl_->ApplyScroll(scroll_node, scroll_state);
   }
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 103dada..2ea2c0e 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1432,7 +1432,7 @@
     is_bundle = true
   }
 
-  if (android_64bit_target_cpu && enable_64_bit_browser) {
+  if (android_64bit_target_cpu) {
     group("monochrome_64_secondary_abi_lib") {
       public_deps = [
         ":monochrome_64($android_secondary_abi_toolchain)",
@@ -1446,13 +1446,11 @@
   }
 } else {
   # 64-bit browser library targets (APK and bundle).
-  if (enable_64_bit_browser) {
-    libmonochrome_apk_or_bundle_tmpl("monochrome_64") {
-      is_bundle = false
-    }
-    libmonochrome_apk_or_bundle_tmpl("monochrome_64_base") {
-      is_bundle = true
-    }
+  libmonochrome_apk_or_bundle_tmpl("monochrome_64") {
+    is_bundle = false
+  }
+  libmonochrome_apk_or_bundle_tmpl("monochrome_64_base") {
+    is_bundle = true
   }
 
   # 32-bit browser library alias targets, pulled in by 64-bit WebView builds.
@@ -2187,7 +2185,7 @@
   }
 }
 
-if (android_64bit_target_cpu && enable_64_bit_browser) {
+if (android_64bit_target_cpu) {
   monochrome_public_bundle_tmpl("monochrome_64_public_bundle") {
     bundle_suffix = "64"
     is_64_bit_browser = true
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index f27be47a..a71fdbb 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -484,7 +484,6 @@
   "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappActionsNotificationTest.java",
-  "javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappDeferredStartupTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappDisplayModeTest.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
index 93f697b..5112350b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -107,7 +107,7 @@
                                 PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE,
                                         () -> mFinalCallback.onResult(mMultiThumbnailBitmap));
                             }
-                        });
+                        }, false);
                     } else {
                         drawBitmapOnCanvasWithFrame(null, i, mEmptyThumbnailPaint);
                         if (mThumbnailsToFetch.decrementAndGet() == 0) {
@@ -201,13 +201,14 @@
     }
 
     @Override
-    public void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> finalCallback) {
+    public void getTabThumbnailWithCallback(
+            Tab tab, Callback<Bitmap> finalCallback, boolean forceUpdate) {
         if (mTabModelSelector.getTabModelFilterProvider()
                         .getCurrentTabModelFilter()
                         .getRelatedTabList(tab.getId())
                         .size()
                 == 1) {
-            mTabContentManager.getTabThumbnailWithCallback(tab, finalCallback);
+            mTabContentManager.getTabThumbnailWithCallback(tab, finalCallback, forceUpdate);
             return;
         }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
index 0d4499a..96050bc 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -83,6 +83,9 @@
                 }
             };
             fetcher.fetch(callback);
+        } else if (TabProperties.IPH_PROVIDER == propertyKey) {
+            TabListMediator.IphProvider provider = item.get(TabProperties.IPH_PROVIDER);
+            if (provider != null) provider.showIPH(holder.thumbnail);
         } else if (TabProperties.TAB_ID == propertyKey) {
             holder.setTabId(item.get(TabProperties.TAB_ID));
         } else if (TabProperties.CREATE_GROUP_LISTENER == propertyKey) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 3e4c594..0ef0858f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -7,9 +7,11 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.View;
 
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordHistogram;
@@ -25,6 +27,8 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
+import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -38,11 +42,13 @@
  * TODO(yusufo): Move some of the logic here to a parent component to make the above true.
  */
 class TabListMediator {
+    private boolean mShownIPH;
+
     /**
      * An interface to get the thumbnails to be shown inside the tab grid cards.
      */
     public interface ThumbnailProvider {
-        void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> callback);
+        void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> callback, boolean forceUpdate);
     }
 
     /**
@@ -57,18 +63,42 @@
     static class ThumbnailFetcher {
         private ThumbnailProvider mThumbnailProvider;
         private Tab mTab;
+        private boolean mForceUpdate;
 
-        ThumbnailFetcher(ThumbnailProvider provider, Tab tab) {
+        ThumbnailFetcher(ThumbnailProvider provider, Tab tab, boolean forceUpdate) {
             mThumbnailProvider = provider;
             mTab = tab;
+            mForceUpdate = forceUpdate;
         }
 
         void fetch(Callback<Bitmap> callback) {
-            mThumbnailProvider.getTabThumbnailWithCallback(mTab, callback);
+            mThumbnailProvider.getTabThumbnailWithCallback(mTab, callback, mForceUpdate);
         }
     }
 
     /**
+     * An interface to show IPH for a tab.
+     */
+    public interface IphProvider { void showIPH(View anchor); }
+
+    private final IphProvider mIphProvider = new IphProvider() {
+        private static final int IPH_DELAY_MS = 1000;
+
+        @Override
+        public void showIPH(View anchor) {
+            if (mShownIPH) return;
+            mShownIPH = true;
+
+            new Handler().postDelayed(
+                    ()
+                            -> TabGroupUtils.maybeShowIPH(
+                                    FeatureConstants.TAB_GROUPS_YOUR_TABS_ARE_TOGETHER_FEATURE,
+                                    anchor),
+                    IPH_DELAY_MS);
+        }
+    };
+
+    /**
      * An interface to get the onClickListener for "Create group" button.
      */
     public interface CreateGroupButtonProvider {
@@ -353,6 +383,10 @@
             createGroupButtonOnClickListener =
                     mCreateGroupButtonProvider.getCreateGroupButtonOnClickListener(tab);
         }
+        boolean showIPH = false;
+        if (mCloseAllRelatedTabs && !mShownIPH) {
+            showIPH = getRelatedTabsForId(tab.getId()).size() > 1;
+        }
 
         PropertyModel tabInfo =
                 new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
@@ -361,6 +395,7 @@
                         .with(TabProperties.FAVICON,
                                 mTabListFaviconProvider.getDefaultFaviconDrawable())
                         .with(TabProperties.IS_SELECTED, isSelected)
+                        .with(TabProperties.IPH_PROVIDER, showIPH ? mIphProvider : null)
                         .with(TabProperties.TAB_SELECTED_LISTENER, mTabSelectedListener)
                         .with(TabProperties.TAB_CLOSED_LISTENER, mTabClosedListener)
                         .with(TabProperties.CREATE_GROUP_LISTENER, createGroupButtonOnClickListener)
@@ -383,7 +418,7 @@
                 tab.getUrl(), tab.isIncognito(), faviconCallback);
 
         if (mThumbnailProvider != null) {
-            ThumbnailFetcher callback = new ThumbnailFetcher(mThumbnailProvider, tab);
+            ThumbnailFetcher callback = new ThumbnailFetcher(mThumbnailProvider, tab, isSelected);
             tabInfo.set(TabProperties.THUMBNAIL_FETCHER, callback);
         }
         tab.addObserver(mTabObserver);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
index 285f0f1..a6f68238 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
@@ -30,6 +30,9 @@
     public static final WritableObjectPropertyKey<TabListMediator.ThumbnailFetcher>
             THUMBNAIL_FETCHER = new WritableObjectPropertyKey<>();
 
+    public static final WritableObjectPropertyKey<TabListMediator.IphProvider> IPH_PROVIDER =
+            new WritableObjectPropertyKey<>();
+
     public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
 
     public static final WritableBooleanPropertyKey IS_SELECTED = new WritableBooleanPropertyKey();
@@ -40,9 +43,9 @@
     public static final PropertyModel.WritableFloatPropertyKey ALPHA =
             new PropertyModel.WritableFloatPropertyKey();
 
-    public static final PropertyKey[] ALL_KEYS_TAB_GRID =
-            new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON,
-                    THUMBNAIL_FETCHER, TITLE, IS_SELECTED, CREATE_GROUP_LISTENER, ALPHA};
+    public static final PropertyKey[] ALL_KEYS_TAB_GRID = new PropertyKey[] {TAB_ID,
+            TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, THUMBNAIL_FETCHER, IPH_PROVIDER,
+            TITLE, IS_SELECTED, CREATE_GROUP_LISTENER, ALPHA};
 
     public static final PropertyKey[] ALL_KEYS_TAB_STRIP = new PropertyKey[] {
             TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, IS_SELECTED, TITLE};
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
index 30e8700..ad0092fe 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
@@ -50,13 +50,14 @@
     private TabListMediator.ThumbnailFetcher mMockThumbnailProvider =
             new TabListMediator.ThumbnailFetcher(new TabListMediator.ThumbnailProvider() {
                 @Override
-                public void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> callback) {
+                public void getTabThumbnailWithCallback(
+                        Tab tab, Callback<Bitmap> callback, boolean forceUpdate) {
                     Bitmap bitmap = mShouldReturnBitmap
                             ? Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
                             : null;
                     callback.onResult(bitmap);
                 }
-            }, null);
+            }, null, false);
 
     private TabListMediator.TabActionListener mMockCloseListener =
             new TabListMediator.TabActionListener() {
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
index d24a540..248202a 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediatorUnitTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -124,7 +125,9 @@
         List<TabModel> tabModelList = new ArrayList<>();
         tabModelList.add(mTabModel);
 
-        doNothing().when(mTabContentManager).getTabThumbnailWithCallback(any(), any());
+        doNothing()
+                .when(mTabContentManager)
+                .getTabThumbnailWithCallback(any(), any(), anyBoolean());
         doReturn(mResources).when(mContext).getResources();
 
         doReturn(mTabModel).when(mTabModelSelector).getCurrentModel();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index 1cc94dc..b8e7fb1 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -108,7 +108,9 @@
         List<TabModel> tabModelList = new ArrayList<>();
         tabModelList.add(mTabModel);
 
-        doNothing().when(mTabContentManager).getTabThumbnailWithCallback(any(), any());
+        doNothing()
+                .when(mTabContentManager)
+                .getTabThumbnailWithCallback(any(), any(), anyBoolean());
         doReturn(mTabModel).when(mTabModelSelector).getCurrentModel();
         doReturn(tabModelList).when(mTabModelSelector).getModels();
         doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
diff --git a/chrome/android/java/res/drawable/logo_translate_round.xml b/chrome/android/java/res/drawable/logo_translate_round.xml
index 438a031..dd6bf7c 100644
--- a/chrome/android/java/res/drawable/logo_translate_round.xml
+++ b/chrome/android/java/res/drawable/logo_translate_round.xml
@@ -4,44 +4,28 @@
      found in the LICENSE file. -->
 
 <vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:height="24dp" android:viewportHeight="48"
-    android:viewportWidth="48" android:width="24dp"
-    tools:targetApi="21">
-
-    <path android:fillColor="#eee" android:pathData="M33.92,43.63C41.09,40.01 46,32.58 46,24c0,-12.15 -9.85,-22 -22,-22 -1.31,0 -2.59,0.12 -3.84,0.34l-0.9,1.6L32.27,43.1l1.65,0.53z"/>
-    <path android:fillColor="#607d8b" android:pathData="M24.71,19v-2h6.31v-2H33v2h6v2h-1.58c-0.57,2.33 -1.84,4.78 -3.46,6.64l4.89,4.85 -1.32,1.32 -4.85,-4.85 -4.85,4.85 -1.32,-1.32 4.89,-4.85c-1.16,-1.34 -2.17,-3.07 -2.85,-4.64h2.08c0.43,0.86 1.1,2.14 2.05,3.25 2.2,-2.57 2.81,-5.25 2.81,-5.25H24.71z"/>
-    <path android:fillAlpha=".2" android:fillColor="#fff" android:pathData="M24,2.23c12.11,0 21.94,9.79 22,21.89v-0.11c0,-12.15 -9.85,-22 -22,-22C11.85,2 2,11.85 2,24v0.11c0.06,-12.09 9.89,-21.88 22,-21.88z"/>
-    <path android:fillAlpha=".15" android:fillColor="#263238" android:pathData="M24,45.77c12.11,0 21.94,-9.79 22,-21.89v0.11c0,12.15 -9.85,22 -22,22C11.85,46 2,36.15 2,24v-0.11c0.06,12.09 9.89,21.88 22,21.88z"/>
-    <path android:fillAlpha=".1" android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0">
-        <aapt:attr name="android:fillColor">
-            <gradient android:centerX="8.649595"
-                android:centerY="8.650356"
-                android:gradientRadius="43.711006" android:type="radial">
-                <item android:color="#FFFFFFFF" android:offset="0"/>
-                <item android:color="#00FFFFFF" android:offset="1"/>
-            </gradient>
-        </aapt:attr>
-    </path>
-    <group>
-        <clip-path android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0 M 0,0"/>
-        <path android:pathData="M20.03 2.25l32.38 32.38V56.9H22.05">
-            <aapt:attr name="android:fillColor">
-                <gradient android:endX="52.411" android:endY="29.579"
-                    android:startX="20.03" android:startY="29.579" android:type="linear">
-                    <item android:color="#19212121" android:offset="0"/>
-                    <item android:color="#05212121" android:offset="1"/>
-                </gradient>
-            </aapt:attr>
-        </path>
-    </group>
-    <group>
-        <clip-path android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0 M 0,0"/>
-        <path android:fillColor="#4285f4" android:pathData="M2 24c0 12.15 9.85 22 22 22 3.57 0 6.94,-.85 9.93,-2.36L20.16 2.33C9.84 4.15 2 13.16 2 24z"/>
-        <path android:fillAlpha="0.1" android:fillColor="#fff"
-            android:pathData="M18.22,-2.68L35.2 48.24l.31.13L18.39,-2.96" android:strokeAlpha="0.1"/>
-        <path android:fillColor="#eee" android:pathData="M16.02 23v2h3.97c-.16 1.43,-1.2 3.42,-3.97 3.42,-2.39 0,-4.34,-1.98,-4.34,-4.42s1.95,-4.42 4.34,-4.42c1.36 0 2.27.58 2.79 1.08l1.9,-1.83c-1.22,-1.14,-2.8,-1.83,-4.69,-1.83,-3.87 0,-7 3.13,-7 7s3.13 7 7 7c4.04 0 6.72,-2.84 6.72,-6.84 0,-.46,-.05,-.81,-.11,-1.16h-6.61z"/>
-    </group>
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:aapt="http://schemas.android.com/aapt"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:width="36dp"
+        android:height="36dp"
+        android:viewportWidth="36.0"
+        android:viewportHeight="36.0"
+        tools:targetApi="21">
+    <path
+        android:pathData="M18,0L18,0C27.94,-0 36,8.06 36,18L36,18C36,27.94 27.94,36 18,36L18,36C8.06,36 0,27.94 0,18L0,18C-0,8.06 8.06,0 18,0Z"
+      android:fillColor="@color/light_active_color"
+        />
+    <path
+        android:pathData="M18.87,21.07L16.33,18.56L16.36,18.53C18.1,16.59 19.34,14.36 20.07,12L23,12L23,10L16,10L16,8L14,8L14,10L7,10L7,11.99L18.17,11.99C17.5,13.92 16.44,15.75 15,17.35C14.07,16.32 13.3,15.19 12.69,14L10.69,14C11.42,15.63 12.42,17.17 13.67,18.56L8.58,23.58L10,25L15,20L18.11,23.11L18.87,21.07Z"
+        android:fillColor="@color/default_icon_color_inverse"
+        />
+    <path
+        android:pathData="M24.5,16l-2,0l-4.5,12l2,0l1.12,-3l4.75,0l1.13,3l2,0z"
+        android:fillColor="@color/default_icon_color_inverse"
+        />
+    <path
+        android:pathData="M21.88,23l1.62,-4.33l1.62,4.33z"
+        android:fillColor="@color/light_active_color"
+        />
 </vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
index f555c64..e35c5266 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
@@ -8,6 +8,7 @@
 
 import org.chromium.base.ObserverList;
 import org.chromium.base.ObserverList.RewindableIterator;
+import org.chromium.base.Supplier;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver;
@@ -23,7 +24,7 @@
 /**
  * A class that provides the current {@link Tab} for various states of the browser's activity.
  */
-public class ActivityTabProvider {
+public class ActivityTabProvider implements Supplier<Tab> {
     /** An interface to track the visible tab for the activity. */
     public interface ActivityTabObserver {
         /**
@@ -79,7 +80,7 @@
                 onObservingDifferentTab(tab);
             };
             mTabProvider.addObserver(mActivityTabObserver);
-            updateObservedTab(mTabProvider.getActivityTab());
+            updateObservedTab(mTabProvider.get());
         }
 
         /**
@@ -174,7 +175,8 @@
     /**
      * @return The activity's current tab.
      */
-    public Tab getActivityTab() {
+    @Override
+    public Tab get() {
         return mActivityTab;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
index b2baace..7e03f03 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java
@@ -430,7 +430,7 @@
      * @return The currently visible {@link Tab}, if any.
      */
     private @Nullable Tab getActiveBrowserTab() {
-        return mActivity.getActivityTabProvider().getActivityTab();
+        return mActivity.getActivityTabProvider().get();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java
index f582e66..6f1962d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityOpenTimeRecorder.java
@@ -71,7 +71,7 @@
         mLastStateChangeTimestampMs = SystemClock.elapsedRealtime();
 
         if (mInVerifiedOrigin && !mTwaOpenedRecorded) {
-            mRecorder.recordTwaOpened(mTabProvider.getActivityTab());
+            mRecorder.recordTwaOpened(mTabProvider.get());
             mTwaOpenedRecorded = true;
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
index 14dab02..58f83d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
@@ -213,7 +213,7 @@
                 closePanel(StateChangeReason.TAB_PROMOTION, false);
                 mActivity.getCurrentTabCreator().createNewTab(
                         new LoadUrlParams(mUrl, PageTransition.LINK), TabLaunchType.FROM_LINK,
-                        mActivity.getActivityTabProvider().getActivityTab());
+                        mActivity.getActivityTabProvider().get());
             }
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
index 3d6b1c7..4ad1ccd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
@@ -4,9 +4,13 @@
 
 package org.chromium.chrome.browser.compositor.layouts.content;
 
+import static java.lang.Math.min;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.support.annotation.Nullable;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 
@@ -242,10 +246,23 @@
      * @param tab The tab to get the thumbnail for.
      * @param callback The callback to send the {@link Bitmap} with.
      */
-    public void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> callback) {
+    public void getTabThumbnailWithCallback(
+            Tab tab, Callback<Bitmap> callback, boolean forceUpdate) {
         if (mNativeTabContentManager == 0 || !mSnapshotsEnabled) return;
 
-        nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), callback);
+        if (forceUpdate) {
+            cacheTabThumbnail(tab, (bitmap) -> {
+                if (bitmap != null) {
+                    callback.onResult(bitmap);
+                    return;
+                }
+                // If invalidation is not needed, cacheTabThumbnail() might not do anything, so we
+                // need to fall back to reading from disk.
+                nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), callback);
+            });
+        } else {
+            nativeGetTabThumbnailWithCallback(mNativeTabContentManager, tab.getId(), callback);
+        }
     }
 
     /**
@@ -253,16 +270,40 @@
      * @param tab The tab whose content we will cache.
      */
     public void cacheTabThumbnail(final Tab tab) {
+        cacheTabThumbnail(tab, null);
+    }
+
+    /**
+     * Cache the content of a tab as a thumbnail.
+     * @param tab The tab whose content we will cache.
+     * @param callback The callback to send the {@link Bitmap} with.
+     */
+    public void cacheTabThumbnail(final Tab tab, @Nullable Callback<Bitmap> callback) {
         if (mNativeTabContentManager != 0 && mSnapshotsEnabled) {
             if (tab.getNativePage() != null) {
                 Bitmap nativePageBitmap = readbackNativePage(tab, mThumbnailScale);
-                if (nativePageBitmap == null) return;
+                if (nativePageBitmap == null) {
+                    if (callback != null) callback.onResult(null);
+                    return;
+                }
                 nativeCacheTabWithBitmap(mNativeTabContentManager, tab, nativePageBitmap,
                         mThumbnailScale);
+                if (callback != null) {
+                    // In portrait mode, we want to show thumbnails in squares.
+                    // Therefore, the thumbnail saved in portrait mode needs to be cropped to
+                    // a square, or it would become too tall and break the layout.
+                    Matrix matrix = new Matrix();
+                    matrix.setScale(0.5f, 0.5f);
+                    Bitmap resized =
+                            Bitmap.createBitmap(nativePageBitmap, 0, 0, nativePageBitmap.getWidth(),
+                                    min(nativePageBitmap.getWidth(), nativePageBitmap.getHeight()),
+                                    matrix, true);
+                    callback.onResult(resized);
+                }
                 nativePageBitmap.recycle();
             } else {
                 if (tab.getWebContents() == null) return;
-                nativeCacheTab(mNativeTabContentManager, tab, mThumbnailScale);
+                nativeCacheTab(mNativeTabContentManager, tab, mThumbnailScale, callback);
             }
         }
     }
@@ -294,7 +335,7 @@
      */
     public void updateVisibleIds(List<Integer> priority, int primaryTabId) {
         if (mNativeTabContentManager != 0) {
-            int idsSize = Math.min(mFullResThumbnailsMaxSize, priority.size());
+            int idsSize = min(mFullResThumbnailsMaxSize, priority.size());
 
             if (idsSize != mPriorityTabIds.length) {
                 mPriorityTabIds = new int[idsSize];
@@ -331,8 +372,8 @@
     private native void nativeAttachTab(long nativeTabContentManager, Tab tab, int tabId);
     private native void nativeDetachTab(long nativeTabContentManager, Tab tab, int tabId);
     private native boolean nativeHasFullCachedThumbnail(long nativeTabContentManager, int tabId);
-    private native void nativeCacheTab(
-            long nativeTabContentManager, Object tab, float thumbnailScale);
+    private native void nativeCacheTab(long nativeTabContentManager, Object tab,
+            float thumbnailScale, Callback<Bitmap> callback);
     private native void nativeCacheTabWithBitmap(long nativeTabContentManager, Object tab,
             Object bitmap, float thumbnailScale);
     private native void nativeInvalidateIfChanged(long nativeTabContentManager, int tabId,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
index 18af8fb9..c34b3cb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
@@ -108,7 +108,7 @@
             // This leaves the handling of the hooks to the responsibility of the activity tab.
             // Restoring them will be then done by the tab that was the activity tab when
             // the panel was shown.
-            Tab activityTab = mTab.getActivity().getActivityTabProvider().getActivityTab();
+            Tab activityTab = mTab.getActivity().getActivityTabProvider().get();
             if (activityTab != mTab) return;
 
             // Removes the hooks if the panel other than contextual search panel just got shown.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBrowserControlsVisibilityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBrowserControlsVisibilityDelegate.java
index 6ba6319d..9514770 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBrowserControlsVisibilityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBrowserControlsVisibilityDelegate.java
@@ -72,7 +72,7 @@
     }
 
     private void updateActiveTabFullscreenEnabledState() {
-        Tab activeTab = mTabProvider.getActivityTab();
+        Tab activeTab = mTabProvider.get();
         if (activeTab != null) {
             activeTab.updateFullscreenEnabledState();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 0c22e7b..79ffd006 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -121,7 +121,7 @@
     /**
      * Indicates the source where the Custom Tab is launched. This is only used for
      * WebApp/WebAPK/TrustedWebActivity. The value is defined as
-     * {@link WebappActivity.ActivityType#WebappActivity}.
+     * {@link LaunchSourceType}.
      */
     public static final String EXTRA_BROWSER_LAUNCH_SOURCE =
             "org.chromium.chrome.browser.customtabs.EXTRA_BROWSER_LAUNCH_SOURCE";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabProvider.java
index fb5c627..76049ed8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabProvider.java
@@ -50,7 +50,7 @@
     /**
      * Returns tab currently managed by the Custom Tab activity.
      *
-     * The difference from {@link ActivityTabProvider#getActivityTab()} is that we may have acquired
+     * The difference from {@link ActivityTabProvider#get()} is that we may have acquired
      * a hidden tab (see {@link CustomTabsConnection#takeHiddenTab}), which is not yet added to a
      * {@link TabModel}. In that case this method returns the hidden tab, and ActivityTabProvider
      * returns null.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModulePageLoadObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModulePageLoadObserver.java
index b9b7abbc..b0b9cb73 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModulePageLoadObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModulePageLoadObserver.java
@@ -87,7 +87,7 @@
 
     private void notifyOnPageMetricEvent(WebContents webContents,
             String metricName, long navigationStartTick, long offset, long navigationId) {
-        if (webContents != mActivityTabProvider.getActivityTab().getWebContents()) return;
+        if (webContents != mActivityTabProvider.get().getWebContents()) return;
         long navigationStartMs = (navigationStartTick - mNativeTickOffsetUs) / 1000;
 
         if (mActivityDelegate == null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
index f248d65c..3413832 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
@@ -69,7 +69,7 @@
         mSideSlideLayout.setEnabled(false);
         mSideSlideLayout.setOnNavigationListener((isForward) -> {
             if (mTabProvider == null) return;
-            Tab tab = mTabProvider.getActivityTab();
+            Tab tab = mTabProvider.get();
             if (isForward) {
                 tab.goForward();
             } else {
@@ -141,7 +141,7 @@
 
     private boolean canNavigate(boolean forward) {
         if (mTabProvider == null) return false;
-        Tab tab = mTabProvider.getActivityTab();
+        Tab tab = mTabProvider.get();
         return forward ? tab.canGoForward() : tab.canGoBack();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcher.java
index 5ca8f18..4a14384 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcher.java
@@ -10,7 +10,9 @@
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -32,7 +34,7 @@
      *
      * @param bridge Bridge used to interact with native.
      */
-    protected CachedImageFetcher(ImageFetcherBridge bridge) {
+    CachedImageFetcher(ImageFetcherBridge bridge) {
         mImageFetcherBridge = bridge;
     }
 
@@ -46,38 +48,70 @@
         // Do nothing, this lives for the lifetime of the application.
     }
 
+    /**
+     * Tries to load the gif from disk, if not it falls back to the bridge.
+     */
     @Override
     public void fetchGif(String url, String clientName, Callback<BaseGifImage> callback) {
         long startTimeMillis = System.currentTimeMillis();
-        String filePath = mImageFetcherBridge.getFilePath(url);
-        // TODO(crbug.com/947176): Use the new PostTask api for deferred tasks.
-        new AsyncTask<BaseGifImage>() {
-            @Override
-            protected BaseGifImage doInBackground() {
-                return tryToLoadGifFromDisk(filePath);
-            }
-
-            @Override
-            protected void onPostExecute(BaseGifImage gif) {
-                if (gif != null) {
-                    callback.onResult(gif);
-                    reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
-                    mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
-                } else {
-                    mImageFetcherBridge.fetchGif(url, clientName, (BaseGifImage gifFromNative) -> {
-                        callback.onResult(gifFromNative);
-                        mImageFetcherBridge.reportTotalFetchTimeFromNative(
-                                clientName, startTimeMillis);
-                    });
-                }
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        PostTask.postTask(TaskTraits.USER_VISIBLE, () -> {
+            // Try to read the gif from disk, then post back to the ui thread.
+            String filePath = mImageFetcherBridge.getFilePath(url);
+            BaseGifImage cachedGif = tryToLoadGifFromDisk(filePath);
+            PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE, () -> {
+                continueFetchGifAfterDisk(url, clientName, callback, cachedGif, startTimeMillis);
+            });
+        });
     }
 
+    @VisibleForTesting
+    void continueFetchGifAfterDisk(String url, String clientName, Callback<BaseGifImage> callback,
+            BaseGifImage cachedGif, long startTimeMillis) {
+        if (cachedGif != null) {
+            callback.onResult(cachedGif);
+            reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
+            mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
+        } else {
+            mImageFetcherBridge.fetchGif(url, clientName, (BaseGifImage gifFromNative) -> {
+                callback.onResult(gifFromNative);
+                mImageFetcherBridge.reportTotalFetchTimeFromNative(clientName, startTimeMillis);
+            });
+        }
+    }
+
+    /**
+     * Tries to load the gif from disk, if not it falls back to the bridge.
+     */
     @Override
     public void fetchImage(
             String url, String clientName, int width, int height, Callback<Bitmap> callback) {
-        fetchImageImpl(url, clientName, width, height, callback);
+        long startTimeMillis = System.currentTimeMillis();
+        PostTask.postTask(TaskTraits.USER_VISIBLE, () -> {
+            // Try to read the bitmap from disk, then post back to the ui thread.
+            String filePath = mImageFetcherBridge.getFilePath(url);
+            Bitmap bitmap = tryToLoadImageFromDisk(filePath);
+            PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE, () -> {
+                continueFetchImageAfterDisk(
+                        url, clientName, width, height, callback, bitmap, startTimeMillis);
+            });
+        });
+    }
+
+    @VisibleForTesting
+    void continueFetchImageAfterDisk(String url, String clientName, int width, int height,
+            Callback<Bitmap> callback, Bitmap cachedBitmap, long startTimeMillis) {
+        if (cachedBitmap != null) {
+            callback.onResult(cachedBitmap);
+            reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
+            mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
+        } else {
+            mImageFetcherBridge.fetchImage(
+                    getConfig(), url, clientName, width, height, (Bitmap bitmapFromNative) -> {
+                        callback.onResult(bitmapFromNative);
+                        mImageFetcherBridge.reportTotalFetchTimeFromNative(
+                                clientName, startTimeMillis);
+                    });
+        }
     }
 
     @Override
@@ -85,45 +119,6 @@
         return ImageFetcherConfig.DISK_CACHE_ONLY;
     }
 
-    /**
-     * Starts an AsyncTask to first check the disk for the desired image, then fetches from the
-     * network if it isn't found.
-     *
-     * @param url The url to fetch the image from.
-     * @param width The new bitmap's desired width (in pixels).
-     * @param height The new bitmap's desired height (in pixels).
-     * @param callback The function which will be called when the image is ready.
-     */
-    @VisibleForTesting
-    void fetchImageImpl(
-            String url, String clientName, int width, int height, Callback<Bitmap> callback) {
-        long startTimeMillis = System.currentTimeMillis();
-        String filePath = mImageFetcherBridge.getFilePath(url);
-        // TODO(crbug.com/947176): Use the new PostTask api for deferred tasks.
-        new AsyncTask<Bitmap>() {
-            @Override
-            protected Bitmap doInBackground() {
-                return tryToLoadImageFromDisk(filePath);
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                if (bitmap != null) {
-                    callback.onResult(bitmap);
-                    reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
-                    mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
-                } else {
-                    mImageFetcherBridge.fetchImage(getConfig(), url, clientName, width, height,
-                            (Bitmap bitmapFromNative) -> {
-                                callback.onResult(bitmapFromNative);
-                                mImageFetcherBridge.reportTotalFetchTimeFromNative(
-                                        clientName, startTimeMillis);
-                            });
-                }
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-    }
-
     /** Wrapper function to decode a file for disk, useful for testing. */
     @VisibleForTesting
     Bitmap tryToLoadImageFromDisk(String filePath) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
index 377cbdc..33a586ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateInfoBarController.java
@@ -73,7 +73,7 @@
     private void showRestartInfobar() {
         if (mActivity == null) return;
 
-        Tab tab = mActivity.getActivityTabProvider().getActivityTab();
+        Tab tab = mActivity.getActivityTabProvider().get();
         if (tab == null) return;
 
         SimpleConfirmInfoBarBuilder.create(tab,
@@ -103,7 +103,7 @@
     private void showFailedInfobar() {
         if (mActivity == null) return;
 
-        Tab tab = mActivity.getActivityTabProvider().getActivityTab();
+        Tab tab = mActivity.getActivityTabProvider().get();
         if (tab == null) return;
 
         SimpleConfirmInfoBarBuilder.create(tab,
@@ -138,4 +138,4 @@
                      Toast.LENGTH_LONG)
                 .show();
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
index 3f25431..e4c7571 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
@@ -148,7 +148,7 @@
 
     @Override
     public boolean doesProcessSuggestion(OmniboxSuggestion suggestion) {
-        Tab activeTab = mTabProvider != null ? mTabProvider.getActivityTab() : null;
+        Tab activeTab = mTabProvider != null ? mTabProvider.get() : null;
 
         // The what-you-typed suggestion can potentially appear as the second suggestion in some
         // cases. If the first suggestion isn't the one we want, ignore all subsequent suggestions.
@@ -208,7 +208,7 @@
         }
         model.set(EditUrlSuggestionProperties.BUTTON_CLICK_LISTENER, this);
 
-        if (mOriginalTitle == null) mOriginalTitle = mTabProvider.getActivityTab().getTitle();
+        if (mOriginalTitle == null) mOriginalTitle = mTabProvider.get().getTitle();
         model.set(EditUrlSuggestionProperties.TITLE_TEXT, mOriginalTitle);
         model.set(EditUrlSuggestionProperties.URL_TEXT, mLastProcessedSuggestion.getUrl());
     }
@@ -256,7 +256,7 @@
 
     @Override
     public void onClick(View view) {
-        Tab activityTab = mTabProvider.getActivityTab();
+        Tab activityTab = mTabProvider.get();
         assert activityTab != null : "A tab is required to make changes to the location bar.";
 
         if (R.id.url_copy_icon == view.getId()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
index 43879fa2..fcbd8d6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivity.java
@@ -18,7 +18,7 @@
 public class SendTabToSelfShareActivity extends ShareActivity {
     @Override
     protected void handleShareAction(ChromeActivity triggeringActivity) {
-        Tab tab = triggeringActivity.getActivityTabProvider().getActivityTab();
+        Tab tab = triggeringActivity.getActivityTabProvider().get();
 
         NavigationHistory history =
                 tab.getWebContents().getNavigationController().getNavigationHistory();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
index 234eb4f..c3764512 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
@@ -130,7 +130,7 @@
     private boolean isActiveTabNTP() {
         if (mActivityTabProvider == null) return false;
 
-        final Tab tab = mActivityTabProvider.getActivityTab();
+        final Tab tab = mActivityTabProvider.get();
         if (tab == null) return false;
 
         return NewTabPage.isNTPUrl(tab.getUrl());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
index a85e8929..3310a28 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
@@ -69,7 +69,7 @@
 
             @Override
             public void onPageLoadStarted(Tab tab, String url) {
-                if (tab != activity.getActivityTabProvider().getActivityTab()) return;
+                if (tab != activity.getActivityTabProvider().get()) return;
                 mDataSavedOnStartPageLoad = DataReductionProxySettings.getInstance()
                                                     .getContentLengthSavedInHistorySummary();
                 mPageLoadTab = tab;
@@ -84,8 +84,7 @@
                 Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
                 if (dataSaved > 0L) tracker.notifyEvent(EventConstants.DATA_SAVED_ON_PAGE_LOAD);
                 if (tab.isPreview()) tracker.notifyEvent(EventConstants.PREVIEWS_PAGE_LOADED);
-                if (tab == activity.getActivityTabProvider().getActivityTab()
-                        && tab.isUserInteractable()) {
+                if (tab == activity.getActivityTabProvider().get() && tab.isUserInteractable()) {
                     maybeShowDataSaverDetail(activity);
                     maybeShowDataSaverMilestonePromo(activity);
                     maybeShowPreviewVerboseStatus(activity);
@@ -137,7 +136,7 @@
 
     // Attempts to show an IPH text bubble for page in preview mode.
     private static void maybeShowPreviewVerboseStatus(ChromeActivity activity) {
-        if (!activity.getActivityTabProvider().getActivityTab().isPreview()) return;
+        if (!activity.getActivityTabProvider().get().isPreview()) return;
 
         final View anchorView = activity.getToolbarManager().getSecurityIconView();
         if (anchorView == null) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 647bdfc1..c6a7902 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -15,7 +15,6 @@
 import android.os.Bundle;
 import android.os.StrictMode;
 import android.os.SystemClock;
-import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.view.View;
@@ -64,8 +63,6 @@
 import org.chromium.ui.base.PageTransition;
 
 import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -76,15 +73,6 @@
 public class WebappActivity extends SingleTabActivity {
     public static final String WEBAPP_SCHEME = "webapp";
 
-    // The activity type of WebappActivity.
-    @IntDef({ActivityType.WEBAPP, ActivityType.WEBAPK})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ActivityType {
-        int OTHER = -1;
-        int WEBAPP = 0;
-        int WEBAPK = 1;
-    }
-
     private static final String TAG = "WebappActivity";
     private static final String HISTOGRAM_NAVIGATION_STATUS = "Webapp.NavigationStatus";
     private static final long MS_BEFORE_NAVIGATING_BACK_FROM_INTERSTITIAL = 1000;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
index da49bfb..b150b27 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
@@ -242,7 +242,7 @@
      * the browser (i.e. the tab switcher may be showing).
      */
     private void unsuppressSheet() {
-        if (!mIsSuppressed || mTabProvider.getActivityTab() == null || !mWasShownForCurrentTab
+        if (!mIsSuppressed || mTabProvider.get() == null || !mWasShownForCurrentTab
                 || isOtherUIObscuring() || VrModuleProvider.getDelegate().isInVr()) {
             return;
         }
@@ -296,7 +296,7 @@
      */
     private boolean loadInternal(BottomSheetContent content) {
         if (content == mBottomSheet.getCurrentSheetContent()) return true;
-        if (mTabProvider.getActivityTab() == null) return false;
+        if (mTabProvider.get() == null) return false;
 
         BottomSheetContent shownContent = mBottomSheet.getCurrentSheetContent();
         boolean shouldSuppressExistingContent = shownContent != null
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
index 6c17812..e01938a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
@@ -51,7 +51,7 @@
         public TestActivityTabTabObserver(ActivityTabProvider provider) {
             super(provider);
             mObserverMoveHelper = new CallbackHelper();
-            mObservedTab = provider.getActivityTab();
+            mObservedTab = provider.get();
         }
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
index 532bda6..f6e69080 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/OSKOverscrollTest.java
@@ -151,7 +151,7 @@
                 int viewportHeightAfterCss = getViewportHeight(webContentsRef.get());
                 int keyboardHeight = mActivityTestRule.getActivity()
                                              .getActivityTabProvider()
-                                             .getActivityTab()
+                                             .get()
                                              .getWebContents()
                                              .getViewAndroidDelegate()
                                              .getSystemWindowInsetBottom();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
index 4c481e93..4ca609e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SubresourceFilterTest.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.infobar.AdsBlockedInfoBar;
 import org.chromium.chrome.browser.infobar.InfoBar;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
@@ -90,6 +91,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/899903")
     public void resourceNotFiltered() throws Exception {
         String url = mTestServer.getURL(PAGE_WITH_JPG);
         mActivityTestRule.loadUrl(url);
@@ -104,6 +106,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/899903")
     public void resourceFilteredClose() throws Exception {
         String url = mTestServer.getURL(PAGE_WITH_JPG);
         MockSafeBrowsingApiHandler.addMockResponse(url, METADATA_FOR_ENFORCEMENT);
@@ -131,6 +134,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/899903")
     public void resourceFilteredClickLearnMore() throws Exception {
         String url = mTestServer.getURL(PAGE_WITH_JPG);
         MockSafeBrowsingApiHandler.addMockResponse(url, METADATA_FOR_ENFORCEMENT);
@@ -172,6 +176,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/899903")
     public void resourceFilteredReload() throws Exception {
         String url = mTestServer.getURL(PAGE_WITH_JPG);
         MockSafeBrowsingApiHandler.addMockResponse(url, METADATA_FOR_ENFORCEMENT);
@@ -203,6 +208,7 @@
     }
 
     @Test
+    @DisabledTest(message = "crbug.com/899903")
     @MediumTest
     public void resourceNotFilteredWithWarning() throws Exception {
         String url = mTestServer.getURL(PAGE_WITH_JPG);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTest.java
deleted file mode 100644
index aad59d6..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTest.java
+++ /dev/null
@@ -1,28 +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.
-
-package org.chromium.chrome.browser.webapps;
-
-import android.support.test.filters.SmallTest;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.LaunchSourceType;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-
-/**
- * Tests for WebappActivity class.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-public class WebappActivityTest {
-    @Test
-    @SmallTest
-    public void testActivityTypeMatchesLaunchSourceType() throws Exception {
-        Assert.assertEquals(LaunchSourceType.OTHER, WebappActivity.ActivityType.OTHER);
-        Assert.assertEquals(LaunchSourceType.WEBAPP, WebappActivity.ActivityType.WEBAPP);
-        Assert.assertEquals(LaunchSourceType.WEBAPK, WebappActivity.ActivityType.WEBAPK);
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
index 943d9985..cf5c11c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
@@ -725,7 +725,7 @@
         when(tab.getWebContents()).thenReturn(mLastMockWebContents);
         mCache.getStateFor(tab).getWebContentsObserverForTesting().wasShown();
         when(tab.getContentView()).thenReturn(mMockContentView);
-        when(mMockActivity.getActivityTabProvider().getActivityTab()).thenReturn(tab);
+        when(mMockActivity.getActivityTabProvider().get()).thenReturn(tab);
         when(mMockTabModelSelector.getCurrentTab()).thenReturn(tab);
         mediator.getTabModelObserverForTesting().didAddTab(tab, FROM_BROWSER_ACTIONS);
         mediator.getTabObserverForTesting().onShown(tab, FROM_NEW);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
index 823e5225..5c13b0a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
@@ -162,7 +162,7 @@
     }
 
     public void changeTab(Tab newTab) {
-        when(activityTabProvider.getActivityTab()).thenReturn(newTab);
+        when(activityTabProvider.get()).thenReturn(newTab);
         for (ActivityTabObserver observer : activityTabObserverCaptor.getAllValues()) {
             observer.onActivityTabChanged(newTab, false);
         }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherTest.java
index 35c3a3b..95a3b32 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherTest.java
@@ -15,7 +15,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Bitmap;
-import android.support.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -27,12 +26,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLooper;
 
 import org.chromium.base.Callback;
-import org.chromium.base.task.test.BackgroundShadowAsyncTask;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulatorTest.ShadowUrlUtilities;
 
 import jp.tomorrowkey.android.gifplayer.BaseGifImage;
 
@@ -40,13 +36,13 @@
  * Unit tests for CachedImageFetcher.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE,
-        shadows = {ShadowUrlUtilities.class, BackgroundShadowAsyncTask.class})
+@Config(manifest = Config.NONE)
 public class CachedImageFetcherTest {
     private static final String UMA_CLIENT_NAME = "TestUmaClient";
     private static final String URL = "http://foo.bar";
     private static final int WIDTH_PX = 100;
     private static final int HEIGHT_PX = 200;
+    private static final long START_TIME = 274127;
 
     CachedImageFetcher mCachedImageFetcher;
 
@@ -75,85 +71,36 @@
     }
 
     @Test
-    @SmallTest
     public void testFetchImageWithDimensionsFoundOnDisk() throws Exception {
-        Mockito.doReturn(mBitmap).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
-                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
+        mCachedImageFetcher.continueFetchImageAfterDisk(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
+                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); }, mBitmap, START_TIME);
 
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
         verify(mImageFetcherBridge, never()) // Should never make it to native.
                 .fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
 
         // Verify metrics have been reported.
         verify(mImageFetcherBridge)
                 .reportEvent(eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT));
-        verify(mImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
+        verify(mImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), eq(START_TIME));
     }
 
     @Test
-    @SmallTest
     public void testFetchImageWithDimensionsCallToNative() throws Exception {
-        Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
-                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
+        mCachedImageFetcher.continueFetchImageAfterDisk(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
+                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); }, null, START_TIME);
 
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
         verify(mImageFetcherBridge)
                 .fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
     }
 
     @Test
-    @SmallTest
-    public void testFetchImageWithNoDimensionsFoundOnDisk() throws Exception {
-        Mockito.doReturn(mBitmap).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(
-                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
-
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
-        verify(mImageFetcherBridge, never())
-                .fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    @SmallTest
-    public void testFetchImageWithNoDimensionsCallToNative() throws Exception {
-        Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(
-                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
-
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
-        verify(mImageFetcherBridge)
-                .fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
-    }
-
-    @Test
-    @SmallTest
     public void testFetchTwoClients() throws Exception {
         Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(
-                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        mCachedImageFetcher.fetchImage(
-                URL, UMA_CLIENT_NAME + "2", (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
+        mCachedImageFetcher.continueFetchImageAfterDisk(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
+                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); }, null, START_TIME);
+        mCachedImageFetcher.continueFetchImageAfterDisk(URL, UMA_CLIENT_NAME + "2", WIDTH_PX,
+                HEIGHT_PX, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); }, null, START_TIME);
 
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
-        verify(mCachedImageFetcher)
-                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME + "2"), eq(0), eq(0), any());
         verify(mImageFetcherBridge)
                 .fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
         verify(mImageFetcherBridge)
@@ -162,13 +109,9 @@
     }
 
     @Test
-    @SmallTest
     public void testFetchGifFoundOnDisk() throws Exception {
-        Mockito.doReturn(mGif).when(mCachedImageFetcher).tryToLoadGifFromDisk(anyObject());
-        mCachedImageFetcher.fetchGif(
-                URL, UMA_CLIENT_NAME, (BaseGifImage gif) -> { assertEquals(gif, mGif); });
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
+        mCachedImageFetcher.continueFetchGifAfterDisk(URL, UMA_CLIENT_NAME,
+                (BaseGifImage gif) -> { assertEquals(gif, mGif); }, mGif, START_TIME);
 
         verify(mImageFetcherBridge, never()) // Should never make it to native.
                 .fetchGif(eq(URL), eq(UMA_CLIENT_NAME), any());
@@ -180,12 +123,9 @@
     }
 
     @Test
-    @SmallTest
     public void testFetchGifCallToNative() throws Exception {
-        Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadGifFromDisk(anyObject());
-        mCachedImageFetcher.fetchGif(URL, UMA_CLIENT_NAME, (BaseGifImage gif) -> {});
-        BackgroundShadowAsyncTask.runBackgroundTasks();
-        ShadowLooper.runUiThreadTasks();
+        mCachedImageFetcher.continueFetchGifAfterDisk(URL, UMA_CLIENT_NAME,
+                (BaseGifImage gif) -> { assertEquals(gif, mGif); }, null, START_TIME);
 
         verify(mImageFetcherBridge).fetchGif(eq(URL), eq(UMA_CLIENT_NAME), any());
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
index b6d7611..3a0472a3 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java
@@ -89,7 +89,7 @@
     public void testHandleShareAction() {
         // Setup the mocked object chain to get to the profile.
         when(mChromeActivity.getActivityTabProvider()).thenReturn(mActivityTabProvider);
-        when(mActivityTabProvider.getActivityTab()).thenReturn(mTab);
+        when(mActivityTabProvider.get()).thenReturn(mTab);
         when(mTab.getProfile()).thenReturn(mProfile);
 
         // Setup the mocked object chain to get to the url, title and timestamp.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index cd69176..c1c24986 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4972,6 +4972,9 @@
       <message name="IDS_NTP_CUSTOM_BG_BACK_LABEL" desc="Accessibility label for the back arrow on the background picker dialog. (On the New Tab Page)">
         Back
       </message>
+      <message name="IDS_NTP_CUSTOM_BG_IMAGE_SELECTED" desc="Label to indicate that an image has been selected in the background picker dialog. (On the New Tab Page)">
+       selected
+      </message>
       <message name="IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL" desc="Accessibility label for the gear icon. (On the New Tab Page)">
         Customize this page
       </message>
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOM_BG_IMAGE_SELECTED.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOM_BG_IMAGE_SELECTED.png.sha1
new file mode 100644
index 0000000..615793f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOM_BG_IMAGE_SELECTED.png.sha1
@@ -0,0 +1 @@
+96471032cdaa48350474e0e527b5aa23e191d898
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index ff8d90b..9719d50 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2559,11 +2559,11 @@
   <message name="IDS_SETTINGS_SAFEBROWSING_ENABLE_REPORTING_DESC" desc="Description for extended safe browsing">
     Sends some system information and page content to Google
   </message>
-  <message name="IDS_SETTINGS_SPELLING_PREF" desc="The documentation string of the 'Use Spelling' preference">
-    Use a web service to help resolve spelling errors
+  <message name="IDS_SETTINGS_SPELLING_PREF" desc="Label for a setting that enhances spell check by sending the text that users type to Google for spelling suggestions.">
+    Use enhanced spell check
   </message>
   <message name="IDS_SETTINGS_SPELLING_DESCRIPTION" desc="Description of using a web serviced to help resolve spelling errors. It is important to convey that what the user types will be sent to Google.">
-    Smarter spell-checking by sending what you type in the browser to Google
+    Chrome sends the text you type in the browser to Google
   </message>
   <message name="IDS_SETTINGS_SPELLING_PREF_UNIFIED_CONSENT" desc="The documentation string of the 'Use Spelling' preference">
     Enhanced spell check
diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc
index c2a97c27..c5d515f1 100644
--- a/chrome/browser/android/compositor/tab_content_manager.cc
+++ b/chrome/browser/android/compositor/tab_content_manager.cc
@@ -42,7 +42,7 @@
 namespace {
 
 const size_t kMaxReadbacks = 1;
-typedef base::Callback<void(float, const SkBitmap&)> TabReadbackCallback;
+using TabReadbackCallback = base::OnceCallback<void(float, const SkBitmap&)>;
 
 }  // namespace
 
@@ -52,9 +52,9 @@
  public:
   TabReadbackRequest(content::RenderWidgetHostView* rwhv,
                      float thumbnail_scale,
-                     const TabReadbackCallback& end_callback)
+                     TabReadbackCallback end_callback)
       : thumbnail_scale_(thumbnail_scale),
-        end_callback_(end_callback),
+        end_callback_(std::move(end_callback)),
         drop_after_readback_(false),
         weak_factory_(this) {
     DCHECK(rwhv);
@@ -78,13 +78,13 @@
 
   void OnFinishGetTabThumbnailBitmap(const SkBitmap& bitmap) {
     if (bitmap.drawsNothing() || drop_after_readback_) {
-      end_callback_.Run(0.f, SkBitmap());
+      std::move(end_callback_).Run(0.f, SkBitmap());
       return;
     }
 
     SkBitmap result_bitmap = bitmap;
     result_bitmap.setImmutable();
-    end_callback_.Run(thumbnail_scale_, bitmap);
+    std::move(end_callback_).Run(thumbnail_scale_, bitmap);
   }
 
   void SetToDropAfterReadback() { drop_after_readback_ = true; }
@@ -211,16 +211,16 @@
   return thumbnail_cache_->Get(tab_id, false, false) != nullptr;
 }
 
-void TabContentManager::CacheTab(JNIEnv* env,
-                                 const JavaParamRef<jobject>& obj,
-                                 const JavaParamRef<jobject>& tab,
-                                 jfloat thumbnail_scale) {
+content::RenderWidgetHostView* TabContentManager::GetRwhvForTab(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& tab) {
   TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
   DCHECK(tab_android);
   const int tab_id = tab_android->GetAndroidId();
   if (pending_tab_readbacks_.find(tab_id) != pending_tab_readbacks_.end() ||
       pending_tab_readbacks_.size() >= kMaxReadbacks) {
-    return;
+    return nullptr;
   }
 
   content::WebContents* web_contents = tab_android->web_contents();
@@ -229,27 +229,49 @@
   content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
   if (web_contents->ShowingInterstitialPage()) {
     if (!web_contents->GetInterstitialPage()->GetMainFrame())
-      return;
+      return nullptr;
 
-    rvh = web_contents->GetInterstitialPage()->GetMainFrame()->
-        GetRenderViewHost();
+    rvh = web_contents->GetInterstitialPage()
+              ->GetMainFrame()
+              ->GetRenderViewHost();
   }
   if (!rvh)
-    return;
+    return nullptr;
 
   content::RenderWidgetHost* rwh = rvh->GetWidget();
   content::RenderWidgetHostView* rwhv = rwh ? rwh->GetView() : nullptr;
   if (!rwhv || !rwhv->IsSurfaceAvailableForCopy())
-    return;
+    return nullptr;
 
-  if (thumbnail_cache_->CheckAndUpdateThumbnailMetaData(
-          tab_id, tab_android->GetURL())) {
-    TabReadbackCallback readback_done_callback =
-        base::Bind(&TabContentManager::PutThumbnailIntoCache,
-                   weak_factory_.GetWeakPtr(), tab_id);
-    pending_tab_readbacks_[tab_id] = std::make_unique<TabReadbackRequest>(
-        rwhv, thumbnail_scale, readback_done_callback);
+  if (!thumbnail_cache_->CheckAndUpdateThumbnailMetaData(tab_id,
+                                                         tab_android->GetURL()))
+    return nullptr;
+
+  return rwhv;
+}
+
+void TabContentManager::CacheTab(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jobject>& tab,
+    jfloat thumbnail_scale,
+    const base::android::JavaParamRef<jobject>& j_callback) {
+  content::RenderWidgetHostView* rwhv = GetRwhvForTab(env, obj, tab);
+  if (!rwhv) {
+    if (j_callback)
+      RunObjectCallbackAndroid(j_callback, nullptr);
+    return;
   }
+
+  TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
+  DCHECK(tab_android);
+  const int tab_id = tab_android->GetAndroidId();
+
+  TabReadbackCallback readback_done_callback = base::BindOnce(
+      &TabContentManager::PutThumbnailIntoCache, weak_factory_.GetWeakPtr(),
+      tab_id, base::android::ScopedJavaGlobalRef<jobject>(j_callback));
+  pending_tab_readbacks_[tab_id] = std::make_unique<TabReadbackRequest>(
+      rwhv, thumbnail_scale, std::move(readback_done_callback));
 }
 
 void TabContentManager::CacheTabWithBitmap(JNIEnv* env,
@@ -267,7 +289,7 @@
   skbitmap.setImmutable();
 
   if (thumbnail_cache_->CheckAndUpdateThumbnailMetaData(tab_id, url))
-    PutThumbnailIntoCache(tab_id, thumbnail_scale, skbitmap);
+    PutThumbnailIntoCache(tab_id, nullptr, thumbnail_scale, skbitmap);
 }
 
 void TabContentManager::InvalidateIfChanged(JNIEnv* env,
@@ -313,10 +335,10 @@
     jint tab_id,
     const base::android::JavaParamRef<jobject>& j_callback) {
   thumbnail_cache_->DecompressThumbnailFromFile(
-      tab_id, base::BindRepeating(
-                  &TabContentManager::TabThumbnailAvailableFromDisk,
-                  weak_factory_.GetWeakPtr(),
-                  base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
+      tab_id,
+      base::BindRepeating(
+          &TabContentManager::TabThumbnailAvailable, weak_factory_.GetWeakPtr(),
+          base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
 }
 
 void TabContentManager::OnUIResourcesWereEvicted() {
@@ -329,20 +351,26 @@
       env, weak_java_tab_content_manager_.get(env), tab_id);
 }
 
-void TabContentManager::PutThumbnailIntoCache(int tab_id,
-                                              float thumbnail_scale,
-                                              const SkBitmap& bitmap) {
+void TabContentManager::PutThumbnailIntoCache(
+    int tab_id,
+    base::android::ScopedJavaGlobalRef<jobject> j_callback,
+    float thumbnail_scale,
+    const SkBitmap& bitmap) {
   TabReadbackRequestMap::iterator readback_iter =
       pending_tab_readbacks_.find(tab_id);
 
   if (readback_iter != pending_tab_readbacks_.end())
     pending_tab_readbacks_.erase(tab_id);
 
+  if (j_callback) {
+    TabThumbnailAvailable(j_callback, true, bitmap);
+  }
+
   if (thumbnail_scale > 0 && !bitmap.empty())
     thumbnail_cache_->Put(tab_id, bitmap, thumbnail_scale);
 }
 
-void TabContentManager::TabThumbnailAvailableFromDisk(
+void TabContentManager::TabThumbnailAvailable(
     base::android::ScopedJavaGlobalRef<jobject> j_callback,
     bool result,
     SkBitmap bitmap) {
diff --git a/chrome/browser/android/compositor/tab_content_manager.h b/chrome/browser/android/compositor/tab_content_manager.h
index f2154891..abe38a2 100644
--- a/chrome/browser/android/compositor/tab_content_manager.h
+++ b/chrome/browser/android/compositor/tab_content_manager.h
@@ -17,6 +17,7 @@
 #include "base/memory/weak_ptr.h"
 #include "cc/layers/ui_resource_layer.h"
 #include "chrome/browser/android/thumbnail/thumbnail_cache.h"
+#include "content/public/browser/render_widget_host_view.h"
 
 using base::android::ScopedJavaLocalRef;
 
@@ -83,7 +84,8 @@
   void CacheTab(JNIEnv* env,
                 const base::android::JavaParamRef<jobject>& obj,
                 const base::android::JavaParamRef<jobject>& tab,
-                jfloat thumbnail_scale);
+                jfloat thumbnail_scale,
+                const base::android::JavaParamRef<jobject>& j_callback);
   void CacheTabWithBitmap(JNIEnv* env,
                           const base::android::JavaParamRef<jobject>& obj,
                           const base::android::JavaParamRef<jobject>& tab,
@@ -120,11 +122,17 @@
   using TabReadbackRequestMap =
       base::flat_map<int, std::unique_ptr<TabReadbackRequest>>;
 
-  void PutThumbnailIntoCache(int tab_id,
-                             float thumbnail_scale,
-                             const SkBitmap& bitmap);
+  content::RenderWidgetHostView* GetRwhvForTab(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jobject>& tab);
+  void PutThumbnailIntoCache(
+      int tab_id,
+      base::android::ScopedJavaGlobalRef<jobject> j_callback,
+      float thumbnail_scale,
+      const SkBitmap& bitmap);
 
-  void TabThumbnailAvailableFromDisk(
+  void TabThumbnailAvailable(
       base::android::ScopedJavaGlobalRef<jobject> j_callback,
       bool result,
       SkBitmap bitmap);
diff --git a/chrome/browser/android/subresource_filter/test_subresource_filter_publisher.cc b/chrome/browser/android/subresource_filter/test_subresource_filter_publisher.cc
index 3031275..35f346e2b 100644
--- a/chrome/browser/android/subresource_filter/test_subresource_filter_publisher.cc
+++ b/chrome/browser/android/subresource_filter/test_subresource_filter_publisher.cc
@@ -19,7 +19,6 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/threading/thread_restrictions.h"
-#include "chrome/browser/after_startup_task_utils.h"
 #include "chrome/browser/browser_process.h"
 #include "components/subresource_filter/content/browser/ruleset_service.h"
 #include "components/subresource_filter/core/common/unindexed_ruleset.h"
@@ -72,9 +71,6 @@
   base::android::ScopedJavaGlobalRef<jobject> publisher;
   publisher.Reset(env, publisher_param);
 
-  // Set the startup as complete, so tasks execute immediately.
-  AfterStartupTaskUtils::SetBrowserStartupIsCompleteForTesting();
-
   // Create the ruleset contents.
   std::string ruleset_contents_str;
   google::protobuf::io::StringOutputStream output(&ruleset_contents_str);
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 6cfb8bc..87b10e7 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -650,6 +650,9 @@
         <include name="IDR_DEVICE_EMULATOR_PAGES_HTML" file="resources\chromeos\emulator\device_emulator_pages.html" type="BINDATA" />
         <include name="IDR_DEVICE_EMULATOR_PAGES_JS" file="resources\chromeos\emulator\device_emulator_pages.js" type="BINDATA" />
         <include name="IDR_DEVICE_EMULATOR_SHARED_STYLES_HTML" file="resources\chromeos\emulator\shared_styles.html" type="BINDATA" />
+        <!-- TODO(jamescook): Create separate files os_settings.grd and
+             os_settings_vulcanized.grd and move this placeholder. -->
+        <include name="IDR_OS_SETTINGS_HTML" file="resources\chromeos\os_settings.html" type="BINDATA" compress="gzip" />
       </if>
       <if expr="chromeos">
         <include name="IDR_SET_TIME_HTML" file="resources\chromeos\md_set_time\set_time.html" type="BINDATA" compress="gzip" />
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 6e2c0a3..bd231c5 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -83,6 +83,7 @@
     "//chromeos/components/drivefs/mojom",
     "//chromeos/components/multidevice",
     "//chromeos/components/multidevice/logging",
+    "//chromeos/components/power",
     "//chromeos/components/proximity_auth",
     "//chromeos/components/tether",
     "//chromeos/cryptohome",
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 62358d8..4acfefe 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -126,6 +126,7 @@
 #include "chromeos/audio/audio_devices_pref_handler_impl.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/components/drivefs/fake_drivefs_launcher_client.h"
+#include "chromeos/components/power/dark_resume_controller.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/cryptohome/async_method_caller.h"
@@ -145,7 +146,6 @@
 #include "chromeos/network/network_cert_loader.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/portal_detector/network_portal_detector_stub.h"
-#include "chromeos/system/dark_resume_controller.h"
 #include "chromeos/system/statistics_provider.h"
 #include "chromeos/tpm/install_attributes.h"
 #include "chromeos/tpm/tpm_token_loader.h"
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 2d3e3b8..b981be25 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1837,11 +1837,9 @@
                               weak_ptr_factory_.GetWeakPtr(), vm_name,
                               std::move(callback), CrostiniResult::SUCCESS));
 
-  // Share folders from Downloads, etc with default VM.
-  if (vm_name == kCrostiniDefaultVmName) {
-    CrostiniSharePath::GetForProfile(profile_)->SharePersistedPaths(
-        base::DoNothing());
-  }
+  // Share folders from Downloads, etc with VM.
+  CrostiniSharePath::GetForProfile(profile_)->SharePersistedPaths(
+      vm_name, base::DoNothing());
 }
 
 void CrostiniManager::OnStartTremplin(std::string vm_name,
diff --git a/chrome/browser/chromeos/crostini/crostini_pref_names.cc b/chrome/browser/chromeos/crostini/crostini_pref_names.cc
index c12dc2b7c..950bca1 100644
--- a/chrome/browser/chromeos/crostini/crostini_pref_names.cc
+++ b/chrome/browser/chromeos/crostini/crostini_pref_names.cc
@@ -20,6 +20,8 @@
 const char kCrostiniMimeTypes[] = "crostini.mime_types";
 const char kCrostiniRegistry[] = "crostini.registry";
 // List of filesystem paths that are shared with the crostini container.
+// TODO(crbug.com/946273): Remove crostini.shared_paths and migration code after
+// M77.
 const char kCrostiniSharedPaths[] = "crostini.shared_paths";
 // List of USB devices with their system guid, a name/description and their
 // enabled state for use with Crostini.
@@ -47,12 +49,17 @@
 // The value of the last sample of the disk space used by Crostini.
 const char kCrostiniLastDiskSize[] = "crostini.last_disk_size";
 
+// Dictionary of filesystem paths mapped to the list of VMs that the paths are
+// shared with.
+const char kGuestOSPathsSharedToVms[] = "guest_os.paths_shared_to_vms";
+
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterBooleanPref(kCrostiniEnabled, false);
   registry->RegisterDictionaryPref(kCrostiniMimeTypes);
   registry->RegisterDictionaryPref(kCrostiniRegistry);
   registry->RegisterListPref(kCrostiniSharedPaths);
   registry->RegisterListPref(kCrostiniSharedUsbDevices);
+  registry->RegisterDictionaryPref(kGuestOSPathsSharedToVms);
 
   // Set a default value for crostini.containers to ensure that we track the
   // default container even if its creation predates this preference. This
diff --git a/chrome/browser/chromeos/crostini/crostini_pref_names.h b/chrome/browser/chromeos/crostini/crostini_pref_names.h
index 556e57b..ae2a24d 100644
--- a/chrome/browser/chromeos/crostini/crostini_pref_names.h
+++ b/chrome/browser/chromeos/crostini/crostini_pref_names.h
@@ -27,6 +27,8 @@
 extern const char kCrostiniLastLaunchTimeWindowStart[];
 extern const char kCrostiniLastDiskSize[];
 
+extern const char kGuestOSPathsSharedToVms[];
+
 void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
 }  // namespace prefs
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path.cc b/chrome/browser/chromeos/crostini/crostini_share_path.cc
index 32af7e1b..e0000b8 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path.cc
+++ b/chrome/browser/chromeos/crostini/crostini_share_path.cc
@@ -122,6 +122,31 @@
   std::string first_failure_reason_;
 };  // class
 
+void RemovePersistedPathFromPrefs(base::DictionaryValue* shared_paths,
+                                  const std::string& vm_name,
+                                  const base::FilePath& path) {
+  // |shared_paths| format is {'path': ['vm1', vm2']}.
+  // If |path| exists, remove |vm_name| from list of VMs.
+  base::Value* found = shared_paths->FindKey(path.value());
+  if (!found) {
+    LOG(WARNING) << "Path not in prefs to ushare path " << path.value()
+                 << " for VM " << vm_name;
+    return;
+  }
+  auto& vms = found->GetList();
+  auto it = std::find(vms.begin(), vms.end(), base::Value(vm_name));
+  if (it == vms.end()) {
+    LOG(WARNING) << "VM not in prefs to ushare path " << path.value()
+                 << " for VM " << vm_name;
+    return;
+  }
+  vms.erase(it);
+  // If VM list is now empty, remove |path| from |shared_paths|.
+  if (vms.size() == 0) {
+    shared_paths->RemoveKey(path.value());
+  }
+}
+
 }  // namespace
 
 namespace crostini {
@@ -205,7 +230,7 @@
       request.set_storage_location(
           vm_tools::seneschal::SharePathRequest::DOWNLOADS);
     }
-    request.set_owner_id(crostini::CryptohomeIdForProfile(profile_));
+    request.set_owner_id(CryptohomeIdForProfile(profile_));
   } else if (base::FeatureList::IsEnabled(chromeos::features::kDriveFs) &&
              integration_service &&
              (drivefs_mount_point_path =
@@ -275,7 +300,7 @@
 
   // Even if VM is not running, save to prefs now.
   if (persist) {
-    RegisterPersistedPath(path);
+    RegisterPersistedPath(vm_name, path);
   }
   RegisterSharedPath(vm_name, path);
 
@@ -383,11 +408,10 @@
 
   if (unpersist) {
     PrefService* pref_service = profile_->GetPrefs();
-    ListPrefUpdate update(pref_service, crostini::prefs::kCrostiniSharedPaths);
-    base::ListValue* shared_paths = update.Get();
-    if (!shared_paths->Remove(base::Value(path.value()), nullptr)) {
-      LOG(WARNING) << "Unshared path not in prefs: " << path.value();
-    }
+    DictionaryPrefUpdate update(pref_service,
+                                crostini::prefs::kGuestOSPathsSharedToVms);
+    base::DictionaryValue* shared_paths = update.Get();
+    RemovePersistedPathFromPrefs(shared_paths, vm_name, path);
   }
 
   CallSeneschalUnsharePath(vm_name, path, std::move(callback));
@@ -402,63 +426,88 @@
   return result;
 }
 
-std::vector<base::FilePath> CrostiniSharePath::GetPersistedSharedPaths() {
+std::vector<base::FilePath> CrostiniSharePath::GetPersistedSharedPaths(
+    const std::string& vm_name) {
   std::vector<base::FilePath> result;
-  PrefService* pref_service = profile_->GetPrefs();
-  const base::ListValue* shared_paths =
-      pref_service->GetList(prefs::kCrostiniSharedPaths);
-  // Paths in <cryptohome>/Downloads need to be migrated to
-  // <cryptohome>/MyFiles/Downloads.
-  bool swap_for_migrate_required = false;
-  base::ListValue migrated_paths;
-  for (const auto& shared_path : *shared_paths) {
-    base::FilePath path(shared_path.GetString());
-    base::FilePath migrated;
-    if (file_manager::util::MigrateFromDownloadsToMyFiles(profile_, path,
-                                                          &migrated)) {
-      swap_for_migrate_required = true;
-      path = migrated;
+  // |shared_paths| format is {'path': ['vm1', vm2']}.
+  const base::DictionaryValue* shared_paths =
+      profile_->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+  for (const auto& it : shared_paths->DictItems()) {
+    base::FilePath path(it.first);
+    auto& vms = it.second.GetList();
+    for (const auto& vm : vms) {
+      // Register all shared paths for all VMs since we want FilePathWatchers
+      // to start immediately.
+      RegisterSharedPath(vm.GetString(), path);
+      // Only add to result if path is shared with specified |vm_name|.
+      if (vm.GetString() == vm_name) {
+        result.emplace_back(path);
+      }
     }
-    migrated_paths.AppendString(path.value());
-    result.emplace_back(path);
-    RegisterSharedPath(kCrostiniDefaultVmName, path);
   }
-
-  // If any paths were modified during migration, update prefs.
-  if (swap_for_migrate_required) {
-    ListPrefUpdate update(pref_service, crostini::prefs::kCrostiniSharedPaths);
-    base::ListValue* shared_paths = update.Get();
-    shared_paths->Swap(&migrated_paths);
-  }
-
   return result;
 }
 
 void CrostiniSharePath::SharePersistedPaths(
+    const std::string& vm_name,
     base::OnceCallback<void(bool, std::string)> callback) {
-  SharePaths(kCrostiniDefaultVmName, GetPersistedSharedPaths(),
-             false /* persist */, std::move(callback));
+  SharePaths(vm_name, GetPersistedSharedPaths(vm_name),
+             /*persist=*/false, std::move(callback));
 }
 
-void CrostiniSharePath::RegisterPersistedPath(const base::FilePath& path) {
+void CrostiniSharePath::RegisterPersistedPath(const std::string& vm_name,
+                                              const base::FilePath& path) {
   PrefService* pref_service = profile_->GetPrefs();
-  ListPrefUpdate update(pref_service, crostini::prefs::kCrostiniSharedPaths);
-  base::ListValue* shared_paths = update.Get();
-  // Check if path exists, remove paths that are children of new path.
-  bool exists = false;
-  auto it = shared_paths->begin();
-  while (it != shared_paths->end()) {
-    base::FilePath existing(it->GetString());
-    if (path == existing) {
-      exists = true;
-    } else if (path.IsParent(existing)) {
-      it = shared_paths->Erase(it, nullptr);
-      continue;
+  DictionaryPrefUpdate update(pref_service,
+                              crostini::prefs::kGuestOSPathsSharedToVms);
+  base::DictionaryValue* shared_paths = update.Get();
+  // Check if path is already shared so we know whether we need to add it.
+  bool already_shared = false;
+  // Remove any paths that are children of this path.
+  // E.g. if path /foo/bar is already shared, and then we share /foo, we
+  // remove /foo/bar from the list since it will be shared as part of /foo.
+  std::vector<base::FilePath> children;
+  for (const auto& it : shared_paths->DictItems()) {
+    base::FilePath shared(it.first);
+    auto& vms = it.second.GetList();
+    auto vm_matches =
+        std::find(vms.begin(), vms.end(), base::Value(vm_name)) != vms.end();
+    if (path == shared) {
+      already_shared = true;
+      if (!vm_matches) {
+        vms.emplace_back(vm_name);
+      }
+    } else if (path.IsParent(shared) && vm_matches) {
+      children.emplace_back(shared);
     }
-    ++it;
   }
-  if (!exists)
-    shared_paths->Append(std::make_unique<base::Value>(path.value()));
+  for (const auto& child : children) {
+    RemovePersistedPathFromPrefs(shared_paths, vm_name, child);
+  }
+  if (!already_shared) {
+    base::Value vms(base::Value::Type::LIST);
+    vms.GetList().emplace_back(base::Value(vm_name));
+    shared_paths->SetKey(path.value(), std::move(vms));
+  }
+}
+
+void CrostiniSharePath::MigratePersistedPathsToMultiVM(
+    PrefService* profile_prefs) {
+  const base::ListValue* shared_paths =
+      profile_prefs->GetList(prefs::kCrostiniSharedPaths);
+  if (shared_paths->GetSize() == 0) {
+    return;
+  }
+  // Convert ['foo', 'bar'] to {'foo':['termina'], 'bar':['termina']}.
+  base::Value dict(base::Value::Type::DICTIONARY);
+  for (const auto& shared_path : *shared_paths) {
+    base::Value termina(base::Value::Type::LIST);
+    termina.GetList().emplace_back(base::Value(kCrostiniDefaultVmName));
+    dict.SetKey(shared_path.GetString(), std::move(termina));
+  }
+  profile_prefs->Set(prefs::kGuestOSPathsSharedToVms, std::move(dict));
+
+  profile_prefs->ClearPref(prefs::kCrostiniSharedPaths);
 }
 
 void CrostiniSharePath::OnVolumeMounted(chromeos::MountError error_code,
@@ -467,18 +516,24 @@
     return;
   }
 
-  // Fetch list of shared paths even if VM is not running so that
-  // FilePathWatchers will be added.
-  auto paths = GetPersistedSharedPaths();
-  if (!crostini::CrostiniManager::GetForProfile(profile_)->IsVmRunning(
-          kCrostiniDefaultVmName)) {
-    return;
-  }
-  for (const auto& path : paths) {
-    if (path == volume.mount_path() || volume.mount_path().IsParent(path)) {
-      CallSeneschalSharePath(kCrostiniDefaultVmName, path, false,
-                             base::BindOnce(mount_event_seneschal_callback_,
-                                            "share-on-mount", path));
+  // Check if any persisted paths match volume.mount_path() or are children
+  // of it then share them with any running VMs.
+  const base::DictionaryValue* shared_paths =
+      profile_->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+  for (const auto& it : shared_paths->DictItems()) {
+    base::FilePath path(it.first);
+    if (path != volume.mount_path() && !volume.mount_path().IsParent(path)) {
+      continue;
+    }
+    const auto& vms = it.second.GetList();
+    for (const auto& vm : vms) {
+      RegisterSharedPath(vm.GetString(), path);
+      if (CrostiniManager::GetForProfile(profile_)->IsVmRunning(
+              vm.GetString())) {
+        CallSeneschalSharePath(vm.GetString(), path, false,
+                               base::BindOnce(mount_event_seneschal_callback_,
+                                              "share-on-mount", path));
+      }
     }
   }
 }
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path.h b/chrome/browser/chromeos/crostini/crostini_share_path.h
index 34f648c1..63cad9d 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path.h
+++ b/chrome/browser/chromeos/crostini/crostini_share_path.h
@@ -54,6 +54,12 @@
                            const base::FilePath& path) = 0;
   };
 
+  // Migrates from crostini.shared_paths to crostini.paths_shared_to_vms which
+  // supports multi VM sharing.
+  // TODO(crbug.com/946273): Remove crostini.shared_paths and migration code
+  // after M77.
+  static void MigratePersistedPathsToMultiVM(PrefService* profile_prefs);
+
   static CrostiniSharePath* GetForProfile(Profile* profile);
   explicit CrostiniSharePath(Profile* profile);
   ~CrostiniSharePath() override;
@@ -88,16 +94,19 @@
   // Returns true the first time it is called on this service.
   bool GetAndSetFirstForSession();
 
-  // Get list of all shared paths for the default crostini container.
-  std::vector<base::FilePath> GetPersistedSharedPaths();
+  // Get list of all shared paths for the specified VM.
+  std::vector<base::FilePath> GetPersistedSharedPaths(
+      const std::string& vm_name);
 
-  // Share all paths configured in prefs for the default crostini container.
+  // Share all paths configured in prefs for the specified VM.
   // Called at container startup.  Callback is invoked once complete.
   void SharePersistedPaths(
+      const std::string& vm_name,
       base::OnceCallback<void(bool, std::string)> callback);
 
-  // Save |path| into prefs.
-  void RegisterPersistedPath(const base::FilePath& path);
+  // Save |path| into prefs for |vm_name|.
+  void RegisterPersistedPath(const std::string& vm_name,
+                             const base::FilePath& path);
 
   // file_manager::VolumeManagerObserver
   void OnVolumeMounted(chromeos::MountError error_code,
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc b/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
index 2fcc58af..96f11a30 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
@@ -43,6 +43,7 @@
   enum class Success { NO, YES };
 
   void SharePathCallback(
+      const std::string& expected_vm_name,
       Persist expected_persist,
       SeneschalClientCalled expected_seneschal_client_called,
       const vm_tools::seneschal::SharePathRequest::StorageLocation*
@@ -53,19 +54,20 @@
       const base::FilePath& container_path,
       bool success,
       std::string failure_reason) {
-    const base::ListValue* prefs =
-        profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-    std::string share_path;
+    const base::DictionaryValue* prefs =
+        profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+    EXPECT_TRUE(prefs->HasKey(shared_path_.value()));
+    EXPECT_EQ(prefs->FindKey(shared_path_.value())->GetList().size(), 1U);
+    EXPECT_EQ(prefs->FindKey(shared_path_.value())->GetList()[0].GetString(),
+              kCrostiniDefaultVmName);
     if (expected_persist == Persist::YES) {
-      EXPECT_EQ(prefs->GetSize(), 2U);
-      prefs->GetString(0, &share_path);
-      EXPECT_EQ(share_path, shared_path_.value());
-      prefs->GetString(1, &share_path);
-      EXPECT_EQ(share_path, share_path_.value());
+      EXPECT_EQ(prefs->size(), 2U);
+      EXPECT_TRUE(prefs->HasKey(share_path_.value()));
+      EXPECT_EQ(prefs->FindKey(share_path_.value())->GetList().size(), 1U);
+      EXPECT_EQ(prefs->FindKey(share_path_.value())->GetList()[0].GetString(),
+                expected_vm_name);
     } else {
-      EXPECT_EQ(prefs->GetSize(), 1U);
-      prefs->GetString(0, &share_path);
-      EXPECT_EQ(share_path, shared_path_.value());
+      EXPECT_EQ(prefs->size(), 1U);
     }
     EXPECT_EQ(fake_seneschal_client_->share_path_called(),
               expected_seneschal_client_called == SeneschalClientCalled::YES);
@@ -86,6 +88,7 @@
   void MountEventSharePathCallback(
       const std::string& expected_operation,
       const base::FilePath& expected_path,
+      const std::string& expected_vm_name,
       Persist expected_persist,
       SeneschalClientCalled expected_seneschal_client_called,
       const vm_tools::seneschal::SharePathRequest::StorageLocation*
@@ -100,18 +103,20 @@
       std::string failure_reason) {
     EXPECT_EQ(expected_operation, operation);
     EXPECT_EQ(expected_path, cros_path);
-    SharePathCallback(expected_persist, expected_seneschal_client_called,
-                      expected_seneschal_storage_location,
-                      expected_seneschal_path, expected_success,
-                      expected_failure_reason, container_path, success,
-                      failure_reason);
+    SharePathCallback(
+        expected_vm_name, expected_persist, expected_seneschal_client_called,
+        expected_seneschal_storage_location, expected_seneschal_path,
+        expected_success, expected_failure_reason, container_path, success,
+        failure_reason);
   }
 
   void SharePersistedPathsCallback(bool success, std::string failure_reason) {
     EXPECT_TRUE(success);
-    EXPECT_EQ(
-        profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths)->GetSize(),
-        2U);
+    EXPECT_EQ(profile()
+                  ->GetPrefs()
+                  ->GetDictionary(prefs::kGuestOSPathsSharedToVms)
+                  ->size(),
+              2U);
     run_loop()->Quit();
   }
 
@@ -133,12 +138,12 @@
       const std::string& expected_failure_reason,
       bool success,
       std::string failure_reason) {
-    const base::ListValue* prefs =
-        profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
+    const base::DictionaryValue* prefs =
+        profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
     if (expected_persist == Persist::YES) {
-      EXPECT_NE(prefs->Find(base::Value(path.value())), prefs->end());
+      EXPECT_TRUE(prefs->HasKey(path.value()));
     } else {
-      EXPECT_EQ(prefs->Find(base::Value(path.value())), prefs->end());
+      EXPECT_FALSE(prefs->HasKey(path.value()));
     }
     EXPECT_EQ(fake_seneschal_client_->unshare_path_called(),
               expected_seneschal_client_called == SeneschalClientCalled::YES);
@@ -198,10 +203,12 @@
     root_ = file_manager::util::GetMyFilesFolderForProfile(profile());
     share_path_ = root_.Append("path-to-share");
     shared_path_ = root_.Append("already-shared");
-    ListPrefUpdate update(profile()->GetPrefs(),
-                          crostini::prefs::kCrostiniSharedPaths);
-    base::ListValue* shared_paths = update.Get();
-    shared_paths->Append(std::make_unique<base::Value>(shared_path_.value()));
+    DictionaryPrefUpdate update(profile()->GetPrefs(),
+                                prefs::kGuestOSPathsSharedToVms);
+    base::DictionaryValue* shared_paths = update.Get();
+    base::Value termina(base::Value::Type::LIST);
+    termina.GetList().emplace_back(base::Value(kCrostiniDefaultVmName));
+    shared_paths->SetKey(shared_path_.value(), std::move(termina));
     volume_downloads_ = file_manager::Volume::CreateForDownloads(root_);
     crostini_share_path_->RegisterSharedPath(kCrostiniDefaultVmName,
                                              shared_path_);
@@ -210,7 +217,7 @@
   void SetUp() override {
     run_loop_ = std::make_unique<base::RunLoop>();
     profile_ = std::make_unique<TestingProfile>();
-    crostini_share_path_ = std::make_unique<CrostiniSharePath>(profile());
+    crostini_share_path_ = CrostiniSharePath::GetForProfile(profile());
     crostini_share_path_->set_no_file_watchers_for_testing();
 
     // Setup for DriveFS.
@@ -243,9 +250,6 @@
  protected:
   base::RunLoop* run_loop() { return run_loop_.get(); }
   Profile* profile() { return profile_.get(); }
-  CrostiniSharePath* crostini_share_path() {
-    return crostini_share_path_.get();
-  }
   base::FilePath root_;
   base::FilePath share_path_;
   base::FilePath shared_path_;
@@ -259,7 +263,7 @@
   std::unique_ptr<base::RunLoop>
       run_loop_;  // run_loop_ must be created on the UI thread.
   std::unique_ptr<TestingProfile> profile_;
-  std::unique_ptr<CrostiniSharePath> crostini_share_path_;
+  CrostiniSharePath* crostini_share_path_;
   base::test::ScopedFeatureList features_;
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   AccountId account_id_;
@@ -272,10 +276,10 @@
 TEST_F(CrostiniSharePathTest, SuccessDownloadsRoot) {
   features_.InitWithFeatures({}, {chromeos::features::kMyFilesVolume});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", root_, PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DOWNLOADS, "",
                      Success::YES, ""));
@@ -287,10 +291,10 @@
   SetUpVolume();
   base::FilePath my_files =
       file_manager::util::GetMyFilesFolderForProfile(profile());
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", my_files, PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::MY_FILES, "",
                      Success::YES, ""));
@@ -300,10 +304,10 @@
 TEST_F(CrostiniSharePathTest, SuccessNoPersist) {
   features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", share_path_, PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::MY_FILES,
                      "path-to-share", Success::YES, ""));
@@ -313,10 +317,10 @@
 TEST_F(CrostiniSharePathTest, SuccessPersist) {
   features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", share_path_, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::YES,
+                     base::Unretained(this), "vm-running", Persist::YES,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::MY_FILES,
                      "path-to-share", Success::YES, ""));
@@ -326,10 +330,10 @@
 TEST_F(CrostiniSharePathTest, SuccessDriveFsMyDrive) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("root").Append("my"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DRIVEFS_MY_DRIVE,
                      "my", Success::YES, ""));
@@ -339,10 +343,10 @@
 TEST_F(CrostiniSharePathTest, FailureDriveFsDisabled) {
   features_.InitWithFeatures({}, {chromeos::features::kDriveFs});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("root").Append("my"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "my", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -351,10 +355,10 @@
 TEST_F(CrostiniSharePathTest, SuccessDriveFsMyDriveRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("root"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DRIVEFS_MY_DRIVE,
                      "", Success::YES, ""));
@@ -364,10 +368,10 @@
 TEST_F(CrostiniSharePathTest, FailDriveFsRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_, PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -376,11 +380,11 @@
 TEST_F(CrostiniSharePathTest, SuccessDriveFsTeamDrives) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("team_drives").Append("team"), PERSIST_NO,
       base::BindOnce(
           &CrostiniSharePathTest::SharePathCallback, base::Unretained(this),
-          Persist::NO, SeneschalClientCalled::YES,
+          "vm-running", Persist::NO, SeneschalClientCalled::YES,
           &vm_tools::seneschal::SharePathRequest::DRIVEFS_TEAM_DRIVES, "team",
           Success::YES, ""));
   run_loop()->Run();
@@ -390,10 +394,10 @@
 TEST_F(CrostiniSharePathTest, DISABLED_SuccessDriveFsComputersGrandRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("Computers"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
                      "pc", Success::YES, ""));
@@ -404,10 +408,10 @@
 TEST_F(CrostiniSharePathTest, Bug917920DriveFsComputersGrandRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("Computers"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -417,10 +421,10 @@
 TEST_F(CrostiniSharePathTest, DISABLED_SuccessDriveFsComputerRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("Computers").Append("pc"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
                      "pc", Success::YES, ""));
@@ -431,10 +435,10 @@
 TEST_F(CrostiniSharePathTest, Bug917920DriveFsComputerRoot) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append("Computers").Append("pc"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -443,12 +447,12 @@
 TEST_F(CrostiniSharePathTest, SuccessDriveFsComputersLevel3) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running",
       drivefs_.Append("Computers").Append("pc").Append("SyncFolder"),
       PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::DRIVEFS_COMPUTERS,
                      "pc/SyncFolder", Success::YES, ""));
@@ -458,11 +462,11 @@
 TEST_F(CrostiniSharePathTest, FailDriveFsTrash) {
   features_.InitWithFeatures({chromeos::features::kDriveFs}, {});
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", drivefs_.Append(".Trash").Append("in-the-trash"),
       PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -470,10 +474,10 @@
 
 TEST_F(CrostiniSharePathTest, SuccessRemovable) {
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", base::FilePath("/media/removable/MyUSB"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::REMOVABLE, "MyUSB",
                      Success::YES, ""));
@@ -482,10 +486,10 @@
 
 TEST_F(CrostiniSharePathTest, FailRemovableRoot) {
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", base::FilePath("/media/removable"), PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -506,10 +510,10 @@
   share_path_response.set_failure_reason("test failure");
   fake_seneschal_client_->set_share_path_response(share_path_response);
 
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "error-seneschal", share_path_, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::YES,
+                     base::Unretained(this), "error-seneschal", Persist::YES,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::MY_FILES,
                      "path-to-share", Success::NO, "test failure"));
@@ -519,10 +523,10 @@
 TEST_F(CrostiniSharePathTest, SharePathErrorPathNotAbsolute) {
   SetUpVolume();
   const base::FilePath path("not/absolute/dir");
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", path, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path must be absolute"));
   run_loop()->Run();
@@ -531,10 +535,10 @@
 TEST_F(CrostiniSharePathTest, SharePathErrorReferencesParent) {
   SetUpVolume();
   const base::FilePath path("/path/../references/parent");
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", path, PERSIST_NO,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path must be absolute"));
   run_loop()->Run();
@@ -543,10 +547,10 @@
 TEST_F(CrostiniSharePathTest, SharePathErrorNotUnderDownloads) {
   SetUpVolume();
   const base::FilePath path("/not/under/downloads");
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-running", path, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::NO,
+                     base::Unretained(this), "vm-running", Persist::NO,
                      SeneschalClientCalled::NO, nullptr, "", Success::NO,
                      "Path is not allowed"));
   run_loop()->Run();
@@ -557,10 +561,10 @@
       {chromeos::features::kMyFilesVolume, features::kCrostini}, {});
   GetFakeUserManager()->LoginUser(account_id_);
   SetUpVolume();
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "vm-to-be-started", share_path_, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::YES,
+                     base::Unretained(this), "vm-to-be-started", Persist::YES,
                      SeneschalClientCalled::YES,
                      &vm_tools::seneschal::SharePathRequest::MY_FILES,
                      "path-to-share", Success::YES, ""));
@@ -573,12 +577,12 @@
   start_vm_response.set_status(vm_tools::concierge::VM_STATUS_FAILURE);
   fake_concierge_client_->set_start_vm_response(start_vm_response);
 
-  crostini_share_path()->SharePath(
+  crostini_share_path_->SharePath(
       "error-vm-could-not-be-started", share_path_, PERSIST_YES,
       base::BindOnce(&CrostiniSharePathTest::SharePathCallback,
-                     base::Unretained(this), Persist::YES,
-                     SeneschalClientCalled::NO, nullptr, "", Success::NO,
-                     "VM could not be started"));
+                     base::Unretained(this), "error-vm-could-not-be-started",
+                     Persist::YES, SeneschalClientCalled::NO, nullptr, "",
+                     Success::NO, "VM could not be started"));
   run_loop()->Run();
 }
 
@@ -588,85 +592,103 @@
   ASSERT_TRUE(base::CreateDirectory(share_path2_));
   CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
       kCrostiniDefaultVmName);
-  base::ListValue shared_paths = base::ListValue();
-  shared_paths.GetList().push_back(base::Value(share_path_.value()));
-  shared_paths.GetList().push_back(base::Value(share_path2_.value()));
-  profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
-  crostini_share_path()->SharePersistedPaths(
+  base::Value shared_paths(base::Value::Type::DICTIONARY);
+  base::Value vms(base::Value::Type::LIST);
+  vms.GetList().emplace_back(base::Value(kCrostiniDefaultVmName));
+  shared_paths.SetKey(share_path_.value(), std::move(vms));
+  base::Value vms2(base::Value::Type::LIST);
+  vms2.GetList().emplace_back(base::Value(kCrostiniDefaultVmName));
+  shared_paths.SetKey(share_path2_.value(), std::move(vms2));
+  profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
+  crostini_share_path_->SharePersistedPaths(
+      kCrostiniDefaultVmName,
       base::BindOnce(&CrostiniSharePathTest::SharePersistedPathsCallback,
                      base::Unretained(this)));
   run_loop()->Run();
 }
 
 TEST_F(CrostiniSharePathTest, RegisterPersistedPaths) {
-  base::ListValue shared_paths = base::ListValue();
+  base::Value shared_paths(base::Value::Type::DICTIONARY);
   SetUpVolume();
-  profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
+  profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
 
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/a/a"));
-  const base::ListValue* prefs =
-      profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-  EXPECT_EQ(prefs->GetSize(), 1U);
-  std::string path;
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/a/a/a");
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/a"));
+  const base::DictionaryValue* prefs =
+      profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+  EXPECT_EQ(prefs->size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v1");
 
-  // Adding the same path again should not cause any changes.
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/a/a"));
-  EXPECT_EQ(prefs->GetSize(), 1U);
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/a/a/a");
+  // Adding the same path again for same VM should not cause any changes.
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/a"));
+  prefs = profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+  EXPECT_EQ(prefs->size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
+
+  // Adding the same path for a new VM adds to the vm list.
+  crostini_share_path_->RegisterPersistedPath("v2", base::FilePath("/a/a/a"));
+  EXPECT_EQ(prefs->size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 2U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v1");
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[1].GetString(), "v2");
 
   // Add more paths.
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/a/b"));
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/a/c"));
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/b/a"));
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/b/a/a"));
-  prefs = profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-  EXPECT_EQ(prefs->GetSize(), 5U);
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/a/a/a");
-  prefs->GetString(1, &path);
-  EXPECT_EQ(path, "/a/a/b");
-  prefs->GetString(2, &path);
-  EXPECT_EQ(path, "/a/a/c");
-  prefs->GetString(3, &path);
-  EXPECT_EQ(path, "/a/b/a");
-  prefs->GetString(4, &path);
-  EXPECT_EQ(path, "/b/a/a");
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/b"));
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a/c"));
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/b/a"));
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/b/a/a"));
+  EXPECT_EQ(prefs->size(), 5U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 2U);
+  EXPECT_EQ(prefs->FindKey("/a/a/b")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/c")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/b/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
 
-  // Adding /a/a should remove /a/a/b, /a/a/c.
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a/a"));
-  prefs = profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-  EXPECT_EQ(prefs->GetSize(), 3U);
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/a/b/a");
-  prefs->GetString(1, &path);
-  EXPECT_EQ(path, "/b/a/a");
-  prefs->GetString(2, &path);
-  EXPECT_EQ(path, "/a/a");
+  // Adding /a/a should remove /a/a/a, /a/a/b, /a/a/c.
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a/a"));
+  EXPECT_EQ(prefs->size(), 4U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
+  EXPECT_EQ(prefs->FindKey("/a/b/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a")->GetList()[0].GetString(), "v1");
 
   // Adding /a should remove /a/a, /a/b/a.
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/a"));
-  prefs = profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-  EXPECT_EQ(prefs->GetSize(), 2U);
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/b/a/a");
-  prefs->GetString(1, &path);
-  EXPECT_EQ(path, "/a");
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/a"));
+  EXPECT_EQ(prefs->size(), 3U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
+  EXPECT_EQ(prefs->FindKey("/b/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a")->GetList()[0].GetString(), "v1");
 
   // Adding / should remove all others.
-  crostini_share_path()->RegisterPersistedPath(base::FilePath("/"));
-  prefs = profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-  EXPECT_EQ(prefs->GetSize(), 1U);
-  prefs->GetString(0, &path);
-  EXPECT_EQ(path, "/");
+  crostini_share_path_->RegisterPersistedPath("v1", base::FilePath("/"));
+  EXPECT_EQ(prefs->size(), 2U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/a/a/a")->GetList()[0].GetString(), "v2");
+  EXPECT_EQ(prefs->FindKey("/")->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/")->GetList()[0].GetString(), "v1");
+
+  // Add / for v2.
+  crostini_share_path_->RegisterPersistedPath("v2", base::FilePath("/"));
+  EXPECT_EQ(prefs->size(), 1U);
+  EXPECT_EQ(prefs->FindKey("/")->GetList().size(), 2U);
+  EXPECT_EQ(prefs->FindKey("/")->GetList()[0].GetString(), "v1");
+  EXPECT_EQ(prefs->FindKey("/")->GetList()[1].GetString(), "v2");
 }
 
 TEST_F(CrostiniSharePathTest, UnsharePathSuccess) {
   features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
   SetUpVolume();
-  crostini_share_path()->UnsharePath(
+  DictionaryPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kGuestOSPathsSharedToVms);
+  base::DictionaryValue* shared_paths = update.Get();
+  base::Value vms(base::Value::Type::LIST);
+  vms.GetList().emplace_back(base::Value("vm-running"));
+  shared_paths->SetKey(shared_path_.value(), std::move(vms));
+  crostini_share_path_->UnsharePath(
       "vm-running", shared_path_, true,
       base::BindOnce(&CrostiniSharePathTest::UnsharePathCallback,
                      base::Unretained(this), shared_path_, Persist::NO,
@@ -678,7 +700,7 @@
 TEST_F(CrostiniSharePathTest, UnsharePathRoot) {
   features_.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
   SetUpVolume();
-  crostini_share_path()->UnsharePath(
+  crostini_share_path_->UnsharePath(
       "vm-running", root_, true,
       base::BindOnce(&CrostiniSharePathTest::UnsharePathCallback,
                      base::Unretained(this), root_, Persist::NO,
@@ -688,7 +710,13 @@
 
 TEST_F(CrostiniSharePathTest, UnsharePathVmNotRunning) {
   SetUpVolume();
-  crostini_share_path()->UnsharePath(
+  DictionaryPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kGuestOSPathsSharedToVms);
+  base::DictionaryValue* shared_paths = update.Get();
+  base::Value vms(base::Value::Type::LIST);
+  vms.GetList().emplace_back(base::Value("vm-not-running"));
+  shared_paths->SetKey(shared_path_.value(), std::move(vms));
+  crostini_share_path_->UnsharePath(
       "vm-not-running", shared_path_, true,
       base::BindOnce(&CrostiniSharePathTest::UnsharePathCallback,
                      base::Unretained(this), shared_path_, Persist::NO,
@@ -700,7 +728,7 @@
 TEST_F(CrostiniSharePathTest, UnsharePathInvalidPath) {
   SetUpVolume();
   base::FilePath invalid("invalid/path");
-  crostini_share_path()->UnsharePath(
+  crostini_share_path_->UnsharePath(
       "vm-running", invalid, true,
       base::BindOnce(&CrostiniSharePathTest::UnsharePathCallback,
                      base::Unretained(this), invalid, Persist::NO,
@@ -709,58 +737,72 @@
   run_loop()->Run();
 }
 
-TEST_F(CrostiniSharePathTest, GetPersistedSharedPaths) {
+TEST_F(CrostiniSharePathTest, MigratePersistedPathsToMultiVM) {
   SetUpVolume();
   base::ListValue shared_paths = base::ListValue();
   base::FilePath downloads_file = profile()->GetPath().Append("Downloads/file");
   shared_paths.AppendString(downloads_file.value());
   base::FilePath not_downloads("/not/downloads");
   shared_paths.AppendString(not_downloads.value());
-  std::string prefstr;
-  // MyFilesVolume disabled.
-  // Return prefs unchanged.
-  {
-    base::test::ScopedFeatureList features;
-    features.InitWithFeatures({}, {chromeos::features::kMyFilesVolume});
-    storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
-        file_manager::util::GetDownloadsMountPointName(profile()));
-    profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
-    std::vector<base::FilePath> paths =
-        crostini_share_path()->GetPersistedSharedPaths();
-    EXPECT_EQ(paths.size(), 2U);
-    EXPECT_EQ(paths[0], downloads_file);
-    EXPECT_EQ(paths[1], not_downloads);
-    const base::ListValue* prefs =
-        profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-    EXPECT_EQ(prefs->GetSize(), 2U);
-    prefs->GetString(0, &prefstr);
-    EXPECT_EQ(prefstr, downloads_file.value());
-    prefs->GetString(1, &prefstr);
-    EXPECT_EQ(prefstr, not_downloads.value());
-  }
-  // MyFilesVolume enabled.
-  // Migrate prefs and return.
-  {
-    base::test::ScopedFeatureList features;
-    features.InitWithFeatures({chromeos::features::kMyFilesVolume}, {});
-    storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
-        file_manager::util::GetDownloadsMountPointName(profile()));
-    profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
-    base::FilePath myfiles_file =
-        profile()->GetPath().Append("MyFiles/Downloads/file");
-    std::vector<base::FilePath> paths =
-        crostini_share_path()->GetPersistedSharedPaths();
-    EXPECT_EQ(paths.size(), 2U);
-    EXPECT_EQ(paths[0], myfiles_file);
-    EXPECT_EQ(paths[1], not_downloads);
-    const base::ListValue* prefs =
-        profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
-    EXPECT_EQ(prefs->GetSize(), 2U);
-    prefs->GetString(0, &prefstr);
-    EXPECT_EQ(prefstr, myfiles_file.value());
-    prefs->GetString(1, &prefstr);
-    EXPECT_EQ(prefstr, not_downloads.value());
-  }
+  profile()->GetPrefs()->Set(prefs::kCrostiniSharedPaths, shared_paths);
+  CrostiniSharePath::MigratePersistedPathsToMultiVM(profile()->GetPrefs());
+  EXPECT_EQ(
+      profile()->GetPrefs()->GetList(prefs::kCrostiniSharedPaths)->GetSize(),
+      0U);
+  const base::DictionaryValue* prefs =
+      profile()->GetPrefs()->GetDictionary(prefs::kGuestOSPathsSharedToVms);
+  EXPECT_EQ(prefs->size(), 2U);
+  EXPECT_EQ(prefs->FindKey(downloads_file.value())->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey(downloads_file.value())->GetList()[0].GetString(),
+            "termina");
+  EXPECT_EQ(prefs->FindKey(not_downloads.value())->GetList().size(), 1U);
+  EXPECT_EQ(prefs->FindKey(not_downloads.value())->GetList()[0].GetString(),
+            "termina");
+}
+
+TEST_F(CrostiniSharePathTest, GetPersistedSharedPaths) {
+  SetUpVolume();
+  // path1:['vm1'], path2:['vm2'], path3:['vm3'], path12:['vm1','vm2']
+  base::Value shared_paths(base::Value::Type::DICTIONARY);
+
+  base::FilePath path1("path1");
+  base::Value path1vms(base::Value::Type::LIST);
+  path1vms.GetList().emplace_back(base::Value("vm1"));
+  shared_paths.SetKey(path1.value(), std::move(path1vms));
+  base::FilePath path2("path2");
+  base::Value path2vms(base::Value::Type::LIST);
+  path2vms.GetList().emplace_back(base::Value("vm2"));
+  shared_paths.SetKey(path2.value(), std::move(path2vms));
+  base::FilePath path3("path3");
+  base::Value path3vms(base::Value::Type::LIST);
+  path3vms.GetList().emplace_back(base::Value("vm3"));
+  shared_paths.SetKey(path3.value(), std::move(path3vms));
+  base::FilePath path12("path12");
+  base::Value path12vms(base::Value::Type::LIST);
+  path12vms.GetList().emplace_back(base::Value("vm1"));
+  path12vms.GetList().emplace_back(base::Value("vm2"));
+  shared_paths.SetKey(path12.value(), std::move(path12vms));
+  profile()->GetPrefs()->Set(prefs::kGuestOSPathsSharedToVms, shared_paths);
+
+  std::vector<base::FilePath> paths =
+      crostini_share_path_->GetPersistedSharedPaths("vm1");
+  std::sort(paths.begin(), paths.end());
+  EXPECT_EQ(paths.size(), 2U);
+  EXPECT_EQ(paths[0], path1);
+  EXPECT_EQ(paths[1], path12);
+
+  paths = crostini_share_path_->GetPersistedSharedPaths("vm2");
+  std::sort(paths.begin(), paths.end());
+  EXPECT_EQ(paths.size(), 2U);
+  EXPECT_EQ(paths[0], path12);
+  EXPECT_EQ(paths[1], path2);
+
+  paths = crostini_share_path_->GetPersistedSharedPaths("vm3");
+  EXPECT_EQ(paths.size(), 1U);
+  EXPECT_EQ(paths[0], path3);
+
+  paths = crostini_share_path_->GetPersistedSharedPaths("vm4");
+  EXPECT_EQ(paths.size(), 0U);
 }
 
 TEST_F(CrostiniSharePathTest, ShareOnMountSuccessParentMount) {
@@ -771,7 +813,8 @@
   crostini_share_path_->set_mount_event_seneschal_callback_for_testing(
       base::BindRepeating(&CrostiniSharePathTest::MountEventSharePathCallback,
                           base::Unretained(this), "share-on-mount",
-                          shared_path_, Persist::NO, SeneschalClientCalled::YES,
+                          shared_path_, kCrostiniDefaultVmName, Persist::NO,
+                          SeneschalClientCalled::YES,
                           &vm_tools::seneschal::SharePathRequest::MY_FILES,
                           "already-shared", Success::YES, ""));
   crostini_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
@@ -789,7 +832,8 @@
   crostini_share_path_->set_mount_event_seneschal_callback_for_testing(
       base::BindRepeating(&CrostiniSharePathTest::MountEventSharePathCallback,
                           base::Unretained(this), "share-on-mount",
-                          shared_path_, Persist::NO, SeneschalClientCalled::YES,
+                          shared_path_, kCrostiniDefaultVmName, Persist::NO,
+                          SeneschalClientCalled::YES,
                           &vm_tools::seneschal::SharePathRequest::MY_FILES,
                           "already-shared", Success::YES, ""));
   crostini_share_path_->OnVolumeMounted(chromeos::MountError::MOUNT_ERROR_NONE,
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index d0d0a71..58acedc 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -556,7 +556,7 @@
       storage::ExternalMountPoints::GetSystemInstance()->GetRegisteredPath(
           file_manager::util::GetDownloadsMountPointName(browser()->profile()),
           &downloads));
-  // Setup prefs crostini.shared_paths.
+  // Setup prefs guest_os.paths_shared_to_vms.
   base::FilePath shared1 = downloads.AppendASCII("shared1");
   base::FilePath shared2 = downloads.AppendASCII("shared2");
   {
@@ -565,11 +565,12 @@
     ASSERT_TRUE(base::CreateDirectory(shared1));
     ASSERT_TRUE(base::CreateDirectory(shared2));
   }
-  base::ListValue shared_paths;
-  shared_paths.AppendString(shared1.value());
-  shared_paths.AppendString(shared2.value());
-  browser()->profile()->GetPrefs()->Set(crostini::prefs::kCrostiniSharedPaths,
-                                        shared_paths);
+  crostini::CrostiniSharePath* crostini_share_path =
+      crostini::CrostiniSharePath::GetForProfile(browser()->profile());
+  crostini_share_path->RegisterPersistedPath(crostini::kCrostiniDefaultVmName,
+                                             shared1);
+  crostini_share_path->RegisterPersistedPath(crostini::kCrostiniDefaultVmName,
+                                             shared2);
 
   ASSERT_TRUE(RunComponentExtensionTest("file_browser/crostini_test"));
 }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 48617d3..befea28 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -746,7 +746,8 @@
       crostini::CrostiniSharePath::GetForProfile(profile);
   bool first_for_session = params->observe_first_for_session &&
                            crostini_share_path->GetAndSetFirstForSession();
-  auto shared_paths = crostini_share_path->GetPersistedSharedPaths();
+  auto shared_paths = crostini_share_path->GetPersistedSharedPaths(
+      crostini::kCrostiniDefaultVmName);
   auto entries = std::make_unique<base::ListValue>();
   for (const base::FilePath& path : shared_paths) {
     std::string mount_name;
diff --git a/chrome/browser/chromeos/network_change_manager_client.cc b/chrome/browser/chromeos/network_change_manager_client.cc
index f64f37f..c3800e7c 100644
--- a/chrome/browser/chromeos/network_change_manager_client.cc
+++ b/chrome/browser/chromeos/network_change_manager_client.cc
@@ -12,6 +12,7 @@
 #include "chromeos/network/network_state_handler.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/common/network_service_util.h"
+#include "net/base/network_change_notifier.h"
 #include "net/base/network_change_notifier_posix.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 
@@ -19,8 +20,8 @@
 
 NetworkChangeManagerClient::NetworkChangeManagerClient(
     net::NetworkChangeNotifierPosix* network_change_notifier)
-    : connection_type_(net::NetworkChangeNotifier::CONNECTION_NONE),
-      connection_subtype_(net::NetworkChangeNotifier::SUBTYPE_NONE),
+    : connection_type_(net::NetworkChangeNotifier::GetConnectionType()),
+      connection_subtype_(net::NetworkChangeNotifier::GetConnectionSubtype()),
       network_change_notifier_(network_change_notifier) {
   PowerManagerClient::Get()->AddObserver(this);
   NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
diff --git a/chrome/browser/chromeos/network_change_manager_client.h b/chrome/browser/chromeos/network_change_manager_client.h
index 9731798..36cc507f 100644
--- a/chrome/browser/chromeos/network_change_manager_client.h
+++ b/chrome/browser/chromeos/network_change_manager_client.h
@@ -42,6 +42,8 @@
   friend class NetworkChangeManagerClientUpdateTest;
   FRIEND_TEST_ALL_PREFIXES(NetworkChangeManagerClientTest,
                            ConnectionTypeFromShill);
+  FRIEND_TEST_ALL_PREFIXES(NetworkChangeManagerClientTest,
+                           NetworkChangeNotifierConnectionTypeUpdated);
 
   void ConnectToNetworkChangeManager();
   void ReconnectToNetworkChangeManager();
diff --git a/chrome/browser/chromeos/network_change_manager_client_unittest.cc b/chrome/browser/chromeos/network_change_manager_client_unittest.cc
index 23e42a6..a2576eb6 100644
--- a/chrome/browser/chromeos/network_change_manager_client_unittest.cc
+++ b/chrome/browser/chromeos/network_change_manager_client_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/string_split.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/power/power_manager_client.h"
+#include "chromeos/dbus/shill/shill_service_client.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/network/network_state.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -106,6 +107,43 @@
   }
 }
 
+TEST(NetworkChangeManagerClientTest,
+     NetworkChangeNotifierConnectionTypeUpdated) {
+  // Create a NetworkChangeNotifier with a non-NONE connection type.
+  content::TestBrowserThreadBundle thread_bundle_;
+  std::unique_ptr<net::NetworkChangeNotifierPosix> network_change_notifier(
+      static_cast<net::NetworkChangeNotifierPosix*>(
+          net::NetworkChangeNotifier::Create()));
+  network_change_notifier->OnConnectionChanged(
+      net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
+            net::NetworkChangeNotifier::GetConnectionType());
+
+  // Initialize DBus and clear services so NetworkHandler thinks we're offline.
+  DBusThreadManager::Initialize();
+  PowerManagerClient::InitializeFake();
+  NetworkHandler::Initialize();
+  DBusThreadManager::Get()
+      ->GetShillServiceClient()
+      ->GetTestInterface()
+      ->ClearServices();
+
+  auto client = std::make_unique<NetworkChangeManagerClient>(
+      network_change_notifier.get());
+
+  // NetworkChangeManagerClient should have read the network state from DBus
+  // and notified NetworkChangeNotifier that we're offline.
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_NONE,
+            client->connection_type_);
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_NONE,
+            net::NetworkChangeNotifier::GetConnectionType());
+
+  client.reset();
+  NetworkHandler::Shutdown();
+  PowerManagerClient::Shutdown();
+  DBusThreadManager::Shutdown();
+}
+
 class NetworkChangeManagerClientUpdateTest : public testing::Test {
  protected:
   NetworkChangeManagerClientUpdateTest() : default_network_("") {}
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 57bac8d..8033e9e 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -371,8 +371,8 @@
   // Crostini
   (*s_whitelist)[crostini::prefs::kCrostiniEnabled] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
-  (*s_whitelist)[crostini::prefs::kCrostiniSharedPaths] =
-      settings_api::PrefType::PREF_TYPE_LIST;
+  (*s_whitelist)[crostini::prefs::kGuestOSPathsSharedToVms] =
+      settings_api::PrefType::PREF_TYPE_DICTIONARY;
   (*s_whitelist)[crostini::prefs::kCrostiniSharedUsbDevices] =
       settings_api::PrefType::PREF_TYPE_LIST;
   (*s_whitelist)[crostini::prefs::kCrostiniContainers] =
diff --git a/chrome/browser/extensions/convert_web_app.cc b/chrome/browser/extensions/convert_web_app.cc
index ce49f80..c47b89d 100644
--- a/chrome/browser/extensions/convert_web_app.cc
+++ b/chrome/browser/extensions/convert_web_app.cc
@@ -36,6 +36,7 @@
 #include "extensions/common/file_util.h"
 #include "extensions/common/image_util.h"
 #include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/file_handler_info.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/color_utils.h"
@@ -49,6 +50,45 @@
 namespace {
 const char kIconsDirName[] = "icons";
 const char kScopeUrlHandlerId[] = "scope";
+
+std::unique_ptr<base::DictionaryValue> CreateFileHandlersForBookmarkApp(
+    blink::Manifest::FileHandler file_handler) {
+  base::Value file_handlers(base::Value::Type::DICTIONARY);
+
+  for (const auto& handler : file_handler) {
+    base::Value file_handler(base::Value::Type::DICTIONARY);
+    file_handler.SetKey(keys::kFileHandlerIncludeDirectories,
+                        base::Value(false));
+    file_handler.SetKey(keys::kFileHandlerVerb,
+                        base::Value(extensions::file_handler_verbs::kOpenWith));
+
+    base::Value mime_types(base::Value::Type::LIST);
+    base::Value file_extensions(base::Value::Type::LIST);
+
+    for (const auto& acceptsUTF16 : handler.accept) {
+      std::string acceptsUTF8 = base::UTF16ToUTF8(acceptsUTF16);
+      if (acceptsUTF8.size() == 0)
+        continue;
+
+      if (acceptsUTF8[0] == '.') {
+        file_extensions.GetList().push_back(base::Value(acceptsUTF8.substr(1)));
+      } else {
+        mime_types.GetList().push_back(base::Value(acceptsUTF8));
+      }
+    }
+
+    file_handler.SetKey(keys::kFileHandlerTypes, std::move(mime_types));
+    file_handler.SetKey(keys::kFileHandlerExtensions,
+                        std::move(file_extensions));
+
+    file_handlers.SetKey(base::UTF16ToUTF8(handler.name),
+                         std::move(file_handler));
+  }
+
+  return base::DictionaryValue::From(
+      base::Value::ToUniquePtrValue(std::move(file_handlers)));
+}
+
 }  // namespace
 
 std::unique_ptr<base::DictionaryValue> CreateURLHandlersForBookmarkApp(
@@ -162,6 +202,11 @@
                                                 web_app.scope, web_app.title));
   }
 
+  if (web_app.file_handler) {
+    root->SetDictionary(keys::kFileHandlers, CreateFileHandlersForBookmarkApp(
+                                                 web_app.file_handler.value()));
+  }
+
   // Add the icons and linked icon information.
   auto icons = std::make_unique<base::DictionaryValue>();
   auto linked_icons = std::make_unique<base::ListValue>();
diff --git a/chrome/browser/extensions/convert_web_app_unittest.cc b/chrome/browser/extensions/convert_web_app_unittest.cc
index 6ff1e4e..25638de3 100644
--- a/chrome/browser/extensions/convert_web_app_unittest.cc
+++ b/chrome/browser/extensions/convert_web_app_unittest.cc
@@ -27,10 +27,12 @@
 #include "extensions/common/extension_icon_set.h"
 #include "extensions/common/extension_resource.h"
 #include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/file_handler_info.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/url_pattern.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "url/gurl.h"
@@ -409,4 +411,70 @@
   EXPECT_EQ(web_app.scope, GetScopeURLFromBookmarkApp(extension.get()));
 }
 
+// Tests that |file_handler| on the WebAppManifest is correctly converted
+// to |file_handlers| on an extension manifest.
+TEST(ExtensionFromWebApp, FileHandlersAreCorrectlyConverted) {
+  base::ScopedTempDir extensions_dir;
+  ASSERT_TRUE(extensions_dir.CreateUniqueTempDir());
+
+  WebApplicationInfo web_app;
+  web_app.title = base::ASCIIToUTF16("Graphr");
+  web_app.description = base::ASCIIToUTF16("A magical graphy thing");
+  web_app.app_url = GURL("https://not-a-real-site.not-a-tld/");
+  web_app.scope = GURL("https://not-a-real-site.not-a-tld/");
+
+  {
+    blink::Manifest::FileHandler file_handler;
+
+    blink::Manifest::FileFilter graph;
+    graph.name = base::ASCIIToUTF16("Graph");
+    graph.accept.push_back(base::ASCIIToUTF16("text/svg+xml"));
+    graph.accept.push_back(base::ASCIIToUTF16(""));
+    graph.accept.push_back(base::ASCIIToUTF16(".svg"));
+    file_handler.push_back(graph);
+
+    blink::Manifest::FileFilter raw;
+    raw.name = base::ASCIIToUTF16("Raw");
+    raw.accept.push_back(base::ASCIIToUTF16(".csv"));
+    raw.accept.push_back(base::ASCIIToUTF16("text/csv"));
+    file_handler.push_back(raw);
+
+    web_app.file_handler =
+        base::Optional<blink::Manifest::FileHandler>(std::move(file_handler));
+  }
+
+  scoped_refptr<Extension> extension = ConvertWebAppToExtension(
+      web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), extensions_dir.GetPath(),
+      Extension::NO_FLAGS, Manifest::INTERNAL);
+
+  ASSERT_TRUE(extension.get());
+
+  const std::vector<extensions::FileHandlerInfo> file_handler_info =
+      *extensions::FileHandlers::GetFileHandlers(extension.get());
+
+  EXPECT_EQ(2u, file_handler_info.size());
+
+  EXPECT_EQ("Graph", file_handler_info[0].id);
+  EXPECT_FALSE(file_handler_info[0].include_directories);
+  EXPECT_EQ(extensions::file_handler_verbs::kOpenWith,
+            file_handler_info[0].verb);
+  // Extensions should contain SVG, and only SVG
+  EXPECT_THAT(file_handler_info[0].extensions,
+              testing::UnorderedElementsAre("svg"));
+  // Mime types should contain text/svg+xml and only text/svg+xml
+  EXPECT_THAT(file_handler_info[0].types,
+              testing::UnorderedElementsAre("text/svg+xml"));
+
+  EXPECT_EQ("Raw", file_handler_info[1].id);
+  EXPECT_FALSE(file_handler_info[1].include_directories);
+  EXPECT_EQ(extensions::file_handler_verbs::kOpenWith,
+            file_handler_info[1].verb);
+  // Extensions should contain csv, and only csv
+  EXPECT_THAT(file_handler_info[1].extensions,
+              testing::UnorderedElementsAre("csv"));
+  // Mime types should contain text/csv and only text/csv
+  EXPECT_THAT(file_handler_info[1].types,
+              testing::UnorderedElementsAre("text/csv"));
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
index cdd9153e..6f4bf35 100644
--- a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
+++ b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
@@ -30,11 +30,9 @@
     : public extensions::ChromeUpdateClientConfig {
  public:
   TestChromeUpdateClientConfig(content::BrowserContext* context,
-                               bool use_JSON,
                                const std::vector<GURL>& update_url,
                                const std::vector<GURL>& ping_url)
       : extensions::ChromeUpdateClientConfig(context),
-        use_JSON_(use_JSON),
         update_url_(update_url),
         ping_url_(ping_url) {}
 
@@ -47,16 +45,13 @@
 
   std::unique_ptr<update_client::ProtocolHandlerFactory>
   GetProtocolHandlerFactory() const final {
-    if (use_JSON_)
-      return std::make_unique<update_client::ProtocolHandlerFactoryJSON>();
-    return std::make_unique<update_client::ProtocolHandlerFactoryXml>();
+    return std::make_unique<update_client::ProtocolHandlerFactoryJSON>();
   }
 
  protected:
   ~TestChromeUpdateClientConfig() override = default;
 
  private:
-  bool use_JSON_ = false;
   std::vector<GURL> update_url_;
   std::vector<GURL> ping_url_;
 
@@ -116,10 +111,9 @@
 
 }  // namespace
 
-ExtensionUpdateClientBaseTest::ExtensionUpdateClientBaseTest(bool use_JSON)
+ExtensionUpdateClientBaseTest::ExtensionUpdateClientBaseTest()
     : https_server_for_update_(net::EmbeddedTestServer::TYPE_HTTPS),
-      https_server_for_ping_(net::EmbeddedTestServer::TYPE_HTTPS),
-      use_JSON_(use_JSON) {}
+      https_server_for_ping_(net::EmbeddedTestServer::TYPE_HTTPS) {}
 
 ExtensionUpdateClientBaseTest::~ExtensionUpdateClientBaseTest() {}
 
@@ -135,12 +129,12 @@
 ExtensionUpdateClientBaseTest::ChromeUpdateClientConfigFactory() const {
   return base::BindRepeating(
       [](const std::vector<GURL>& update_url, const std::vector<GURL>& ping_url,
-         bool use_JSON, content::BrowserContext* context)
+         content::BrowserContext* context)
           -> scoped_refptr<ChromeUpdateClientConfig> {
         return base::MakeRefCounted<TestChromeUpdateClientConfig>(
-            context, use_JSON, update_url, ping_url);
+            context, update_url, ping_url);
       },
-      GetUpdateUrls(), GetPingUrls(), use_JSON_);
+      GetUpdateUrls(), GetPingUrls());
 }
 
 void ExtensionUpdateClientBaseTest::SetUp() {
diff --git a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
index dd17e73..68d6e87 100644
--- a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
+++ b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
@@ -40,7 +40,7 @@
  public:
   using ConfigFactoryCallback = ChromeUpdateClientConfig::FactoryCallback;
 
-  explicit ExtensionUpdateClientBaseTest(bool use_JSON);
+  ExtensionUpdateClientBaseTest();
   ~ExtensionUpdateClientBaseTest() override;
 
   // ExtensionBrowserTest:
@@ -87,8 +87,6 @@
   net::EmbeddedTestServer https_server_for_update_;
   net::EmbeddedTestServer https_server_for_ping_;
 
-  bool use_JSON_ = false;
-
  private:
   bool OnRequest(content::URLLoaderInterceptor::RequestParams* params);
 
diff --git a/chrome/browser/extensions/updater/update_service_browsertest.cc b/chrome/browser/extensions/updater/update_service_browsertest.cc
index 0edfb421..84970c1 100644
--- a/chrome/browser/extensions/updater/update_service_browsertest.cc
+++ b/chrome/browser/extensions/updater/update_service_browsertest.cc
@@ -40,10 +40,9 @@
 
 }  // namespace
 
-class UpdateServiceTest : public ExtensionUpdateClientBaseTest,
-                          public testing::WithParamInterface<bool> {
+class UpdateServiceTest : public ExtensionUpdateClientBaseTest {
  public:
-  UpdateServiceTest() : ExtensionUpdateClientBaseTest(GetParam()) {}
+  UpdateServiceTest() : ExtensionUpdateClientBaseTest() {}
   ~UpdateServiceTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -56,27 +55,15 @@
   bool ShouldEnableContentVerification() override { return true; }
 };
 
-// This test is parameterized for using JSON or XML serialization. |true| means
-// JSON serialization is used.
-INSTANTIATE_TEST_SUITE_P(Parameterized, UpdateServiceTest, testing::Bool());
-
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, NoUpdate) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, NoUpdate) {
   // Verify that UpdateService runs correctly when there's no update.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock a no-update response.
-  if (use_JSON_) {
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
-        test_data_dir_.AppendASCII(
-            "updater/updatecheck_reply_noupdate_1.json")));
-  } else {
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        test_data_dir_.AppendASCII(
-            "updater/updatecheck_reply_noupdate_1.xml")));
-  }
+  ASSERT_TRUE(update_interceptor_->ExpectRequest(
+      std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+      test_data_dir_.AppendASCII("updater/updatecheck_reply_noupdate_1.json")));
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   const Extension* extension =
@@ -117,37 +104,24 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
     EXPECT_TRUE(app.FindKey("enabled")->GetBool());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.10")", kExtensionId)));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
-  }
 }
 
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, UpdateCheckError) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UpdateCheckError) {
   // Verify that UpdateService works correctly when there's an error in the
   // update check phase.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock an update check error.
-  if (use_JSON_) {
     ASSERT_TRUE(update_interceptor_->ExpectRequest(
         std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
         net::HTTP_FORBIDDEN));
-  } else {
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        net::HTTP_FORBIDDEN));
-  }
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   const Extension* extension =
@@ -190,43 +164,27 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
     EXPECT_TRUE(app.FindKey("enabled")->GetBool());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.10")", kExtensionId)));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
-  }
 }
 
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, TwoUpdateCheckErrors) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, TwoUpdateCheckErrors) {
   // Verify that the UMA counters are emitted properly when there are 2 update
   // checks with different number of extensions, both of which result in errors.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock update check errors.
-  if (use_JSON_) {
     ASSERT_TRUE(update_interceptor_->ExpectRequest(
         std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
         net::HTTP_NOT_MODIFIED));
     ASSERT_TRUE(update_interceptor_->ExpectRequest(
         std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
         net::HTTP_USE_PROXY));
-  } else {
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        net::HTTP_NOT_MODIFIED));
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        net::HTTP_USE_PROXY));
-  }
 
   const base::FilePath crx_path1 = test_data_dir_.AppendASCII("updater/v1.crx");
   const base::FilePath crx_path2 = test_data_dir_.AppendASCII("updater/v2.crx");
@@ -276,12 +234,11 @@
       << ping_interceptor_->GetRequestsAsString();
 }
 
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, SuccessfulUpdate) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, SuccessfulUpdate) {
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock an update response.
-  if (use_JSON_) {
     const base::FilePath update_response =
         test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
     const base::FilePath ping_response =
@@ -292,18 +249,6 @@
     ASSERT_TRUE(ping_interceptor_->ExpectRequest(
         std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
         ping_response));
-  } else {
-    const base::FilePath update_response =
-        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-    const base::FilePath ping_response =
-        test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
-  }
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   set_interceptor_hook(base::BindLambdaForTesting(
@@ -354,28 +299,20 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
     EXPECT_EQ("0.10", app.FindKey("version")->GetString());
     EXPECT_TRUE(app.FindKey("enabled")->GetBool());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.10")", kExtensionId)));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
-  }
 }
 
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, PolicyCorrupted) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, PolicyCorrupted) {
   base::ScopedAllowBlockingForTesting allow_io;
 
   ExtensionSystem* system = ExtensionSystem::Get(profile());
   ExtensionService* service = extension_service();
 
-  if (use_JSON_) {
     const base::FilePath update_response =
         test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
     const base::FilePath ping_response =
@@ -386,18 +323,6 @@
     ASSERT_TRUE(ping_interceptor_->ExpectRequest(
         std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
         ping_response));
-  } else {
-    const base::FilePath update_response =
-        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-    const base::FilePath ping_response =
-        test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
-  }
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   set_interceptor_hook(base::BindLambdaForTesting(
@@ -460,7 +385,6 @@
   // - <disabled reason="1024"/>
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -472,21 +396,9 @@
     const auto& disabled = app.FindKey("disabled")->GetList()[0];
     EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
               disabled.FindKey("reason")->GetInt());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.0.0.0")", kExtensionId)));
-    EXPECT_THAT(
-        update_request,
-        ::testing::HasSubstr(
-            R"(installsource="reinstall" installedby="policy" enabled="0")"));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                    R"(<disabled reason="%d"/>)",
-                                    disable_reason::DISABLE_CORRUPTED)));
-  }
 }
 
-IN_PROC_BROWSER_TEST_P(UpdateServiceTest, UninstallExtensionWhileUpdating) {
+IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UninstallExtensionWhileUpdating) {
   // This test is to verify that the extension updater engine (update client)
   // works correctly when an extension is uninstalled when the extension updater
   // is in progress.
@@ -525,7 +437,7 @@
 class PolicyUpdateServiceTest : public ExtensionUpdateClientBaseTest,
                                 public testing::WithParamInterface<bool> {
  public:
-  PolicyUpdateServiceTest() : ExtensionUpdateClientBaseTest(GetParam()) {}
+  PolicyUpdateServiceTest() : ExtensionUpdateClientBaseTest() {}
   ~PolicyUpdateServiceTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -574,7 +486,6 @@
                                                        params->client.get());
           return true;
         }));
-    if (use_JSON_) {
       const base::FilePath update_response =
           test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
       const base::FilePath ping_response =
@@ -604,37 +515,6 @@
       ASSERT_TRUE(ping_interceptor_->ExpectRequest(
           std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
           ping_response));
-    } else {
-      const base::FilePath update_response =
-          test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-      const base::FilePath ping_response =
-          test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-
-      ASSERT_TRUE(update_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-          update_response));
-      ASSERT_TRUE(update_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-          update_response));
-      ASSERT_TRUE(update_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-          update_response));
-      ASSERT_TRUE(update_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-          update_response));
-      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("eventtype"),
-          ping_response));
-      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("eventtype"),
-          ping_response));
-      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("eventtype"),
-          ping_response));
-      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-          std::make_unique<update_client::PartialMatch>("eventtype"),
-          ping_response));
-    }
   }
 
   std::vector<GURL> GetUpdateUrls() const override {
@@ -655,16 +535,10 @@
   content_verifier_test::DownloaderTestDelegate downloader_;
 };
 
-// This test is parameterized for using JSON or XML serialization. |true| means
-// JSON serialization is used.
-INSTANTIATE_TEST_SUITE_P(Parameterized,
-                         PolicyUpdateServiceTest,
-                         testing::Bool());
-
 // Tests that if CheckForExternalUpdates() fails, then we retry reinstalling
 // corrupted policy extensions. For example: if network is unavailable,
 // CheckForExternalUpdates() will fail.
-IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, FailedUpdateRetries) {
+IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, FailedUpdateRetries) {
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
   ExtensionService* service = extension_service();
   ContentVerifier* verifier =
@@ -711,7 +585,6 @@
   // - <disabled reason="1024"/>
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -723,21 +596,9 @@
     const auto& disabled = app.FindKey("disabled")->GetList()[0];
     EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
               disabled.FindKey("reason")->GetInt());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
-    EXPECT_THAT(
-        update_request,
-        ::testing::HasSubstr(
-            R"(installsource="reinstall" installedby="policy" enabled="0")"));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                    R"(<disabled reason="%d"/>)",
-                                    disable_reason::DISABLE_CORRUPTED)));
-  }
 }
 
-IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, Backoff) {
+IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, Backoff) {
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
   ContentVerifier* verifier =
       ExtensionSystem::Get(profile())->content_verifier();
@@ -789,7 +650,7 @@
 
 // We want to test what happens at startup with a corroption-disabled policy
 // force installed extension. So we set that up in the PRE test here.
-IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, PRE_PolicyCorruptedOnStartup) {
+IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, PRE_PolicyCorruptedOnStartup) {
   // This is to not allow any corrupted resintall to proceed.
   content_verifier_test::DelayTracker delay_tracker;
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
@@ -818,7 +679,7 @@
 }
 
 // Now actually test what happens on the next startup after the PRE test above.
-IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, PolicyCorruptedOnStartup) {
+IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, PolicyCorruptedOnStartup) {
   // Depdending on timing, the extension may have already been reinstalled
   // between SetUpInProcessBrowserTestFixture and now (usually not during local
   // testing on a developer machine, but sometimes on a heavily loaded system
@@ -843,7 +704,6 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(update_request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -855,18 +715,6 @@
     const auto& disabled = app.FindKey("disabled")->GetList()[0];
     EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
               disabled.FindKey("reason")->GetInt());
-  } else {
-    EXPECT_THAT(update_request,
-                ::testing::HasSubstr(base::StringPrintf(
-                    R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
-    EXPECT_THAT(
-        update_request,
-        ::testing::HasSubstr(
-            R"(installsource="reinstall" installedby="policy" enabled="0")"));
-    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                    R"(<disabled reason="%d"/>)",
-                                    disable_reason::DISABLE_CORRUPTED)));
-  }
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/loader/chrome_navigation_data.cc b/chrome/browser/loader/chrome_navigation_data.cc
index 227fac7..575f2d483 100644
--- a/chrome/browser/loader/chrome_navigation_data.cc
+++ b/chrome/browser/loader/chrome_navigation_data.cc
@@ -5,33 +5,56 @@
 #include "chrome/browser/loader/chrome_navigation_data.h"
 
 #include "base/memory/ptr_util.h"
+#include "base/supports_user_data.h"
 #include "net/url_request/url_request.h"
 
 const void* const kChromeNavigationDataUserDataKey =
     &kChromeNavigationDataUserDataKey;
 
+namespace {
+
+// UserData object that owns ChromeNavigationData. This is used rather than
+// having ChromeNavigationData directly extend base::SupportsUserData::Data to
+// avoid naming conflicts between Data::Clone() and
+// content::NavigationData::Clone().
+class NavigationDataOwner : public base::SupportsUserData::Data {
+ public:
+  NavigationDataOwner() = default;
+  ~NavigationDataOwner() override = default;
+
+  ChromeNavigationData* data() { return &data_; }
+
+ private:
+  ChromeNavigationData data_;
+
+  DISALLOW_COPY_AND_ASSIGN(NavigationDataOwner);
+};
+
+}  // namespace
+
 ChromeNavigationData::ChromeNavigationData() {}
 
 ChromeNavigationData::~ChromeNavigationData() {}
 
+// static
 ChromeNavigationData* ChromeNavigationData::GetDataAndCreateIfNecessary(
     net::URLRequest* request) {
   if (!request)
     return nullptr;
-  ChromeNavigationData* data = static_cast<ChromeNavigationData*>(
+  NavigationDataOwner* data_owner_ptr = static_cast<NavigationDataOwner*>(
       request->GetUserData(kChromeNavigationDataUserDataKey));
-  if (data)
-    return data;
-  data = new ChromeNavigationData();
-  request->SetUserData(kChromeNavigationDataUserDataKey,
-                       base::WrapUnique(data));
-  return data;
+  if (data_owner_ptr)
+    return data_owner_ptr->data();
+  std::unique_ptr<NavigationDataOwner> data_owner =
+      std::make_unique<NavigationDataOwner>();
+  data_owner_ptr = data_owner.get();
+  request->SetUserData(kChromeNavigationDataUserDataKey, std::move(data_owner));
+  return data_owner_ptr->data();
 }
 
 std::unique_ptr<content::NavigationData> ChromeNavigationData::Clone() const {
   std::unique_ptr<ChromeNavigationData> copy(new ChromeNavigationData());
-  if (data_reduction_proxy_data_) {
+  if (data_reduction_proxy_data_)
     copy->SetDataReductionProxyData(data_reduction_proxy_data_->DeepCopy());
-  }
   return std::move(copy);
 }
diff --git a/chrome/browser/loader/chrome_navigation_data.h b/chrome/browser/loader/chrome_navigation_data.h
index 854be05..cb7672f 100644
--- a/chrome/browser/loader/chrome_navigation_data.h
+++ b/chrome/browser/loader/chrome_navigation_data.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/supports_user_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "content/public/browser/navigation_data.h"
 
@@ -16,8 +15,7 @@
 class URLRequest;
 }
 
-class ChromeNavigationData : public content::NavigationData,
-                             public base::SupportsUserData::Data {
+class ChromeNavigationData : public content::NavigationData {
  public:
   ChromeNavigationData();
   ~ChromeNavigationData() override;
diff --git a/chrome/browser/notifications/notification_schedule_service_factory.cc b/chrome/browser/notifications/notification_schedule_service_factory.cc
index baa6860..a259c32 100644
--- a/chrome/browser/notifications/notification_schedule_service_factory.cc
+++ b/chrome/browser/notifications/notification_schedule_service_factory.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/notifications/notification_schedule_service_factory.h"
 
-#include "chrome/browser/notifications/scheduler/notification_schedule_service_impl.h"
+#include "chrome/browser/notifications/notification_background_task_scheduler_impl.h"
+#include "chrome/browser/notifications/scheduler/notification_schedule_service.h"
+#include "chrome/browser/notifications/scheduler/notification_scheduler_context.h"
+#include "chrome/browser/notifications/scheduler/schedule_service_factory_helper.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
@@ -33,9 +36,12 @@
 
 KeyedService* NotificationScheduleServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  // TODO(xingliu): Build the actual instance here.
-  return static_cast<KeyedService*>(
-      new notifications::NotificationScheduleServiceImpl());
+  // Pass all dependencies to notification scheduler and build the service
+  // instance.
+  auto background_task_scheduler =
+      std::make_unique<NotificationBackgroundTaskSchedulerImpl>();
+  return notifications::CreateNotificationScheduleService(
+      std::move(background_task_scheduler));
 }
 
 content::BrowserContext*
diff --git a/chrome/browser/notifications/scheduler/BUILD.gn b/chrome/browser/notifications/scheduler/BUILD.gn
index 53c9dd0..a3af336 100644
--- a/chrome/browser/notifications/scheduler/BUILD.gn
+++ b/chrome/browser/notifications/scheduler/BUILD.gn
@@ -9,10 +9,8 @@
 }
 
 group("scheduler") {
-  # TODO(xingliu): Change this to a source set when we have code to be used by
-  # NotificationScheduleServiceFactory.
   deps = [
-    ":lib",
+    ":factory",
   ]
 
   public_deps = [
@@ -39,9 +37,25 @@
   ]
 }
 
+# The embedder can depend on this target to get the internal library.
+source_set("factory") {
+  sources = [
+    "schedule_service_factory_helper.cc",
+    "schedule_service_factory_helper.h",
+  ]
+
+  deps = [
+    ":lib",
+    ":public",
+    "//base",
+    "//components/keyed_service/core",
+  ]
+}
+
 # Internal library that embedders should not directly depend on.
 source_set("lib") {
   visibility = [
+    ":factory",
     ":scheduler",
     ":unit_tests",
     "//chrome/browser/notifications/scheduler/test:test_lib",
@@ -66,6 +80,8 @@
     "notification_entry.h",
     "notification_schedule_service_impl.cc",
     "notification_schedule_service_impl.h",
+    "notification_scheduler.cc",
+    "notification_scheduler.h",
     "notification_scheduler_context.cc",
     "notification_scheduler_context.h",
     "proto_conversion.cc",
diff --git a/chrome/browser/notifications/scheduler/display_decider.cc b/chrome/browser/notifications/scheduler/display_decider.cc
index 574a3643..6a948a4 100644
--- a/chrome/browser/notifications/scheduler/display_decider.cc
+++ b/chrome/browser/notifications/scheduler/display_decider.cc
@@ -75,9 +75,7 @@
       // TODO(xingliu): Ensure deprecated clients will not have data in storage.
       DCHECK(std::find(clients_.begin(), clients_.end(), client_state->type) !=
              clients_.end());
-      for (const auto& impression_it : client_state->impressions) {
-        const auto& impression = impression_it.second;
-
+      for (const auto& impression : client_state->impressions) {
         // Tracks last notification shown to the user.
         if (impression.create_time > last_shown_time) {
           last_shown_time = impression.create_time;
diff --git a/chrome/browser/notifications/scheduler/icon_store.cc b/chrome/browser/notifications/scheduler/icon_store.cc
index 004964a..60245c0 100644
--- a/chrome/browser/notifications/scheduler/icon_store.cc
+++ b/chrome/browser/notifications/scheduler/icon_store.cc
@@ -11,12 +11,12 @@
 
 namespace leveldb_proto {
 
-void DataToProto(const notifications::IconEntry& icon_entry,
+void DataToProto(notifications::IconEntry* icon_entry,
                  notifications::proto::Icon* proto) {
   IconEntryToProto(icon_entry, proto);
 }
 
-void ProtoToData(const notifications::proto::Icon& proto,
+void ProtoToData(notifications::proto::Icon* proto,
                  notifications::IconEntry* icon_entry) {
   IconProtoToEntry(proto, icon_entry);
 }
diff --git a/chrome/browser/notifications/scheduler/icon_store.h b/chrome/browser/notifications/scheduler/icon_store.h
index 70f117c..a05645b 100644
--- a/chrome/browser/notifications/scheduler/icon_store.h
+++ b/chrome/browser/notifications/scheduler/icon_store.h
@@ -17,10 +17,10 @@
 
 // Forward declaration for proto conversion.
 namespace leveldb_proto {
-void DataToProto(const notifications::IconEntry& icon_entry,
+void DataToProto(notifications::IconEntry* icon_entry,
                  notifications::proto::Icon* proto);
 
-void ProtoToData(const notifications::proto::Icon& proto,
+void ProtoToData(notifications::proto::Icon* proto,
                  notifications::IconEntry* icon_entry);
 }  // namespace leveldb_proto
 
diff --git a/chrome/browser/notifications/scheduler/icon_store_unittest.cc b/chrome/browser/notifications/scheduler/icon_store_unittest.cc
index 77f63b5..c15c4c8 100644
--- a/chrome/browser/notifications/scheduler/icon_store_unittest.cc
+++ b/chrome/browser/notifications/scheduler/icon_store_unittest.cc
@@ -36,7 +36,7 @@
     entry.uuid = kEntryId;
     entry.data = kEntryData;
     proto::Icon proto;
-    leveldb_proto::DataToProto(entry, &proto);
+    leveldb_proto::DataToProto(&entry, &proto);
     db_entries_.emplace(kEntryKey, proto);
 
     auto db =
diff --git a/chrome/browser/notifications/scheduler/impression_history_tracker.cc b/chrome/browser/notifications/scheduler/impression_history_tracker.cc
index 6052a5a..c482f21 100644
--- a/chrome/browser/notifications/scheduler/impression_history_tracker.cc
+++ b/chrome/browser/notifications/scheduler/impression_history_tracker.cc
@@ -36,7 +36,7 @@
 
   for (auto it = client_state->impressions.begin();
        it != client_state->impressions.end();) {
-    auto* impression = &it->second;
+    auto* impression = &*it;
 
     // Prune out expired impression.
     if (now - impression->create_time > config_.impression_expiration) {
diff --git a/chrome/browser/notifications/scheduler/impression_history_tracker_unittest.cc b/chrome/browser/notifications/scheduler/impression_history_tracker_unittest.cc
index 2f4b8c1..a199a4a0 100644
--- a/chrome/browser/notifications/scheduler/impression_history_tracker_unittest.cc
+++ b/chrome/browser/notifications/scheduler/impression_history_tracker_unittest.cc
@@ -35,8 +35,10 @@
     DCHECK(output_it != output.end());
     EXPECT_EQ(*expected.second, *output_it->second)
         << "Unmatch client states: \n"
-        << "Expected:" << expected.second->DebugPrint() << " \n"
-        << "Acutual: " << output_it->second->DebugPrint();
+        << "Expected: \n"
+        << expected.second->DebugPrint() << " \n"
+        << "Acutual: \n"
+        << output_it->second->DebugPrint();
   }
 }
 
@@ -87,16 +89,24 @@
   TestCase test_case;
   auto expired_create_time = base::Time::Now() - base::TimeDelta::FromDays(1) -
                              config().impression_expiration;
+  auto not_expired_time = base::Time::Now() + base::TimeDelta::FromDays(1) -
+                          config().impression_expiration;
+  Impression expired{expired_create_time, UserFeedback::kUnknown,
+                     ImpressionResult::kUnknown, false /* integrated */};
+  Impression not_expired{not_expired_time, UserFeedback::kUnknown,
+                         ImpressionResult::kUnknown, false /* integrated */};
+
   test_case.input = {{SchedulerClientType::kTest1,
                       2 /* current_max_daily_show */,
-                      {{expired_create_time, UserFeedback::kUnknown,
-                        ImpressionResult::kUnknown, false /* integrated */}},
+                      {expired, expired, not_expired},
                       base::nullopt /* suppression_info */}};
 
   // Expired impression created in |expired_create_time| should be deleted.
+  // No change expected on the next impression, which is not expired and no user
+  // feedback .
   test_case.expected = {{SchedulerClientType::kTest1,
                          2 /* current_max_daily_show */,
-                         {},
+                         {not_expired},
                          base::nullopt /* suppression_info */}};
 
   RunTestCase(std::move(test_case));
diff --git a/chrome/browser/notifications/scheduler/impression_types.cc b/chrome/browser/notifications/scheduler/impression_types.cc
index 92ca5f5..efdf022 100644
--- a/chrome/browser/notifications/scheduler/impression_types.cc
+++ b/chrome/browser/notifications/scheduler/impression_types.cc
@@ -46,8 +46,7 @@
       "impressions.size(): %zu \n",
       static_cast<int>(type), current_max_daily_show, impressions.size());
 
-  for (const auto& it : impressions) {
-    const auto& impression = it.second;
+  for (const auto& impression : impressions) {
     std::ostringstream stream;
     stream << "Impression, create_time:" << impression.create_time << " \n"
            << "feedback: " << static_cast<int>(impression.feedback) << " \n"
diff --git a/chrome/browser/notifications/scheduler/impression_types.h b/chrome/browser/notifications/scheduler/impression_types.h
index 4627197..53f2c31d 100644
--- a/chrome/browser/notifications/scheduler/impression_types.h
+++ b/chrome/browser/notifications/scheduler/impression_types.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_IMPRESSION_TYPES_H_
 #define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_IMPRESSION_TYPES_H_
 
+#include <deque>
 #include <map>
 
 #include "base/optional.h"
@@ -69,7 +70,7 @@
 // to the user and the history of user interactions to a particular notification
 // client.
 struct ClientState {
-  using Impressions = std::map<base::Time, Impression>;
+  using Impressions = std::deque<Impression>;
   explicit ClientState(SchedulerClientType type);
   explicit ClientState(const ClientState& other);
   ~ClientState();
diff --git a/chrome/browser/notifications/scheduler/notification_schedule_service_impl.cc b/chrome/browser/notifications/scheduler/notification_schedule_service_impl.cc
index 4ba86c4e..fa5dd88 100644
--- a/chrome/browser/notifications/scheduler/notification_schedule_service_impl.cc
+++ b/chrome/browser/notifications/scheduler/notification_schedule_service_impl.cc
@@ -4,20 +4,22 @@
 
 #include "chrome/browser/notifications/scheduler/notification_schedule_service_impl.h"
 
-#include <memory>
-
 #include "base/logging.h"
 #include "chrome/browser/notifications/scheduler/notification_params.h"
+#include "chrome/browser/notifications/scheduler/notification_scheduler.h"
+#include "chrome/browser/notifications/scheduler/notification_scheduler_context.h"
 
 namespace notifications {
 
-NotificationScheduleServiceImpl::NotificationScheduleServiceImpl() = default;
+NotificationScheduleServiceImpl::NotificationScheduleServiceImpl(
+    std::unique_ptr<NotificationSchedulerContext> context)
+    : scheduler_(NotificationScheduler::Create(std::move(context))) {}
 
 NotificationScheduleServiceImpl::~NotificationScheduleServiceImpl() = default;
 
 void NotificationScheduleServiceImpl::Schedule(
     std::unique_ptr<NotificationParams> notification_params) {
-  NOTIMPLEMENTED();
+  scheduler_->Schedule(std::move(notification_params));
 }
 
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/notification_schedule_service_impl.h b/chrome/browser/notifications/scheduler/notification_schedule_service_impl.h
index 5b394c3..46b14e0 100644
--- a/chrome/browser/notifications/scheduler/notification_schedule_service_impl.h
+++ b/chrome/browser/notifications/scheduler/notification_schedule_service_impl.h
@@ -12,11 +12,14 @@
 
 namespace notifications {
 
+class NotificationScheduler;
+class NotificationSchedulerContext;
 struct NotificationParams;
 
 class NotificationScheduleServiceImpl : public NotificationScheduleService {
  public:
-  NotificationScheduleServiceImpl();
+  NotificationScheduleServiceImpl(
+      std::unique_ptr<NotificationSchedulerContext> context);
   ~NotificationScheduleServiceImpl() override;
 
  private:
@@ -24,6 +27,9 @@
   void Schedule(
       std::unique_ptr<NotificationParams> notification_params) override;
 
+  // Provides the actual notification scheduling functionalities.
+  std::unique_ptr<NotificationScheduler> scheduler_;
+
   DISALLOW_COPY_AND_ASSIGN(NotificationScheduleServiceImpl);
 };
 
diff --git a/chrome/browser/notifications/scheduler/notification_scheduler.cc b/chrome/browser/notifications/scheduler/notification_scheduler.cc
new file mode 100644
index 0000000..af50f3b
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/notification_scheduler.cc
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/notifications/scheduler/notification_scheduler.h"
+
+#include "base/logging.h"
+#include "chrome/browser/notifications/scheduler/notification_params.h"
+#include "chrome/browser/notifications/scheduler/notification_scheduler_context.h"
+
+namespace notifications {
+namespace {
+
+// Implementation of NotificationScheduler.
+class NotificationSchedulerImpl : public NotificationScheduler {
+ public:
+  NotificationSchedulerImpl(
+      std::unique_ptr<NotificationSchedulerContext> context)
+      : context_(std::move(context)) {}
+
+  ~NotificationSchedulerImpl() override = default;
+
+ private:
+  // NotificationScheduler implementation.
+  void Schedule(
+      std::unique_ptr<NotificationParams> notification_params) override {
+    NOTIMPLEMENTED();
+  }
+
+  std::unique_ptr<NotificationSchedulerContext> context_;
+
+  DISALLOW_COPY_AND_ASSIGN(NotificationSchedulerImpl);
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<NotificationScheduler> NotificationScheduler::Create(
+    std::unique_ptr<NotificationSchedulerContext> context) {
+  return std::make_unique<NotificationSchedulerImpl>(std::move(context));
+}
+
+NotificationScheduler::NotificationScheduler() = default;
+
+NotificationScheduler::~NotificationScheduler() = default;
+
+}  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/notification_scheduler.h b/chrome/browser/notifications/scheduler/notification_scheduler.h
new file mode 100644
index 0000000..5605ce3
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/notification_scheduler.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_NOTIFICATION_SCHEDULER_H_
+#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_NOTIFICATION_SCHEDULER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace notifications {
+
+class NotificationSchedulerContext;
+struct NotificationParams;
+
+// Provides notification scheduling and throttling functionalities. This class
+// glues all the subsystems together for notification scheduling system.
+class NotificationScheduler {
+ public:
+  static std::unique_ptr<NotificationScheduler> Create(
+      std::unique_ptr<NotificationSchedulerContext> context);
+
+  NotificationScheduler();
+  virtual ~NotificationScheduler();
+
+  // Schedules a notification to show in the future. Throttling logic may apply
+  // based on |notification_params|.
+  virtual void Schedule(
+      std::unique_ptr<NotificationParams> notification_params) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NotificationScheduler);
+};
+
+}  // namespace notifications
+
+#endif  // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_NOTIFICATION_SCHEDULER_H_
diff --git a/chrome/browser/notifications/scheduler/notification_scheduler_context.cc b/chrome/browser/notifications/scheduler/notification_scheduler_context.cc
index cc939b9..cde8211 100644
--- a/chrome/browser/notifications/scheduler/notification_scheduler_context.cc
+++ b/chrome/browser/notifications/scheduler/notification_scheduler_context.cc
@@ -5,18 +5,16 @@
 #include "chrome/browser/notifications/scheduler/notification_scheduler_context.h"
 
 #include "chrome/browser/notifications/scheduler/notification_background_task_scheduler.h"
+#include "chrome/browser/notifications/scheduler/scheduler_config.h"
 
 namespace notifications {
 
 NotificationSchedulerContext::NotificationSchedulerContext(
-    std::unique_ptr<NotificationBackgroundTaskScheduler> scheduler)
-    : background_task_scheduler_(std::move(scheduler)) {}
+    std::unique_ptr<NotificationBackgroundTaskScheduler> scheduler,
+    std::unique_ptr<SchedulerConfig> config)
+    : background_task_scheduler_(std::move(scheduler)),
+      config_(std::move(config)) {}
 
 NotificationSchedulerContext::~NotificationSchedulerContext() = default;
 
-NotificationBackgroundTaskScheduler*
-NotificationSchedulerContext::GetBackgroundTaskScheduler() {
-  return background_task_scheduler_.get();
-}
-
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/notification_scheduler_context.h b/chrome/browser/notifications/scheduler/notification_scheduler_context.h
index bf4ddbf..a5738db3 100644
--- a/chrome/browser/notifications/scheduler/notification_scheduler_context.h
+++ b/chrome/browser/notifications/scheduler/notification_scheduler_context.h
@@ -12,6 +12,7 @@
 namespace notifications {
 
 class NotificationBackgroundTaskScheduler;
+struct SchedulerConfig;
 
 // Context that contains necessary components needed by the notification
 // scheduler to perform tasks.
@@ -20,14 +21,22 @@
 class NotificationSchedulerContext {
  public:
   NotificationSchedulerContext(
-      std::unique_ptr<NotificationBackgroundTaskScheduler> scheduler);
+      std::unique_ptr<NotificationBackgroundTaskScheduler> scheduler,
+      std::unique_ptr<SchedulerConfig> config);
   ~NotificationSchedulerContext();
 
-  NotificationBackgroundTaskScheduler* GetBackgroundTaskScheduler();
+  // Gets the background task scheduler.
+  NotificationBackgroundTaskScheduler* background_task_scheduler() {
+    return background_task_scheduler_.get();
+  }
+
+  // Gets system configuration.
+  const SchedulerConfig* config() const { return config_.get(); }
 
  private:
   std::unique_ptr<NotificationBackgroundTaskScheduler>
       background_task_scheduler_;
+  std::unique_ptr<SchedulerConfig> config_;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationSchedulerContext);
 };
diff --git a/chrome/browser/notifications/scheduler/proto_conversion.cc b/chrome/browser/notifications/scheduler/proto_conversion.cc
index 928f940..c42457c 100644
--- a/chrome/browser/notifications/scheduler/proto_conversion.cc
+++ b/chrome/browser/notifications/scheduler/proto_conversion.cc
@@ -11,20 +11,16 @@
 
 namespace notifications {
 
-void IconEntryToProto(const IconEntry& entry,
-                      notifications::proto::Icon* proto) {
-  proto->set_uuid(entry.uuid);
-
-  // Copy large chunk of data to proto.
-  proto->set_icon(entry.data);
+void IconEntryToProto(IconEntry* entry, notifications::proto::Icon* proto) {
+  proto->mutable_uuid()->swap(entry->uuid);
+  proto->mutable_icon()->swap(entry->data);
 }
 
-void IconProtoToEntry(const proto::Icon& proto,
-                      notifications::IconEntry* entry) {
-  DCHECK(proto.has_uuid());
-  DCHECK(proto.has_icon());
-  entry->data = proto.icon();
-  entry->uuid = proto.uuid();
+void IconProtoToEntry(proto::Icon* proto, notifications::IconEntry* entry) {
+  DCHECK(proto->has_uuid());
+  DCHECK(proto->has_icon());
+  entry->data.swap(*proto->mutable_icon());
+  entry->uuid.swap(*proto->mutable_uuid());
 }
 
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/proto_conversion.h b/chrome/browser/notifications/scheduler/proto_conversion.h
index c0ef3c6f..eee7422 100644
--- a/chrome/browser/notifications/scheduler/proto_conversion.h
+++ b/chrome/browser/notifications/scheduler/proto_conversion.h
@@ -14,12 +14,10 @@
 namespace notifications {
 
 // Converts an icon entry to icon proto.
-void IconEntryToProto(const IconEntry& entry,
-                      notifications::proto::Icon* proto);
+void IconEntryToProto(IconEntry* entry, notifications::proto::Icon* proto);
 
 // Converts an icon proto to icon entry.
-void IconProtoToEntry(const proto::Icon& proto,
-                      notifications::IconEntry* entry);
+void IconProtoToEntry(proto::Icon* proto, notifications::IconEntry* entry);
 
 }  // namespace notifications
 
diff --git a/chrome/browser/notifications/scheduler/proto_conversion_unittest.cc b/chrome/browser/notifications/scheduler/proto_conversion_unittest.cc
index e4d5ae0d..ba37e9ad 100644
--- a/chrome/browser/notifications/scheduler/proto_conversion_unittest.cc
+++ b/chrome/browser/notifications/scheduler/proto_conversion_unittest.cc
@@ -21,7 +21,7 @@
   proto.set_icon(kData);
   IconEntry entry;
 
-  IconProtoToEntry(proto, &entry);
+  IconProtoToEntry(&proto, &entry);
 
   // Verify entry data.
   EXPECT_EQ(entry.uuid, kUuid);
@@ -34,7 +34,7 @@
   entry.uuid = kUuid;
   IconProto proto;
 
-  IconEntryToProto(entry, &proto);
+  IconEntryToProto(&entry, &proto);
 
   // Verify proto data.
   EXPECT_EQ(proto.icon(), kData);
diff --git a/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc
new file mode 100644
index 0000000..899e3c0
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc
@@ -0,0 +1,24 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/notifications/scheduler/schedule_service_factory_helper.h"
+
+#include "chrome/browser/notifications/scheduler/notification_background_task_scheduler.h"
+#include "chrome/browser/notifications/scheduler/notification_schedule_service_impl.h"
+#include "chrome/browser/notifications/scheduler/notification_scheduler_context.h"
+#include "chrome/browser/notifications/scheduler/scheduler_config.h"
+
+namespace notifications {
+
+KeyedService* CreateNotificationScheduleService(
+    std::unique_ptr<NotificationBackgroundTaskScheduler>
+        background_task_scheduler) {
+  auto config = SchedulerConfig::Create();
+  auto context = std::make_unique<NotificationSchedulerContext>(
+      std::move(background_task_scheduler), std::move(config));
+  return static_cast<KeyedService*>(
+      new NotificationScheduleServiceImpl(std::move(context)));
+}
+
+}  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/schedule_service_factory_helper.h b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.h
new file mode 100644
index 0000000..b30e421
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULE_SERVICE_FACTORY_HELPER_H_
+#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULE_SERVICE_FACTORY_HELPER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace notifications {
+
+class NotificationBackgroundTaskScheduler;
+
+// Creates the notification schedule service with all the embedder level
+// dependencies. This layer is mainly to forbid the embedder to depend on
+// notification scheduler internal code.
+KeyedService* CreateNotificationScheduleService(
+    std::unique_ptr<NotificationBackgroundTaskScheduler>
+        background_task_scheduler);
+
+}  // namespace notifications
+
+#endif  // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULE_SERVICE_FACTORY_HELPER_H_
diff --git a/chrome/browser/notifications/scheduler/test/test_utils.cc b/chrome/browser/notifications/scheduler/test/test_utils.cc
index b56b28d..38f17cd 100644
--- a/chrome/browser/notifications/scheduler/test/test_utils.cc
+++ b/chrome/browser/notifications/scheduler/test/test_utils.cc
@@ -29,10 +29,10 @@
   for (const auto& test_data : test_data) {
     auto client_state = std::make_unique<ClientState>(test_data.type);
     client_state->current_max_daily_show = test_data.current_max_daily_show;
-    for (const auto& impression : test_data.impressions)
-      client_state->impressions.emplace(impression.create_time, impression);
+    for (const auto& impression : test_data.impressions) {
+      client_state->impressions.emplace_back(impression);
+    }
     client_state->suppression_info = test_data.suppression_info;
-
     client_states->emplace(test_data.type, std::move(client_state));
   }
 }
diff --git a/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc b/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
index b64d6b5..4077f4fd 100644
--- a/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
+++ b/chrome/browser/offline_pages/offline_page_mhtml_archiver.cc
@@ -125,8 +125,7 @@
   params.remove_popup_overlay = create_archive_params.remove_popup_overlay;
   params.use_page_problem_detectors =
       create_archive_params.use_page_problem_detectors;
-  params.use_mojo_for_mhtml_serialization =
-      IsOnTheFlyMhtmlHashComputationEnabled();
+  params.compute_contents_hash = IsOnTheFlyMhtmlHashComputationEnabled();
 
   web_contents->GenerateMHTML(
       params,
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 5350a4d..796c1821 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -156,6 +156,7 @@
 #include "extensions/browser/api/runtime/runtime_api.h"
 #include "extensions/browser/extension_prefs.h"
 #if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
 #include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service.h"
 #include "chrome/browser/chromeos/settings/stats_reporting_controller.h"
 #include "chrome/browser/component_updater/metadata_table_chromeos.h"
@@ -985,6 +986,8 @@
   }
 #endif
 
+  // Added 4/2019
+  crostini::CrostiniSharePath::MigratePersistedPathsToMultiVM(profile_prefs);
 #endif
 
   // Added 1/2019.
diff --git a/chrome/browser/prerender/prerender_link_manager.cc b/chrome/browser/prerender/prerender_link_manager.cc
index 76421d7..2c50824 100644
--- a/chrome/browser/prerender/prerender_link_manager.cc
+++ b/chrome/browser/prerender/prerender_link_manager.cc
@@ -25,6 +25,7 @@
 #include "content/public/common/referrer.h"
 #include "extensions/buildflags/buildflags.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/prerender/prerender_rel_type.h"
 #include "ui/gfx/geometry/size.h"
 #include "url/gurl.h"
 
@@ -41,12 +42,8 @@
 
 namespace {
 
-static_assert(PrerenderRelTypePrerender == 0x1,
-              "RelTypeHistogrameEnum must match PrerenderRelType");
-static_assert(PrerenderRelTypeNext == 0x2,
-              "RelTypeHistogramEnum must match PrerenderRelType");
 constexpr int kRelTypeHistogramEnumMax =
-    (PrerenderRelTypePrerender | PrerenderRelTypeNext) + 1;
+    (blink::kPrerenderRelTypePrerender | blink::kPrerenderRelTypeNext) + 1;
 
 void RecordLinkManagerAdded(const uint32_t rel_types) {
   UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkAdded",
@@ -350,7 +347,7 @@
       abandoned_prerenders.pop_front();
     }
 
-    if (!(PrerenderRelTypePrerender & it->rel_types)) {
+    if (!(blink::kPrerenderRelTypePrerender & it->rel_types)) {
       prerenders_.erase(it);
       continue;
     }
diff --git a/chrome/browser/prerender/prerender_manager.cc b/chrome/browser/prerender/prerender_manager.cc
index 1f9273e..87883ec 100644
--- a/chrome/browser/prerender/prerender_manager.cc
+++ b/chrome/browser/prerender/prerender_manager.cc
@@ -67,6 +67,7 @@
 #include "extensions/common/constants.h"
 #include "net/http/http_cache.h"
 #include "net/http/http_request_headers.h"
+#include "third_party/blink/public/common/prerender/prerender_rel_type.h"
 #include "ui/gfx/geometry/rect.h"
 
 using chrome_browser_net::NetworkPredictionStatus;
@@ -220,9 +221,9 @@
     const uint32_t rel_types,
     const content::Referrer& referrer,
     const gfx::Size& size) {
-  Origin origin = rel_types & PrerenderRelTypePrerender ?
-                      ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
-                      ORIGIN_LINK_REL_NEXT;
+  Origin origin = rel_types & blink::kPrerenderRelTypePrerender
+                      ? ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
+                      : ORIGIN_LINK_REL_NEXT;
   SessionStorageNamespace* session_storage_namespace = nullptr;
   // Unit tests pass in a process_id == -1.
   if (process_id != -1) {
diff --git a/chrome/browser/prerender/prerender_unittest.cc b/chrome/browser/prerender/prerender_unittest.cc
index 9f44120..06cbcab 100644
--- a/chrome/browser/prerender/prerender_unittest.cc
+++ b/chrome/browser/prerender/prerender_unittest.cc
@@ -49,6 +49,7 @@
 #include "net/http/http_cache.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/prerender/prerender_rel_type.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 #include "url/gurl.h"
@@ -131,7 +132,7 @@
 
 const gfx::Size kSize(640, 480);
 
-const uint32_t kDefaultRelTypes = PrerenderRelTypePrerender;
+const uint32_t kDefaultRelTypes = blink::kPrerenderRelTypePrerender;
 
 }  // namespace
 
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 3b08d0d..6abb20b 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -20,6 +20,7 @@
         "discards:closure_compile",
         "downloads:closure_compile",
         "history:closure_compile",
+        "local_ntp:closure_compile",
         "local_state:closure_compile",
         "management:closure_compile",
         "md_user_manager:closure_compile",
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.css b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.css
index 0502cb7..7bf022a 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.css
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.css
@@ -2,6 +2,26 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file. */
 
+#arc-detailed-view-overlay {
+  background-color: rgb(255, 255, 192);
+  border: 1px solid #aaa;
+  display: none;
+  margin: 0;
+  max-height: 50%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  position: absolute;
+}
+
+#arc-detailed-view-overlay::-webkit-scrollbar {
+  height: 4px;
+  width: 4px;
+}
+
+#arc-detailed-view-overlay::-webkit-scrollbar-thumb {
+  background-color: #888;
+}
+
 #arc-event-bands {
   margin: 0 auto;
   overflow: auto;
@@ -11,7 +31,7 @@
 
 #arc-event-band-tooltip {
   background-color: rgb(255, 255, 192);
-  border: 1px solid #a0a0a0;
+  border: 1px solid #aaa;
   display: none;
   pointer-events: none;
   position: absolute;
@@ -19,25 +39,27 @@
   width: 240px;
 }
 
-#arc-event-band-tooltip.active {
+#arc-event-band-tooltip.active,
+#arc-detailed-view-overlay.active {
   display: block;
 }
 
-.arc-events-inner-band {
+.arc-cpu-view-title {
+  border-bottom: 1px solid #888;
+  font-size: 14px;
+  margin: 4px 0 4px 0;
+}
+
+.arc-events-band {
+  display: block;
+  margin: 0;
+}
+
+.arc-events-cpu-detailed-band {
   display: block;
   margin: 0 0 4px 0;
 }
 
-.arc-events-inner-band-last-buffer {
-  display: block;
-  margin: 0 0 12px 0;
-}
-
-.arc-events-top-band {
-  display: block;
-  margin: 0 0 8px 0;
-}
-
 .arc-events-band-title {
   align-items: center;
   border: none;
@@ -71,7 +93,6 @@
   content: '\002B';
 }
 
-.hidden.arc-events-inner-band,
-.hidden.arc-events-top-band {
+.hidden.arc-events-band {
   display: none;
 }
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.html b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.html
index fd8a787..b42be2a 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.html
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.html
@@ -23,6 +23,7 @@
     </div>
     <hr>
     <div id='arc-event-bands'></div>
-    <svg id='arc-event-band-tooltip'></svg>
+    <div id='arc-detailed-view-overlay'></div>
+    <div id='arc-event-band-tooltip'></div>
   </body>
 </html>
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.js b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.js
index 54bd0cd9..76b3125 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.js
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing.js
@@ -18,6 +18,7 @@
       }, false);
       chrome.send('ready');
       chrome.send('setStopOnJank', [stopOnJank.checked]);
+      initializeUi();
     },
 
     setStatus: function(statusText) {
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
index 3fb41fa..ab4af53f1 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
@@ -22,6 +22,11 @@
  * event, |name| is used in tooltips.
  */
 var eventAttributes = {
+  // kIdleIn
+  0: {color: bandColor, name: 'idle'},
+  // kIdleOut
+  1: {color: '#ffbf00', name: 'active'},
+
   // kBufferQueueDequeueStart
   100: {color: '#99cc00', name: 'app requests buffer'},
   // kBufferQueueDequeueDone
@@ -80,6 +85,10 @@
   504: {color: '#65f441', name: 'swap done'},
   // kChromeOSJank
   505: {color: '#ff0000', name: 'Chrome composition jank', width: 1.0},
+
+  // Service events.
+  // kTimeMark
+  10000: {color: '#fff', name: 'Time mark', width: 0.75},
 };
 
 /**
@@ -92,6 +101,8 @@
  * sequence.
  */
 var endSequenceEvents = {
+  // kIdleIn
+  0: [],
   // kBufferQueueQueueDone
   103: [],
   // kBufferQueueReleased
@@ -108,28 +119,36 @@
   // is the last. Different
   // pipelines may produce different sequences. Both event type may indicate
   // the end of the
-  /// sequence.
+  // sequence.
   503: [500 /* kChromeOSDraw */],
   // kChromeOSSwapDone
   504: [500 /* kChromeOSDraw */],
 };
 
 /**
- * Converts timestamp into pixel offset. 1 pixel corresponds 100 microseconds.
- *
- * @param {number} timestamp in microseconds.
+ * @type {DetailedInfoView}.
+ * Currently active detailed view.
  */
-function timestampToOffset(timestamp) {
-  return timestamp / 100.0;
+var activeDetailedInfoView = null;
+
+/**
+ * Discards active detailed view if it exists.
+ */
+function discardDetailedInfo() {
+  if (activeDetailedInfoView) {
+    activeDetailedInfoView.discard();
+    activeDetailedInfoView = null;
+  }
 }
 
 /**
- * Opposite conversion of |timestampToOffset|
- *
- * @param {number} offset in pixels.
+ * Shows detailed view for |eventBand| in response to mouse click event
+ * |mouseEvent|.
  */
-function offsetToTime(offset) {
-  return offset * 100.0;
+function showDetailedInfoForBand(eventBand, mouseEvent) {
+  discardDetailedInfo();
+  activeDetailedInfoView = eventBand.showDetailedInfo(mouseEvent);
+  mouseEvent.preventDefault();
 }
 
 /**
@@ -142,10 +161,33 @@
   return (timestamp / 1000.0).toFixed(1);
 }
 
+/**
+ * Initialises UI by setting keyboard and mouse listeners to discard detailed
+ * view overlay.
+ */
+function initializeUi() {
+  document.body.onkeydown = function(event) {
+    // Escape and Enter.
+    if (event.key === 'Escape' || event.key === 'Enter') {
+      discardDetailedInfo();
+    }
+  };
+
+  window.onclick = function(event) {
+    // Detect click outside the detailed view.
+    if (event.defaultPrevented || activeDetailedInfoView == null) {
+      return;
+    }
+    if (!activeDetailedInfoView.overlay.contains(event.target)) {
+      discardDetailedInfo();
+    }
+  };
+}
+
 /** Factory class for SVG elements. */
 class SVG {
   // Creates rectangle element in the |svg| with provided attributes.
-  static addRect(svg, x, y, width, height, color) {
+  static addRect(svg, x, y, width, height, color, opacity) {
     var rect = document.createElementNS(svgNS, 'rect');
     rect.setAttributeNS(null, 'x', x);
     rect.setAttributeNS(null, 'y', y);
@@ -153,7 +195,11 @@
     rect.setAttributeNS(null, 'height', height);
     rect.setAttributeNS(null, 'fill', color);
     rect.setAttributeNS(null, 'stroke', 'none');
+    if (opacity) {
+      rect.setAttributeNS(null, 'fill-opacity', opacity);
+    }
     svg.appendChild(rect);
+    return rect;
   }
 
   // Creates line element in the |svg| with provided attributes.
@@ -166,6 +212,7 @@
     line.setAttributeNS(null, 'stroke', color);
     line.setAttributeNS(null, 'stroke-width', width);
     svg.appendChild(line);
+    return line;
   }
 
   // Creates circle element in the |svg| with provided attributes.
@@ -178,6 +225,7 @@
     circle.setAttributeNS(null, 'stroke', strokeColor);
     circle.setAttributeNS(null, 'stroke-width', strokeWidth);
     svg.appendChild(circle);
+    return circle;
   }
 
   // Creates text element in the |svg| with provided attributes.
@@ -189,6 +237,7 @@
     text.setAttributeNS(null, 'font-size', fontSize);
     text.appendChild(document.createTextNode(textContent));
     svg.appendChild(text);
+    return text;
   }
 }
 
@@ -197,9 +246,9 @@
  * content.
  */
 class EventBandTitle {
-  constructor(title, opt_iconContent) {
+  constructor(parent, title, className, opt_iconContent) {
     this.div = document.createElement('div');
-    this.div.classList.add('arc-events-band-title');
+    this.div.classList.add(className);
     if (opt_iconContent) {
       var icon = document.createElement('img');
       icon.src = 'data:image/png;base64,' + opt_iconContent;
@@ -210,8 +259,8 @@
     this.div.appendChild(span);
     this.controlledItems = [];
     this.div.onclick = this.onClick_.bind(this);
-    var parent = $('arc-event-bands');
-    parent.appendChild(this.div);
+    this.parent = parent;
+    this.parent.appendChild(this.div);
   }
 
   /**
@@ -241,14 +290,18 @@
    * @param {string} className class name of the svg element that represents
    *     this band. 'arc-events-top-band' is used for top-level events and
    *     'arc-events-inner-band' is used for per-buffer events.
-   * @param {number} duration of bands in microseconds.
+   * @param {number} resolution the resolution of bands microseconds per 1
+   *     pixel.
+   * @param {number} minTimestamp the minimum timestamp to display on bands.
+   * @param {number} minTimestamp the maximum timestamp to display on bands.
    */
-  constructor(title, className, duration) {
+  constructor(title, className, resolution, minTimestamp, maxTimestamp) {
     // Keep information about bands and their bounds.
     this.bands = [];
     this.globalEvents = [];
-    this.duration = duration;
-    this.width = timestampToOffset(duration);
+    this.resolution = resolution;
+    this.minTimestamp = minTimestamp;
+    this.maxTimestamp = maxTimestamp;
     this.height = 0;
     // Offset of the next band of events.
     this.nextYOffset = 0;
@@ -256,14 +309,50 @@
     this.svg.setAttributeNS(
         'http://www.w3.org/2000/xmlns/', 'xmlns:xlink',
         'http://www.w3.org/1999/xlink');
-    this.svg.setAttribute('width', this.width + 'px');
+    this.setBandOffsetX(0);
+    this.setWidth(0);
     this.svg.setAttribute('height', this.height + 'px');
     this.svg.classList.add(className);
 
     this.setTooltip_();
     title.addContolledItems(this.svg);
-    var parent = $('arc-event-bands');
-    parent.appendChild(this.svg);
+    title.parent.appendChild(this.svg);
+  }
+
+  /**
+   * Sets the horizontal offset to render bands.
+   * @param {number} offsetX offset in pixels.
+   */
+  setBandOffsetX(offsetX) {
+    this.bandOffsetX = offsetX;
+  }
+
+  /**
+   * Sets the widths of event bands.
+   * @param {number} width width in pixels.
+   */
+  setWidth(width) {
+    this.width = width;
+    this.svg.setAttribute('width', this.width + 'px');
+  }
+
+  /**
+   * Converts timestamp into pixel offset. 1 pixel corresponds resolution
+   * microseconds.
+   *
+   * @param {number} timestamp in microseconds.
+   */
+  timestampToOffset(timestamp) {
+    return (timestamp - this.minTimestamp) / this.resolution;
+  }
+
+  /**
+   * Opposite conversion of |timestampToOffset|
+   *
+   * @param {number} offset in pixels.
+   */
+  offsetToTime(offset) {
+    return offset * this.resolution + this.minTimestamp;
   }
 
   /**
@@ -276,15 +365,14 @@
    */
   addBand(eventBand, height, padding) {
     var currentColor = bandColor;
-    var x = 0;
-    var eventIndex = -1;
-    while (true) {
-      eventIndex = eventBand.getNextEvent(eventIndex, 1 /* direction */);
-      if (eventIndex < 0) {
+    var x = this.bandOffsetX;
+    var eventIndex = eventBand.getFirstAfter(this.minTimestamp);
+    while (eventIndex >= 0) {
+      var event = eventBand.events[eventIndex];
+      if (event[1] >= this.maxTimestamp) {
         break;
       }
-      var event = eventBand.events[eventIndex];
-      var nextX = timestampToOffset(event[1]);
+      var nextX = this.timestampToOffset(event[1]) + this.bandOffsetX;
       SVG.addRect(
           this.svg, x, this.nextYOffset, nextX - x, height, currentColor);
       if (eventBand.isEndOfSequence(eventIndex)) {
@@ -293,9 +381,12 @@
         currentColor = eventAttributes[event[0]].color;
       }
       x = nextX;
+      eventIndex = eventBand.getNextEvent(eventIndex, 1 /* direction */);
     }
     SVG.addRect(
-        this.svg, x, this.nextYOffset, this.width - x, height, currentColor);
+        this.svg, x, this.nextYOffset,
+        this.timestampToOffset(this.maxTimestamp) - x + this.bandOffsetX,
+        height, currentColor);
 
     this.bands.push({
       band: eventBand,
@@ -323,7 +414,7 @@
       }
       var event = events.events[eventIndex];
       var attributes = events.getEventAttributes(eventIndex);
-      var x = timestampToOffset(event[1]);
+      var x = this.timestampToOffset(event[1]) + this.bandOffsetX;
       SVG.addLine(
           this.svg, x, 0, x, this.height, attributes.color, attributes.width);
     }
@@ -332,10 +423,13 @@
 
   /** Initializes tooltip support by observing mouse events */
   setTooltip_() {
-    this.tooltip = getSVGElement('arc-event-band-tooltip');
+    this.tooltip = $('arc-event-band-tooltip');
     this.svg.onmouseover = this.showToolTip_.bind(this);
     this.svg.onmouseout = this.hideToolTip_.bind(this);
     this.svg.onmousemove = this.updateToolTip_.bind(this);
+    this.svg.onclick = (event) => {
+      showDetailedInfoForBand(this, event);
+    };
   }
 
   /** Updates tooltip and shows it for this band. */
@@ -391,6 +485,12 @@
     var lineHeight = 16;
     var fontSize = 12;
 
+    var offsetX = event.offsetX - this.bandOffsetX;
+    if (offsetX < 0) {
+      this.tooltip.classList.remove('active');
+      return;
+    }
+
     // Find band for this mouse event.
     var eventBand = undefined;
 
@@ -407,10 +507,16 @@
       return;
     }
 
+    var svg = document.createElementNS(svgNS, 'svg');
+    svg.setAttributeNS(
+        'http://www.w3.org/2000/xmlns/', 'xmlns:xlink',
+        'http://www.w3.org/1999/xlink');
+    this.tooltip.appendChild(svg);
+
     var yOffset = verticalGap + lineHeight;
-    var eventTimestamp = offsetToTime(event.offsetX);
+    var eventTimestamp = this.offsetToTime(offsetX);
     SVG.addText(
-        this.tooltip, horizontalGap, yOffset, fontSize,
+        svg, horizontalGap, yOffset, fontSize,
         timestempToMsText(eventTimestamp) + ' ms');
     yOffset += lineHeight;
 
@@ -432,14 +538,15 @@
       // Show the global event info.
       var attributes = eventAttributes[globalEvent[0]];
       SVG.addText(
-          this.tooltip, horizontalGap, yOffset, 12,
+          svg, horizontalGap, yOffset, 12,
           attributes.name + ' ' + timestempToMsText(globalEvent[1]) + ' ms.');
     } else if (index < 0 || eventBand.isEndOfSequence(index)) {
       // In case cursor points to idle event, show its interval.
       var startIdle = index < 0 ? 0 : eventBand.events[index][1];
-      var endIdle = index < 0 ? this.duration : eventBand.events[nextIndex][1];
+      var endIdle =
+          nextIndex < 0 ? this.maxTimestamp : eventBand.events[nextIndex][1];
       SVG.addText(
-          this.tooltip, horizontalGap, yOffset, 12,
+          svg, horizontalGap, yOffset, 12,
           'Idle ' + timestempToMsText(startIdle) + '...' +
               timestempToMsText(endIdle) + ' ms.');
       yOffset += lineHeight;
@@ -492,16 +599,19 @@
       }
       for (var i = 0; i < entriesToShow.length; ++i) {
         var entryToShow = entriesToShow[i];
-        SVG.addText(
-            this.tooltip, horizontalGap, yOffset, fontSize, entryToShow.prefix);
+        SVG.addText(svg, horizontalGap, yOffset, fontSize, entryToShow.prefix);
         SVG.addCircle(
-            this.tooltip, eventIconOffset, yOffset - eventIconRadius,
-            eventIconRadius, 1, entryToShow.color, 'black');
-        SVG.addText(
-            this.tooltip, eventNameOffset, yOffset, fontSize, entryToShow.text);
+            svg, eventIconOffset, yOffset - eventIconRadius, eventIconRadius, 1,
+            entryToShow.color, 'black');
+        SVG.addText(svg, eventNameOffset, yOffset, fontSize, entryToShow.text);
         yOffset += lineHeight;
       }
     }
+    if (this.canShowDetailedInfo()) {
+      SVG.addText(
+          svg, horizontalGap, yOffset, fontSize, 'Click for detailed info');
+      yOffset += lineHeight;
+    }
     yOffset += verticalGap;
 
     this.tooltip.style.left = event.clientX + 'px';
@@ -509,16 +619,261 @@
     this.tooltip.style.height = yOffset + 'px';
     this.tooltip.classList.add('active');
   }
+
+  /**
+   * Returns true in case band can show detailed info.
+   */
+  canShowDetailedInfo() {
+    return false;
+  }
+
+  /**
+   * Shows detailed info for the position under mouse event |event|. By default
+   * it creates nothing.
+   */
+  showDetailedInfo(event) {
+    return null;
+  }
+}
+
+/**
+ * Base class for detailed info view.
+ */
+class DetailedInfoView {
+  discard() {}
+}
+
+/**
+ * CPU detailed info view. Renders 4x zoomed CPU events split by processes and
+ * threads.
+ */
+class CpuDetailedInfoView extends DetailedInfoView {
+  create(overviewBand) {
+    this.overlay = $('arc-detailed-view-overlay');
+    var overviewRect = overviewBand.svg.getBoundingClientRect();
+
+    // Clear previous content.
+    this.overlay.textContent = '';
+
+    // UI constants to render.
+    var columnWidth = 140;
+    var scrollBarWidth = 3;
+    var zoomFactor = 4.0;
+    var cpuBandHeight = 14;
+    var processInfoHeight = 14;
+    var padding = 2;
+    var fontSize = 12;
+    var processInfoPadding = 2;
+    var threadInfoPadding = 6;
+
+    // Use minimum 80% of inner width or 600 pixels to display detailed view
+    // zoomed |zoomFactor| times.
+    var availableWidthPixels =
+        window.innerWidth * 0.8 - columnWidth - scrollBarWidth;
+    availableWidthPixels = Math.max(availableWidthPixels, 600);
+    var availableForHalfBandMcs = Math.floor(
+        overviewBand.offsetToTime(availableWidthPixels) / (2.0 * zoomFactor));
+    // Determine the interval to display.
+    var eventTimestamp =
+        overviewBand.offsetToTime(event.offsetX - overviewBand.bandOffsetX);
+    var minTimestamp = eventTimestamp - availableForHalfBandMcs;
+    var maxTimestamp = eventTimestamp + availableForHalfBandMcs + 1;
+    var duration = maxTimestamp - minTimestamp;
+
+    // Construct sub-model of active/idle events per each thread, active within
+    // this interval.
+    var eventsPerTid = {};
+    for (var cpuId = 0; cpuId < overviewBand.model.cpu.events.length; cpuId++) {
+      var activeEvents = new Events(
+          overviewBand.model.cpu.events[cpuId], 3 /* kActive */,
+          3 /* kActive */);
+      var activeTid = 0;
+      var index = activeEvents.getFirstAfter(minTimestamp);
+      var activeStartTimestamp = minTimestamp;
+      while (index >= 0 && activeEvents.events[index][1] < maxTimestamp) {
+        this.addActivityTime_(
+            eventsPerTid, activeTid, activeStartTimestamp,
+            activeEvents.events[index][1]);
+        activeTid = activeEvents.events[index][2];
+        activeStartTimestamp = activeEvents.events[index][1];
+        index = activeEvents.getNextEvent(index, 1 /* direction */);
+      }
+      this.addActivityTime_(
+          eventsPerTid, activeTid, activeStartTimestamp, maxTimestamp - 1);
+    }
+
+    // The same thread might be executed on different CPU cores. Sort events.
+    for (var tid in eventsPerTid) {
+      eventsPerTid[tid].events.sort(function(a, b) {
+        return a[1] - b[1];
+      });
+    }
+
+    // Group threads by process.
+    var threadsPerPid = {};
+    var pids = [];
+    var totalTime = 0;
+    for (var tid in eventsPerTid) {
+      var thread = eventsPerTid[tid];
+      var pid = overviewBand.model.cpu.threads[tid].pid;
+      if (!(pid in threadsPerPid)) {
+        pids.push(pid);
+        threadsPerPid[pid] = {};
+        threadsPerPid[pid].totalTime = 0;
+        threadsPerPid[pid].threads = [];
+      }
+      threadsPerPid[pid].totalTime += thread.totalTime;
+      threadsPerPid[pid].threads.push(thread);
+      totalTime += thread.totalTime;
+    }
+
+    // Sort processes per time usage.
+    pids.sort(function(a, b) {
+      return threadsPerPid[b].totalTime - threadsPerPid[a].totalTime;
+    });
+
+    var totalUsage = 100.0 * totalTime / duration;
+    var cpuInfo = 'CPU view. ' + pids.length + '/' +
+        Object.keys(eventsPerTid).length +
+        ' active processes/threads. Total cpu usage: ' + totalUsage.toFixed(2) +
+        '%.';
+    var title = new EventBandTitle(this.overlay, cpuInfo, 'arc-cpu-view-title');
+    var bands = new EventBands(
+        title, 'arc-events-cpu-detailed-band',
+        overviewBand.resolution / zoomFactor, minTimestamp, maxTimestamp);
+    bands.setBandOffsetX(columnWidth);
+    var bandsWidth = bands.timestampToOffset(maxTimestamp);
+    var totalWidth = bandsWidth + columnWidth;
+    bands.setWidth(totalWidth);
+
+    for (i = 0; i < pids.length; i++) {
+      var pid = pids[i];
+      var threads = threadsPerPid[pid].threads;
+      var processName;
+      if (pid in overviewBand.model.cpu.threads) {
+        processName = overviewBand.model.cpu.threads[pid].name;
+      } else {
+        processName = 'Others';
+      }
+      bands.nextYOffset += (processInfoHeight + padding);
+      var processCpuUsage = 100.0 * threadsPerPid[pid].totalTime / duration;
+      var processInfo = processName + ' <' + pid +
+          '>, cpu usage: ' + processCpuUsage.toFixed(2) + '%.';
+      SVG.addText(
+          bands.svg, processInfoPadding, bands.nextYOffset - 2 * padding,
+          fontSize, processInfo);
+      bands.svg.setAttribute('height', bands.nextYOffset + 'px');
+
+      // Sort threads per time usage.
+      threads.sort(function(a, b) {
+        return eventsPerTid[b.tid].totalTime - eventsPerTid[a.tid].totalTime;
+      });
+
+      for (j = 0; j < threads.length; j++) {
+        var tid = threads[j].tid;
+        bands.addBand(
+            new Events(eventsPerTid[tid].events, 0, 1), cpuBandHeight, padding);
+        var threadName = overviewBand.model.cpu.threads[tid].name;
+        var threadCpuUsage = 100.0 * threads[j].totalTime / duration;
+        var threadInfo = threadName + ' ' + threadCpuUsage.toFixed(2) + '%';
+        SVG.addText(
+            bands.svg, threadInfoPadding, bands.nextYOffset - padding, fontSize,
+            threadInfo);
+      }
+    }
+
+    // Add center and boundary lines.
+    var kTimeMark = 10000;
+    var timeEvents = [
+      [kTimeMark, minTimestamp], [kTimeMark, eventTimestamp],
+      [kTimeMark, maxTimestamp - 1]
+    ];
+    bands.addGlobal(new Events(timeEvents, kTimeMark, kTimeMark));
+
+    // Mark zoomed interval in overview.
+    var overviewX = overviewBand.timestampToOffset(minTimestamp);
+    var overviewWidth =
+        overviewBand.timestampToOffset(maxTimestamp) - overviewX;
+    this.bandSelection = SVG.addRect(
+        overviewBand.svg, overviewX, 0, overviewWidth, overviewBand.height,
+        '#000' /* color */, 0.1 /* opacity */);
+
+    // Align position in overview and middle line here if possible.
+    var left = Math.max(
+        Math.min(
+            Math.round(event.clientX - columnWidth - bandsWidth * 0.5),
+            window.innerWidth - totalWidth),
+        0);
+    this.overlay.style.left = left + 'px';
+    // Place below the overview with small gap.
+    this.overlay.style.top = (overviewRect.bottom + window.scrollY + 2) + 'px';
+    this.overlay.classList.add('active');
+  }
+
+  discard() {
+    this.overlay.classList.remove('active');
+    this.bandSelection.remove();
+  }
+
+  /**
+   * Helper that adds kIdleIn/kIdle events into the dictionary.
+   *
+   * @param {Object} eventsPerTid dictionary to fill. Key is thread id and
+   *     value is object that contains all events for thread with related
+   *     information.
+   * @param {number} tid thread id.
+   * @param {number} timestampFrom start time of thread activity.
+   * @param {number} timestampTo end time of thread activity.
+   */
+  addActivityTime_(eventsPerTid, tid, timestampFrom, timestampTo) {
+    if (tid == 0) {
+      // Don't process idle thread.
+      return;
+    }
+    if (!(tid in eventsPerTid)) {
+      // Create the band for the new thread.
+      eventsPerTid[tid] = {};
+      eventsPerTid[tid].totalTime = 0;
+      eventsPerTid[tid].events = [];
+      eventsPerTid[tid].tid = tid;
+    }
+    eventsPerTid[tid].events.push([1 /* kIdleOut */, timestampFrom]);
+    eventsPerTid[tid].events.push([0 /* kIdleIn */, timestampTo]);
+    // Update total time for this thread.
+    eventsPerTid[tid].totalTime += (timestampTo - timestampFrom);
+  }
+}
+
+class CpuEventBands extends EventBands {
+  setModel(model) {
+    this.model = model;
+    var bandHeight = 6;
+    var padding = 2;
+    for (var cpuId = 0; cpuId < this.model.cpu.events.length; cpuId++) {
+      this.addBand(
+          new Events(this.model.cpu.events[cpuId], 0, 1), bandHeight, padding);
+    }
+  }
+
+  canShowDetailedInfo() {
+    return true;
+  }
+
+  showDetailedInfo(event) {
+    var view = new CpuDetailedInfoView();
+    view.create(this);
+    return view;
+  }
 }
 
 /** Represents one band with events. */
 class Events {
   /**
-   * Assigns events for this band. Events with type between |eventTypeMin|
-   * and |eventTypeMax| are only displayed on the band.
+   * Assigns events for this band. Events with type between |eventTypeMin| and
+   * |eventTypeMax| are only displayed on the band.
    *
-   * @param {Object[]} events non-filtered list of all events. Each has
-   *     array where first element is type and second is timestamp.
+   * @param {Object[]} events non-filtered list of all events. Each has array
+   *     where first element is type and second is timestamp.
    * @param {number} eventTypeMin minimum inclusive type of the event to be
    *     displayed on this band.
    * @param {number} eventTypeMax maximum inclusive type of the event to be
@@ -535,10 +890,10 @@
    * only processed.
    *
    * @param {number} index starting index for the search, not inclusive.
-   * @param {direction} direction to search, 1 means to find the next event
-   *     and -1 means the previous event.
-   * @returns {number} index of the next or previous event or -1 in case
-   *     not found.
+   * @param {direction} direction to search, 1 means to find the next event and
+   *     -1 means the previous event.
+   * @returns {number} index of the next or previous event or -1 in case not
+   *     found.
    */
   getNextEvent(index, direction) {
     while (true) {
@@ -629,6 +984,22 @@
       }
     }
   }
+
+  /**
+   * Returns the index of the first event after or on requested |timestamp|.
+   *
+   * @param {number} timestamp to search.
+   */
+  getFirstAfter(timestamp) {
+    var closest = this.getClosest(timestamp);
+    if (closest < 0) {
+      return -1;
+    }
+    if (this.events[closest][1] >= timestamp) {
+      return closest;
+    }
+    return this.getNextEvent(closest, 1 /* direction */);
+  }
 }
 
 /**
@@ -640,27 +1011,48 @@
   // Clear previous content.
   $('arc-event-bands').textContent = '';
 
+  // Microseconds per pixel.
+  var resolution = 100.0;
+  var parent = $('arc-event-bands');
+
+  var topBandHeight = 16;
+  var topBandPadding = 4;
+  var innerBandHeight = 12;
+  var innerBandPadding = 2;
+  var innerLastBandPadding = 12;
+
+  var cpusTitle = new EventBandTitle(parent, 'CPUs', 'arc-events-band-title');
+  var cpusBands = new CpuEventBands(
+      cpusTitle, 'arc-events-band', resolution, 0, model.duration);
+  cpusBands.setWidth(cpusBands.timestampToOffset(model.duration));
+  cpusBands.setModel(model);
+
   var vsyncEvents = new Events(
       model.android.global_events, 400 /* kVsync */, 400 /* kVsync */);
 
-  var chromeTitle = new EventBandTitle('Chrome');
-  var chromeBands =
-      new EventBands(chromeTitle, 'arc-events-top-band', model.duration);
+  var chromeTitle =
+      new EventBandTitle(parent, 'Chrome graphics', 'arc-events-band-title');
+  var chromeBands = new EventBands(
+      chromeTitle, 'arc-events-band', resolution, 0, model.duration);
+  chromeBands.setWidth(chromeBands.timestampToOffset(model.duration));
   for (i = 0; i < model.chrome.buffers.length; i++) {
     chromeBands.addBand(
-        new Events(model.chrome.buffers[i], 500, 599), 20 /* height */,
-        4 /* padding */);
+        new Events(model.chrome.buffers[i], 500, 599), topBandHeight,
+        topBandPadding);
   }
+
   chromeBands.addGlobal(new Events(
       model.chrome.global_events, 505 /* kChromeOSJank */,
       505 /* kChromeOSJank */));
 
-  var androidTitle = new EventBandTitle('Android');
-  var androidBands =
-      new EventBands(androidTitle, 'arc-events-top-band', model.duration);
+  var androidTitle =
+      new EventBandTitle(parent, 'Android graphics', 'arc-events-band-title');
+  var androidBands = new EventBands(
+      androidTitle, 'arc-events-band', resolution, 0, model.duration);
+  androidBands.setWidth(androidBands.timestampToOffset(model.duration));
   androidBands.addBand(
-      new Events(model.android.buffers[0], 400, 499), 20 /* height */,
-      0 /* padding */);
+      new Events(model.android.buffers[0], 400, 499), topBandHeight,
+      topBandPadding);
   // Add vsync events
   androidBands.addGlobal(vsyncEvents);
   androidBands.addGlobal(new Events(
@@ -678,24 +1070,23 @@
     } else {
       activityTitleText = 'Task #' + view.task_id + ' - ' + view.activity;
     }
-    var activityTitle = new EventBandTitle(activityTitleText, icon);
-    var activityBands =
-        new EventBands(activityTitle, 'arc-events-inner-band', model.duration);
+    var activityTitle = new EventBandTitle(
+        parent, activityTitleText, 'arc-events-band-title', icon);
+    var activityBands = new EventBands(
+        activityTitle, 'arc-events-band', resolution, 0, model.duration);
+    activityBands.setWidth(activityBands.timestampToOffset(model.duration));
     for (j = 0; j < view.buffers.length; j++) {
-      var androidBand = new Events(
-          activityTitle, 'arc-events-inner-band', model.duration, 14);
+      var androidBand =
+          new Events(activityTitle, 'arc-events-band', model.duration, 14);
       // Android buffer events.
       activityBands.addBand(
-          new Events(view.buffers[j], 100, 199), 14 /* height */,
-          4 /* padding */);
+          new Events(view.buffers[j], 100, 199), innerBandHeight,
+          innerBandPadding);
       // exo events.
       activityBands.addBand(
-          new Events(view.buffers[j], 200, 299), 14 /* height */,
-          4 /* padding */);
-      // Chrome buffer events.
-      activityBands.addBand(
-          new Events(view.buffers[j], 300, 399), 14 /* height */,
-          12 /* padding */);
+          new Events(view.buffers[j], 200, 299), innerBandHeight,
+          innerLastBandPadding);
+      // Chrome buffer events are not displayed at this time.
     }
     // Add vsync events
     activityBands.addGlobal(vsyncEvents);
diff --git a/chrome/browser/resources/chromeos/os_settings.html b/chrome/browser/resources/chromeos/os_settings.html
new file mode 100644
index 0000000..0cf91ae
--- /dev/null
+++ b/chrome/browser/resources/chromeos/os_settings.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<html>
+<!-- TODO(jamescook): Text direction, language, title, etc. -->
+<body>
+  Hello, settings!
+</body>
+</html>
diff --git a/chrome/browser/resources/local_ntp/BUILD.gn b/chrome/browser/resources/local_ntp/BUILD.gn
new file mode 100644
index 0000000..f7d4bd9
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/BUILD.gn
@@ -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.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  deps = [
+    ":local_ntp",
+  ]
+}
+
+js_library("local_ntp") {
+  sources = [
+    "animations.js",
+    "custom_backgrounds.js",
+    "custom_links_edit.js",
+    "doodles.js",
+    "instant_iframe_validation.js",
+    "local_ntp.js",
+    "most_visited_single.js",
+    "most_visited_title.js",
+    "most_visited_util.js",
+    "utils.js",
+    "voice.js",
+  ]
+  externs_list = [ "externs.js" ]
+}
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js
index 63f8059..73bf1f7 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.js
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -48,7 +48,7 @@
 
 /**
  * Enum for key codes.
- * @enum {int}
+ * @enum {number}
  * @const
  */
 customBackgrounds.KEYCODES = {
@@ -134,7 +134,7 @@
 
 /**
  * Enum for background sources.
- * @enum {int}
+ * @enum {number}
  * @const
  */
 customBackgrounds.SOURCES = {
@@ -145,7 +145,7 @@
 
 /**
  * Enum for background option menu entries, in the order they appear in the UI.
- * @enum {int}
+ * @enum {number}
  * @const
  */
 customBackgrounds.MENU_ENTRIES = {
@@ -177,7 +177,7 @@
 
 /* Type of collection that is being browsed, needed in order
  * to return from the image dialog.
- * @type {int}
+ * @type {number}
  */
 customBackgrounds.dialogCollectionsSource = customBackgrounds.SOURCES.NONE;
 
@@ -186,14 +186,14 @@
  * @type {?Function}
  * @private
  */
-customBackgrounds.showErrorNotification;
+customBackgrounds.showErrorNotification = null;
 
 /*
  * Called when the custom link notification should be hidden.
  * @type {?Function}
  * @private
  */
-customBackgrounds.hideCustomLinkNotification;
+customBackgrounds.hideCustomLinkNotification = null;
 
 /**
  * Sets the visibility of the settings menu and individual options depending on
@@ -308,7 +308,7 @@
   tile.style.backgroundImage = 'url(' + data.previewImageUrl + ')';
   tile.dataset.id = data.collectionId;
   tile.dataset.name = data.collectionName;
-  fadeInImageTile(tile, data.previewImageUrl);
+  fadeInImageTile(tile, data.previewImageUrl, null);
   return tile;
 };
 
@@ -331,8 +331,8 @@
 
 /* Get the next tile when the arrow keys are used to navigate the grid.
  * Returns null if the tile doesn't exist.
- * @param {int} deltaX Change in the x direction.
- * @param {int} deltaY Change in the y direction.
+ * @param {number} deltaX Change in the x direction.
+ * @param {number} deltaY Change in the y direction.
  * @param {string} current Number of the current tile.
  */
 customBackgrounds.getNextTile = function(deltaX, deltaY, current) {
@@ -343,10 +343,10 @@
   }
 
   if (deltaX != 0) {
-    let target = parseInt(current) + deltaX;
+    let target = parseInt(current, /*radix=*/ 10) + deltaX;
     return $(idPrefix + target);
   } else if (deltaY != 0) {
-    let target = parseInt(current);
+    let target = parseInt(current, /*radix=*/ 10);
     let nextTile = $(idPrefix + target);
     let startingTop = nextTile.getBoundingClientRect().top;
     let startingLeft = nextTile.getBoundingClientRect().left;
@@ -364,7 +364,7 @@
 
 /**
  * Show dialog for selecting a Chrome background.
- * @param {int} collectionsSource The enum value of the source to fetch
+ * @param {number} collectionsSource The enum value of the source to fetch
  *              collection data from.
  */
 customBackgrounds.showCollectionSelectionDialog = function(collectionsSource) {
@@ -429,8 +429,7 @@
       imgScript.onload = function() {
         // Verify that the individual image data was successfully loaded.
         var imageDataLoaded =
-            (coll_img.length > 0 &&
-             coll_img[0].collectionId == tile.dataset.id);
+            (collImg.length > 0 && collImg[0].collectionId == tile.dataset.id);
 
         // Dependent upon the success of the load, populate the image selection
         // dialog or close the current dialog.
@@ -438,7 +437,7 @@
           customBackgrounds.resetSelectionDialog();
           customBackgrounds.showImageSelectionDialog(tile.dataset.name);
         } else {
-          customBackgrounds.handleError(coll_img_errors);
+          customBackgrounds.handleError(collImgErrors);
         }
       };
     };
@@ -462,20 +461,22 @@
         if (event.keyCode === customBackgrounds.KEYCODES.LEFT) {
           target = customBackgrounds.getNextTile(
               document.documentElement.classList.contains('rtl') ? 1 : -1, 0,
-              this.dataset.tile_num);
+              event.currentTarget.dataset.tile_num);
         } else if (event.keyCode === customBackgrounds.KEYCODES.UP) {
-          target = customBackgrounds.getNextTile(0, -1, this.dataset.tile_num);
+          target = customBackgrounds.getNextTile(
+              0, -1, event.currentTarget.dataset.tile_num);
         } else if (event.keyCode === customBackgrounds.KEYCODES.RIGHT) {
           target = customBackgrounds.getNextTile(
               document.documentElement.classList.contains('rtl') ? -1 : 1, 0,
-              this.dataset.tile_num);
+              event.currentTarget.dataset.tile_num);
         } else if (event.keyCode === customBackgrounds.KEYCODES.DOWN) {
-          target = customBackgrounds.getNextTile(0, 1, this.dataset.tile_num);
+          target = customBackgrounds.getNextTile(
+              0, 1, event.currentTarget.dataset.tile_num);
         }
         if (target) {
           target.focus();
         } else {
-          this.focus();
+          event.currentTarget.focus();
         }
       }
     };
@@ -490,7 +491,7 @@
 
 /**
  * Apply border and checkmark when a tile is selected
- * @param {div} tile The tile to apply styling to.
+ * @param {!Element} tile The tile to apply styling to.
  */
 customBackgrounds.applySelectedState = function(tile) {
   tile.classList.add(customBackgrounds.CLASSES.COLLECTION_SELECTED);
@@ -511,7 +512,7 @@
 
 /**
  * Remove border and checkmark when a tile is un-selected
- * @param {div} tile The tile to remove styling from.
+ * @param {!Element} tile The tile to remove styling from.
  */
 customBackgrounds.removeSelectedState = function(tile) {
   tile.classList.remove(customBackgrounds.CLASSES.COLLECTION_SELECTED);
@@ -521,7 +522,7 @@
 
 /**
  * Show dialog for selecting an image. Image data should previous have been
- * loaded into coll_img via
+ * loaded into collImg via
  * chrome-search://local-ntp/ntp-background-images.js?collection_id=<collection_id>
  * @param {string} dialogTitle The title to be displayed at the top of the
  *                 dialog.
@@ -539,7 +540,7 @@
   let preLoadTiles = [];
   let postLoadTiles = [];
 
-  for (var i = 0; i < coll_img.length; ++i) {
+  for (var i = 0; i < collImg.length; ++i) {
     let tileBackground = document.createElement('div');
     tileBackground.classList.add(
         customBackgrounds.CLASSES.COLLECTION_TILE_BG);
@@ -550,23 +551,23 @@
 
     // TODO(crbug.com/854028): Remove this hardcoded check when wallpaper
     // previews are supported.
-    if (coll_img[i].collectionId === 'solidcolors') {
+    if (collImg[i].collectionId === 'solidcolors') {
       tile.dataset.attributionLine1 = '';
       tile.dataset.attributionLine2 = '';
       tile.dataset.attributionActionUrl = '';
     } else {
       tile.dataset.attributionLine1 =
-          (coll_img[i].attributions[0] !== undefined ?
-               coll_img[i].attributions[0] :
+          (collImg[i].attributions[0] !== undefined ?
+               collImg[i].attributions[0] :
                '');
       tile.dataset.attributionLine2 =
-          (coll_img[i].attributions[1] !== undefined ?
-               coll_img[i].attributions[1] :
+          (collImg[i].attributions[1] !== undefined ?
+               collImg[i].attributions[1] :
                '');
-      tile.dataset.attributionActionUrl = coll_img[i].attributionActionUrl;
+      tile.dataset.attributionActionUrl = collImg[i].attributionActionUrl;
     }
-    tile.setAttribute('aria-label', coll_img[i].attributions[0]);
-    tile.dataset.url = coll_img[i].imageUrl;
+    tile.setAttribute('aria-label', collImg[i].attributions[0]);
+    tile.dataset.url = collImg[i].imageUrl;
 
     tile.id = 'img_tile_' + i;
     tile.dataset.tile_num = i;
@@ -604,11 +605,15 @@
       let clickCount = event.detail;
       // Control + option + space will fire the onclick event with 0 clickCount.
       if (clickCount <= 1) {
-        tileInteraction(this);
-      } else if (clickCount === 2 && customBackgrounds.selectedTile === this) {
-        customBackgrounds.setBackground(this.dataset.url,
-            this.dataset.attributionLine1, this.dataset.attributionLine2,
-            this.dataset.attributionActionUrl);
+        tileInteraction(event.currentTarget);
+      } else if (
+          clickCount === 2 &&
+          customBackgrounds.selectedTile === event.currentTarget) {
+        customBackgrounds.setBackground(
+            event.currentTarget.dataset.url,
+            event.currentTarget.dataset.attributionLine1,
+            event.currentTarget.dataset.attributionLine2,
+            event.currentTarget.dataset.attributionActionUrl);
       }
     };
     tile.onkeydown = function(event) {
@@ -616,7 +621,7 @@
       if (event.keyCode === customBackgrounds.KEYCODES.ENTER) {
         event.preventDefault();
         event.stopPropagation();
-        tileInteraction(this);
+        tileInteraction(event.currentTarget);
       } else if (
           event.keyCode === customBackgrounds.KEYCODES.LEFT ||
           event.keyCode === customBackgrounds.KEYCODES.UP ||
@@ -630,20 +635,22 @@
         if (event.keyCode == customBackgrounds.KEYCODES.LEFT) {
           target = customBackgrounds.getNextTile(
               document.documentElement.classList.contains('rtl') ? 1 : -1, 0,
-              this.dataset.tile_num);
+              event.currentTarget.dataset.tile_num);
         } else if (event.keyCode == customBackgrounds.KEYCODES.UP) {
-          target = customBackgrounds.getNextTile(0, -1, this.dataset.tile_num);
+          target = customBackgrounds.getNextTile(
+              0, -1, event.currentTarget.dataset.tile_num);
         } else if (event.keyCode == customBackgrounds.KEYCODES.RIGHT) {
           target = customBackgrounds.getNextTile(
               document.documentElement.classList.contains('rtl') ? -1 : 1, 0,
-              this.dataset.tile_num);
+              event.currentTarget.dataset.tile_num);
         } else if (event.keyCode == customBackgrounds.KEYCODES.DOWN) {
-          target = customBackgrounds.getNextTile(0, 1, this.dataset.tile_num);
+          target = customBackgrounds.getNextTile(
+              0, 1, event.currentTarget.dataset.tile_num);
         }
         if (target) {
           target.focus();
         } else {
-          this.focus();
+          event.currentTarget.focus();
         }
       }
     };
@@ -653,11 +660,11 @@
   }
   let tileGetsLoaded = 0;
   for (let tile of preLoadTiles) {
-    loadTile(tile, coll_img, () => {
+    loadTile(tile, collImg, () => {
       // After the preloaded tiles finish loading, the rest of the tiles start
       // loading.
       if (++tileGetsLoaded === preLoadTiles.length) {
-        postLoadTiles.forEach((tile) => loadTile(tile, coll_img));
+        postLoadTiles.forEach((tile) => loadTile(tile, collImg, null));
       }
     });
   }
@@ -668,8 +675,8 @@
 /**
  * Add background image src to the tile and add animation for the tile once it
  * successfully loaded.
- * @param {Object} tile the tile that needs to be loaded.
- * @param {object} imageData the source imageData.
+ * @param {!Object} tile the tile that needs to be loaded.
+ * @param {!Object} imageData the source imageData.
  * @param {?Function} countLoad If not null, called after the tile finishes
  * loading.
  */
@@ -690,7 +697,7 @@
  * Fade in effect for both collection and image tile. Once the image
  * successfully loads, we can assume the background image with the same source
  * has also loaded. Then, we set opacity for the tile to start the animation.
- * @param {Object} tile The tile to add the fade in animation to.
+ * @param {!Object} tile The tile to add the fade in animation to.
  * @param {string} imageUrl the image url for the tile
  * @param {?Function} countLoad If not null, called after the tile finishes
  * loading.
@@ -731,8 +738,8 @@
 /*
  * Get the next visible option. There are times when various combinations of
  * options are hidden.
- * @param {int} current_index Index of the option the key press occurred on.
- * @param {int} deltaY Direction to search in, -1 for up, 1 for down.
+ * @param {number} current_index Index of the option the key press occurred on.
+ * @param {number} deltaY Direction to search in, -1 for up, 1 for down.
  */
 customBackgrounds.getNextOption = function(current_index, deltaY) {
   // Create array corresponding to the menu. Important that this is in the same
@@ -895,7 +902,7 @@
   $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).onkeydown = function(
       event) {
     if (event.keyCode === customBackgrounds.KEYCODES.ENTER) {
-      customLinksRestoreDefaultInteraction(event);
+      customLinksRestoreDefaultInteraction();
     } else if (event.keyCode === customBackgrounds.KEYCODES.UP) {
       // Handle arrow key navigation.
       event.preventDefault();
@@ -960,21 +967,21 @@
   $(customBackgrounds.IDS.DONE).disabled = true;
 
   // Interactions with the "Upload an image" option.
-  var uploadImageInteraction = function(event) {
+  var uploadImageInteraction = function() {
     window.chrome.embeddedSearch.newTabPage.selectLocalBackgroundImage();
     ntpApiHandle.logEvent(
         BACKGROUND_CUSTOMIZATION_LOG_TYPE.NTP_CUSTOMIZE_LOCAL_IMAGE_CLICKED);
   };
 
-  $(customBackgrounds.IDS.UPLOAD_IMAGE).onclick = () => {
+  $(customBackgrounds.IDS.UPLOAD_IMAGE).onclick = (event) => {
     if (!$(customBackgrounds.IDS.UPLOAD_IMAGE).classList.contains(
         customBackgrounds.CLASSES.OPTION_DISABLED)) {
       uploadImageInteraction();
     }
-  } ;
+  };
   $(customBackgrounds.IDS.UPLOAD_IMAGE).onkeydown = function(event) {
     if (event.keyCode === customBackgrounds.KEYCODES.ENTER) {
-      uploadImageInteraction(event);
+      uploadImageInteraction();
     }
 
     // Handle arrow key navigation.
@@ -993,14 +1000,14 @@
   };
 
   // Interactions with the "Restore default background" option.
-  var restoreDefaultInteraction = function(event) {
+  var restoreDefaultInteraction = function() {
     editDialog.close();
     customBackgrounds.clearAttribution();
     window.chrome.embeddedSearch.newTabPage.setBackgroundURL('');
     ntpApiHandle.logEvent(BACKGROUND_CUSTOMIZATION_LOG_TYPE
                               .NTP_CUSTOMIZE_RESTORE_BACKGROUND_CLICKED);
   };
-  $(customBackgrounds.IDS.RESTORE_DEFAULT).onclick = () => {
+  $(customBackgrounds.IDS.RESTORE_DEFAULT).onclick = (event) => {
     if (!$(customBackgrounds.IDS.RESTORE_DEFAULT).classList.contains(
         customBackgrounds.CLASSES.OPTION_DISABLED)) {
       restoreDefaultInteraction();
@@ -1008,7 +1015,7 @@
   };
   $(customBackgrounds.IDS.RESTORE_DEFAULT).onkeydown = function(event) {
     if (event.keyCode === customBackgrounds.KEYCODES.ENTER) {
-      restoreDefaultInteraction(event);
+      restoreDefaultInteraction();
     }
 
     // Handle arrow key navigation.
@@ -1035,13 +1042,13 @@
         customBackgrounds.showCollectionSelectionDialog(
             customBackgrounds.SOURCES.CHROME_BACKGROUNDS);
       } else {
-        customBackgrounds.handleError(coll_errors);
+        customBackgrounds.handleError(collErrors);
       }
     };
     ntpApiHandle.logEvent(BACKGROUND_CUSTOMIZATION_LOG_TYPE
                               .NTP_CUSTOMIZE_CHROME_BACKGROUNDS_CLICKED);
   };
-  $(customBackgrounds.IDS.DEFAULT_WALLPAPERS).onclick = function() {
+  $(customBackgrounds.IDS.DEFAULT_WALLPAPERS).onclick = function(event) {
     $(customBackgrounds.IDS.MENU)
         .classList.add(customBackgrounds.CLASSES.MOUSE_NAV);
     defaultWallpapersInteraction(event);
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.js b/chrome/browser/resources/local_ntp/custom_links_edit.js
index 541b7ce3..afb18410 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.js
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.js
@@ -179,7 +179,7 @@
   // Small delay to allow the dialog to close before cleaning up.
   window.setTimeout(() => {
     $(IDS.FORM).reset();
-    $(IDS.TITLE_FIELD).dir = null;
+    $(IDS.TITLE_FIELD).dir = '';
     $(IDS.URL_FIELD_CONTAINER).classList.remove('invalid');
     $(IDS.DELETE).disabled = false;
     $(IDS.DONE).disabled = false;
@@ -210,7 +210,7 @@
 
 /**
  * Handler for the 'updateTheme' message from the host page.
- * @param {object} info Data received in the message.
+ * @param {!Object} info Data received in the message.
  */
 function updateTheme(info) {
   document.documentElement.setAttribute('darkmode', info.isDarkMode);
diff --git a/chrome/browser/resources/local_ntp/doodles.js b/chrome/browser/resources/local_ntp/doodles.js
index 9632a18..bfb601d 100644
--- a/chrome/browser/resources/local_ntp/doodles.js
+++ b/chrome/browser/resources/local_ntp/doodles.js
@@ -275,7 +275,7 @@
           console.log(error);
           return;
         }
-        doodles.handleDdllogResponse(json.ddllog, isAnimated);
+        doodles.handleDdllogResponse(json['ddllog'], isAnimated);
       })
       .catch(function(error) {
         console.log('Error logging doodle impression to "' + logUrl + '":');
@@ -294,7 +294,7 @@
  * Logs a doodle sharing event.
  * Uses the ct param provided in metadata.onClickUrl to track the doodle.
  *
- * @param {string} platform Social media platform the doodle will be shared to.
+ * @param {number} platform Social media platform the doodle will be shared to.
  */
 doodles.logDoodleShare = function(platform) {
   if (doodles.targetDoodle.metadata.onClickUrl) {
@@ -305,7 +305,7 @@
       url.searchParams.append('atyp', 'i');
       url.searchParams.append('ct', 'doodle');
       url.searchParams.append('cad', 'sh,' + platform + ',ct:' + ct);
-      url.searchParams.append('ntp', 1);
+      url.searchParams.append('ntp', '1');
       if (doodles.ei && doodles.ei != '') {
         url.searchParams.append('ei', doodles.ei);
       }
@@ -422,17 +422,17 @@
  * Starts fading out the given element, which should be either the default logo
  * or the doodle.
  *
- * @param {HTMLElement} element
+ * @param {?Element} element
  */
 doodles.startFadeOut = function(element) {
-  if (!element.classList.contains(doodles.CLASSES.SHOW_LOGO)) {
+  if (!element || !element.classList.contains(doodles.CLASSES.SHOW_LOGO)) {
     return;
   }
 
   // Compute style now, to ensure that the transition from 1 -> 0 is properly
   // recognized. Otherwise, if a 0 -> 1 -> 0 transition is too fast, the
   // element might stay invisible instead of appearing then fading out.
-  window.getComputedStyle(element).opacity;
+  const style = window.getComputedStyle(element).opacity;
 
   element.classList.add(doodles.CLASSES.FADE);
   element.classList.remove(doodles.CLASSES.SHOW_LOGO);
@@ -475,7 +475,8 @@
   $(doodles.IDS.LOGO_DEFAULT).classList.add(doodles.CLASSES.FADE);
   doodles.showLogoOrDoodle(/*fromCache=*/ false);
 
-  this.removeEventListener('transitionend', doodles.onDoodleFadeOutComplete);
+  e.target.removeEventListener(
+      'transitionend', doodles.onDoodleFadeOutComplete);
 };
 
 
@@ -498,7 +499,8 @@
 
         // Ping the static interaction_log_url if there is one.
         if (doodles.targetDoodle.staticInteractionLogUrl) {
-          navigator.sendBeacon(doodles.targetDoodle.staticInteractionLogUrl);
+          navigator.sendBeacon(
+              doodles.targetDoodle.staticInteractionLogUrl.href);
           doodles.targetDoodle.staticInteractionLogUrl = null;
         }
 
@@ -520,7 +522,8 @@
 
         // Ping the static interaction_log_url if there is one.
         if (doodles.targetDoodle.staticInteractionLogUrl) {
-          navigator.sendBeacon(doodles.targetDoodle.staticInteractionLogUrl);
+          navigator.sendBeacon(
+              doodles.targetDoodle.staticInteractionLogUrl.href);
           doodles.targetDoodle.staticInteractionLogUrl = null;
         }
 
@@ -541,7 +544,7 @@
           // Ping the animated interaction_log_url if there is one.
           if (doodles.targetDoodle.animatedInteractionLogUrl) {
             navigator.sendBeacon(
-                doodles.targetDoodle.animatedInteractionLogUrl);
+                doodles.targetDoodle.animatedInteractionLogUrl.href);
             doodles.targetDoodle.animatedInteractionLogUrl = null;
           }
 
diff --git a/chrome/browser/resources/local_ntp/externs.js b/chrome/browser/resources/local_ntp/externs.js
new file mode 100644
index 0000000..8b3735d
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/externs.js
@@ -0,0 +1,381 @@
+// 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.
+
+/**
+ * @fileoverview Externs for objects sent from C++ to
+ * chrome-search://local-ntp/local-ntp.html.
+ * @externs
+ */
+
+/**
+ * The type of the most visited data object. The definition is based on
+ * chrome/common/search/instant_types.h:
+ *     InstantMostVisitedItem
+ *  @typedef {{dataGenerationTime: Date,
+ *             isAddButton: boolean,
+ *             tid: number,
+ *             tileSource: number,
+ *             tileTitleSource: number,
+ *             title: ?,
+ *             url: string}}
+ */
+let MostVisitedData;
+
+/**
+ * The type of the config data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     LocalNtpSource::SearchConfigurationProvider::UpdateConfigData()
+ * @typedef {{translatedStrings: Array<string>,
+ *            isGooglePage: boolean,
+ *            googleBaseUrl: string,
+ *            isAccessibleBrowser: boolean,
+ *            removeFakebox: boolean,
+ *            alternateFakebox: boolean,
+ *            fakeboxSearchIcon: boolean,
+ *            hideShortcuts: boolean}}
+ */
+let configData;
+
+/**
+ * The type of the image collections object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     ConvertCollectionInfoToDict()
+ * @typedef {{collectionId: string,
+ *            collectionName: string,
+ *            previewImageUrl: string}}
+ */
+let coll;
+
+/**
+ * The type of the individual image data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     ConvertCollectionImageToDict()
+ * @typedef {{thumbnailImageUrl: string,
+ *            imageUrl: string,
+ *            collectionId: string,
+ *            attributions: Array<string>,
+ *            attributionActionUrl: string}}
+ */
+let collImg;
+
+/**
+ * The type of the errors when fetching individual images object. The
+ * definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     GetErrorDict()
+ * @typedef {{net_error: boolean,
+ *            service_error: boolean,
+ *            net_error_no: number}}
+ */
+let collImgErrors;
+
+/**
+ * The type of the errors when fetching collection object. The definition is
+ * based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     GetErrorDict()
+ * @typedef {{net_error: boolean,
+ *            service_error: boolean,
+ *            net_error_no: number}}
+ */
+let collErrors;
+
+/**
+ * The type of the Doodle data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     LocalNtpSource::DesktopLogoObserver::OnLogoAvailable()
+ * @typedef {{usable: boolean,
+ *            image: string,
+ *            metadata: string}}
+ */
+let ddl;
+
+/**
+ * The type of the OneGoogleBar data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     ConvertOGBDataToDict()
+ * @typedef {{barHtml: string,
+ *            inHeadScript: string,
+ *            inHeadStyle: string,
+ *            afterBarScript: string,
+ *            endOfBodyHtml: string,
+ *            endOfBodyScript: string}}
+ */
+let og;
+
+/**
+ * The type of the middle-slot promo data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     ConvertPromoDataToDict()
+ * @typedef {{promoHtml: string,
+ *            promoLogUrl: string}}
+ */
+let promo;
+
+/**
+ * The type of the search suggestions data object. The definition is based on
+ * chrome/browser/search/local_ntp_source.cc:
+ *     ConvertSearchSuggestDataToDict()
+ * @typedef {{suggestionsHtml: string,
+ *            suggestionsEndOfBodyScript: string}}
+ */
+let searchSuggestions;
+
+/**
+ * Types created by the OneGoogleBar scripts when injected into the page.
+ * Defined in google3/javascript/externs/api/one_google/gbar.js
+ */
+window.gbar;
+window.gbar.a;
+window.gbar.a.pc;
+
+/**
+ * Used for running NTP javascript unit tests. Defined in
+ * src/chrome/test/data/local_ntp/local_ntp_browsertest.html
+ */
+window.localNTPUnitTest;
+
+/**************************** Embedded Search API ****************************/
+
+/**
+ * Embedded Search API methods defined in
+ * chrome/renderer/searchbox/searchbox_extension.cc:
+ *  NewTabPageBindings::GetObjectTemplateBuilder()
+ */
+
+window.chrome;
+window.chrome.embeddedSearch;
+window.chrome.embeddedSearch.newTabPage;
+
+/**
+ * @param {number} task_version
+ * @param {number} task_id
+ */
+window.chrome.embeddedSearch.newTabPage.blacklistSearchSuggestion;
+
+/**
+ * @param {number} task_version
+ * @param {number} task_id
+ * @param {string} hash
+ */
+window.chrome.embeddedSearch.newTabPage.blacklistSearchSuggestionWithHash;
+
+/**
+ * @param {string} identity
+ */
+window.chrome.embeddedSearch.newTabPage.checkIsUserSignedIntoChromeAs;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.checkIsUserSyncingHistory;
+
+/**
+ * @param {number} tid
+ */
+window.chrome.embeddedSearch.newTabPage.deleteMostVisitedItem;
+
+/**
+ * @param {string} url
+ */
+window.chrome.embeddedSearch.newTabPage.fixupAndValidateUrl;
+
+/**
+ * @param {number} tid
+ */
+window.chrome.embeddedSearch.newTabPage.getMostVisitedItemData;
+
+/**
+ * @return {boolean} isCustomLinks
+ */
+window.chrome.embeddedSearch.newTabPage.isCustomLinks;
+
+/**
+ * @return {boolean} isInputInProgress
+ */
+window.chrome.embeddedSearch.newTabPage.isInputInProgress;
+
+/**
+ * @param {number} event
+ */
+window.chrome.embeddedSearch.newTabPage.logEvent;
+
+/**
+ * @param {number} position
+ * @param {number} tile_title_source
+ * @param {number} tile_source
+ * @param {number} tile_type
+ * @param {number} data_generation_time
+ */
+window.chrome.embeddedSearch.newTabPage.logMostVisitedImpression;
+
+/**
+ * @param {number} position
+ * @param {number} tile_title_source
+ * @param {number} tile_source
+ * @param {number} tile_type
+ * @param {number} data_generation_time
+ */
+window.chrome.embeddedSearch.newTabPage.logMostVisitedNavigation;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.mostVisited;
+
+/**
+ * @return {boolean} mostVisitedAvailable
+ */
+window.chrome.embeddedSearch.newTabPage.mostVisitedAvailable;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.optOutOfSearchSuggestions;
+
+/**
+ * @param {number} rid
+ * @param {number} new_pos
+ */
+window.chrome.embeddedSearch.newTabPage.reorderCustomLink;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.resetCustomLinks;
+
+/**
+ * @param {number} task_version
+ * @param {number} task_id
+ * @param {string} hash
+ */
+window.chrome.embeddedSearch.newTabPage.searchSuggestionSelected;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.selectLocalBackgroundImage;
+
+/**
+ * @param {string} background_url
+ */
+window.chrome.embeddedSearch.newTabPage.setBackgroundURL;
+
+/**
+ * @param {string} background_url
+ * @param {string} attribution_line_1
+ * @param {string} attribution_line_2
+ * @param {string} attribution_action_url
+ */
+window.chrome.embeddedSearch.newTabPage.setBackgroundURLWithAttributions;
+
+/**
+ * @return {Object} theme_background_info
+ */
+window.chrome.embeddedSearch.newTabPage.themeBackgroundInfo;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.undoAllMostVisitedDeletions;
+
+/**
+ * No params.
+ */
+window.chrome.embeddedSearch.newTabPage.undoCustomLinkAction;
+
+/**
+ * @param {number} rid_value
+ */
+window.chrome.embeddedSearch.newTabPage.undoMostVisitedDeletion;
+
+/**
+ * @param {number} rid
+ * @param {string} url
+ */
+window.chrome.embeddedSearch.newTabPage.updateCustomLink;
+
+/**
+ * Embedded Search API methods defined in
+ * chrome/renderer/searchbox/searchbox_extension.cc:
+ *  SearchBoxBindings::GetObjectTemplateBuilder()
+ */
+window.chrome.embeddedSearch.searchBox;
+window.chrome.embeddedSearch.searchBox.isKeyCaptureEnabled;
+window.chrome.embeddedSearch.searchBox.paste;
+window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes;
+window.chrome.embeddedSearch.searchBox.stopCapturingKeyStrokes;
+
+
+/**************************** Translated Strings *****************************/
+
+/**
+ * Translated strings defined in
+ * chrome/browser/search/local_ntp_source.cc:
+ *  GetTranslatedStrings()
+ */
+
+configData.translatedStrings.addLinkTitle;
+configData.translatedStrings.addLinkTooltip;
+configData.translatedStrings.attributionIntro;
+configData.translatedStrings.audioError;
+configData.translatedStrings.backLabel;
+configData.translatedStrings.backgroundsUnavailable;
+configData.translatedStrings.clickToViewDoodle;
+configData.translatedStrings.connectionError;
+configData.translatedStrings.connectionErrorNoPeriod;
+configData.translatedStrings.copyLink;
+configData.translatedStrings.customizeBackground;
+configData.translatedStrings.customizeButtonLabel;
+configData.translatedStrings.customizeThisPage;
+configData.translatedStrings.dailyRefresh;
+configData.translatedStrings.defaultWallpapers;
+configData.translatedStrings.details;
+configData.translatedStrings.editLinkTitle;
+configData.translatedStrings.editLinkTooltip;
+configData.translatedStrings.fakeboxMicrophoneTooltip;
+configData.translatedStrings.invalidUrl;
+configData.translatedStrings.languageError;
+configData.translatedStrings.learnMore;
+configData.translatedStrings.linkAddedMsg;
+configData.translatedStrings.linkCancel;
+configData.translatedStrings.linkCantCreate;
+configData.translatedStrings.linkCantEdit;
+configData.translatedStrings.linkCantRemove;
+configData.translatedStrings.linkDone;
+configData.translatedStrings.linkEditedMsg;
+configData.translatedStrings.linkRemove;
+configData.translatedStrings.linkRemovedMsg;
+configData.translatedStrings.listening;
+configData.translatedStrings.moreInfo;
+configData.translatedStrings.mostVisitedTitle;
+configData.translatedStrings.nameField;
+configData.translatedStrings.networkError;
+configData.translatedStrings.noTranslation;
+configData.translatedStrings.noVoice;
+configData.translatedStrings.otherError;
+configData.translatedStrings.permissionError;
+configData.translatedStrings.ready;
+configData.translatedStrings.removeThumbnailTooltip;
+configData.translatedStrings.restoreDefaultBackground;
+configData.translatedStrings.restoreDefaultLinks;
+configData.translatedStrings.restoreThumbnailsShort;
+configData.translatedStrings.searchboxPlaceholder;
+configData.translatedStrings.selectChromeWallpaper;
+configData.translatedStrings.selectedLabel;
+configData.translatedStrings.selectionCancel;
+configData.translatedStrings.selectionDone;
+configData.translatedStrings.shareClose;
+configData.translatedStrings.shareDoodle;
+configData.translatedStrings.shareFacebook;
+configData.translatedStrings.shareLink;
+configData.translatedStrings.shareMail;
+configData.translatedStrings.shareTwitter;
+configData.translatedStrings.thumbnailRemovedNotification;
+configData.translatedStrings.title;
+configData.translatedStrings.tryAgain;
+configData.translatedStrings.undoThumbnailRemove;
+configData.translatedStrings.uploadImage;
+configData.translatedStrings.urlField;
+configData.translatedStrings.waiting;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index e51029a..fdbf521 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -53,8 +53,8 @@
  *
  * @type {{
  *   numTitleLines: number,
- *   titleColor: string,
- *   titleColorAgainstDark: string,
+ *   titleColor: Array<number>,
+ *   titleColorAgainstDark: Array<number>,
  * }}
  */
 var NTP_DESIGN = {
@@ -359,7 +359,6 @@
  * when considering darkness. Therefore, dark mode should only be checked if
  * this is the default NTP. Dark mode is considered a dark theme if enabled.
  *
- * @param {ThemeBackgroundInfo|undefined} info Theme background information.
  * @return {boolean} Whether the theme is dark.
  * @private
  */
@@ -384,7 +383,7 @@
  * is the case when dark mode is enabled and a background image (from a custom
  * background or user theme) is not set.
  *
- * @param {ThemeBackgroundInfo|undefined} info Theme background information.
+ * @param {!Object} info Theme background information.
  * @return {boolean} Whether the chips should be dark.
  * @private
  */
@@ -588,9 +587,9 @@
  * @private
  */
 function setCustomThemeStyle(themeInfo) {
-  var textColor = null;
-  var textColorLight = null;
-  var mvxFilter = null;
+  var textColor = '';
+  var textColorLight = '';
+  var mvxFilter = '';
   if (!themeInfo.usingDefaultTheme) {
     textColor = convertToRGBAColor(themeInfo.textColorRgba);
     textColorLight = convertToRGBAColor(themeInfo.textColorLightRgba);
@@ -703,7 +702,8 @@
   if (success) {
     showNotification(configData.translatedStrings.linkAddedMsg);
   } else {
-    showErrorNotification(configData.translatedStrings.linkCantCreate);
+    showErrorNotification(
+        configData.translatedStrings.linkCantCreate, null, null);
   }
   ntpApiHandle.logEvent(LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_DONE);
 }
@@ -719,7 +719,8 @@
   if (success) {
     showNotification(configData.translatedStrings.linkEditedMsg);
   } else {
-    showErrorNotification(configData.translatedStrings.linkCantEdit);
+    showErrorNotification(
+        configData.translatedStrings.linkCantEdit, null, null);
   }
 }
 
@@ -734,7 +735,8 @@
   if (success) {
     showNotification(configData.translatedStrings.linkRemovedMsg);
   } else {
-    showErrorNotification(configData.translatedStrings.linkCantRemove);
+    showErrorNotification(
+        configData.translatedStrings.linkCantRemove, null, null);
   }
 }
 
@@ -788,10 +790,15 @@
  * Animates the specified notification to float up. Automatically hides any
  * pre-existing notification and sets a delayed timer to hide the new
  * notification.
- * @param {!Element} notification The notification element.
- * @param {!Element} notificationContainer The notification container element.
+ * @param {?Element} notification The notification element.
+ * @param {?Element} notificationContainer The notification container element.
  */
 function floatUpNotification(notification, notificationContainer) {
+  if (!notification || !notificationContainer) {
+    return;
+  }
+
+  // Hide any pre-existing notification.
   if (delayedHideNotification) {
     // Hide the current notification if it's a different type (i.e. error vs
     // success). Otherwise, simply clear the notification timeout and reset it
@@ -838,11 +845,15 @@
 /**
  * Animates the pop-up notification to float down, and clears the timeout to
  * hide the notification.
- * @param {!Element} notification The notification element.
- * @param {!Element} notificationContainer The notification container element.
+ * @param {?Element} notification The notification element.
+ * @param {?Element} notificationContainer The notification container element.
  * @param {boolean} showPromo Do show the promo if present.
  */
 function floatDownNotification(notification, notificationContainer, showPromo) {
+  if (!notification || !notificationContainer) {
+    return;
+  }
+
   if (!notificationContainer.classList.contains(CLASSES.FLOAT_UP)) {
     return;
   }
@@ -874,7 +885,7 @@
     $(IDS.UNDO_LINK).blur();
     $(IDS.RESTORE_ALL_LINK).blur();
     if (notification.classList.contains(CLASSES.HAS_LINK)) {
-      notification.classlist.remove(CLASSES.HAS_LINK);
+      notification.classList.remove(CLASSES.HAS_LINK);
       $(IDS.ERROR_NOTIFICATION_LINK).blur();
     }
     // Hide the notification
@@ -968,8 +979,9 @@
  * @return {boolean} True if the click occurred in an enabled fakebox.
  */
 function isFakeboxClick(event) {
-  return $(IDS.FAKEBOX).contains(event.target) &&
-      !$(IDS.FAKEBOX_MICROPHONE).contains(event.target);
+  return $(IDS.FAKEBOX).contains(/** @type HTMLElement */ (event.target)) &&
+      !$(IDS.FAKEBOX_MICROPHONE)
+           .contains(/** @type HTMLElement */ (event.target));
 }
 
 
@@ -1079,7 +1091,7 @@
     ssScript.async = false;
     document.body.appendChild(ssScript);
     ssScript.onload = function() {
-      injectSearchSuggestions(search_suggestions);
+      injectSearchSuggestions(searchSuggestions);
     };
   }
 }
@@ -1098,7 +1110,7 @@
 
   // Hide notifications after fade out, so we can't focus on links via keyboard.
   $(IDS.NOTIFICATION).addEventListener('transitionend', (event) => {
-    if (event.properyName === 'opacity') {
+    if (event.propertyName === 'opacity') {
       hideNotification();
     }
   });
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js
index fef9f4d4..06723921 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -9,7 +9,7 @@
 
 /**
  * Enum for key codes.
- * @enum {int}
+ * @enum {number}
  * @const
  */
 const KEYCODES = {
@@ -156,7 +156,7 @@
  * or 'chrome-search://local-ntp' for the local NTP.
  * @const {string}
  */
-var DOMAIN_ORIGIN = '{{ORIGIN}}';
+const DOMAIN_ORIGIN = '{{ORIGIN}}';
 
 
 /**
@@ -329,7 +329,7 @@
 
 /**
  * Handler for the 'show' message from the host page.
- * @param {object} info Data received in the message.
+ * @param {!Object} info Data received in the message.
  */
 var showTiles = function(info) {
   logEvent(LOG_TYPE.NTP_ALL_TILES_RECEIVED);
@@ -340,7 +340,7 @@
 
 /**
  * Handler for the 'updateTheme' message from the host page.
- * @param {object} info Data received in the message.
+ * @param {!Object} info Data received in the message.
  */
 var updateTheme = function(info) {
   document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
@@ -361,7 +361,7 @@
  * Handler for 'focusMenu' message from the host page. Focuses the edited tile's
  * menu or the add shortcut tile after closing the custom link edit dialog
  * without saving.
- * @param {object} info Data received in the message.
+ * @param {!Object} info Data received in the message.
  */
 var focusTileMenu = function(info) {
   let tile = document.querySelector(`a.md-tile[data-tid="${info.tid}"]`);
@@ -402,6 +402,9 @@
       'title': queryArgs['addLink'],
       'url': '',
       'isAddButton': true,
+      'dataGenerationTime': new Date(),
+      'tileSource': -1,
+      'tileTitleSource': -1
     };
     tiles.appendChild(renderMaterialDesignTile(data));
   }
@@ -441,7 +444,7 @@
   // getComputedStyle causes the initial style (opacity 0) to be applied, so
   // that when we then set it to 1, that triggers the CSS transition.
   if (fadeIn) {
-    window.getComputedStyle(cur).opacity;
+    const style = window.getComputedStyle(cur).opacity;
   }
   cur.style.opacity = 1.0;
 
@@ -475,7 +478,7 @@
  * Handler for the 'show' message from the host page, called when it wants to
  * add a suggestion tile.
  * It's also used to fill up our tiles to |maxNumTiles| if necessary.
- * @param {object} args Data for the tile to be rendered.
+ * @param {?MostVisitedData} args Data for the tile to be rendered.
  */
 var addTile = function(args) {
   if (isFinite(args.rid)) {
@@ -595,23 +598,28 @@
 
       // Cancel the timeout if the user drags the mouse off the tile and
       // releases or if the mouse if released.
-      let dragend = document.addEventListener('dragend', () => {
+      let dragend = () => {
         window.clearTimeout(timeout);
-      }, {once: true});
-      let mouseup = document.addEventListener('mouseup', () => {
+      };
+      document.addEventListener('dragend', dragend, {once: true});
+
+      let mouseup = () => {
         if (event.button == 0 /* LEFT CLICK */) {
           window.clearTimeout(timeout);
         }
-      }, {once: true});
+      };
+      document.addEventListener('mouseup', mouseup, {once: true});
 
-      // Wait for |REORDER_TIMEOUT_DELAY| before starting the reorder flow.
-      timeout = window.setTimeout(() => {
+      let timeoutFunc = (dragend_in, mouseup_in) => {
         if (!reordering) {
           startReorder(tile);
         }
-        document.removeEventListener('dragend', dragend);
-        document.removeEventListener('mouseup', mouseup);
-      }, REORDER_TIMEOUT_DELAY);
+        document.removeEventListener('dragend', dragend_in);
+        document.removeEventListener('mouseup', mouseup_in);
+      };
+      // Wait for |REORDER_TIMEOUT_DELAY| before starting the reorder flow.
+      timeout = window.setTimeout(
+          timeoutFunc.bind(dragend, mouseup), REORDER_TIMEOUT_DELAY);
     }
   });
 
@@ -639,10 +647,11 @@
 
 /**
  * Renders a MostVisited tile to the DOM.
- * @param {object} data Object containing rid, url, title, favicon, thumbnail,
- *     and optionally isAddButton. isAddButton is true if you want to construct
- *     an add custom link button. data is null if you want to construct an
- *     empty tile. isAddButton can only be set if custom links is enabled.
+ * @param {?MostVisitedData} data Object containing rid, url, title, favicon,
+ *     thumbnail, and optionally isAddButton. isAddButton is true if you want to
+ *     construct an add custom link button. data is null if you want to
+ *     construct an empty tile. isAddButton can only be set if custom links is
+ *     enabled.
  */
 var renderTile = function(data) {
   return renderMaterialDesignTile(data);
@@ -651,9 +660,10 @@
 
 /**
  * Renders a MostVisited tile with Material Design styles.
- * @param {object} data Object containing rid, url, title, favicon, and
- *     optionally isAddButton. isAddButton is if you want to construct an add
- *     custom link button. data is null if you want to construct an empty tile.
+ * @param {?MostVisitedData} data Object containing rid, url, title, favicon,
+ *     and optionally isAddButton. isAddButton is if you want to construct an
+ *     add custom link button. data is null if you want to construct an empty
+ *     tile.
  * @return {Element}
  */
 function renderMaterialDesignTile(data) {
@@ -684,7 +694,7 @@
 
   mdTile.addEventListener('click', function(ev) {
     if (data.isAddButton) {
-      editCustomLink();
+      editCustomLink(null);
       logEvent(LOG_TYPE.NTP_CUSTOMIZE_ADD_SHORTCUT_CLICKED);
     } else {
       logMostVisitedNavigation(
@@ -820,7 +830,7 @@
     // Don't allow the event to bubble out to the containing tile, as that would
     // trigger navigation to the tile URL.
     mdMenu.addEventListener('keydown', function(ev) {
-      event.stopPropagation();
+      ev.stopPropagation();
     });
     utils.disableOutlineOnMouseClick(mdMenu);
 
diff --git a/chrome/browser/resources/local_ntp/most_visited_util.js b/chrome/browser/resources/local_ntp/most_visited_util.js
index 00057a814..d568a79 100644
--- a/chrome/browser/resources/local_ntp/most_visited_util.js
+++ b/chrome/browser/resources/local_ntp/most_visited_util.js
@@ -15,11 +15,11 @@
  * The origin of this request.
  * @const {string}
  */
-var DOMAIN_ORIGIN = '{{ORIGIN}}';
+const MV_DOMAIN_ORIGIN = '{{ORIGIN}}';
 
 /**
  * Parses query parameters from Location.
- * @param {string} location The URL to generate the CSS url for.
+ * @param {!Location} location The URL to generate the CSS url for.
  * @return {Object} Dictionary containing name value pairs for URL.
  */
 function parseQueryParams(location) {
@@ -49,7 +49,7 @@
  * @param {string} title The title for the link.
  * @param {string|undefined} text The text for the link or none.
  * @param {string|undefined} direction The text direction.
- * @return {HTMLAnchorElement} A new link element.
+ * @return {!Element} A new link element.
  */
 function createMostVisitedLink(params, href, title, text, direction) {
   var styles = getMostVisitedStyles(params, !!text);
@@ -87,17 +87,18 @@
     link.appendChild(spanWrap);
   }
   link.addEventListener('focus', function() {
-    window.parent.postMessage('linkFocused', DOMAIN_ORIGIN);
+    window.parent.postMessage('linkFocused', MV_DOMAIN_ORIGIN);
   });
   link.addEventListener('blur', function() {
-    window.parent.postMessage('linkBlurred', DOMAIN_ORIGIN);
+    window.parent.postMessage('linkBlurred', MV_DOMAIN_ORIGIN);
   });
 
   link.addEventListener('keydown', function(event) {
     if (event.keyCode == 46 /* DELETE */ ||
         event.keyCode == 8 /* BACKSPACE */) {
       event.preventDefault();
-      window.parent.postMessage('tileBlacklisted,' + params.pos, DOMAIN_ORIGIN);
+      window.parent.postMessage(
+          'tileBlacklisted,' + params['pos'], MV_DOMAIN_ORIGIN);
     } else if (
         event.keyCode == 13 /* ENTER */ || event.keyCode == 32 /* SPACE */) {
       // Event target is the <a> tag. Send a click event on it, which will
@@ -197,7 +198,7 @@
 
 
 /**
- * @param {string} location A location containing URL parameters.
+ * @param {!Location} location A location containing URL parameters.
  * @param {function(Object, Object)} fill A function called with styles and
  *     data to fill.
  */
diff --git a/chrome/browser/resources/local_ntp/utils.js b/chrome/browser/resources/local_ntp/utils.js
index 90f489b..dd9dc02 100644
--- a/chrome/browser/resources/local_ntp/utils.js
+++ b/chrome/browser/resources/local_ntp/utils.js
@@ -17,7 +17,7 @@
 /**
  * Alias for document.getElementById.
  * @param {string} id The ID of the element to find.
- * @return {HTMLElement} The found element or null if not found.
+ * @return {Element} The found element or null if not found.
  */
 function $(id) {
   // eslint-disable-next-line no-restricted-properties
diff --git a/chrome/browser/resources/local_ntp/voice.js b/chrome/browser/resources/local_ntp/voice.js
index 106dfe7..fe95c7df 100644
--- a/chrome/browser/resources/local_ntp/voice.js
+++ b/chrome/browser/resources/local_ntp/voice.js
@@ -7,17 +7,6 @@
 
 
 /**
- * Alias for document.getElementById.
- * @param {string} id The ID of the element to find.
- * @return {HTMLElement} The found element or null if not found.
- */
-function $(id) {
-  // eslint-disable-next-line no-restricted-properties
-  return document.getElementById(id);
-}
-
-
-/**
  * Get the preferred language for UI localization. Represents Chrome's UI
  * language, which might not coincide with the user's "preferred" language
  * in the Settings. For more details, see:
@@ -307,7 +296,7 @@
 
 /**
  * Log an event from Voice Search.
- * @param {!number} eventType Event from |LOG_TYPE|.
+ * @param {number} eventType Event from |LOG_TYPE|.
  */
 speech.logEvent = function(eventType) {
   window.chrome.embeddedSearch.newTabPage.logEvent(eventType);
@@ -317,14 +306,18 @@
 /**
  * Initialize the speech module as part of the local NTP. Adds event handlers
  * and shows the fakebox microphone icon.
- * @param {!string} googleBaseUrl Base URL for sending queries to Search.
+ * @param {string} googleBaseUrl Base URL for sending queries to Search.
  * @param {!Object} translatedStrings Dictionary of localized string messages.
- * @param {!HTMLElement} fakeboxMicrophoneElem Fakebox microphone icon element.
+ * @param {?Element} fakeboxMicrophoneElem Fakebox microphone icon element.
  * @param {!Object} searchboxApiHandle SearchBox API handle.
  */
 speech.init = function(
     googleBaseUrl, translatedStrings, fakeboxMicrophoneElem,
     searchboxApiHandle) {
+  if (!fakeboxMicrophoneElem) {
+    throw new Error('Speech button element not found.');
+  }
+
   if (speech.currentState_ != speech.State_.UNINITIALIZED) {
     throw new Error(
         'Trying to re-initialize speech when not in UNINITIALIZED state.');
@@ -666,7 +659,7 @@
 
 /**
  * Determines, if the given KeyboardEvent |code| is a space or enter key.
- * @param {!string} A KeyboardEvent's |code| property.
+ * @param {string} code A KeyboardEvent's |code| property.
  * @return True, iff the code represents a space or enter key.
  * @private
  */
@@ -684,7 +677,7 @@
 
 /**
  * Determines if the given event's target id is for a button or navigation link.
- * @param {!string} An event's target id.
+ * @param {string} id An event's target id.
  * @return True, iff the id is for a button or link.
  * @private
  */
@@ -706,7 +699,7 @@
  * - <ESC> aborts voice input when the recognition interface is active.
  * - <ENTER> or <SPACE> interprets as a click if the target is a button or
  *   navigation link, otherwise it submits the speech query if there is one
- * @param {KeyboardEvent} event The keydown event.
+ * @param {!Event} event The keydown event.
  */
 speech.onKeyDown = function(event) {
   if (speech.isUiDefinitelyHidden_()) {
@@ -796,7 +789,7 @@
 
 /**
  * Change the location of this tab to the new URL. Used for query submission.
- * @param {!URL} The URL to navigate to.
+ * @param {!URL} url The URL to navigate to.
  * @private
  */
 speech.navigateToUrl_ = function(url) {
@@ -819,11 +812,11 @@
   // before stopping speech.
   searchParams.append('q', speech.finalResult_);
   // Add a parameter to indicate that this request is a voice search.
-  searchParams.append('gs_ivs', 1);
+  searchParams.append('gs_ivs', '1');
 
   // Build the query URL.
   const queryUrl = new URL('/search', speech.googleBaseUrl_);
-  queryUrl.search = searchParams;
+  queryUrl.search = searchParams.toString();
 
   speech.logEvent(LOG_TYPE.ACTION_QUERY_SUBMITTED);
   speech.stop();
@@ -1110,8 +1103,8 @@
 
 /**
  * Updates the text elements with new recognition results.
- * @param {!string} interimText Low confidence speech recognition result text.
- * @param {!string} opt_finalText High confidence speech recognition result
+ * @param {string} interimText Low confidence speech recognition result text.
+ * @param {string} opt_finalText High confidence speech recognition result
  *     text, defaults to an empty string.
  */
 text.updateTextArea = function(interimText, opt_finalText = '') {
@@ -1690,7 +1683,7 @@
 
 /**
  * Makes sure that a click anywhere closes the UI when it is active.
- * @param {!MouseEvent} event The click event.
+ * @param {!Event} event The click event.
  * @private
  */
 view.onWindowClick_ = function(event) {
diff --git a/chrome/browser/resources/md_extensions/keyboard_shortcuts.html b/chrome/browser/resources/md_extensions/keyboard_shortcuts.html
index d8d6b472..ab08b83 100644
--- a/chrome/browser/resources/md_extensions/keyboard_shortcuts.html
+++ b/chrome/browser/resources/md_extensions/keyboard_shortcuts.html
@@ -53,7 +53,7 @@
       }
 
       .command-entry .md-select {
-        /* TODO(scottchen): line-height needs adjustment to fix large fonts. */
+        /* TODO(johntlee): line-height needs adjustment to fix large fonts. */
         line-height: 22px;
         margin-inline-start: var(--cr-section-padding);
       }
diff --git a/chrome/browser/resources/md_extensions/shared_style.html b/chrome/browser/resources/md_extensions/shared_style.html
index 397c118..765e6c5 100644
--- a/chrome/browser/resources/md_extensions/shared_style.html
+++ b/chrome/browser/resources/md_extensions/shared_style.html
@@ -77,8 +77,8 @@
         margin-inline-end: var(--cr-section-padding);
 
         /* Makes the tappable area fill its parent.
-         * TODO(scottchen): This is an explicit reminder to override once
-         * .separator styling is extracted from settings. */
+         * TODO(crbug.com/949697): This is an explicit reminder to override
+         * once .separator styling is extracted from settings. */
         margin-inline-start: 0;
       }
 
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_section.js b/chrome/browser/resources/settings/autofill_page/autofill_section.js
index b4d4d46..423ae7f 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_section.js
+++ b/chrome/browser/resources/settings/autofill_page/autofill_section.js
@@ -161,8 +161,6 @@
   onAddressMenuTap_: function(e) {
     const menuEvent = /** @type {!{model: !{item: !Object}}} */ (e);
 
-    /* TODO(scottchen): drop the [dataHost][dataHost] once this bug is fixed:
-     https://github.com/Polymer/polymer/issues/2574 */
     // TODO(dpapad): The [dataHost][dataHost] workaround is only necessary for
     // Polymer 1. Remove once migration to Polymer 2 has completed.
     const item = Polymer.DomIf ? menuEvent.model.item :
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
index 57f35c3..225da32 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
@@ -45,8 +45,11 @@
      */
     setCrostiniUsbDeviceShared(guid, shared) {}
 
-    /** @param {string} path Path to stop sharing. */
-    removeCrostiniSharedPath(path) {}
+    /**
+     * @param {string} vmName VM to stop sharing path with.
+     * @param {string} path Path to stop sharing.
+     */
+    removeCrostiniSharedPath(vmName, path) {}
 
     /* Request chrome send a crostini-installer-status-changed event with the
     current installer status */
@@ -87,8 +90,8 @@
     }
 
     /** @override */
-    removeCrostiniSharedPath(path) {
-      chrome.send('removeCrostiniSharedPath', [path]);
+    removeCrostiniSharedPath(vmName, path) {
+      chrome.send('removeCrostiniSharedPath', [vmName, path]);
     }
 
     /** @override */
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js b/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js
index cc04e9ec..0072edd 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_shared_paths.js
@@ -7,6 +7,15 @@
  * 'crostini-shared-paths' is the settings shared paths subpage for Crostini.
  */
 
+(function() {
+
+/**
+ * The default crostini VM is named 'termina'.
+ * https://cs.chromium.org/chromium/src/chrome/browser/chromeos/crostini/crostini_util.h?q=kCrostiniDefaultVmName&dr=CSs
+ * @type {string}
+ */
+const DEFAULT_CROSTINI_VM = 'termina';
+
 Polymer({
   is: 'settings-crostini-shared-paths',
 
@@ -26,22 +35,27 @@
     sharedPaths_: Array,
   },
 
-  observers:
-      ['onCrostiniSharedPathsChanged_(prefs.crostini.shared_paths.value)'],
+  observers: [
+    'onCrostiniSharedPathsChanged_(prefs.guest_os.paths_shared_to_vms.value)'
+  ],
 
   /**
-   * @param {!Array<string>} paths
+   * @param {!Object<!Array<string>>} paths
    * @private
    */
   onCrostiniSharedPathsChanged_: function(paths) {
+    const vmPaths = [];
+    for (const path in paths) {
+      const vms = paths[path];
+      if (vms.includes(DEFAULT_CROSTINI_VM)) {
+        vmPaths.push(path);
+      }
+    }
     settings.CrostiniBrowserProxyImpl.getInstance()
-        .getCrostiniSharedPathsDisplayText(paths)
+        .getCrostiniSharedPathsDisplayText(vmPaths)
         .then(text => {
-          const sharedPaths = [];
-          for (let i = 0; i < paths.length; i++) {
-            sharedPaths.push({path: paths[i], pathDisplayText: text[i]});
-          }
-          this.sharedPaths_ = sharedPaths;
+          this.sharedPaths_ = vmPaths.map(
+              (path, i) => ({path: path, pathDisplayText: text[i]}));
         });
   },
 
@@ -51,6 +65,7 @@
    */
   onRemoveSharedPathTap_: function(event) {
     settings.CrostiniBrowserProxyImpl.getInstance().removeCrostiniSharedPath(
-        event.model.item.path);
+        DEFAULT_CROSTINI_VM, event.model.item.path);
   },
 });
+})();
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.html b/chrome/browser/resources/settings/languages_page/languages_page.html
index ba44641..624536b 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.html
+++ b/chrome/browser/resources/settings/languages_page/languages_page.html
@@ -23,6 +23,7 @@
 <link rel="import" href="../controls/settings_toggle_button.html">
 <link rel="import" href="../icons.html">
 <link rel="import" href="../lifetime_browser_proxy.html">
+<link rel="import" href="../prefs/prefs_behavior.html">
 <link rel="import" href="../route.html">
 <link rel="import" href="../settings_page/settings_animated_pages.html">
 <link rel="import" href="../settings_page/settings_subpage.html">
diff --git a/chrome/browser/resources/settings/languages_page/languages_page.js b/chrome/browser/resources/settings/languages_page/languages_page.js
index 289b434..63a360b 100644
--- a/chrome/browser/resources/settings/languages_page/languages_page.js
+++ b/chrome/browser/resources/settings/languages_page/languages_page.js
@@ -26,6 +26,10 @@
 Polymer({
   is: 'settings-languages-page',
 
+  behaviors: [
+    PrefsBehavior,
+  ],
+
   properties: {
     /**
      * Preferences state.
@@ -543,6 +547,17 @@
     if (this.prefs == undefined) {
       return;
     }
+
+    // <if expr="_google_chrome">
+    // When spell check is disabled, automatically disable using the spelling
+    // service. This resets the spell check option to 'Use basic spell check'
+    // when spell check is turned off. This check is in an observer so that it
+    // can also correct any users who land on the Settings page and happen
+    // to have spelling service enabled but spell check disabled.
+    if (!this.getPref('browser.enable_spellchecking').value) {
+      this.setPrefValue('spellcheck.use_spelling_service', false);
+    }
+    // </if>
   },
 
   /**
diff --git a/chrome/browser/resources/settings/people_page/people_page.js b/chrome/browser/resources/settings/people_page/people_page.js
index 85affc8d..f91c493 100644
--- a/chrome/browser/resources/settings/people_page/people_page.js
+++ b/chrome/browser/resources/settings/people_page/people_page.js
@@ -29,7 +29,7 @@
     /**
      * This flag is used to conditionally show a set of new sign-in UIs to the
      * profiles that have been migrated to be consistent with the web sign-ins.
-     * TODO(scottchen): In the future when all profiles are completely migrated,
+     * TODO(tangltom): In the future when all profiles are completely migrated,
      * this should be removed, and UIs hidden behind it should become default.
      * @private
      */
@@ -44,7 +44,7 @@
     /**
      * This flag is used to conditionally show a set of sync UIs to the
      * profiles that have been migrated to have a unified consent flow.
-     * TODO(scottchen): In the future when all profiles are completely migrated,
+     * TODO(tangltom): In the future when all profiles are completely migrated,
      * this should be removed, and UIs hidden behind it should become default.
      * @private
      */
diff --git a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
index 87ea931..6467115 100644
--- a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
+++ b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.html
@@ -19,7 +19,7 @@
           <cr-input class="printer-name-input" autofocus
               id="printerName"
               value="{{activePrinter.printerName}}"
-              on-value-changed="onPrinterInfoChange_"
+              on-input="onPrinterInfoChange_"
               label="$i18n{printerName}"
               maxlength=64>
           </cr-input>
@@ -28,7 +28,7 @@
           <cr-input label="$i18n{printerAddress}"
               id="printerAddress"
               value="{{activePrinter.printerAddress}}"
-              on-value-changed="onPrinterInfoChange_"
+              on-input="onPrinterInfoChange_"
               disabled="[[!networkProtocolActive_]]"
               maxlength=128>
           </cr-input>
@@ -72,7 +72,7 @@
         <div class="settings-box two-line">
           <cr-input id="printerQueue" label="$i18n{printerQueue}"
               value="{{activePrinter.printerQueue}}"
-              on-value-changed="onPrinterInfoChange_"
+              on-input="onPrinterInfoChange_"
               disabled="[[!networkProtocolActive_]]"
               maxlength=64>
           </cr-input>
@@ -84,12 +84,14 @@
         </div>
         <div class="settings-box two-line">
           <cr-searchable-drop-down items="[[manufacturerList]]"
+              id="printerPPDManufacturer"
               label="$i18n{printerManufacturer}"
               value="{{activePrinter.ppdManufacturer}}">
           </cr-searchable-drop-down>
         </div>
         <div class="settings-box two-line">
           <cr-searchable-drop-down items="[[modelList]]"
+              id="printerPPDModel"
               label="$i18n{printerModel}"
               value="{{activePrinter.ppdModel}}">
           </cr-searchable-drop-down>
@@ -111,7 +113,8 @@
           $i18n{cancel}
         </paper-button>
         <paper-button class="action-button" on-click="onSaveTap_"
-            disabled="[[!canSavePrinter_(activePrinter.*)]]">
+            disabled="[[!canSavePrinter_(activePrinter.*,
+                        printerInfoChanged_)]]">
           $i18n{editPrinterButtonText}
         </paper-button>
       </div>
diff --git a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js
index 643c84b..83f5ab6 100644
--- a/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js
+++ b/chrome/browser/resources/settings/printing_page/cups_edit_printer_dialog.js
@@ -38,6 +38,18 @@
 
 
     /**
+     * Tracks whether the dialog is fully initialized. This is required because
+     * the dialog isn't fully initialized until Model and Manufacturer are set.
+     * Allows us to ignore changes made to these fields until initialization is
+     * complete.
+     * @private
+     */
+    arePrinterFieldsInitialized_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /**
      * If the printer info has changed since loading this dialog. This will
      * only track the freeform input fields, since the other fields contain
      * input selected from dropdown menus.
@@ -78,6 +90,7 @@
   observers: [
     'printerPathChanged_(activePrinter.*)',
     'selectedEditManufacturerChanged_(activePrinter.ppdManufacturer)',
+    'onModelChanged_(activePrinter.ppdModel)',
   ],
 
   /** @override */
@@ -110,6 +123,7 @@
    */
   onProtocolChange_: function(event) {
     this.set('activePrinter.printerProtocol', event.target.value);
+    this.onPrinterInfoChange_();
   },
 
   /** @private */
@@ -125,8 +139,8 @@
   /** @private */
   onSaveTap_: function() {
     if (this.needsReconfigured_) {
-      settings.CupsPrintersBrowserProxyImpl.getInstance().addCupsPrinter(
-          this.activePrinter);
+      settings.CupsPrintersBrowserProxyImpl.getInstance()
+          .reconfigureCupsPrinter(this.activePrinter);
     } else {
       settings.CupsPrintersBrowserProxyImpl.getInstance().updateCupsPrinter(
           this.activePrinter.printerId, this.activePrinter.printerName);
@@ -190,7 +204,7 @@
    * @private
    */
   canSavePrinter_: function() {
-    return !this.printerInfoChanged_ ||
+    return this.printerInfoChanged_ &&
         (settings.printing.isNameAndAddressValid(this.activePrinter) &&
          settings.printing.isPPDInfoValid(
              this.activePrinter.ppdManufacturer, this.activePrinter.ppdModel,
@@ -213,6 +227,16 @@
     }
   },
 
+  /**
+   * Sets printerInfoChanged_ to true to show that the model has changed.
+   * @private
+   */
+  onModelChanged_: function() {
+    if (this.arePrinterFieldsInitialized_) {
+      this.printerInfoChanged_ = true;
+    }
+  },
+
   /** @private */
   onBrowseFile_: function() {
     settings.CupsPrintersBrowserProxyImpl.getInstance()
@@ -243,6 +267,8 @@
   modelListChanged_: function(modelsInfo) {
     if (modelsInfo.success) {
       this.modelList = modelsInfo.models;
+      // ModelListChanged_ is the final step of initializing activePrinter.
+      this.arePrinterFieldsInitialized_ = true;
     }
   },
 
@@ -253,6 +279,10 @@
   printerPPDPathChanged_: function(path) {
     this.set('activePrinter.printerPPDPath', path);
     this.invalidPPD_ = !path;
+    if (!this.invalidPPD_) {
+      // A new valid PPD file should be treated as a saveable change.
+      this.onPrinterInfoChange_();
+    }
     this.userPPD_ = settings.printing.getBaseName(path);
   },
 });
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js b/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js
index 61eaa1c7..d083e82 100644
--- a/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_browser_proxy.js
@@ -139,6 +139,11 @@
      */
     addCupsPrinter(newPrinter) {}
 
+    /**
+     * @param {!CupsPrinterInfo} printer
+     */
+    reconfigureCupsPrinter(printer) {}
+
     startDiscoveringPrinters() {}
     stopDiscoveringPrinters() {}
 
@@ -202,6 +207,11 @@
     }
 
     /** @override */
+    reconfigureCupsPrinter(printer) {
+      chrome.send('reconfigureCupsPrinter', [printer]);
+    }
+
+    /** @override */
     getCupsPrinterPPDPath() {
       return cr.sendWithPromise('selectPPDFile');
     }
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index bfb43ba..3077a57 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -22,6 +22,7 @@
     "..:route",
     "../controls:settings_toggle_button",
     "../people_page:sync_browser_proxy",
+    "../prefs:prefs_behavior",
     "//ui/webui/resources/js:web_ui_listener_behavior",
   ]
   externs_list = [ "$externs_path/settings_private.js" ]
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.html b/chrome/browser/resources/settings/privacy_page/personalization_options.html
index 13d16bb..7efe994e 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.html
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.html
@@ -6,6 +6,7 @@
 <link rel="import" href="../controls/settings_toggle_button.html">
 <link rel="import" href="../lifetime_browser_proxy.html">
 <link rel="import" href="../people_page/sync_browser_proxy.html">
+<link rel="import" href="../prefs/prefs_behavior.html">
 <link rel="import" href="../route.html">
 <link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="privacy_page_browser_proxy.html">
@@ -78,6 +79,7 @@
 <if expr="_google_chrome">
     <settings-toggle-button id="spellCheckControl"
         pref="{{prefs.spellcheck.use_spelling_service}}"
+        on-settings-boolean-control-change="onUseSpellingServiceToggle_"
         label="$i18n{spellingPref}"
         sub-label="$i18n{spellingDescription}"
         hidden="[[!showSpellCheckControl_(prefs.spellcheck.dictionaries)]]">
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.js b/chrome/browser/resources/settings/privacy_page/personalization_options.js
index c6ffda1..f3a7ad0 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.js
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.js
@@ -13,6 +13,7 @@
   is: 'settings-personalization-options',
 
   behaviors: [
+    PrefsBehavior,
     WebUIListenerBehavior,
   ],
 
@@ -107,6 +108,20 @@
   },
   // </if>
 
+  // <if expr="_google_chrome">
+  /**
+   * @param {!Event} event
+   * @private
+   */
+  onUseSpellingServiceToggle_: function(event) {
+    // If turning on using the spelling service, automatically turn on
+    // spellcheck so that the spelling service can run.
+    if (event.target.checked) {
+      this.setPrefValue('browser.enable_spellchecking', true);
+    }
+  },
+  // </if>
+
   /**
    * @return {boolean}
    * @private
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chrome/browser/resources/settings/privacy_page/privacy_page.js
index e89e377..9fde0dc 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.js
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.js
@@ -186,7 +186,7 @@
     /**
      * This flag is used to conditionally show a set of sync UIs to the
      * profiles that have been migrated to have a unified consent flow.
-     * TODO(scottchen): In the future when all profiles are completely migrated,
+     * TODO(tangltom): In the future when all profiles are completely migrated,
      * this should be removed, and UIs hidden behind it should become default.
      * @private
      */
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 90a2745..5507464 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -687,11 +687,11 @@
         new URLSearchParams(window.location.search), true);
   });
 
-  // TODO(scottchen): Change to 'get routes() {}' in export when we fix a bug in
+  // TODO(dpapad): Change to 'get routes() {}' in export when we fix a bug in
   // ChromePass that limits the syntax of what can be returned from cr.define().
   const routes = routerInstance.getRoutes();
 
-  // TODO(scottchen): Stop exposing all those methods directly on settings.*,
+  // TODO(dpapad): Stop exposing all those methods directly on settings.*,
   // and instead update all clients to use the singleton instance directly
   const getCurrentRoute = routerInstance.getCurrentRoute.bind(routerInstance);
   const getRouteForPath = routerInstance.getRouteForPath.bind(routerInstance);
diff --git a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js
index a1e3687f..1611ba92 100644
--- a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js
+++ b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation.js
@@ -93,7 +93,7 @@
     }
   }
 
-  // TODO(scottchen): clearFocus and setUserImageURL are called directly by the
+  // TODO(tangltom): clearFocus and setUserImageURL are called directly by the
   // C++ handler. C++ handlers should not be calling JS functions by name
   // anymore. They should be firing events with FireWebuiListener and have the
   // page itself decide whether to listen or not listen to the event.
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 9617283aa..b7a963c 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -312,7 +312,6 @@
       "//chrome/common/safe_browsing:proto",
       "//components/safe_browsing/db:database_manager",
       "//components/safe_browsing/db:test_database_manager",
-      "//components/safe_browsing/db:v4_feature_list",
       "//components/safe_browsing/db:v4_protocol_manager_util",
       "//content/public/browser:browser",
     ]
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc
deleted file mode 100644
index 97708b2..0000000
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_browsertest.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/files/file_path.h"
-#include "base/path_service.h"
-#include "base/run_loop.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/safe_browsing/proto/csd.pb.h"
-#include "components/safe_browsing/web_ui/safe_browsing_ui.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/download_manager.h"
-#include "content/public/browser/site_instance.h"
-#include "content/public/test/browser_test.h"
-#include "crypto/sha2.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/window_open_disposition.h"
-
-namespace safe_browsing {
-
-class DownloadProtectionServiceBrowserTest : public InProcessBrowserTest {
- protected:
-  base::FilePath GetTestDataDirectory() {
-    base::FilePath test_file_directory;
-    base::PathService::Get(chrome::DIR_TEST_DATA, &test_file_directory);
-    return test_file_directory;
-  }
-
-  void DownloadAndWait(GURL url) {
-    content::DownloadManager* download_manager =
-        content::BrowserContext::GetDownloadManager(browser()->profile());
-
-    // This call will block until the navigation completes, but will not wait
-    // for the download to finish.
-    ui_test_utils::NavigateToURLWithDisposition(
-        browser(), url, WindowOpenDisposition::CURRENT_TAB,
-        ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
-
-    // Waits for the download to complete.
-    while (download_manager->InProgressCount() > 0) {
-      base::RunLoop run_loop;
-      run_loop.RunUntilIdle();
-    }
-  }
-
-  // The hash of 10 A's, followed by a newline. Was generated as follows:
-  //   echo "AAAAAAAAAA" > a.zip
-  //   sha256sum a.zip
-  static const uint8_t kAZipDigest[];
-
-  // The hash of 9 B's, followed by a newline. Was generated as follows:
-  //   echo "BBBBBBBBB" > a.zip
-  //   sha256sum a.zip
-  static const uint8_t kBZipDigest[];
-};
-
-const uint8_t DownloadProtectionServiceBrowserTest::kAZipDigest[] = {
-    0x70, 0x5d, 0x29, 0x0c, 0x12, 0x89, 0x59, 0x01, 0xf8, 0x09, 0xf6,
-    0xc2, 0xfe, 0x77, 0x2a, 0x94, 0xdb, 0x51, 0x81, 0xd7, 0x26, 0x46,
-    0x4d, 0x84, 0x06, 0x82, 0x10, 0x6f, 0x4a, 0x93, 0x4b, 0x87};
-
-const uint8_t DownloadProtectionServiceBrowserTest::kBZipDigest[] = {
-    0x94, 0x1e, 0x17, 0x3f, 0x62, 0xbc, 0x04, 0x50, 0x6f, 0xeb, 0xb5,
-    0xe2, 0x8c, 0x38, 0x6c, 0xb2, 0x11, 0x91, 0xf3, 0x77, 0xa7, 0x2c,
-    0x11, 0x92, 0xe0, 0x25, 0xb0, 0xe5, 0xc7, 0x70, 0x3b, 0x23};
-
-IN_PROC_BROWSER_TEST_F(DownloadProtectionServiceBrowserTest, VerifyZipHash) {
-  embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  WebUIInfoSingleton::GetInstance()->AddListenerForTesting();
-
-  GURL url = embedded_test_server()->GetURL(
-      "/safe_browsing/download_protection/zipfile_two_archives.zip");
-  DownloadAndWait(url);
-
-  const std::vector<std::unique_ptr<ClientDownloadRequest>>& requests =
-      WebUIInfoSingleton::GetInstance()->client_download_requests_sent();
-
-  ASSERT_EQ(1u, requests.size());
-  ASSERT_EQ(2, requests[0]->archived_binary_size());
-
-  EXPECT_EQ("a.zip", requests[0]->archived_binary(0).file_basename());
-  EXPECT_EQ(std::string(kAZipDigest, kAZipDigest + crypto::kSHA256Length),
-            requests[0]->archived_binary(0).digests().sha256());
-
-  EXPECT_EQ("b.zip", requests[0]->archived_binary(1).file_basename());
-  EXPECT_EQ(std::string(kBZipDigest, kBZipDigest + crypto::kSHA256Length),
-            requests[0]->archived_binary(1).digests().sha256());
-}
-
-IN_PROC_BROWSER_TEST_F(DownloadProtectionServiceBrowserTest, VerifyRarHash) {
-  embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  WebUIInfoSingleton::GetInstance()->AddListenerForTesting();
-
-  GURL url =
-      embedded_test_server()->GetURL("/safe_browsing/rar/has_two_archives.rar");
-  DownloadAndWait(url);
-
-  const std::vector<std::unique_ptr<ClientDownloadRequest>>& requests =
-      WebUIInfoSingleton::GetInstance()->client_download_requests_sent();
-
-  ASSERT_EQ(1u, requests.size());
-  ASSERT_EQ(2, requests[0]->archived_binary_size());
-
-  EXPECT_EQ("a.zip", requests[0]->archived_binary(0).file_basename());
-  EXPECT_EQ(std::string(kAZipDigest, kAZipDigest + crypto::kSHA256Length),
-            requests[0]->archived_binary(0).digests().sha256());
-
-  EXPECT_EQ("b.zip", requests[0]->archived_binary(1).file_basename());
-  EXPECT_EQ(std::string(kBZipDigest, kBZipDigest + crypto::kSHA256Length),
-            requests[0]->archived_binary(1).digests().sha256());
-}
-
-}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
index 00421a2..d42033c9 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
@@ -69,7 +69,6 @@
 #include "components/safe_browsing/db/test_database_manager.h"
 #include "components/safe_browsing/db/util.h"
 #include "components/safe_browsing/db/v4_database.h"
-#include "components/safe_browsing/db/v4_feature_list.h"
 #include "components/safe_browsing/db/v4_get_hash_protocol_manager.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/db/v4_test_util.h"
diff --git a/chrome/browser/safe_browsing/test_safe_browsing_service.cc b/chrome/browser/safe_browsing/test_safe_browsing_service.cc
index 3eb4ee65..51daa003 100644
--- a/chrome/browser/safe_browsing/test_safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/test_safe_browsing_service.cc
@@ -10,7 +10,6 @@
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "components/safe_browsing/db/database_manager.h"
 #include "components/safe_browsing/db/test_database_manager.h"
-#include "components/safe_browsing/db/v4_feature_list.h"
 
 namespace safe_browsing {
 
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index 028e4d2..0bb1d77 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -222,6 +222,8 @@
               IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL);
     AddString(translated_strings.get(), "backLabel",
               IDS_NTP_CUSTOM_BG_BACK_LABEL);
+    AddString(translated_strings.get(), "selectedLabel",
+              IDS_NTP_CUSTOM_BG_IMAGE_SELECTED);
 
     // Custom Links
     AddString(translated_strings.get(), "addLinkTitle",
@@ -855,7 +857,7 @@
     // check must be changed.
     if (one_google_bar_service_->language_code() != kEnUSLanguageCode) {
       std::string no_suggestions =
-          "var search_suggestions = {suggestionsHtml: ''}";
+          "var searchSuggestions = {suggestionsHtml: ''}";
       callback.Run(base::RefCountedString::TakeString(&no_suggestions));
       return;
     }
@@ -1051,7 +1053,7 @@
     return;
 
   std::string js_errors =
-      "var coll_errors = " +
+      "var collErrors = " +
       GetErrorDict(ntp_background_service_->collection_error_info());
 
   scoped_refptr<base::RefCountedString> result;
@@ -1089,7 +1091,7 @@
     return;
 
   std::string js_errors =
-      "var coll_img_errors = " +
+      "var collImgErrors = " +
       GetErrorDict(ntp_background_service_->collection_images_error_info());
 
   scoped_refptr<base::RefCountedString> result;
@@ -1097,7 +1099,7 @@
   base::JSONWriter::Write(ConvertCollectionImageToDict(
                               ntp_background_service_->collection_images()),
                           &js);
-  js = "var coll_img = " + js + "; " + js_errors;
+  js = "var collImg = " + js + "; " + js_errors;
   result = base::RefCountedString::TakeString(&js);
 
   base::TimeTicks now = base::TimeTicks::Now();
@@ -1204,7 +1206,7 @@
   scoped_refptr<base::RefCountedString> result;
   std::string js;
   base::JSONWriter::Write(*ConvertSearchSuggestDataToDict(data), &js);
-  js = "var search_suggestions  = " + js + ";";
+  js = "var searchSuggestions  = " + js + ";";
   result = base::RefCountedString::TakeString(&js);
   callback.Run(result);
 }
diff --git a/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index 3bbf083..d5198ac 100644
--- a/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -42,7 +42,7 @@
 
   // Returns the collection of default datatypes.
   std::vector<syncer::ModelType> DefaultDatatypes() {
-    static_assert(44 == syncer::MODEL_TYPE_COUNT,
+    static_assert(44 == syncer::ModelType::NUM_ENTRIES,
                   "When adding a new type, you probably want to add it here as "
                   "well (assuming it is already enabled).");
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3f9150d..dcc5061 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1762,6 +1762,8 @@
       "webui/settings/chromeos/internet_handler.h",
       "webui/settings/chromeos/multidevice_handler.cc",
       "webui/settings/chromeos/multidevice_handler.h",
+      "webui/settings/chromeos/os_settings_ui.cc",
+      "webui/settings/chromeos/os_settings_ui.h",
       "webui/settings/tts_handler.cc",
       "webui/settings/tts_handler.h",
       "webui/signin/inline_login_handler_chromeos.cc",
diff --git a/chrome/browser/ui/OWNERS b/chrome/browser/ui/OWNERS
index d7e7476..744ff0a 100644
--- a/chrome/browser/ui/OWNERS
+++ b/chrome/browser/ui/OWNERS
@@ -21,4 +21,6 @@
 per-file avatar_*=file://components/signin/OWNERS
 per-file signin_*=file://components/signin/OWNERS
 
+per-file settings_window_manager_chromeos*=file://chrome/browser/ui/ash/OWNERS
+
 # COMPONENT: UI>Browser
diff --git a/chrome/browser/ui/settings_window_manager_chromeos.cc b/chrome/browser/ui/settings_window_manager_chromeos.cc
index d24a0f3..b4d67bb 100644
--- a/chrome/browser/ui/settings_window_manager_chromeos.cc
+++ b/chrome/browser/ui/settings_window_manager_chromeos.cc
@@ -24,7 +24,6 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/ui_base_features.h"
 #include "url/gurl.h"
-#include "url/url_constants.h"
 
 namespace chrome {
 
@@ -112,8 +111,7 @@
 
 void SettingsWindowManager::ShowOSSettings(Profile* profile) {
   if (base::FeatureList::IsEnabled(chromeos::features::kSplitSettings)) {
-    // TODO(jamescook): Add an "os-settings" URL and host.
-    ShowChromePageForProfile(profile, GURL(url::kAboutBlankURL));
+    ShowChromePageForProfile(profile, GURL(kChromeUIOSSettingsURL));
     return;
   }
   ShowChromePageForProfile(profile, GURL(kChromeUISettingsURL));
diff --git a/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos.cc b/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos.cc
index 7f0e4c6..36a14cf 100644
--- a/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos.cc
+++ b/chrome/browser/ui/views/ime_driver/input_method_bridge_chromeos.cc
@@ -64,7 +64,8 @@
   ui::KeyEvent* key_event = event->AsKeyEvent();
   if (IsActiveInputContextHandler(input_method_chromeos_.get()) &&
       !key_event->is_char()) {
-    input_method_chromeos_->DispatchKeyEvent(key_event, std::move(callback));
+    input_method_chromeos_->DispatchKeyEventAsync(key_event,
+                                                  std::move(callback));
   } else {
     const bool handled = false;
     std::move(callback).Run(handled);
diff --git a/chrome/browser/ui/views/media_router/cast_dialog_view.cc b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
index 0c372bf..9a0b58f 100644
--- a/chrome/browser/ui/views/media_router/cast_dialog_view.cc
+++ b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
@@ -65,6 +65,16 @@
 }
 
 // static
+void CastDialogView::ShowDialogCenteredForBrowserWindow(
+    CastDialogController* controller,
+    Browser* browser,
+    const base::Time& start_time) {
+  ShowDialog(BrowserView::GetBrowserViewForBrowser(browser)->top_container(),
+             views::BubbleBorder::TOP_CENTER, controller, browser->profile(),
+             start_time);
+}
+
+// static
 void CastDialogView::ShowDialogCentered(const gfx::Rect& bounds,
                                         CastDialogController* controller,
                                         Profile* profile,
diff --git a/chrome/browser/ui/views/media_router/cast_dialog_view.h b/chrome/browser/ui/views/media_router/cast_dialog_view.h
index 270731a..61fa985 100644
--- a/chrome/browser/ui/views/media_router/cast_dialog_view.h
+++ b/chrome/browser/ui/views/media_router/cast_dialog_view.h
@@ -53,6 +53,13 @@
                                           Browser* browser,
                                           const base::Time& start_time);
 
+  // Shows the singleton dialog anchored to the top-center of the browser
+  // window.
+  static void ShowDialogCenteredForBrowserWindow(
+      CastDialogController* controller,
+      Browser* browser,
+      const base::Time& start_time);
+
   // Shows the singleton dialog anchored to the bottom of |bounds|, horizontally
   // centered.
   static void ShowDialogCentered(const gfx::Rect& bounds,
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
index 63ba6184..9a9dda47 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views.cc
@@ -72,10 +72,8 @@
       CastDialogView::ShowDialogWithToolbarAction(ui_.get(), browser,
                                                   dialog_creation_time);
     } else {
-      const gfx::Rect& anchor_bounds =
-          browser_view->top_container()->GetBoundsInScreen();
-      CastDialogView::ShowDialogCentered(anchor_bounds, ui_.get(), profile,
-                                         dialog_creation_time);
+      CastDialogView::ShowDialogCenteredForBrowserWindow(ui_.get(), browser,
+                                                         dialog_creation_time);
     }
   } else {
     gfx::Rect anchor_bounds = initiator()->GetContainerBounds();
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
index 84edbb9..d44abc6 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
@@ -267,6 +267,10 @@
   }
 }
 
+bool TabHoverCardBubbleView::IsFadingOut() const {
+  return fade_animation_delegate_->IsFadingOut();
+}
+
 int TabHoverCardBubbleView::GetDialogButtons() const {
   return ui::DIALOG_BUTTON_NONE;
 }
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
index ddd14efa86..f65412ca 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
@@ -37,6 +37,8 @@
 
   void FadeOutToHide();
 
+  bool IsFadingOut() const;
+
   // BubbleDialogDelegateView:
   int GetDialogButtons() const override;
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 607215a..9580e58 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -2715,7 +2715,7 @@
     return false;
 
   return hover_card_ && hover_card_->GetWidget()->IsVisible() &&
-         hover_card_->GetAnchorView() == tab;
+         !hover_card_->IsFadingOut() && hover_card_->GetAnchorView() == tab;
 }
 
 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 2327bcf..56cb558 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -67,6 +67,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
+#include "chrome/common/webui_url_constants.h"
 #include "components/dom_distiller/core/dom_distiller_constants.h"
 #include "components/dom_distiller/core/dom_distiller_features.h"
 #include "components/dom_distiller/core/dom_distiller_service.h"
@@ -167,6 +168,7 @@
 #include "chrome/browser/ui/webui/chromeos/smb_shares/smb_credentials_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/smb_shares/smb_share_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_ui.h"
+#include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
 #include "chrome/browser/ui/webui/signin/inline_login_ui.h"
 #include "chromeos/components/multidevice/debug_webui/proximity_auth_ui.h"
 #include "chromeos/components/multidevice/debug_webui/url_constants.h"
@@ -513,6 +515,8 @@
     return &NewWebUI<chromeos::NetworkUI>;
   if (url.host_piece() == chrome::kChromeUIOobeHost)
     return &NewWebUI<chromeos::OobeUI>;
+  if (url.host_piece() == chrome::kChromeUIOSSettingsHost)
+    return &NewWebUI<chromeos::settings::OSSettingsUI>;
   if (url.host_piece() == chrome::kChromeUIPowerHost)
     return &NewWebUI<chromeos::PowerUI>;
   if (url.host_piece() == chromeos::multidevice::kChromeUIProximityAuthHost)
diff --git a/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_handler.cc b/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_handler.cc
index 4277488..22fee22 100644
--- a/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_handler.cc
+++ b/chrome/browser/ui/webui/extensions/chromeos/kiosk_apps_handler.cc
@@ -111,9 +111,9 @@
       weak_ptr_factory_(this) {}
 
 KioskAppsHandler::~KioskAppsHandler() {
-  // TODO(scottchen): This is needed because OnJavascriptDisallowed only called
+  // TODO(tommycli): This is needed because OnJavascriptDisallowed only called
   // with refresh or off-page navigation, otherwise DCHECK triggered when
-  // exiting. Ask tommycli for more information.
+  // exiting.
   kiosk_app_manager_->RemoveObserver(this);
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
index 99048ab..7559e03 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
@@ -120,12 +120,14 @@
 
 void CrostiniHandler::HandleRemoveCrostiniSharedPath(
     const base::ListValue* args) {
-  CHECK_EQ(1U, args->GetSize());
+  CHECK_EQ(2U, args->GetSize());
+  std::string vm_name;
+  CHECK(args->GetString(0, &vm_name));
   std::string path;
-  CHECK(args->GetString(0, &path));
+  CHECK(args->GetString(1, &path));
 
   crostini::CrostiniSharePath::GetForProfile(profile_)->UnsharePath(
-      crostini::kCrostiniDefaultVmName, base::FilePath(path),
+      vm_name, base::FilePath(path),
       /*unpersist=*/true,
       base::BindOnce(
           [](const std::string& path, bool result, std::string failure_reason) {
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index decd9f8..8eeeea9 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -309,6 +309,10 @@
       base::BindRepeating(&CupsPrintersHandler::HandleAddCupsPrinter,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
+      "reconfigureCupsPrinter",
+      base::BindRepeating(&CupsPrintersHandler::HandleReconfigureCupsPrinter,
+                          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
       "getPrinterInfo",
       base::BindRepeating(&CupsPrintersHandler::HandleGetPrinterInfo,
                           base::Unretained(this)));
@@ -387,7 +391,7 @@
   printer.set_display_name(printer_name);
 
   if (!profile_->GetPrefs()->GetBoolean(prefs::kUserNativePrintersAllowed)) {
-    PRINTER_LOG(DEBUG) << "HandleAddCupsPrinter() called when "
+    PRINTER_LOG(DEBUG) << "HandleUpdateCupsPrinter() called when "
                           "kUserNativePrintersAllowed is set to false";
     // Used to log UMA metrics.
     OnAddedOrEditedPrinterCommon(
@@ -586,7 +590,17 @@
 
 void CupsPrintersHandler::HandleAddCupsPrinter(const base::ListValue* args) {
   AllowJavascript();
+  AddOrReconfigurePrinter(args, false /* is_printer_edit */);
+}
 
+void CupsPrintersHandler::HandleReconfigureCupsPrinter(
+    const base::ListValue* args) {
+  AllowJavascript();
+  AddOrReconfigurePrinter(args, true /* is_printer_edit */);
+}
+
+void CupsPrintersHandler::AddOrReconfigurePrinter(const base::ListValue* args,
+                                                  bool is_printer_edit) {
   const base::DictionaryValue* printer_dict = nullptr;
   CHECK(args->GetDictionary(0, &printer_dict));
 
@@ -598,7 +612,7 @@
   }
 
   if (!profile_->GetPrefs()->GetBoolean(prefs::kUserNativePrintersAllowed)) {
-    PRINTER_LOG(DEBUG) << "HandleAddCupsPrinter() called when "
+    PRINTER_LOG(DEBUG) << "AddOrReconfigurePrinter() called when "
                           "kUserNativePrintersAllowed is set to false";
     // Used to log UMA metrics.
     OnAddedOrEditedPrinterCommon(
@@ -616,18 +630,22 @@
     return;
   }
 
-  // If the provided printer already exists, grab the existing printer object
-  // and check that we are not making any changes that will make the
-  // printerexisting_printer unusable.
-  const bool is_existing_printer = !printer->id().empty();
-  if (is_existing_printer) {
-    std::unique_ptr<Printer> existing_printer =
-        printers_manager_->GetPrinter(printer->id());
-    if (existing_printer) {
-      if (!IsValidUriChange(*existing_printer, *printer)) {
-        OnAddOrEditPrinterError(PrinterSetupResult::kInvalidPrinterUpdate);
-        return;
-      }
+  // Grab the existing printer object and check that we are not making any
+  // changes that will make |existing_printer_object| unusable.
+  if (printer->id().empty()) {
+    // If the printer object has not already been created, error out since this
+    // is not a valid case.
+    PRINTER_LOG(ERROR) << "Failed to parse printer ID";
+    OnAddOrEditPrinterError(PrinterSetupResult::kFatalError);
+    return;
+  }
+
+  std::unique_ptr<Printer> existing_printer_object =
+      printers_manager_->GetPrinter(printer->id());
+  if (existing_printer_object) {
+    if (!IsValidUriChange(*existing_printer_object, *printer)) {
+      OnAddOrEditPrinterError(PrinterSetupResult::kInvalidPrinterUpdate);
+      return;
     }
   }
 
@@ -691,9 +709,9 @@
   }
 
   printer_configurer_->SetUpPrinter(
-      *printer, base::BindOnce(
-                    &CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter,
-                    weak_factory_.GetWeakPtr(), *printer, is_existing_printer));
+      *printer,
+      base::BindOnce(&CupsPrintersHandler::OnAddedOrEditedSpecifiedPrinter,
+                     weak_factory_.GetWeakPtr(), *printer, is_printer_edit));
 }
 
 void CupsPrintersHandler::OnAddedOrEditedPrinterCommon(
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
index d6a1a8c..3e4d7d2 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
@@ -103,10 +103,15 @@
 
   void HandleAddCupsPrinter(const base::ListValue* args);
 
+  void HandleReconfigureCupsPrinter(const base::ListValue* args);
+
+  void AddOrReconfigurePrinter(const base::ListValue* args,
+                               bool is_printer_edit);
+
   // Handles the result of adding a printer which the user specified the
   // location of (i.e. a printer that was not 'discovered' automatically).
   void OnAddedOrEditedSpecifiedPrinter(const Printer& printer,
-                                       bool is_existing_printer,
+                                       bool is_printer_edit,
                                        PrinterSetupResult result);
 
   // Handles the result of failure to add a printer. |result_code| is used to
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
new file mode 100644
index 0000000..0375c27
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/browser_resources.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace chromeos {
+namespace settings {
+
+OSSettingsUI::OSSettingsUI(content::WebUI* web_ui)
+    : content::WebUIController(web_ui) {
+  Profile* profile = Profile::FromWebUI(web_ui);
+  content::WebUIDataSource* html_source =
+      content::WebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
+
+  // TODO(jamescook): Factor out page handlers from MdSettingsUI and initialize
+  // them both there and here.
+
+  html_source->UseGzip();
+  html_source->SetDefaultResource(IDR_OS_SETTINGS_HTML);
+
+  content::WebUIDataSource::Add(profile, html_source);
+}
+
+OSSettingsUI::~OSSettingsUI() = default;
+
+}  // namespace settings
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
new file mode 100644
index 0000000..6f5fdbd
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_OS_SETTINGS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_OS_SETTINGS_UI_H_
+
+#include "base/macros.h"
+#include "content/public/browser/web_ui_controller.h"
+
+namespace chromeos {
+namespace settings {
+
+// The WebUI handler for chrome://os-settings.
+class OSSettingsUI : public content::WebUIController {
+ public:
+  explicit OSSettingsUI(content::WebUI* web_ui);
+  ~OSSettingsUI() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OSSettingsUI);
+};
+
+}  // namespace settings
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_OS_SETTINGS_UI_H_
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.h b/chrome/browser/ui/webui/settings/md_settings_ui.h
index f9182a0..3aa5270 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.h
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.h
@@ -5,8 +5,6 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_UI_H_
 
-#include <unordered_set>
-
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "content/public/browser/web_contents_observer.h"
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 9cd3011..a813b89 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -680,8 +680,10 @@
       "//chrome/test:test_support",
       "//chrome/test:test_support_ui",
       "//device/vr:vr",
+      "//device/vr/buildflags:buildflags",
       "//device/vr/public/mojom:mojom",
       "//device/vr/public/mojom:test_mojom",
+      "//services/service_manager/sandbox",
     ]
     data_deps = []
 
diff --git a/chrome/browser/vr/test/webvr_browser_test.h b/chrome/browser/vr/test/webvr_browser_test.h
index 1ee7466..418c29e 100644
--- a/chrome/browser/vr/test/webvr_browser_test.h
+++ b/chrome/browser/vr/test/webvr_browser_test.h
@@ -5,11 +5,17 @@
 #ifndef CHROME_BROWSER_VR_TEST_WEBVR_BROWSER_TEST_H_
 #define CHROME_BROWSER_VR_TEST_WEBVR_BROWSER_TEST_H_
 
+#include "build/build_config.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
 #include "chrome/common/chrome_features.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "device/vr/buildflags/buildflags.h"
+
+#if defined(OS_WIN)
+#include "services/service_manager/sandbox/features.h"
+#endif
 
 namespace vr {
 
@@ -37,6 +43,14 @@
  public:
   WebVrBrowserTestOpenVrDisabled() {
     append_switches_.push_back(switches::kEnableWebVR);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 
@@ -48,6 +62,14 @@
   WebVrBrowserTestStandard() {
     append_switches_.push_back(switches::kEnableWebVR);
     enable_features_.push_back(features::kOpenVR);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 
@@ -56,6 +78,14 @@
  public:
   WebVrBrowserTestWebVrDisabled() {
     enable_features_.push_back(features::kOpenVR);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 #endif  // OS_WIN
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index fdc042a..644ce4c 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -5,11 +5,17 @@
 #ifndef CHROME_BROWSER_VR_TEST_WEBXR_VR_BROWSER_TEST_H_
 #define CHROME_BROWSER_VR_TEST_WEBXR_VR_BROWSER_TEST_H_
 
+#include "build/build_config.h"
 #include "chrome/browser/vr/test/webxr_browser_test.h"
 #include "chrome/browser/vr/test/xr_browser_test.h"
 #include "chrome/common/chrome_features.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "device/vr/buildflags/buildflags.h"
+
+#if defined(OS_WIN)
+#include "services/service_manager/sandbox/features.h"
+#endif
 
 namespace vr {
 
@@ -36,6 +42,14 @@
  public:
   WebXrVrBrowserTestOpenVrDisabled() {
     enable_features_.push_back(features::kWebXr);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 
@@ -47,6 +61,14 @@
   WebXrVrBrowserTestStandard() {
     enable_features_.push_back(features::kOpenVR);
     enable_features_.push_back(features::kWebXr);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 
@@ -55,6 +77,14 @@
  public:
   WebXrVrBrowserTestWebXrDisabled() {
     enable_features_.push_back(features::kOpenVR);
+
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+    disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+
+#if defined(OS_WIN)
+    disable_features_.push_back(service_manager::features::kXRSandbox);
+#endif
   }
 };
 #endif  // OS_WIN
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 13471f9..ae2ea5e 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -40,6 +40,11 @@
 constexpr char XrBrowserTestBase::kVrLogPathEnvVar[];
 constexpr char XrBrowserTestBase::kVrLogPathVal[];
 constexpr char XrBrowserTestBase::kTestFileDir[];
+const std::vector<std::string> XrBrowserTestBase::kRequiredTestSwitches{
+    "enable-gpu", "enable-pixel-output-in-tests"};
+const std::vector<std::pair<std::string, std::string>>
+    XrBrowserTestBase::kRequiredTestSwitchesWithValues{
+        std::pair<std::string, std::string>("test-launcher-jobs", "1")};
 
 XrBrowserTestBase::XrBrowserTestBase() : env_(base::Environment::Create()) {}
 
@@ -79,6 +84,24 @@
 }
 
 void XrBrowserTestBase::SetUp() {
+  // Check whether the required flags were passed to the test - without these,
+  // we can fail in ways that are non-obvious, so fail more explicitly here if
+  // they aren't present.
+  auto* cmd_line = base::CommandLine::ForCurrentProcess();
+  for (auto req_switch : kRequiredTestSwitches) {
+    ASSERT_TRUE(cmd_line->HasSwitch(req_switch))
+        << "Missing switch " << req_switch << " required to run tests properly";
+  }
+  for (auto req_switch_pair : kRequiredTestSwitchesWithValues) {
+    ASSERT_TRUE(cmd_line->HasSwitch(req_switch_pair.first))
+        << "Missing switch " << req_switch_pair.first
+        << " required to run tests properly";
+    ASSERT_TRUE(cmd_line->GetSwitchValueASCII(req_switch_pair.first) ==
+                req_switch_pair.second)
+        << "Have required switch " << req_switch_pair.first
+        << ", but not required value " << req_switch_pair.second;
+  }
+
   // Set the environment variable to use the mock OpenVR client.
   EXPECT_TRUE(
       env_->SetVar(kVrOverrideEnvVar, MakeExecutableRelative(kVrOverrideVal)))
@@ -93,9 +116,9 @@
   // Set any command line flags that subclasses have set, e.g. enabling WebVR
   // and OpenVR support.
   for (const auto& switch_string : append_switches_) {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(switch_string);
+    cmd_line->AppendSwitch(switch_string);
   }
-  scoped_feature_list_.InitWithFeatures(enable_features_, {});
+  scoped_feature_list_.InitWithFeatures(enable_features_, disable_features_);
 
   InProcessBrowserTest::SetUp();
 }
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index 996d8df..dc9160c 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -48,6 +48,9 @@
   static constexpr char kVrLogPathVal[] = "./";
   static constexpr char kTestFileDir[] =
       "chrome/test/data/xr/e2e_test_files/html/";
+  static const std::vector<std::string> kRequiredTestSwitches;
+  static const std::vector<std::pair<std::string, std::string>>
+      kRequiredTestSwitchesWithValues;
   enum class TestStatus {
     STATUS_RUNNING = 0,
     STATUS_PASSED = 1,
@@ -188,6 +191,7 @@
  protected:
   std::unique_ptr<base::Environment> env_;
   std::vector<base::Feature> enable_features_;
+  std::vector<base::Feature> disable_features_;
   std::vector<std::string> append_switches_;
 
  private:
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.cc b/chrome/browser/web_applications/components/web_app_install_utils.cc
index 12ef100..db0e17f 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils.cc
@@ -83,6 +83,9 @@
   // we picked up from the web_app stuff.
   if (!web_app_icons.empty())
     web_app_info->icons = std::move(web_app_icons);
+
+  // Copy across the file handler info.
+  web_app_info->file_handler = manifest.file_handler;
 }
 
 std::set<int> SizesToGenerate() {
diff --git a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
index bf68681..8850575 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
@@ -35,6 +35,16 @@
   manifest.short_name =
       base::NullableString16(base::UTF8ToUTF16(kAppShortName), false);
 
+  {
+    blink::Manifest::FileHandler file_handler;
+    blink::Manifest::FileFilter file;
+    file.accept.push_back(base::UTF8ToUTF16(".png"));
+    file.name = base::UTF8ToUTF16("Images");
+    file_handler.push_back(file);
+    manifest.file_handler =
+        base::Optional<blink::Manifest::FileHandler>(std::move(file_handler));
+  }
+
   UpdateWebAppInfoFromManifest(manifest, &web_app_info,
                                ForInstallableSite::kNo);
   EXPECT_EQ(base::UTF8ToUTF16(kAppShortName), web_app_info.title);
@@ -67,6 +77,14 @@
   EXPECT_EQ(2u, web_app_info.icons.size());
   EXPECT_EQ(GURL(kAppIcon2), web_app_info.icons[0].url);
   EXPECT_EQ(GURL(kAppIcon3), web_app_info.icons[1].url);
+
+  // Check file handlers were updated
+  EXPECT_TRUE(web_app_info.file_handler.has_value());
+  auto file_handler = web_app_info.file_handler.value();
+  EXPECT_EQ(1u, file_handler.size());
+  EXPECT_EQ(base::UTF8ToUTF16("Images"), file_handler[0].name);
+  EXPECT_EQ(1u, file_handler[0].accept.size());
+  EXPECT_EQ(base::UTF8ToUTF16(".png"), file_handler[0].accept[0]);
 }
 
 // Tests "scope" is only set for installable sites.
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 754ae59..8b269df6 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -295,7 +295,7 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
 // Controls Windows Mixed Reality support.
 const base::Feature kWindowsMixedReality{"WindowsMixedReality",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 #endif  // ENABLE_WINDOWS_MR
 
 #endif  // BUILDFLAG(ENABLE_VR)
diff --git a/chrome/common/ppapi_utils.cc b/chrome/common/ppapi_utils.cc
index e04512c..acfb297b 100644
--- a/chrome/common/ppapi_utils.cc
+++ b/chrome/common/ppapi_utils.cc
@@ -114,6 +114,7 @@
 #include "ppapi/thunk/interfaces_ppb_private_pdf.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev_channel.h"
+#include "ppapi/thunk/interfaces_ppb_public_socket.h"
 #include "ppapi/thunk/interfaces_ppb_public_stable.h"
 
 #undef PROXIED_IFACE
diff --git a/chrome/common/prerender_types.h b/chrome/common/prerender_types.h
index b45242e..e6c98dc 100644
--- a/chrome/common/prerender_types.h
+++ b/chrome/common/prerender_types.h
@@ -7,13 +7,6 @@
 
 namespace prerender {
 
-// PrerenderRelType is a bitfield since multiple rel attributes can be set
-// on the same link. Must be the same as blink::WebPrerenderRelType.
-enum PrerenderRelType {
-  PrerenderRelTypePrerender = 0x1,
-  PrerenderRelTypeNext = 0x2,
-};
-
 enum PrerenderMode {
   // Neither prefetch nor prerender.
   NO_PRERENDER = 0,
diff --git a/chrome/common/web_application_info.h b/chrome/common/web_application_info.h
index 34f7a49..a270efc 100644
--- a/chrome/common/web_application_info.h
+++ b/chrome/common/web_application_info.h
@@ -11,6 +11,7 @@
 
 #include "base/optional.h"
 #include "base/strings/string16.h"
+#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/size.h"
@@ -66,6 +67,9 @@
   // Whether the app should be opened in a window. If false, the app will be
   // opened in a tab.
   bool open_as_window;
+
+  // The extensions and mime types the app can handle.
+  base::Optional<blink::Manifest::FileHandler> file_handler;
 };
 
 #endif  // CHROME_COMMON_WEB_APPLICATION_INFO_H_
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 65f05bc..cf6f7dd 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -214,6 +214,8 @@
 const char kChromeUINetworkHost[] = "network";
 const char kChromeUIOSCreditsHost[] = "os-credits";
 const char kChromeUIOSCreditsURL[] = "chrome://os-credits/";
+const char kChromeUIOSSettingsHost[] = "os-settings";
+const char kChromeUIOSSettingsURL[] = "chrome://os-settings/";
 const char kChromeUIOobeHost[] = "oobe";
 const char kChromeUIOobeURL[] = "chrome://oobe/";
 const char kChromeUIPowerHost[] = "power";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 002c96b..1a06412 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -209,6 +209,8 @@
 extern const char kChromeUINetworkHost[];
 extern const char kChromeUIOSCreditsHost[];
 extern const char kChromeUIOSCreditsURL[];
+extern const char kChromeUIOSSettingsHost[];
+extern const char kChromeUIOSSettingsURL[];
 extern const char kChromeUIOobeHost[];
 extern const char kChromeUIOobeURL[];
 extern const char kChromeUIPowerHost[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 8321735..667280d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -867,7 +867,6 @@
       "../browser/resource_coordinator/tab_activity_watcher_browsertest.cc",
       "../browser/resource_coordinator/tab_manager_browsertest.cc",
       "../browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc",
-      "../browser/safe_browsing/download_protection/download_protection_service_browsertest.cc",
       "../browser/safe_browsing/test_safe_browsing_database_helper.cc",
       "../browser/safe_browsing/test_safe_browsing_database_helper.h",
       "../browser/safe_json_parser_browsertest.cc",
diff --git a/chrome/test/data/extensions/api_test/file_browser/crostini_test/test.js b/chrome/test/data/extensions/api_test/file_browser/crostini_test/test.js
index 0bb5c08..fb97a45 100644
--- a/chrome/test/data/extensions/api_test/file_browser/crostini_test/test.js
+++ b/chrome/test/data/extensions/api_test/file_browser/crostini_test/test.js
@@ -52,16 +52,16 @@
     chrome.fileManagerPrivate.getCrostiniSharedPaths(observeFirstForSession,
         chrome.test.callbackPass((entries, firstForSession) => {
           // 2 entries inserted in setup, and 1 successful entry added above.
-          chrome.test.assertEq(3, entries.length);
-          chrome.test.assertEq(urlPrefix + '/shared1', entries[0].toURL());
+          chrome.test.assertEq(urlPrefix + '/share_dir', entries[0].toURL());
           chrome.test.assertTrue(entries[0].isDirectory);
-          chrome.test.assertEq('/shared1', entries[0].fullPath);
-          chrome.test.assertEq(urlPrefix + '/shared2', entries[1].toURL());
+          chrome.test.assertEq('/share_dir', entries[0].fullPath);
+          chrome.test.assertEq(3, entries.length);
+          chrome.test.assertEq(urlPrefix + '/shared1', entries[1].toURL());
           chrome.test.assertTrue(entries[1].isDirectory);
-          chrome.test.assertEq('/shared2', entries[1].fullPath);
-          chrome.test.assertEq(urlPrefix + '/share_dir', entries[2].toURL());
+          chrome.test.assertEq('/shared1', entries[1].fullPath);
+          chrome.test.assertEq(urlPrefix + '/shared2', entries[2].toURL());
           chrome.test.assertTrue(entries[2].isDirectory);
-          chrome.test.assertEq('/share_dir', entries[2].fullPath);
+          chrome.test.assertEq('/shared2', entries[2].fullPath);
           // When observerFirstForSession is false, firstForSession is false.
           chrome.test.assertFalse(firstForSession);
         }));
diff --git a/chrome/test/data/local_ntp/custom_backgrounds_browsertest.js b/chrome/test/data/local_ntp/custom_backgrounds_browsertest.js
index a98f61b..40b25b5e 100644
--- a/chrome/test/data/local_ntp/custom_backgrounds_browsertest.js
+++ b/chrome/test/data/local_ntp/custom_backgrounds_browsertest.js
@@ -272,7 +272,7 @@
         previewImageUrl: 'chrome-search://local-ntp/background.jpg'
       }
     ];
-    coll_errors = {};
+    collErrors = {};
   };
 
   // Append a call to onload to the end of the click handler.
@@ -294,7 +294,7 @@
   var oldImageLoader = $(tile_id).onclick;
   $(tile_id).onclick = function(event) {
     oldImageLoader(event);
-    coll_img = [
+    collImg = [
       {
         attributionActionUrl: 'https://www.google.com',
         attributions: ['test1', 'attribution1'],
@@ -331,7 +331,7 @@
         thumbnailImageUrl: 'chrome-search://local-ntp/background_thumbnail.jpg5'
       }
     ];
-    coll_img_errors = {};
+    collImgErrors = {};
     $('ntp-images-loader').onload();
   }
 };
@@ -346,8 +346,8 @@
   let oldImageLoader = $(tile_id).onclick;
   $(tile_id).onclick = function(event) {
     oldImageLoader(event);
-    coll_img = [];
-    coll_img_errors = { net_error: true, net_error_no: -106 };
+    collImg = [];
+    collImgErrors = {net_error: true, net_error_no: -106};
     $('ntp-images-loader').onload();
   }
 };
@@ -377,7 +377,7 @@
   assertTrue(elementIsVisible($('bg-sel-menu')));
   assertTrue($('bg-sel-menu').classList.contains('is-img-sel'));
   assertTrue(
-      document.getElementsByClassName('bg-sel-tile').length == coll_img.length);
+      document.getElementsByClassName('bg-sel-tile').length == collImg.length);
   assertTrue(elementIsVisible($('bg-sel-back')));
   assertTrue(elementIsVisible($('bg-sel-footer-cancel')));
   assertTrue(elementIsVisible($('bg-sel-footer-done')));
diff --git a/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip b/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip
deleted file mode 100644
index d7af463..0000000
--- a/chrome/test/data/safe_browsing/download_protection/zipfile_two_archives.zip
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/safe_browsing/rar/has_two_archives.rar b/chrome/test/data/safe_browsing/rar/has_two_archives.rar
deleted file mode 100644
index 416bd852..0000000
--- a/chrome/test/data/safe_browsing/rar/has_two_archives.rar
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/webui/settings/crostini_page_test.js b/chrome/test/data/webui/settings/crostini_page_test.js
index d46b901..bdbe01a 100644
--- a/chrome/test/data/webui/settings/crostini_page_test.js
+++ b/chrome/test/data/webui/settings/crostini_page_test.js
@@ -12,11 +12,11 @@
   crostiniPage.prefs = {
     crostini: {
       enabled: {value: enabled},
-      shared_paths: {value: opt_sharedPaths || []},
+    },
+    guest_os: {
+      paths_shared_to_vms: {value: opt_sharedPaths || {}},
     }
   };
-  crostiniBrowserProxy.enabled = enabled;
-  crostiniBrowserProxy.sharedPaths = opt_sharedPaths || [];
   crostiniBrowserProxy.sharedUsbDevices = opt_sharedUsbDevices || [];
   Polymer.dom.flush();
 }
@@ -54,8 +54,9 @@
 
       button.click();
       Polymer.dom.flush();
-      setCrostiniPrefs(crostiniBrowserProxy.enabled);
-      assertTrue(crostiniPage.prefs.crostini.enabled.value);
+      assertEquals(
+          1, crostiniBrowserProxy.getCallCount('requestCrostiniInstallerView'));
+      setCrostiniPrefs(true);
 
       assertTrue(!!crostiniPage.$$('.subpage-arrow'));
     });
@@ -146,8 +147,9 @@
     test('Remove', function() {
       assertTrue(!!subpage.$$('#remove paper-button'));
       subpage.$$('#remove paper-button').click();
-      setCrostiniPrefs(crostiniBrowserProxy.enabled);
-      assertFalse(crostiniPage.prefs.crostini.enabled.value);
+      assertEquals(
+          1, crostiniBrowserProxy.getCallCount('requestRemoveCrostini'));
+      setCrostiniPrefs(false);
       return whenPopState().then(function() {
         assertEquals(settings.getCurrentRoute(), settings.routes.CROSTINI);
         assertTrue(!!crostiniPage.$$('#enable'));
@@ -185,7 +187,7 @@
     let subpage;
 
     setup(function() {
-      setCrostiniPrefs(true, crostiniBrowserProxy.sharedPaths);
+      setCrostiniPrefs(true, {'path1': ['termina'], 'path2': ['termina']});
       return flushAsync().then(() => {
         settings.navigateTo(settings.routes.CROSTINI_SHARED_PATHS);
         return flushAsync().then(() => {
@@ -206,9 +208,13 @@
       assertTrue(!!subpage.$$('.list-item button'));
       // Remove first shared path, still one left.
       subpage.$$('.list-item button').click();
-      assertEquals(1, crostiniBrowserProxy.sharedPaths.length);
-      setCrostiniPrefs(true, crostiniBrowserProxy.sharedPaths);
-      return flushAsync()
+      return crostiniBrowserProxy.whenCalled('removeCrostiniSharedPath')
+          .then(([vmName, path]) => {
+            assertEquals('termina', vmName);
+            assertEquals('path1', path);
+            setCrostiniPrefs(true, {'path2': ['termina']});
+            return flushAsync();
+          })
           .then(() => {
             Polymer.dom.flush();
             assertEquals(
@@ -216,9 +222,14 @@
             assertFalse(subpage.$.crostiniInstructionsRemove.hidden);
 
             // Remove remaining shared path, none left.
+            crostiniBrowserProxy.resetResolver('removeCrostiniSharedPath');
             subpage.$$('.list-item button').click();
-            assertEquals(0, crostiniBrowserProxy.sharedPaths.length);
-            setCrostiniPrefs(true, crostiniBrowserProxy.sharedPaths);
+            return crostiniBrowserProxy.whenCalled('removeCrostiniSharedPath');
+          })
+          .then(([vmName, path]) => {
+            assertEquals('termina', vmName);
+            assertEquals('path2', path);
+            setCrostiniPrefs(true, {});
             return flushAsync();
           })
           .then(() => {
@@ -235,7 +246,7 @@
     let subpage;
 
     setup(function() {
-      setCrostiniPrefs(true, [], [
+      setCrostiniPrefs(true, {}, [
         {'shared': true, 'guid': '0001', 'name': 'usb_dev1'},
         {'shared': false, 'guid': '0002', 'name': 'usb_dev2'},
         {'shared': true, 'guid': '0003', 'name': 'usb_dev3'}
diff --git a/chrome/test/data/webui/settings/cups_printer_page_tests.js b/chrome/test/data/webui/settings/cups_printer_page_tests.js
index 388946dc..02fabdd 100644
--- a/chrome/test/data/webui/settings/cups_printer_page_tests.js
+++ b/chrome/test/data/webui/settings/cups_printer_page_tests.js
@@ -17,6 +17,7 @@
       'stopDiscoveringPrinters',
       'cancelPrinterSetUp',
       'updateCupsPrinter',
+      'reconfigureCupsPrinter',
     ]);
 
     this.printerList = [];
@@ -85,6 +86,11 @@
     this.methodCalled('getPrinterPpdManufacturerAndModel', printerId);
     return Promise.resolve(this.printerPpdMakeModel);
   }
+
+  /** @override */
+  reconfigureCupsPrinter(printer) {
+    this.methodCalled('reconfigureCupsPrinter', printer);
+  }
 }
 
 suite('CupsAddPrinterDialogTests', function() {
@@ -561,6 +567,7 @@
     const nameField = dialog.$$('.printer-name-input');
     assertTrue(!!nameField);
     nameField.value = 'edited printer';
+    nameField.fire('input');
 
     // Assert that the "Save" button is enabled.
     const saveButton = dialog.$$('.action-button');
@@ -600,6 +607,12 @@
 
     const saveButton = dialog.$$('.action-button');
     assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    // Change printer name to something valid.
+    printerName = dialog.$.printerName;
+    printerName.value = 'new printer name';
+    printerName.fire('input');
     assertFalse(saveButton.disabled);
 
     // Change printer address to something invalid.
@@ -610,7 +623,7 @@
     dialog.$.printerAddress.value = 'abcdef:1234';
     assertFalse(saveButton.disabled);
 
-    // Change printer name to empty
+    // Change printer name to empty.
     dialog.$.printerName.value = '';
     assertTrue(saveButton.disabled);
   });
@@ -637,7 +650,6 @@
       printerQueue: 'moreinfohere',
       printerStatus: '',
     };
-
     setPpdManufacturerAndPpdModel('manufacture', 'model');
 
     // Initializing activePrinter will set |needsReconfigured_| to true. Reset
@@ -683,7 +695,6 @@
       printerQueue: 'moreinfohere',
       printerStatus: '',
     };
-
     setPpdManufacturerAndPpdModel('manufacture', 'model');
 
     // Initializing activePrinter will set |needsReconfigured_| to true. Reset
@@ -711,10 +722,198 @@
     const saveButton = dialog.$$('.action-button');
     saveButton.click();
 
-    return cupsPrintersBrowserProxy.whenCalled('addCupsPrinter')
+    return cupsPrintersBrowserProxy.whenCalled('reconfigureCupsPrinter')
         .then(function() {
           assertEquals(expectedAddress, dialog.activePrinter.printerAddress);
           assertEquals(expectedQueue, dialog.activePrinter.printerQueue);
         });
   });
+
+  test('TestChangingNameEnablesSaveButton', function() {
+    dialog.activePrinter_ = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: 'test:123',
+      printerAutoconf: false,
+      printerDescription: '',
+      printerId: 'id_123',
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: 'Test Printer',
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    setPpdManufacturerAndPpdModel('manufacture', 'model');
+
+    const saveButton = dialog.$$('.action-button');
+    assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    const nameField = dialog.$$('.printer-name-input');
+    assertTrue(!!nameField);
+    nameField.value = 'edited printer';
+    nameField.fire('input');
+
+    assertTrue(!saveButton.disabled);
+  });
+
+  test('TestChangingAddressEnablesSaveButton', function() {
+    dialog.activePrinter = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: 'test:123',
+      printerAutoconf: false,
+      printerDescription: '',
+      printerId: 'id_123',
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: 'Test Printer',
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    setPpdManufacturerAndPpdModel('manufacture', 'model');
+
+    const saveButton = dialog.$$('.action-button');
+    assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    const addressField = dialog.$$('#printerAddress');
+    assertTrue(!!addressField);
+    addressField.value = 'newAddress:789';
+    addressField.fire('input');
+
+    assertTrue(!saveButton.disabled);
+  });
+
+  test('TestChangingQueueEnablesSaveButton', function() {
+    dialog.activePrinter = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: 'test:123',
+      printerAutoconf: false,
+      printerDescription: '',
+      printerId: 'id_123',
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: 'Test Printer',
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    setPpdManufacturerAndPpdModel('manufacture', 'model');
+
+    const saveButton = dialog.$$('.action-button');
+    assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    const queueField = dialog.$$('#printerQueue');
+    assertTrue(!!queueField);
+    queueField.value = 'newqueueinfo';
+    queueField.fire('input');
+
+    assertTrue(!saveButton.disabled);
+  });
+
+  test('TestChangingProtocolEnablesSaveButton', function() {
+    dialog.activePrinter = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: 'test:123',
+      printerAutoconf: false,
+      printerDescription: '',
+      printerId: 'id_123',
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: 'Test Printer',
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    setPpdManufacturerAndPpdModel('manufacture', 'model');
+    Polymer.dom.flush();
+
+    const saveButton = dialog.$$('.action-button');
+    assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    const dropDown = dialog.$$('.md-select');
+    dropDown.value = 'http';
+    dropDown.dispatchEvent(new CustomEvent('change'), {'bubbles': true});
+    Polymer.dom.flush();
+    assertTrue(!saveButton.disabled);
+  });
+
+  test('TestChangingModelEnablesSaveButton', function() {
+    dialog.activePrinter = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: 'test:123',
+      printerAutoconf: false,
+      printerDescription: '',
+      printerId: 'id_123',
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: 'Test Printer',
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    setPpdManufacturerAndPpdModel('manufacture', 'model');
+    // Printers are considered initialized for editing after PPD make/models
+    // are set.
+    dialog.arePrinterFieldsInitialized_ = true;
+
+    const saveButton = dialog.$$('.action-button');
+    assertTrue(!!saveButton);
+    assertTrue(saveButton.disabled);
+
+    makeDropDown = dialog.$$('#printerPPDManufacturer');
+    makeDropDown.value = 'HP';
+    makeDropDown.dispatchEvent(new CustomEvent('change'), {'bubbles': true});
+    // Saving is disabled until a model is selected.
+    assertTrue(saveButton.disabled);
+
+    modelDropDown = dialog.$$('#printerPPDModel');
+    modelDropDown.value = 'HP 910';
+    modelDropDown.dispatchEvent(new CustomEvent('change'), {'bubbles': true});
+    Polymer.dom.flush();
+    assertTrue(!saveButton.disabled);
+  });
 });
diff --git a/chrome/test/data/webui/settings/languages_page_tests.js b/chrome/test/data/webui/settings/languages_page_tests.js
index bcc67124..6ccc6e31 100644
--- a/chrome/test/data/webui/settings/languages_page_tests.js
+++ b/chrome/test/data/webui/settings/languages_page_tests.js
@@ -696,6 +696,15 @@
             previousValue,
             languagesPage.prefs.spellcheck.use_spelling_service.value);
       });
+
+      test('disabling spell check turns off spelling service', () => {
+        languageHelper.setPrefValue('browser.enable_spellchecking', true);
+        languageHelper.setPrefValue('spellcheck.use_spelling_service', true);
+        languagesPage.$.enableSpellcheckingToggle.click();
+        Polymer.dom.flush();
+        assertFalse(
+            languageHelper.getPref('spellcheck.use_spelling_service').value);
+      });
     });
   });
 
diff --git a/chrome/test/data/webui/settings/people_page_test.js b/chrome/test/data/webui/settings/people_page_test.js
index 573c02a..2817423 100644
--- a/chrome/test/data/webui/settings/people_page_test.js
+++ b/chrome/test/data/webui/settings/people_page_test.js
@@ -125,7 +125,7 @@
 
       // This makes sure UI meant for DICE-enabled profiles are not leaked to
       // non-dice profiles.
-      // TODO(scottchen): This should be removed once all profiles are fully
+      // TODO(tangltom): This should be removed once all profiles are fully
       // migrated.
       test('NoManageProfileRow', function() {
         assertFalse(!!peoplePage.$$('#edit-profile'));
diff --git a/chrome/test/data/webui/settings/personalization_options_test.js b/chrome/test/data/webui/settings/personalization_options_test.js
index 1b67286..b0d7e6cc 100644
--- a/chrome/test/data/webui/settings/personalization_options_test.js
+++ b/chrome/test/data/webui/settings/personalization_options_test.js
@@ -77,6 +77,17 @@
       testElement.prefs = {spellcheck: {dictionaries: {value: []}}};
       Polymer.dom.flush();
       assertTrue(testElement.$.spellCheckControl.hidden);
+
+      testElement.prefs = {
+        browser: {enable_spellchecking: {value: false}},
+        spellcheck: {
+          dictionaries: {value: ['en-US']},
+          use_spelling_service: {value: false}
+        }
+      };
+      Polymer.dom.flush();
+      testElement.$.spellCheckControl.click();
+      assertTrue(testElement.prefs.spellcheck.use_spelling_service.value);
     });
 
     test('NoUnifiedConsent spellcheck toggle', function() {
@@ -88,6 +99,14 @@
       testElement.prefs = {spellcheck: {dictionaries: {value: []}}};
       Polymer.dom.flush();
       assertFalse(testElement.$.spellCheckControl.hidden);
+
+      testElement.prefs = {
+        browser: {enable_spellchecking: {value: false}},
+        spellcheck: {use_spelling_service: {value: false}}
+      };
+      Polymer.dom.flush();
+      testElement.$.spellCheckControl.click();
+      assertTrue(testElement.prefs.spellcheck.use_spelling_service.value);
     });
   });
 });
diff --git a/chrome/test/data/webui/settings/test_crostini_browser_proxy.js b/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
index a3f8597..af3ac42 100644
--- a/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
@@ -15,21 +15,17 @@
       'exportCrostiniContainer',
       'importCrostiniContainer',
     ]);
-    this.enabled = false;
-    this.sharedPaths = ['path1', 'path2'];
     this.sharedUsbDevices = [];
   }
 
   /** @override */
   requestCrostiniInstallerView() {
     this.methodCalled('requestCrostiniInstallerView');
-    this.enabled = true;
   }
 
   /** override */
   requestRemoveCrostini() {
     this.methodCalled('requestRemoveCrostini');
-    this.enabled = false;
   }
 
   /** override */
@@ -50,9 +46,10 @@
   }
 
   /** override */
-  removeCrostiniSharedPath(path) {
-    this.sharedPaths = this.sharedPaths.filter(p => p !== path);
+  removeCrostiniSharedPath(vmName, path) {
+    this.methodCalled('removeCrostiniSharedPath', [vmName, path]);
   }
+
   /** @override */
   requestCrostiniInstallerStatus() {
     cr.webUIListenerCallback('crostini-installer-status-changed', false);
diff --git a/chrome/test/mini_installer/test_chrome_with_chromedriver.py b/chrome/test/mini_installer/test_chrome_with_chromedriver.py
index d1706683..b492bb4 100644
--- a/chrome/test/mini_installer/test_chrome_with_chromedriver.py
+++ b/chrome/test/mini_installer/test_chrome_with_chromedriver.py
@@ -136,6 +136,11 @@
 
 
 def main():
+  # DISABLING
+  # https://crbug.com/949727
+  # DISABLING
+  return 0
+
   """Main entry point."""
   parser = parser = argparse.ArgumentParser(
     description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index cf29b41..f2d1f96 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -117,11 +117,7 @@
     cast_media_unittests_filter = {
       test_name = "cast_media_unittests"
 
-      # TODO(almasrymina) : Track this test as a separate bug
-      #   (internal : b/115400220)
-      gtest_excludes = [ "AudioVideoPipelineDeviceTest.Mp4Playback" ]
-
-      gtest_excludes += vendor_cast_media_gtest_excludes
+      gtest_excludes = vendor_cast_media_gtest_excludes
 
       # --test-launcher-jobs=1 => so internal code can bind to port
       args = [ "--test-launcher-jobs=1" ]
diff --git a/chromecast/media/cma/backend/cplay/cplay.cc b/chromecast/media/cma/backend/cplay/cplay.cc
index 5f319cf..f4bbaee5 100644
--- a/chromecast/media/cma/backend/cplay/cplay.cc
+++ b/chromecast/media/cma/backend/cplay/cplay.cc
@@ -20,7 +20,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/no_destructor.h"
-#include "base/numerics/range.h"
+#include "base/numerics/ranges.h"
 #include "chromecast/media/cma/backend/cast_audio_json.h"
 #include "chromecast/media/cma/backend/cplay/wav_header.h"
 #include "chromecast/media/cma/backend/mixer_input.h"
@@ -29,7 +29,7 @@
 #include "chromecast/media/cma/backend/post_processing_pipeline_parser.h"
 #include "chromecast/media/cma/backend/volume_map.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
-#include "media/audio/sounds/wav_audio_handler.h"
+#include "media/audio/wav_audio_handler.h"
 #include "media/base/audio_bus.h"
 #include "media/base/audio_sample_types.h"
 
@@ -50,7 +50,7 @@
 
 namespace {
 
-const int kReadSize = 256;
+const int kReadSize = 1024;
 
 void PrintHelp(const std::string& command) {
   LOG(INFO) << "Usage: " << command;
@@ -298,15 +298,14 @@
   auto factory = std::make_unique<PostProcessingPipelineFactoryImpl>();
   auto pipeline = MixerPipeline::CreateMixerPipeline(&parser, factory.get());
   CHECK(pipeline);
-  pipeline->Initialize(params.output_samples_per_second);
+  pipeline->Initialize(params.output_samples_per_second, kReadSize);
   LOG(INFO) << "Initialized Cast Audio Pipeline at "
             << params.output_samples_per_second << " samples per second";
 
   // Add |input_source| to |pipeline|
   FilterGroup* input_group = pipeline->GetInputGroup(params.device_id);
   CHECK(input_group);
-  MixerInput mixer_input(&input_source, params.output_samples_per_second,
-                         kReadSize, MixerInput::RenderingDelay(), input_group);
+  MixerInput mixer_input(&input_source, input_group);
 
   // Set volume.
   std::string contents;
diff --git a/chromecast/media/cma/backend/filter_group.cc b/chromecast/media/cma/backend/filter_group.cc
index 208ad6e..8c3c678 100644
--- a/chromecast/media/cma/backend/filter_group.cc
+++ b/chromecast/media/cma/backend/filter_group.cc
@@ -32,12 +32,37 @@
   DCHECK_EQ(input->GetOutputChannelCount(), num_channels_);
 }
 
-void FilterGroup::Initialize(int output_samples_per_second) {
+void FilterGroup::AddStreamType(const std::string& stream_type) {
+  stream_types_.push_back(stream_type);
+}
+
+void FilterGroup::Initialize(int output_samples_per_second,
+                             int output_frames_per_write) {
   output_samples_per_second_ = output_samples_per_second;
+  output_frames_per_write_ = output_frames_per_write;
+
   CHECK(post_processing_pipeline_->SetOutputSampleRate(
       output_samples_per_second_));
+  input_samples_per_second_ = post_processing_pipeline_->GetInputSampleRate();
+  input_frames_per_write_ = output_frames_per_write *
+                            input_samples_per_second_ /
+                            output_samples_per_second_;
+  DCHECK_EQ(input_frames_per_write_ * output_samples_per_second_,
+            output_frames_per_write_ * input_samples_per_second_)
+      << "Unable to produce stable buffer sizes for resampling rate "
+      << input_samples_per_second_ << " : " << output_samples_per_second_;
+
+  for (FilterGroup* input : mixed_inputs_) {
+    input->Initialize(input_samples_per_second_, input_frames_per_write_);
+  }
   post_processing_pipeline_->SetContentType(content_type_);
   active_inputs_.clear();
+  ResizeBuffers();
+
+  // Run a buffer of 0's to initialize rendering delay.
+  delay_seconds_ = post_processing_pipeline_->ProcessFrames(
+      interleaved_.get(), input_frames_per_write_, last_volume_,
+      true /* is_silence */);
 }
 
 void FilterGroup::AddInput(MixerInput* input) {
@@ -52,11 +77,10 @@
 }
 
 float FilterGroup::MixAndFilter(
-    int num_frames,
+    int num_output_frames,
     MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay) {
   DCHECK_NE(output_samples_per_second_, 0);
-
-  bool resize_needed = ResizeBuffersIfNecessary(num_frames);
+  DCHECK_EQ(num_output_frames, output_frames_per_write_);
 
   float volume = 0.0f;
   AudioContentType content_type = static_cast<AudioContentType>(-1);
@@ -66,8 +90,8 @@
 
   // Recursively mix inputs.
   for (auto* filter_group : mixed_inputs_) {
-    volume = std::max(volume,
-                      filter_group->MixAndFilter(num_frames, rendering_delay));
+    volume = std::max(volume, filter_group->MixAndFilter(
+                                  input_frames_per_write_, rendering_delay));
     content_type = std::max(content_type, filter_group->content_type());
   }
 
@@ -78,17 +102,12 @@
   // b) The output volume is 0 and has NEVER been non-zero,
   //    since FilterGroup will use last_volume_ if volume is 0.
   //    In this case, there was never any data in the pipeline.
-  // However, we still need to ensure that output buffers are initialized &
-  // large enough to hold |num_frames|, so we cannot use this shortcut if
-  // |resize_needed|.
   if (active_inputs_.empty() && volume == 0.0f &&
-      !post_processing_pipeline_->IsRinging() && !resize_needed) {
-    if (frames_zeroed_ < num_frames) {
-      // Ensure OutputBuffer() is zeros.
-      // TODO(bshaya): Determine if this is necessary - if RingingTime is
-      //               calculated correctly, then we could skip the fill_n.
-      std::fill_n(GetOutputBuffer(), num_frames * GetOutputChannelCount(), 0);
-      frames_zeroed_ = num_frames;
+      !post_processing_pipeline_->IsRinging()) {
+    if (frames_zeroed_ < num_output_frames) {
+      std::fill_n(GetOutputBuffer(),
+                  num_output_frames * GetOutputChannelCount(), 0);
+      frames_zeroed_ = num_output_frames;
     }
     return 0.0f;  // Output will be silence, no need to mix.
   }
@@ -96,12 +115,13 @@
   frames_zeroed_ = 0;
 
   // Mix InputQueues
-  mixed_->ZeroFramesPartial(0, num_frames);
+  mixed_->ZeroFramesPartial(0, input_frames_per_write_);
   for (MixerInput* input : active_inputs_) {
     DCHECK_LT(input->num_channels(), static_cast<int>(temp_buffers_.size()));
     DCHECK(temp_buffers_[input->num_channels()]);
     ::media::AudioBus* temp = temp_buffers_[input->num_channels()].get();
-    int filled = input->FillAudioData(num_frames, rendering_delay, temp);
+    int filled =
+        input->FillAudioData(input_frames_per_write_, rendering_delay, temp);
     if (filled > 0) {
       int in_c = 0;
       for (int out_c = 0; out_c < num_channels_; ++out_c) {
@@ -119,13 +139,13 @@
   }
 
   mixed_->ToInterleaved<::media::FloatSampleTypeTraits<float>>(
-      num_frames, interleaved_.get());
+      input_frames_per_write_, interleaved_.get());
 
   // Mix FilterGroups
   for (FilterGroup* group : mixed_inputs_) {
     if (group->last_volume() > 0.0f) {
-      ::media::vector_math::FMAC(group->interleaved_.get(), 1.0f,
-                                 num_frames * num_channels_,
+      ::media::vector_math::FMAC(group->GetOutputBuffer(), 1.0f,
+                                 input_frames_per_write_ * num_channels_,
                                  interleaved_.get());
     }
   }
@@ -133,7 +153,7 @@
   if (playout_channel_selection_ != kChannelAll) {
     // Duplicate selected channel to all channels.
     float* data = interleaved_.get();
-    for (int f = 0; f < num_frames; ++f) {
+    for (int f = 0; f < input_frames_per_write_; ++f) {
       float selected = data[f * num_channels_ + playout_channel_selection_];
       for (int c = 0; c < num_channels_; ++c)
         data[f * num_channels_ + c] = selected;
@@ -155,7 +175,7 @@
   }
 
   delay_seconds_ = post_processing_pipeline_->ProcessFrames(
-      interleaved_.get(), num_frames, last_volume_, is_silence);
+      interleaved_.get(), input_frames_per_write_, last_volume_, is_silence);
   return last_volume_;
 }
 
@@ -175,23 +195,19 @@
   return rendering_delay_to_output_;
 }
 
-int FilterGroup::GetOutputChannelCount() {
+int FilterGroup::GetOutputChannelCount() const {
   return post_processing_pipeline_->NumOutputChannels();
 }
 
-bool FilterGroup::ResizeBuffersIfNecessary(int num_frames) {
-  if (mixed_ && mixed_->frames() >= num_frames) {
-    return false;
-  }
-  mixed_ = ::media::AudioBus::Create(num_channels_, num_frames);
+void FilterGroup::ResizeBuffers() {
+  mixed_ = ::media::AudioBus::Create(num_channels_, input_frames_per_write_);
   temp_buffers_.clear();
   for (MixerInput* input : active_inputs_) {
-    AddTempBuffer(input->num_channels(), num_frames);
+    AddTempBuffer(input->num_channels(), input_frames_per_write_);
   }
-  interleaved_.reset(static_cast<float*>(
-      base::AlignedAlloc(num_frames * num_channels_ * sizeof(float),
-                         ::media::AudioBus::kChannelAlignment)));
-  return true;
+  interleaved_.reset(static_cast<float*>(base::AlignedAlloc(
+      input_frames_per_write_ * num_channels_ * sizeof(float),
+      ::media::AudioBus::kChannelAlignment)));
 }
 
 void FilterGroup::AddTempBuffer(int num_channels, int num_frames) {
@@ -223,13 +239,37 @@
 }
 
 void FilterGroup::PrintTopology() const {
-  std::string input_groups;
+  std::string filter_groups;
   for (const FilterGroup* mixed_input : mixed_inputs_) {
     mixed_input->PrintTopology();
-    input_groups += mixed_input->name() + ", ";
+    filter_groups += "[GROUP]" + mixed_input->name() + ", ";
   }
 
-  LOG(INFO) << input_groups << " -> " << num_channels_ << "ch -> " << name_;
+  std::string input_groups;
+  for (const std::string& stream_type : stream_types_) {
+    input_groups += "[STREAM]" + stream_type + ", ";
+  }
+
+  // Trim trailing comma.
+  if (!filter_groups.empty()) {
+    filter_groups.resize(filter_groups.size() - 2);
+  }
+  if (!input_groups.empty()) {
+    input_groups.resize(input_groups.size() - 2);
+  }
+
+  std::string all_inputs;
+  if (filter_groups.empty()) {
+    all_inputs = input_groups;
+  } else if (input_groups.empty()) {
+    all_inputs = filter_groups;
+  } else {
+    all_inputs = input_groups + " + " + filter_groups;
+  }
+  LOG(INFO) << all_inputs << ": " << num_channels_ << "ch@"
+            << input_samples_per_second_ << "hz -> [GROUP]" << name_ << " -> "
+            << GetOutputChannelCount() << "ch@" << output_samples_per_second_
+            << "hz";
 }
 
 }  // namespace media
diff --git a/chromecast/media/cma/backend/filter_group.h b/chromecast/media/cma/backend/filter_group.h
index 8158f35..3f81c982 100644
--- a/chromecast/media/cma/backend/filter_group.h
+++ b/chromecast/media/cma/backend/filter_group.h
@@ -50,8 +50,16 @@
   // than one FilterGroup will result in incorrect behavior.
   void AddMixedInput(FilterGroup* input);
 
-  // Sets the sample rate of the post-processors.
-  void Initialize(int output_samples_per_second);
+  // Recursively sets the sample rate of the post-processors and FilterGroups.
+  // This should only be called externally on the output node of the FilterGroup
+  // tree.
+  // The output rate of this group will be |output_samples_per_second|.
+  // The output block size, i.e. the number of frames written in each call to
+  // MixAndFilter() of this group will be |output_frames_per_write|.
+  // Groups that feed this group may receive different values due to resampling.
+  // After calling Initialize(), input_samples_per_second() and
+  // input_frames_per_write() may be called to determine the input rate/size.
+  void Initialize(int output_samples_per_second, int output_frames_per_write);
 
   // Adds/removes |input| from |active_inputs_|.
   void AddInput(MixerInput* input);
@@ -85,7 +93,10 @@
   std::string name() const { return name_; }
 
   // Returns number of audio output channels from the filter group.
-  int GetOutputChannelCount();
+  int GetOutputChannelCount() const;
+
+  // Returns the expected sample rate for inputs to this group.
+  int GetInputSampleRate() const { return input_samples_per_second_; }
 
   // Sends configuration string |config| to all post processors with the given
   // |name|.
@@ -101,19 +112,28 @@
   // Recursively print the layout of the pipeline.
   void PrintTopology() const;
 
+  // Add |stream_type| to the list of streams this processor handles.
+  void AddStreamType(const std::string& stream_type);
+
+  int input_frames_per_write() const { return input_frames_per_write_; }
+  int input_samples_per_second() const { return input_samples_per_second_; }
+
  private:
-  // Resizes temp_ and mixed_ if they are too small to hold |num_frames| frames.
-  // Returns |true| if |num_frames| is larger than all previous |num_frames|.
-  bool ResizeBuffersIfNecessary(int num_frames);
+  // Resizes temp_buffers_ and mixed_.
+  void ResizeBuffers();
   void AddTempBuffer(int num_channels, int num_frames);
 
   const int num_channels_;
   const std::string name_;
   std::vector<FilterGroup*> mixed_inputs_;
+  std::vector<std::string> stream_types_;
   base::flat_set<MixerInput*> active_inputs_;
 
   int playout_channel_selection_ = kChannelAll;
   int output_samples_per_second_ = 0;
+  int input_samples_per_second_ = 0;
+  int output_frames_per_write_ = 0;
+  int input_frames_per_write_ = 0;
   int frames_zeroed_ = 0;
   float last_volume_ = 0.0;
   double delay_seconds_ = 0;
diff --git a/chromecast/media/cma/backend/filter_group_unittest.cc b/chromecast/media/cma/backend/filter_group_unittest.cc
index 9b74560..5582dc2 100644
--- a/chromecast/media/cma/backend/filter_group_unittest.cc
+++ b/chromecast/media/cma/backend/filter_group_unittest.cc
@@ -65,10 +65,10 @@
     sample_rate_ = sample_rate;
     return true;
   }
-  int GetInputSampleRate() { return sample_rate_; }
+  int GetInputSampleRate() const override { return sample_rate_; }
   bool IsRinging() override { return false; }
   float* GetOutputBuffer() override { return output_buffer_; }
-  int NumOutputChannels() override { return num_output_channels_; }
+  int NumOutputChannels() const override { return num_output_channels_; }
   int delay() { return 0; }
   std::string name() const { return "mock"; }
   double StorePtr(float* data,
@@ -125,7 +125,7 @@
     sample_rate_ = sample_rate;
     return true;
   }
-  int GetInputSampleRate() override { return sample_rate_; }
+  int GetInputSampleRate() const override { return sample_rate_; }
 
   bool IsRinging() override { return false; }
   int delay() { return 0; }
@@ -170,9 +170,7 @@
 class FilterGroupTest : public testing::Test {
  protected:
   using RenderingDelay = MixerInput::RenderingDelay;
-  FilterGroupTest()
-      : source_(kInputSampleRate),
-        input_(&source_, kInputSampleRate, 0, RenderingDelay(), nullptr) {
+  FilterGroupTest() : source_(kInputSampleRate) {
     source_.SetData(GetTestData());
   }
 
@@ -185,8 +183,9 @@
     EXPECT_CALL(*post_processor_, UpdatePlayoutChannel(kDefaultPlayoutChannel));
     filter_group_ = std::make_unique<FilterGroup>(
         kNumInputChannels, "test_filter", std::move(post_processor));
-    filter_group_->Initialize(kInputSampleRate);
-    filter_group_->AddInput(&input_);
+    input_ = std::make_unique<MixerInput>(&source_, filter_group_.get());
+    filter_group_->Initialize(kInputSampleRate, kInputFrames);
+    filter_group_->AddInput(input_.get());
     filter_group_->UpdatePlayoutChannel(kChannelAll);
   }
 
@@ -211,8 +210,8 @@
   float RightInput(int frame) { return Input(1, frame); }
 
   NiceMock<MockMixerSource> source_;
-  MixerInput input_;
   std::unique_ptr<FilterGroup> filter_group_;
+  std::unique_ptr<MixerInput> input_;
   MockPostProcessingPipeline* post_processor_ = nullptr;
 
  private:
@@ -254,13 +253,11 @@
 
   NiceMock<MockMixerSource> tts_source(kInputSampleRate);
   tts_source.set_content_type(AudioContentType::kCommunication);
-  MixerInput tts_input(&tts_source, kInputSampleRate, 0, RenderingDelay(),
-                       nullptr);
+  MixerInput tts_input(&tts_source, filter_group_.get());
 
   NiceMock<MockMixerSource> alarm_source(kInputSampleRate);
   alarm_source.set_content_type(AudioContentType::kAlarm);
-  MixerInput alarm_input(&alarm_source, kInputSampleRate, 0, RenderingDelay(),
-                         nullptr);
+  MixerInput alarm_input(&alarm_source, filter_group_.get());
 
   // Media input stream + tts input stream -> tts content type.
   filter_group_->AddInput(&tts_input);
diff --git a/chromecast/media/cma/backend/mixer_input.cc b/chromecast/media/cma/backend/mixer_input.cc
index 6a80a06..9ffb1ed4 100644
--- a/chromecast/media/cma/backend/mixer_input.cc
+++ b/chromecast/media/cma/backend/mixer_input.cc
@@ -34,15 +34,11 @@
 
 }  // namespace
 
-MixerInput::MixerInput(Source* source,
-                       int output_samples_per_second,
-                       int read_size,
-                       RenderingDelay initial_rendering_delay,
-                       FilterGroup* filter_group)
+MixerInput::MixerInput(Source* source, FilterGroup* filter_group)
     : source_(source),
       num_channels_(source->num_channels()),
       input_samples_per_second_(source->input_samples_per_second()),
-      output_samples_per_second_(output_samples_per_second),
+      output_samples_per_second_(filter_group->input_samples_per_second()),
       primary_(source->primary()),
       device_id_(source->device_id()),
       content_type_(source->content_type()),
@@ -58,7 +54,10 @@
   DCHECK_GT(num_channels_, 0);
   DCHECK_GT(input_samples_per_second_, 0);
 
-  int source_read_size = read_size;
+  MediaPipelineBackend::AudioDecoder::RenderingDelay initial_rendering_delay =
+      filter_group->GetRenderingDelayToOutput();
+
+  int source_read_size = filter_group->input_frames_per_write();
   if (output_samples_per_second_ > 0 &&
       output_samples_per_second_ != input_samples_per_second_) {
     // Round up to nearest multiple of SincResampler::kKernelSize. The read size
diff --git a/chromecast/media/cma/backend/mixer_input.h b/chromecast/media/cma/backend/mixer_input.h
index 6a5c16c..8707e8d 100644
--- a/chromecast/media/cma/backend/mixer_input.h
+++ b/chromecast/media/cma/backend/mixer_input.h
@@ -82,9 +82,6 @@
   };
 
   MixerInput(Source* source,
-             int output_samples_per_second,
-             int read_size,
-             RenderingDelay initial_rendering_delay,
              FilterGroup* filter_group);
   ~MixerInput();
 
diff --git a/chromecast/media/cma/backend/mixer_pipeline.cc b/chromecast/media/cma/backend/mixer_pipeline.cc
index 4f8656a8..3c377bc 100644
--- a/chromecast/media/cma/backend/mixer_pipeline.cc
+++ b/chromecast/media/cma/backend/mixer_pipeline.cc
@@ -126,8 +126,6 @@
         loopback_output_group_;
   }
 
-  output_group_->PrintTopology();
-
   return true;
 }
 
@@ -155,14 +153,17 @@
       return false;
     }
     stream_sinks_[stream_type] = filter_group;
+    filter_group->AddStreamType(stream_type);
   }
   return true;
 }
 
-void MixerPipeline::Initialize(int output_samples_per_second_) {
-  for (auto&& filter_group : filter_groups_) {
-    filter_group->Initialize(output_samples_per_second_);
-  }
+void MixerPipeline::Initialize(int output_samples_per_second,
+                               int frames_per_write) {
+  // The output group will recursively set the sample rate of all other
+  // FilterGroups.
+  output_group_->Initialize(output_samples_per_second, frames_per_write);
+  output_group_->PrintTopology();
 }
 
 FilterGroup* MixerPipeline::GetInputGroup(const std::string& device_id) {
diff --git a/chromecast/media/cma/backend/mixer_pipeline.h b/chromecast/media/cma/backend/mixer_pipeline.h
index 779dd19..5e9bf67 100644
--- a/chromecast/media/cma/backend/mixer_pipeline.h
+++ b/chromecast/media/cma/backend/mixer_pipeline.h
@@ -41,7 +41,7 @@
   ~MixerPipeline();
 
   // Sets the sample rate of all processors.
-  void Initialize(int samples_per_second);
+  void Initialize(int samples_per_second, int frames_per_write);
 
   // Returns the FilterGroup that should process a stream with |device_id| or
   // |nullptr| if no matching FilterGroup is found.
diff --git a/chromecast/media/cma/backend/mock_post_processor_factory.h b/chromecast/media/cma/backend/mock_post_processor_factory.h
index c8a66e0..22b4f7f 100644
--- a/chromecast/media/cma/backend/mock_post_processor_factory.h
+++ b/chromecast/media/cma/backend/mock_post_processor_factory.h
@@ -38,12 +38,12 @@
     sample_rate_ = sample_rate;
     return true;
   }
-  int GetInputSampleRate() override { return sample_rate_; }
+  int GetInputSampleRate() const override { return sample_rate_; }
   bool IsRinging() override { return ringing_; }
   int delay() { return rendering_delay_frames_; }
   std::string name() const { return name_; }
   float* GetOutputBuffer() override { return output_buffer_; }
-  int NumOutputChannels() override { return num_output_channels_; }
+  int NumOutputChannels() const override { return num_output_channels_; }
 
   MOCK_METHOD2(SetPostProcessorConfig,
                void(const std::string& name, const std::string& config));
diff --git a/chromecast/media/cma/backend/post_processing_pipeline.h b/chromecast/media/cma/backend/post_processing_pipeline.h
index f20e301..d0a5cbd 100644
--- a/chromecast/media/cma/backend/post_processing_pipeline.h
+++ b/chromecast/media/cma/backend/post_processing_pipeline.h
@@ -27,10 +27,10 @@
                                float current_multiplier,
                                bool is_silence) = 0;
   virtual float* GetOutputBuffer() = 0;
-  virtual int NumOutputChannels() = 0;
+  virtual int NumOutputChannels() const = 0;
 
   virtual bool SetOutputSampleRate(int sample_rate) = 0;
-  virtual int GetInputSampleRate() = 0;
+  virtual int GetInputSampleRate() const = 0;
   virtual bool IsRinging() = 0;
   virtual void SetPostProcessorConfig(const std::string& name,
                                       const std::string& config) = 0;
diff --git a/chromecast/media/cma/backend/post_processing_pipeline_impl.cc b/chromecast/media/cma/backend/post_processing_pipeline_impl.cc
index 0f9284c..90fcf65 100644
--- a/chromecast/media/cma/backend/post_processing_pipeline_impl.cc
+++ b/chromecast/media/cma/backend/post_processing_pipeline_impl.cc
@@ -104,10 +104,10 @@
     LOG(INFO) << "Creating an instance of " << library_path << "("
               << processor_config_string << ")";
 
-    processors_.emplace_back(
-        PostProcessorInfo{factory_.CreatePostProcessor(
-                              library_path, processor_config_string, channels),
-                          processor_name});
+    processors_.emplace_back(PostProcessorInfo{
+        factory_.CreatePostProcessor(library_path, processor_config_string,
+                                     channels),
+        1.0 /* output_frames_per_input_frame */, processor_name});
     channels = processors_.back().ptr->GetStatus().output_channels;
   }
   num_output_channels_ = channels;
@@ -116,7 +116,7 @@
 PostProcessingPipelineImpl::~PostProcessingPipelineImpl() = default;
 
 double PostProcessingPipelineImpl::ProcessFrames(float* data,
-                                                 int num_frames,
+                                                 int num_input_frames,
                                                  float current_multiplier,
                                                  bool is_silence) {
   DCHECK_GT(input_sample_rate_, 0);
@@ -128,7 +128,7 @@
     if (!IsRinging()) {
       return delay_s_;  // Output will be silence.
     }
-    silence_frames_processed_ += num_frames;
+    silence_frames_processed_ += num_input_frames;
   } else {
     silence_frames_processed_ = 0;
   }
@@ -137,8 +137,12 @@
 
   delay_s_ = 0;
   for (auto& processor : processors_) {
-    processor.ptr->ProcessFrames(output_buffer_, num_frames, cast_volume_,
+    processor.ptr->ProcessFrames(output_buffer_, num_input_frames, cast_volume_,
                                  current_dbfs_);
+    DCHECK_EQ(
+        num_input_frames * processor.output_frames_per_input_frame,
+        std::floor(num_input_frames * processor.output_frames_per_input_frame));
+    num_input_frames *= processor.output_frames_per_input_frame;
     const auto& status = processor.ptr->GetStatus();
     delay_s_ += static_cast<double>(status.rendering_delay_frames) /
                 status.input_sample_rate;
@@ -147,7 +151,7 @@
   return delay_s_;
 }
 
-int PostProcessingPipelineImpl::NumOutputChannels() {
+int PostProcessingPipelineImpl::NumOutputChannels() const {
   return num_output_channels_;
 }
 
@@ -159,23 +163,30 @@
 
 bool PostProcessingPipelineImpl::SetOutputSampleRate(int sample_rate) {
   output_sample_rate_ = sample_rate;
-  input_sample_rate_ = sample_rate;
+  int processor_output_rate = sample_rate;
+  int processor_input_rate = sample_rate;
 
   // Each Processor's output rate must be the following processor's input rate.
-  for (int i = processors_.size() - 1; i >= 0; --i) {
+  for (int i = static_cast<int>(processors_.size()) - 1; i >= 0; --i) {
     AudioPostProcessor2::Config config;
-    config.output_sample_rate = input_sample_rate_;
+    config.output_sample_rate = processor_output_rate;
     if (!processors_[i].ptr->SetConfig(config)) {
       return false;
     }
-    input_sample_rate_ = processors_[i].ptr->GetStatus().input_sample_rate;
+    processor_input_rate = processors_[i].ptr->GetStatus().input_sample_rate;
+    DCHECK_GT(processor_input_rate, 0) << processors_[i].name;
+    processors_[i].output_frames_per_input_frame =
+        static_cast<double>(processor_output_rate) / processor_input_rate;
+    processor_output_rate = processor_input_rate;
   }
+
+  input_sample_rate_ = processor_input_rate;
   ringing_time_in_frames_ = GetRingingTimeInFrames();
   silence_frames_processed_ = 0;
   return true;
 }
 
-int PostProcessingPipelineImpl::GetInputSampleRate() {
+int PostProcessingPipelineImpl::GetInputSampleRate() const {
   return input_sample_rate_;
 }
 
@@ -218,8 +229,8 @@
               [&name](PostProcessorInfo& p) { return p.name == name; });
   if (it != processors_.end()) {
     it->ptr->UpdateParameters(config);
-    LOG(INFO) << "Config string: " << config
-              << " was delivered to postprocessor " << name;
+    DVLOG(2) << "Config string: " << config
+             << " was delivered to postprocessor " << name;
   }
 }
 
diff --git a/chromecast/media/cma/backend/post_processing_pipeline_impl.h b/chromecast/media/cma/backend/post_processing_pipeline_impl.h
index a87bf7b8..31ad0e2 100644
--- a/chromecast/media/cma/backend/post_processing_pipeline_impl.h
+++ b/chromecast/media/cma/backend/post_processing_pipeline_impl.h
@@ -38,10 +38,10 @@
                        bool is_silence) override;
 
   float* GetOutputBuffer() override;
-  int NumOutputChannels() override;
+  int NumOutputChannels() const override;
 
   bool SetOutputSampleRate(int sample_rate) override;
-  int GetInputSampleRate() override;
+  int GetInputSampleRate() const override;
   bool IsRinging() override;
 
   // Send string |config| to post processor |name|.
@@ -55,6 +55,7 @@
   // structs.
   typedef struct {
     std::unique_ptr<AudioPostProcessor2> ptr;
+    double output_frames_per_input_frame;
     std::string name;
   } PostProcessorInfo;
 
diff --git a/chromecast/media/cma/backend/post_processors/post_processor_wrapper.cc b/chromecast/media/cma/backend/post_processors/post_processor_wrapper.cc
index ee7858f..b51d5a3 100644
--- a/chromecast/media/cma/backend/post_processors/post_processor_wrapper.cc
+++ b/chromecast/media/cma/backend/post_processors/post_processor_wrapper.cc
@@ -29,7 +29,7 @@
 
 bool AudioPostProcessorWrapper::SetConfig(
     const AudioPostProcessor2::Config& config) {
-  if (!pp_->SetSampleRate(status_.input_sample_rate)) {
+  if (!pp_->SetSampleRate(config.output_sample_rate)) {
     return false;
   }
   status_.input_sample_rate = config.output_sample_rate;
diff --git a/chromecast/media/cma/backend/stream_mixer.cc b/chromecast/media/cma/backend/stream_mixer.cc
index 728d790c..529748d 100644
--- a/chromecast/media/cma/backend/stream_mixer.cc
+++ b/chromecast/media/cma/backend/stream_mixer.cc
@@ -120,7 +120,7 @@
   task_runner->PostTask(FROM_HERE, std::move(task));
 }
 
-int GetFixedSampleRate() {
+int GetFixedOutputSampleRate() {
   int fixed_sample_rate = GetSwitchValueNonNegativeInt(
       switches::kAudioOutputSampleRate, MixerOutputStream::kInvalidSampleRate);
 
@@ -186,7 +186,7 @@
           GetSwitchValueBoolean(switches::kAlsaEnableUpsampling, false)
               ? kLowSampleRateCutoff
               : MixerOutputStream::kInvalidSampleRate),
-      fixed_sample_rate_(GetFixedSampleRate()),
+      fixed_output_sample_rate_(GetFixedOutputSampleRate()),
       no_input_close_timeout_(GetNoInputCloseTimeout()),
       filter_frame_alignment_(kDefaultFilterFrameAlignment),
       state_(kStateStopped),
@@ -226,8 +226,8 @@
     input_task_runner_ = mixer_task_runner_;
   }
 
-  if (fixed_sample_rate_ != MixerOutputStream::kInvalidSampleRate) {
-    LOG(INFO) << "Setting fixed sample rate to " << fixed_sample_rate_;
+  if (fixed_output_sample_rate_ != MixerOutputStream::kInvalidSampleRate) {
+    LOG(INFO) << "Setting fixed sample rate to " << fixed_output_sample_rate_;
   }
 
   CreatePostProcessors([](bool, const std::string&) {},
@@ -314,7 +314,7 @@
   CHECK(PostProcessorsHaveCorrectNumOutputs());
 
   if (state_ == kStateRunning) {
-    mixer_pipeline_->Initialize(output_samples_per_second_);
+    mixer_pipeline_->Initialize(output_samples_per_second_, frames_per_write_);
   }
 
   if (callback) {
@@ -383,8 +383,8 @@
   DCHECK(output_);
 
   int requested_sample_rate;
-  if (fixed_sample_rate_ != MixerOutputStream::kInvalidSampleRate) {
-    requested_sample_rate = fixed_sample_rate_;
+  if (fixed_output_sample_rate_ != MixerOutputStream::kInvalidSampleRate) {
+    requested_sample_rate = fixed_output_sample_rate_;
   } else if (low_sample_rate_cutoff_ != MixerOutputStream::kInvalidSampleRate &&
              requested_output_samples_per_second_ < low_sample_rate_cutoff_) {
     requested_sample_rate =
@@ -407,7 +407,7 @@
   CHECK_GT(frames_per_write_, 0);
 
   // Initialize filters.
-  mixer_pipeline_->Initialize(output_samples_per_second_);
+  mixer_pipeline_->Initialize(output_samples_per_second_, frames_per_write_);
 
   for (auto& redirector : audio_output_redirectors_) {
     redirector.second->Start(output_samples_per_second_);
@@ -491,7 +491,7 @@
   // may need to change the output sample rate to match the input sample rate.
   // We only change the output rate if it is not set to a fixed value.
   if ((input_source->primary() || inputs_.empty()) &&
-      fixed_sample_rate_ == MixerOutputStream::kInvalidSampleRate) {
+      fixed_output_sample_rate_ == MixerOutputStream::kInvalidSampleRate) {
     CheckChangeOutputRate(input_source->input_samples_per_second());
   }
 
@@ -506,11 +506,11 @@
   DCHECK(input_group) << "Could not find a processor for "
                       << input_source->device_id();
 
-  LOG(INFO) << "Add input " << input_source << " to " << input_group->name();
+  LOG(INFO) << "Add input " << input_source << " to " << input_group->name()
+            << " @ " << input_group->GetInputSampleRate()
+            << " samples per second.";
 
-  auto input = std::make_unique<MixerInput>(
-      input_source, output_samples_per_second_, frames_per_write_,
-      GetTotalRenderingDelay(input_group), input_group);
+  auto input = std::make_unique<MixerInput>(input_source, input_group);
   if (state_ != kStateRunning) {
     // Mixer error occurred, signal error.
     MixerInput* input_ptr = input.get();
diff --git a/chromecast/media/cma/backend/stream_mixer.h b/chromecast/media/cma/backend/stream_mixer.h
index 19111ef..66ebb376 100644
--- a/chromecast/media/cma/backend/stream_mixer.h
+++ b/chromecast/media/cma/backend/stream_mixer.h
@@ -238,7 +238,7 @@
 
   int num_output_channels_;
   const int low_sample_rate_cutoff_;
-  const int fixed_sample_rate_;
+  const int fixed_output_sample_rate_;
   const base::TimeDelta no_input_close_timeout_;
   // Force data to be filtered in multiples of |filter_frame_alignment_| frames.
   // Must be a multiple of 4 for some NEON implementations. Some
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 13c0d28..55b487a 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -36,9 +36,7 @@
     "//chromeos/dbus/constants",
     "//components/policy/proto",
     "//google_apis",
-    "//services/device/public/mojom",
     "//services/network/public/cpp:cpp",
-    "//services/service_manager/public/cpp",
     "//third_party/protobuf:protobuf_lite",
   ]
   sources = [
@@ -74,8 +72,6 @@
     "process_proxy/process_proxy_registry.h",
     "system/cpu_temperature_reader.cc",
     "system/cpu_temperature_reader.h",
-    "system/dark_resume_controller.cc",
-    "system/dark_resume_controller.h",
     "system/devicemode.cc",
     "system/devicemode.h",
     "system/factory_ping_embargo_check.cc",
@@ -183,11 +179,8 @@
     "//mojo/core/embedder",
     "//net",
     "//net:test_support",
-    "//services/device/public/cpp/test:test_support",
-    "//services/device/public/mojom",
     "//services/network:test_support",
     "//services/network/public/cpp",
-    "//services/service_manager/public/cpp/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/icu",
@@ -207,7 +200,6 @@
     "process_proxy/process_output_watcher_unittest.cc",
     "process_proxy/process_proxy_unittest.cc",
     "system/cpu_temperature_reader_unittest.cc",
-    "system/dark_resume_controller_unittest.cc",
     "system/factory_ping_embargo_check_unittest.cc",
     "system/name_value_pairs_parser_unittest.cc",
     "test/run_all_unittests.cc",
diff --git a/chromeos/components/BUILD.gn b/chromeos/components/BUILD.gn
index 708e751..13050e2 100644
--- a/chromeos/components/BUILD.gn
+++ b/chromeos/components/BUILD.gn
@@ -21,6 +21,7 @@
     "//chromeos/components/drivefs:unit_tests",
     "//chromeos/components/multidevice:unit_tests",
     "//chromeos/components/nearby:unit_tests",
+    "//chromeos/components/power:unit_tests",
     "//chromeos/components/proximity_auth:unit_tests",
     "//chromeos/components/tether:unit_tests",
     "//mojo/core/embedder",
diff --git a/chromeos/components/power/BUILD.gn b/chromeos/components/power/BUILD.gn
new file mode 100644
index 0000000..f45b728
--- /dev/null
+++ b/chromeos/components/power/BUILD.gn
@@ -0,0 +1,42 @@
+# 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.
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+component("power") {
+  output_name = "chromeos_power"
+  defines = [ "IS_CHROMEOS_POWER_IMPL" ]
+  deps = [
+    "//base",
+
+    # TODO(stevenjb): Replace this with //chromeos/dbus/power once extracted.
+    # https://crbug.com/644348.
+    "//chromeos/dbus",
+    "//services/device/public/mojom",
+    "//services/service_manager/public/cpp",
+  ]
+
+  sources = [
+    "dark_resume_controller.cc",
+    "dark_resume_controller.h",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [
+    "dark_resume_controller_unittest.cc",
+  ]
+
+  deps = [
+    ":power",
+    "//base/test:test_support",
+    "//chromeos/dbus",
+    "//services/device/public/cpp/test:test_support",
+    "//services/device/public/mojom",
+    "//services/service_manager/public/cpp/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chromeos/system/dark_resume_controller.cc b/chromeos/components/power/dark_resume_controller.cc
similarity index 98%
rename from chromeos/system/dark_resume_controller.cc
rename to chromeos/components/power/dark_resume_controller.cc
index 09bcc52..0782ee9 100644
--- a/chromeos/system/dark_resume_controller.cc
+++ b/chromeos/components/power/dark_resume_controller.cc
@@ -1,7 +1,7 @@
 // Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-#include "chromeos/system/dark_resume_controller.h"
+#include "chromeos/components/power/dark_resume_controller.h"
 
 #include <utility>
 
diff --git a/chromeos/system/dark_resume_controller.h b/chromeos/components/power/dark_resume_controller.h
similarity index 94%
rename from chromeos/system/dark_resume_controller.h
rename to chromeos/components/power/dark_resume_controller.h
index 3004f34..9655795 100644
--- a/chromeos/system/dark_resume_controller.h
+++ b/chromeos/components/power/dark_resume_controller.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROMEOS_SYSTEM_DARK_RESUME_CONTROLLER_H_
-#define CHROMEOS_SYSTEM_DARK_RESUME_CONTROLLER_H_
+#ifndef CHROMEOS_COMPONENTS_POWER_DARK_RESUME_CONTROLLER_H_
+#define CHROMEOS_COMPONENTS_POWER_DARK_RESUME_CONTROLLER_H_
 
 #include <memory>
 
+#include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
-#include "chromeos/chromeos_export.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/device/public/mojom/wake_lock.mojom.h"
@@ -43,7 +43,7 @@
 //
 // 5. If the system transitions to a full resume all dark resume related state
 // and timers are cleared as the system wakes up.
-class CHROMEOS_EXPORT DarkResumeController
+class COMPONENT_EXPORT(CHROMEOS_POWER) DarkResumeController
     : public chromeos::PowerManagerClient::Observer,
       public device::mojom::WakeLockObserver {
  public:
@@ -129,4 +129,4 @@
 }  // namespace system
 }  // namespace chromeos
 
-#endif  // CHROMEOS_SYSTEM_DARK_RESUME_CONTROLLER_H_
+#endif  // CHROMEOS_COMPONENTS_POWER_DARK_RESUME_CONTROLLER_H_
diff --git a/chromeos/system/dark_resume_controller_unittest.cc b/chromeos/components/power/dark_resume_controller_unittest.cc
similarity index 98%
rename from chromeos/system/dark_resume_controller_unittest.cc
rename to chromeos/components/power/dark_resume_controller_unittest.cc
index 71f3f55..7ba188b9 100644
--- a/chromeos/system/dark_resume_controller_unittest.cc
+++ b/chromeos/components/power/dark_resume_controller_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/system/dark_resume_controller.h"
+#include "chromeos/components/power/dark_resume_controller.h"
 
 #include <memory>
 #include <utility>
diff --git a/components/download/quarantine/quarantine_win.cc b/components/download/quarantine/quarantine_win.cc
index bd4827c..59701ee 100644
--- a/components/download/quarantine/quarantine_win.cc
+++ b/components/download/quarantine/quarantine_win.cc
@@ -92,6 +92,39 @@
              : QuarantineFileResult::ANNOTATION_FAILED;
 }
 
+// Maps a return code from an unsuccessful IAttachmentExecute::Save() call to a
+// QuarantineFileResult.
+//
+// Typical return codes from IAttachmentExecute::Save():
+//   S_OK   : The file was okay. If any viruses were found, they were cleaned.
+//   E_FAIL : Virus infected.
+//   INET_E_SECURITY_PROBLEM : The file was blocked due to security policy.
+//
+// Any other return value indicates an unexpected error during the scan.
+QuarantineFileResult FailedSaveResultToQuarantineResult(HRESULT result) {
+  switch (result) {
+    case INET_E_SECURITY_PROBLEM:  // 0x800c000e
+      // This is returned if the download was blocked due to security
+      // restrictions. E.g. if the source URL was in the Restricted Sites zone
+      // and downloads are blocked on that zone, then the download would be
+      // deleted and this error code is returned.
+      return QuarantineFileResult::BLOCKED_BY_POLICY;
+
+    case E_FAIL:  // 0x80004005
+      // Returned if an anti-virus product reports an infection in the
+      // downloaded file during IAE::Save().
+      return QuarantineFileResult::VIRUS_INFECTED;
+
+    default:
+      // Any other error that occurs during IAttachmentExecute::Save() likely
+      // indicates a problem with the security check, but not necessarily the
+      // download. This also includes cases where SUCCEEDED(result) is true. In
+      // the latter case we are likely dealing with a situation where the file
+      // is missing after a successful scan. See http://crbug.com/153212.
+      return QuarantineFileResult::SECURITY_CHECK_FAILED;
+  }
+}
+
 // Invokes IAttachmentExecute::Save on CLSID_AttachmentServices to validate the
 // downloaded file. The call may scan the file for viruses and if necessary,
 // annotate it with evidence.  As a result of the validation, the file may be
@@ -99,19 +132,12 @@
 //
 // IAE::Save() will delete the file if it was found to be blocked by local
 // security policy or if it was found to be infected. The call may also delete
-// the file due to other failures (http://crbug.com/153212). A failure code will
-// be returned in these cases.
+// the file due to other failures (http://crbug.com/153212). In these cases,
+// |result| will contain the failure code.
 //
 // The return value is |false| iff the function fails to invoke
 // IAttachmentExecute::Save(). If the function returns |true|, then the result
-// of invoking IAttachmentExecute::Save() is stored in |save_result|.
-//
-// Typical |save_result| values:
-//   S_OK   : The file was okay. If any viruses were found, they were cleaned.
-//   E_FAIL : Virus infected.
-//   INET_E_SECURITY_PROBLEM : The file was blocked due to security policy.
-//
-// Any other return value indicates an unexpected error during the scan.
+// of invoking IAttachmentExecute::Save() is stored in |result|.
 //
 // |full_path| : is the path to the downloaded file. This should be the final
 //               path of the download. Must be present.
@@ -121,16 +147,15 @@
 //               will not be set.
 // |client_guid|: the GUID to be set in the IAttachmentExecute client slot.
 //                Used to identify the app to the system AV function.
-// |save_result|: Receives the result of invoking IAttachmentExecute::Save().
+// |result|: Receives the result of invoking IAttachmentExecute::Save().
 bool InvokeAttachmentServices(const base::FilePath& full_path,
                               const GURL& source_url,
                               const GURL& referrer_url,
                               const GUID& client_guid,
-                              HRESULT* save_result) {
+                              QuarantineFileResult* result) {
   Microsoft::WRL::ComPtr<IAttachmentExecute> attachment_services;
   HRESULT hr = ::CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL,
                                   IID_PPV_ARGS(&attachment_services));
-  *save_result = S_OK;
 
   if (FAILED(hr)) {
     // The thread must have COM initialized.
@@ -175,41 +200,32 @@
       return false;
   }
 
+  HRESULT save_result = S_OK;
   {
     // This method has been known to take longer than 10 seconds in some
     // instances.
     SCOPED_UMA_HISTOGRAM_LONG_TIMER("Download.AttachmentServices.Duration");
-    *save_result = attachment_services->Save();
+    save_result = attachment_services->Save();
   }
+
+  // If the download file is missing after the call, then treat this as an
+  // interrupted download.
+  //
+  // If IAttachmentExecute::Save() failed, but the downloaded file is still
+  // around, then don't interrupt the download. Attachment Execution Services
+  // deletes the submitted file if the downloaded file is blocked by policy or
+  // if it was found to be infected.
+  //
+  // If the file is still there, then the error could be due to Windows
+  // Attachment Services not being available or some other error during the AES
+  // invocation. In either case, we don't surface the error to the user.
+  *result = base::PathExists(full_path)
+                ? QuarantineFileResult::OK
+                : FailedSaveResultToQuarantineResult(save_result);
+
   return true;
 }
 
-// Maps a return code from an unsuccessful IAttachmentExecute::Save() call to a
-// QuarantineFileResult.
-QuarantineFileResult FailedSaveResultToQuarantineResult(HRESULT result) {
-  switch (result) {
-    case INET_E_SECURITY_PROBLEM:  // 0x800c000e
-      // This is returned if the download was blocked due to security
-      // restrictions. E.g. if the source URL was in the Restricted Sites zone
-      // and downloads are blocked on that zone, then the download would be
-      // deleted and this error code is returned.
-      return QuarantineFileResult::BLOCKED_BY_POLICY;
-
-    case E_FAIL:  // 0x80004005
-      // Returned if an anti-virus product reports an infection in the
-      // downloaded file during IAE::Save().
-      return QuarantineFileResult::VIRUS_INFECTED;
-
-    default:
-      // Any other error that occurs during IAttachmentExecute::Save() likely
-      // indicates a problem with the security check, but not necessarily the
-      // download. This also includes cases where SUCCEEDED(result) is true. In
-      // the latter case we are likely dealing with a situation where the file
-      // is missing after a successful scan. See http://crbug.com/153212.
-      return QuarantineFileResult::SECURITY_CHECK_FAILED;
-  }
-}
-
 }  // namespace
 
 QuarantineFileResult QuarantineFile(const base::FilePath& file,
@@ -238,8 +254,6 @@
     return SetInternetZoneIdentifierDirectly(file, source_url, referrer_url);
   }
 
-  HRESULT save_result = S_OK;
-
   // Check if the attachment services should be invoked based on the experiment
   // state. Not invoking the attachment services means that the Zone Identifier
   // will always be set to 3 (Internet), regardless of URL zones configurations.
@@ -252,27 +266,14 @@
       base::IsMachineExternallyManaged() ||
       base::FeatureList::IsEnabled(kInvokeAttachmentServices);
 
-  bool attachment_services_available =
-      should_invoke_attachment_services &&
+  QuarantineFileResult attachment_services_result = QuarantineFileResult::OK;
+  if (should_invoke_attachment_services &&
       InvokeAttachmentServices(file, source_url, referrer_url, guid,
-                               &save_result);
-  if (!attachment_services_available)
-    return SetInternetZoneIdentifierDirectly(file, source_url, referrer_url);
+                               &attachment_services_result)) {
+    return attachment_services_result;
+  }
 
-  // If the download file is missing after the call, then treat this as an
-  // interrupted download.
-  //
-  // If InvokeAttachmentServices() failed, but the downloaded file is still
-  // around, then don't interrupt the download. Attachment Execution Services
-  // deletes the submitted file if the downloaded file is blocked by policy or
-  // if it was found to be infected.
-  //
-  // If the file is still there, then the error could be due to Windows
-  // Attachment Services not being available or some other error during the AES
-  // invocation. In either case, we don't surface the error to the user.
-  if (!base::PathExists(file))
-    return FailedSaveResultToQuarantineResult(save_result);
-  return QuarantineFileResult::OK;
+  return SetInternetZoneIdentifierDirectly(file, source_url, referrer_url);
 }
 
 }  // namespace download
diff --git a/components/leveldb_proto/internal/proto_database_impl.h b/components/leveldb_proto/internal/proto_database_impl.h
index 7759022..d80bd8c 100644
--- a/components/leveldb_proto/internal/proto_database_impl.h
+++ b/components/leveldb_proto/internal/proto_database_impl.h
@@ -180,15 +180,15 @@
           typename T,
           std::enable_if_t<std::is_base_of<google::protobuf::MessageLite,
                                            T>::value>* = nullptr>
-std::string SerializeAsString(const T& entry) {
-  return entry.SerializeAsString();
+std::string SerializeAsString(T* entry) {
+  return entry->SerializeAsString();
 }
 
 template <typename P,
           typename T,
           std::enable_if_t<!std::is_base_of<google::protobuf::MessageLite,
                                             T>::value>* = nullptr>
-std::string SerializeAsString(const T& entry) {
+std::string SerializeAsString(T* entry) {
   P proto;
   DataToProto(entry, &proto);
   return proto.SerializeAsString();
@@ -221,7 +221,7 @@
   if (!ParseToProto<P>(serialized_entry, &proto))
     return false;
 
-  ProtoToData(proto, entry);
+  ProtoToData(&proto, entry);
   return true;
 }
 
@@ -237,8 +237,8 @@
     Callbacks::UpdateCallback callback) {
   // Serialize the values from Proto to string before passing on to database.
   auto pairs_to_save = std::make_unique<KeyValueVector>();
-  for (const auto& pair : *entries_to_save) {
-    auto serialized = SerializeAsString<P, T>(pair.second);
+  for (auto& pair : *entries_to_save) {
+    auto serialized = SerializeAsString<P, T>(&pair.second);
     pairs_to_save->push_back(std::make_pair(pair.first, serialized));
   }
 
@@ -258,8 +258,8 @@
     Callbacks::UpdateCallback callback) {
   // Serialize the values from Proto to string before passing on to database.
   auto pairs_to_save = std::make_unique<KeyValueVector>();
-  for (const auto& pair : *entries_to_save) {
-    auto serialized = SerializeAsString<P, T>(pair.second);
+  for (auto& pair : *entries_to_save) {
+    auto serialized = SerializeAsString<P, T>(&pair.second);
     pairs_to_save->push_back(std::make_pair(pair.first, serialized));
   }
 
diff --git a/components/leveldb_proto/internal/proto_database_impl_unittest.cc b/components/leveldb_proto/internal/proto_database_impl_unittest.cc
index 8c026233..68d35b6 100644
--- a/components/leveldb_proto/internal/proto_database_impl_unittest.cc
+++ b/components/leveldb_proto/internal/proto_database_impl_unittest.cc
@@ -37,10 +37,8 @@
   // The methods below are convenience methods to have a similar API as protocol
   // buffers for the test framework. This is NOT required for uses of client
   // structs.
-  const std::string& id() const { return id_; }
-  void set_id(const std::string& id) { id_ = id; }
-  const std::string& data() const { return data_; }
-  void set_data(const std::string& data) { data_ = data; }
+  std::string id() const { return id_; }
+  std::string data() const { return data_; }
 
   std::string id_;
   std::string data_;
@@ -61,18 +59,18 @@
                 const std::string& data,
                 ClientStruct* as_struct) {
   // Ensure the DB key, the id-field and data-field are all unique values.
-  as_struct->set_id(key + key);
-  as_struct->set_data(key + key + key);
+  as_struct->id_ = key + key;
+  as_struct->data_ = key + key + key;
 }
 
-void DataToProto(const ClientStruct& data, TestProto* proto) {
-  proto->set_id(data.id());
-  proto->set_data(data.data());
+void DataToProto(ClientStruct* data, TestProto* proto) {
+  proto->mutable_id()->swap(data->id_);
+  proto->mutable_data()->swap(data->data_);
 }
 
-void ProtoToData(const TestProto& proto, ClientStruct* data) {
-  data->set_id(proto.id());
-  data->set_data(proto.data());
+void ProtoToData(TestProto* proto, ClientStruct* data) {
+  proto->mutable_id()->swap(data->id_);
+  proto->mutable_data()->swap(data->data_);
 }
 
 }  // namespace
diff --git a/components/leveldb_proto/testing/fake_db.h b/components/leveldb_proto/testing/fake_db.h
index 85a699b..4f6fa10 100644
--- a/components/leveldb_proto/testing/fake_db.h
+++ b/components/leveldb_proto/testing/fake_db.h
@@ -139,15 +139,15 @@
           typename T,
           std::enable_if_t<std::is_base_of<google::protobuf::MessageLite,
                                            T>::value>* = nullptr>
-void DataToProtoWrap(const T& data, P* proto) {
-  *proto = data;
+void DataToProtoWrap(T* data, P* proto) {
+  proto->Swap(data);
 }
 
 template <typename P,
           typename T,
           std::enable_if_t<!std::is_base_of<google::protobuf::MessageLite,
                                             T>::value>* = nullptr>
-void DataToProtoWrap(const T& data, P* proto) {
+void DataToProtoWrap(T* data, P* proto) {
   DataToProto(data, proto);
 }
 
@@ -155,16 +155,17 @@
           typename T,
           std::enable_if_t<std::is_base_of<google::protobuf::MessageLite,
                                            T>::value>* = nullptr>
-void ProtoToDataWrap(const P& proto, T* copy) {
-  *copy = proto;
+void ProtoToDataWrap(const P& proto, T* data) {
+  *data = proto;
 }
 
 template <typename P,
           typename T,
           std::enable_if_t<!std::is_base_of<google::protobuf::MessageLite,
                                             T>::value>* = nullptr>
-void ProtoToDataWrap(const P& proto, T* copy) {
-  ProtoToData(proto, copy);
+void ProtoToDataWrap(const P& proto, T* data) {
+  P copy = proto;
+  ProtoToData(&copy, data);
 }
 
 }  // namespace
@@ -208,8 +209,8 @@
     std::unique_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
     std::unique_ptr<std::vector<std::string>> keys_to_remove,
     Callbacks::UpdateCallback callback) {
-  for (const auto& pair : *entries_to_save)
-    DataToProtoWrap(pair.second, &(*db_)[pair.first]);
+  for (auto& pair : *entries_to_save)
+    DataToProtoWrap(&pair.second, &(*db_)[pair.first]);
 
   for (const auto& key : *keys_to_remove)
     db_->erase(key);
@@ -223,7 +224,7 @@
     const KeyFilter& delete_key_filter,
     Callbacks::UpdateCallback callback) {
   for (auto& pair : *entries_to_save)
-    DataToProtoWrap(pair.second, &(*db_)[pair.first]);
+    DataToProtoWrap(&pair.second, &(*db_)[pair.first]);
 
   auto it = db_->begin();
   while (it != db_->end()) {
diff --git a/components/offline_items_collection/core/BUILD.gn b/components/offline_items_collection/core/BUILD.gn
index 917698f4..2a9bc1d 100644
--- a/components/offline_items_collection/core/BUILD.gn
+++ b/components/offline_items_collection/core/BUILD.gn
@@ -9,6 +9,7 @@
 
 static_library("core") {
   sources = [
+    "fail_state.cc",
     "fail_state.h",
     "filtered_offline_item_observer.cc",
     "filtered_offline_item_observer.h",
diff --git a/components/offline_items_collection/core/fail_state.cc b/components/offline_items_collection/core/fail_state.cc
new file mode 100644
index 0000000..aa0caa1
--- /dev/null
+++ b/components/offline_items_collection/core/fail_state.cc
@@ -0,0 +1,49 @@
+// 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 "components/offline_items_collection/core/fail_state.h"
+
+namespace offline_items_collection {
+
+bool ToFailState(int value, FailState* fail_state) {
+  switch (static_cast<FailState>(value)) {
+    case FailState::NO_FAILURE:
+    case FailState::CANNOT_DOWNLOAD:
+    case FailState::NETWORK_INSTABILITY:
+    case FailState::FILE_FAILED:
+    case FailState::FILE_ACCESS_DENIED:
+    case FailState::FILE_NO_SPACE:
+    case FailState::FILE_NAME_TOO_LONG:
+    case FailState::FILE_TOO_LARGE:
+    case FailState::FILE_VIRUS_INFECTED:
+    case FailState::FILE_TRANSIENT_ERROR:
+    case FailState::FILE_BLOCKED:
+    case FailState::FILE_SECURITY_CHECK_FAILED:
+    case FailState::FILE_TOO_SHORT:
+    case FailState::FILE_HASH_MISMATCH:
+    case FailState::FILE_SAME_AS_SOURCE:
+    case FailState::NETWORK_FAILED:
+    case FailState::NETWORK_TIMEOUT:
+    case FailState::NETWORK_DISCONNECTED:
+    case FailState::NETWORK_SERVER_DOWN:
+    case FailState::NETWORK_INVALID_REQUEST:
+    case FailState::SERVER_FAILED:
+    case FailState::SERVER_NO_RANGE:
+    case FailState::SERVER_BAD_CONTENT:
+    case FailState::SERVER_UNAUTHORIZED:
+    case FailState::SERVER_CERT_PROBLEM:
+    case FailState::SERVER_FORBIDDEN:
+    case FailState::SERVER_UNREACHABLE:
+    case FailState::SERVER_CONTENT_LENGTH_MISMATCH:
+    case FailState::SERVER_CROSS_ORIGIN_REDIRECT:
+    case FailState::USER_CANCELED:
+    case FailState::USER_SHUTDOWN:
+    case FailState::CRASH:
+      *fail_state = static_cast<FailState>(value);
+      return true;
+  }
+  return false;
+}
+
+}  // namespace offline_items_collection
diff --git a/components/offline_items_collection/core/fail_state.h b/components/offline_items_collection/core/fail_state.h
index 606a2b3..f1547b40 100644
--- a/components/offline_items_collection/core/fail_state.h
+++ b/components/offline_items_collection/core/fail_state.h
@@ -5,8 +5,12 @@
 #ifndef COMPONENTS_OFFLINE_ITEMS_COLLECTION_CORE_FAIL_STATE_H_
 #define COMPONENTS_OFFLINE_ITEMS_COLLECTION_CORE_FAIL_STATE_H_
 
+#include <iosfwd>
+
 namespace offline_items_collection {
 
+// Warning: These enumeration values are saved to a database, enumeration values
+// should not be changed.
 // A Java counterpart will be generated for this enum.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.offline_items_collection
 enum class FailState {
@@ -126,6 +130,8 @@
   CRASH,
 };
 
+bool ToFailState(int value, FailState* fail_state);
+
 // Implemented for testing only. See test_support/offline_item_test_support.cc.
 std::ostream& operator<<(std::ostream& os, FailState state);
 
diff --git a/components/offline_pages/core/background/request_queue_store.cc b/components/offline_pages/core/background/request_queue_store.cc
index 9d74f45..c8ecd19 100644
--- a/components/offline_pages/core/background/request_queue_store.cc
+++ b/components/offline_pages/core/background/request_queue_store.cc
@@ -178,6 +178,10 @@
   return transaction.Commit();
 }
 
+// Enum conversion code. Database corruption is possible, so make sure enum
+// values are in the domain. Because corruption is rare, there is not robust
+// error handling.
+
 SavePageRequest::AutoFetchNotificationState AutoFetchNotificationStateFromInt(
     int value) {
   switch (static_cast<SavePageRequest::AutoFetchNotificationState>(value)) {
@@ -185,9 +189,30 @@
     case SavePageRequest::AutoFetchNotificationState::kShown:
       return static_cast<SavePageRequest::AutoFetchNotificationState>(value);
   }
+  DLOG(ERROR) << "Invalid AutoFetchNotificationState value: " << value;
   return SavePageRequest::AutoFetchNotificationState::kUnknown;
 }
 
+SavePageRequest::RequestState ToRequestState(int value) {
+  switch (static_cast<SavePageRequest::RequestState>(value)) {
+    case SavePageRequest::RequestState::AVAILABLE:
+    case SavePageRequest::RequestState::PAUSED:
+    case SavePageRequest::RequestState::OFFLINING:
+      return static_cast<SavePageRequest::RequestState>(value);
+  }
+  DLOG(ERROR) << "Invalid RequestState value: " << value;
+  return SavePageRequest::RequestState::AVAILABLE;
+}
+
+offline_items_collection::FailState ToFailState(int value) {
+  offline_items_collection::FailState state = FailState::NO_FAILURE;
+  if (!offline_items_collection::ToFailState(value, &state)) {
+    DLOG(ERROR) << "Invalid FailState: " << value;
+  }
+
+  return state;
+}
+
 // Create a save page request from the first row of an SQL result. The result
 // must have the exact columns from the |REQUEST_QUEUE_FIELDS| macro.
 std::unique_ptr<SavePageRequest> MakeSavePageRequest(
@@ -200,7 +225,7 @@
   const int64_t started_attempt_count = statement.ColumnInt64(4);
   const int64_t completed_attempt_count = statement.ColumnInt64(5);
   const SavePageRequest::RequestState state =
-      static_cast<SavePageRequest::RequestState>(statement.ColumnInt64(6));
+      ToRequestState(statement.ColumnInt64(6));
   const GURL url(statement.ColumnString(7));
   const ClientId client_id(statement.ColumnString(8),
                            statement.ColumnString(9));
@@ -221,7 +246,7 @@
   request->set_request_state(state);
   request->set_original_url(std::move(original_url));
   request->set_request_origin(std::move(request_origin));
-  request->set_fail_state(static_cast<FailState>(statement.ColumnInt64(12)));
+  request->set_fail_state(ToFailState(statement.ColumnInt64(12)));
   request->set_auto_fetch_notification_state(
       AutoFetchNotificationStateFromInt(statement.ColumnInt(13)));
   return request;
diff --git a/components/offline_pages/core/background/save_page_request.h b/components/offline_pages/core/background/save_page_request.h
index 77f02c0..9e42ce5 100644
--- a/components/offline_pages/core/background/save_page_request.h
+++ b/components/offline_pages/core/background/save_page_request.h
@@ -23,7 +23,7 @@
 class SavePageRequest {
  public:
   // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.offlinepages
-  enum class RequestState {
+  enum class RequestState : int {
     AVAILABLE = 0,  // Request can be scheduled when preconditions are met.
     PAUSED = 1,     // Request is not available until it is unpaused.
     OFFLINING = 2,  // Request is actively offlining.
diff --git a/components/offline_pages/core/prefetch/prefetch_types.cc b/components/offline_pages/core/prefetch/prefetch_types.cc
index e71c50ad..2b4ca22 100644
--- a/components/offline_pages/core/prefetch/prefetch_types.cc
+++ b/components/offline_pages/core/prefetch/prefetch_types.cc
@@ -115,6 +115,8 @@
       return "STALE_AT_UNKNOWN";
     case PrefetchItemErrorCode::STUCK:
       return "STUCK";
+    case PrefetchItemErrorCode::INVALID_ITEM:
+      return "INVALID_ITEM";
     case PrefetchItemErrorCode::GET_OPERATION_MAX_ATTEMPTS_REACHED:
       return "GET_OPERATION_MAX_ATTEMPTS_REACHED";
     case PrefetchItemErrorCode::
@@ -215,6 +217,35 @@
   return base::nullopt;
 }
 
+base::Optional<PrefetchItemErrorCode> ToPrefetchItemErrorCode(int value) {
+  switch (static_cast<PrefetchItemErrorCode>(value)) {
+    case PrefetchItemErrorCode::SUCCESS:
+    case PrefetchItemErrorCode::TOO_MANY_NEW_URLS:
+    case PrefetchItemErrorCode::DOWNLOAD_ERROR:
+    case PrefetchItemErrorCode::IMPORT_ERROR:
+    case PrefetchItemErrorCode::ARCHIVING_FAILED:
+    case PrefetchItemErrorCode::ARCHIVING_LIMIT_EXCEEDED:
+    case PrefetchItemErrorCode::STALE_AT_NEW_REQUEST:
+    case PrefetchItemErrorCode::STALE_AT_AWAITING_GCM:
+    case PrefetchItemErrorCode::STALE_AT_RECEIVED_GCM:
+    case PrefetchItemErrorCode::STALE_AT_RECEIVED_BUNDLE:
+    case PrefetchItemErrorCode::STALE_AT_DOWNLOADING:
+    case PrefetchItemErrorCode::STALE_AT_IMPORTING:
+    case PrefetchItemErrorCode::STALE_AT_UNKNOWN:
+    case PrefetchItemErrorCode::STUCK:
+    case PrefetchItemErrorCode::INVALID_ITEM:
+    case PrefetchItemErrorCode::GET_OPERATION_MAX_ATTEMPTS_REACHED:
+    case PrefetchItemErrorCode::
+        GENERATE_PAGE_BUNDLE_REQUEST_MAX_ATTEMPTS_REACHED:
+    case PrefetchItemErrorCode::DOWNLOAD_MAX_ATTEMPTS_REACHED:
+    case PrefetchItemErrorCode::MAXIMUM_CLOCK_BACKWARD_SKEW_EXCEEDED:
+    case PrefetchItemErrorCode::IMPORT_LOST:
+    case PrefetchItemErrorCode::SUGGESTION_INVALIDATED:
+      return static_cast<PrefetchItemErrorCode>(value);
+  }
+  return base::nullopt;
+}
+
 std::ostream& operator<<(std::ostream& out,
                          PrefetchBackgroundTaskRescheduleType value) {
   return out << PrefetchEnumToString(value);
diff --git a/components/offline_pages/core/prefetch/prefetch_types.h b/components/offline_pages/core/prefetch/prefetch_types.h
index a4c425f2..2816518 100644
--- a/components/offline_pages/core/prefetch/prefetch_types.h
+++ b/components/offline_pages/core/prefetch/prefetch_types.h
@@ -159,7 +159,7 @@
 // Changes to this enum must be reflected in the respective metrics enum named
 // OflinePrefetchItemErrorCode in enums.xml. Use the exact same integer value
 // for each mirrored entry.
-enum class PrefetchItemErrorCode {
+enum class PrefetchItemErrorCode : int {
   // The entry had gone through the pipeline and successfully completed
   // prefetching. Explicitly setting to 0 as that is the default value for the
   // respective SQLite column.
@@ -192,6 +192,8 @@
   // The item was terminated because it remained in the pipeline for more than
   // 7 days.
   STUCK = 1150,
+  // The item had some invalid data, probably due to database corruption.
+  INVALID_ITEM = 1175,
   // Exceeded maximum retries for get operation request.
   GET_OPERATION_MAX_ATTEMPTS_REACHED = 1200,
   // Exceeded maximum retries limit for generate page bundle request.
@@ -210,6 +212,8 @@
   kMaxValue = SUGGESTION_INVALIDATED
 };
 
+base::Optional<PrefetchItemErrorCode> ToPrefetchItemErrorCode(int value);
+
 // Callback invoked upon completion of a prefetch request.
 using PrefetchRequestFinishedCallback =
     base::OnceCallback<void(PrefetchRequestStatus status,
diff --git a/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc b/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
index 27c2068..45fd12f 100644
--- a/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
+++ b/components/offline_pages/core/prefetch/store/prefetch_store_test_util.cc
@@ -115,7 +115,11 @@
   item.archive_body_length = statement.ColumnInt64(5);
   item.creation_time = store_utils::FromDatabaseTime(statement.ColumnInt64(6));
   item.freshness_time = store_utils::FromDatabaseTime(statement.ColumnInt64(7));
-  item.error_code = static_cast<PrefetchItemErrorCode>(statement.ColumnInt(8));
+  base::Optional<PrefetchItemErrorCode> error_code =
+      ToPrefetchItemErrorCode(statement.ColumnInt(8));
+  if (!error_code)
+    return base::nullopt;
+  item.error_code = error_code.value();
   item.guid = statement.ColumnString(9);
   item.client_id.name_space = statement.ColumnString(10);
   item.client_id.id = statement.ColumnString(11);
diff --git a/components/offline_pages/core/prefetch/tasks/metrics_finalization_task.cc b/components/offline_pages/core/prefetch/tasks/metrics_finalization_task.cc
index 413d099..a93e27b 100644
--- a/components/offline_pages/core/prefetch/tasks/metrics_finalization_task.cc
+++ b/components/offline_pages/core/prefetch/tasks/metrics_finalization_task.cc
@@ -67,6 +67,10 @@
 
   std::vector<PrefetchItemStats> urls;
   while (statement.Step()) {
+    PrefetchItemErrorCode error_code =
+        ToPrefetchItemErrorCode(statement.ColumnInt(6))
+            .value_or(PrefetchItemErrorCode::INVALID_ITEM);
+
     urls.emplace_back(statement.ColumnInt64(0),  // offline_id
                       statement.ColumnInt(1),    // generate_bundle_attempts
                       statement.ColumnInt(2),    // get_operation_attempts
@@ -74,9 +78,8 @@
                       statement.ColumnInt64(4),  // archive_body_length
                       store_utils::FromDatabaseTime(
                           statement.ColumnInt64(5)),  // creation_time
-                      static_cast<PrefetchItemErrorCode>(
-                          statement.ColumnInt(6)),  // error_code
-                      statement.ColumnInt64(7));    // file_size
+                      error_code,                     // error_code
+                      statement.ColumnInt64(7));      // file_size
   }
 
   return urls;
diff --git a/components/offline_pages/core/prefetch/tasks/stale_entry_finalizer_task.cc b/components/offline_pages/core/prefetch/tasks/stale_entry_finalizer_task.cc
index e2078f2..fc31faf 100644
--- a/components/offline_pages/core/prefetch/tasks/stale_entry_finalizer_task.cc
+++ b/components/offline_pages/core/prefetch/tasks/stale_entry_finalizer_task.cc
@@ -177,8 +177,11 @@
     statement.BindInt64(2, static_cast<int>(PrefetchItemState::ZOMBIE));
 
     while (statement.Step()) {
-      base::UmaHistogramSparse("OfflinePages.Prefetching.StuckItemState",
-                               statement.ColumnInt(0));
+      int state_int = statement.ColumnInt(0);
+      if (ToPrefetchItemState(state_int)) {  // Only report valid enum values.
+        base::UmaHistogramSparse("OfflinePages.Prefetching.StuckItemState",
+                                 state_int);
+      }
     }
   }
   // Finalize.
diff --git a/components/password_manager/core/browser/sync/password_syncable_service.cc b/components/password_manager/core/browser/sync/password_syncable_service.cc
index 56f8ac8..915a401 100644
--- a/components/password_manager/core/browser/sync/password_syncable_service.cc
+++ b/components/password_manager/core/browser/sync/password_syncable_service.cc
@@ -340,7 +340,7 @@
     // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
     UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
                               ModelTypeToHistogramInt(syncer::PASSWORDS),
-                              static_cast<int>(syncer::MODEL_TYPE_COUNT));
+                              static_cast<int>(syncer::ModelType::NUM_ENTRIES));
     return false;
   }
   password_entries->resize(autofillable_entries.size() +
diff --git a/components/previews/content/previews_hints.cc b/components/previews/content/previews_hints.cc
index 4b4e021..005ad55 100644
--- a/components/previews/content/previews_hints.cc
+++ b/components/previews/content/previews_hints.cc
@@ -347,10 +347,11 @@
       }
       if (static_cast<int>(bloom_filter_proto.num_bits()) >
           previews::params::
-                  LitePageRedirectPreviewMaxServerBlacklistByteSize() /
+                  LitePageRedirectPreviewMaxServerBlacklistByteSize() *
               8) {
         DLOG(ERROR) << "Bloom filter data exceeds maximum size of "
-                    << previews::params::PreviewServerLoadshedMaxSeconds()
+                    << previews::params::
+                           LitePageRedirectPreviewMaxServerBlacklistByteSize()
                     << " bytes";
         RecordOptimizationFilterStatus(
             previews_type.value(),
diff --git a/components/safe_browsing/browser/mojo_safe_browsing_impl.cc b/components/safe_browsing/browser/mojo_safe_browsing_impl.cc
index 75472f0..0b39040 100644
--- a/components/safe_browsing/browser/mojo_safe_browsing_impl.cc
+++ b/components/safe_browsing/browser/mojo_safe_browsing_impl.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/supports_user_data.h"
 #include "components/safe_browsing/browser/safe_browsing_url_checker_impl.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
@@ -56,6 +57,22 @@
   Callback callback_;
 };
 
+// UserData object that owns MojoSafeBrowsingImpl. This is used rather than
+// having MojoSafeBrowsingImpl directly extend base::SupportsUserData::Data to
+// avoid naming conflicts between Data::Clone() and
+// mojom::SafeBrowsing::Clone().
+class SafeBrowserUserData : public base::SupportsUserData::Data {
+ public:
+  explicit SafeBrowserUserData(std::unique_ptr<MojoSafeBrowsingImpl> impl)
+      : impl_(std::move(impl)) {}
+  ~SafeBrowserUserData() override = default;
+
+ private:
+  std::unique_ptr<MojoSafeBrowsingImpl> impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(SafeBrowserUserData);
+};
+
 }  // namespace
 
 MojoSafeBrowsingImpl::MojoSafeBrowsingImpl(
@@ -95,11 +112,11 @@
       std::move(delegate), render_process_id, resource_context));
   impl->Clone(std::move(request));
 
-  // Need to store the value of |impl.get()| in a temp variable instead of
-  // getting the value on the same line as |std::move(impl)|, because the
-  // evalution order is unspecified.
-  const void* key = impl.get();
-  resource_context->SetUserData(key, std::move(impl));
+  MojoSafeBrowsingImpl* raw_impl = impl.get();
+  std::unique_ptr<SafeBrowserUserData> user_data =
+      std::make_unique<SafeBrowserUserData>(std::move(impl));
+  raw_impl->user_data_key_ = user_data.get();
+  resource_context->SetUserData(raw_impl->user_data_key_, std::move(user_data));
 }
 
 void MojoSafeBrowsingImpl::CreateCheckerAndCheck(
@@ -150,7 +167,7 @@
 
 void MojoSafeBrowsingImpl::OnConnectionError() {
   if (bindings_.empty()) {
-    resource_context_->RemoveUserData(this);
+    resource_context_->RemoveUserData(user_data_key_);
     // This object is destroyed.
   }
 }
diff --git a/components/safe_browsing/browser/mojo_safe_browsing_impl.h b/components/safe_browsing/browser/mojo_safe_browsing_impl.h
index e627f1d..7a670b7 100644
--- a/components/safe_browsing/browser/mojo_safe_browsing_impl.h
+++ b/components/safe_browsing/browser/mojo_safe_browsing_impl.h
@@ -7,7 +7,6 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/supports_user_data.h"
 #include "components/safe_browsing/browser/url_checker_delegate.h"
 #include "components/safe_browsing/common/safe_browsing.mojom.h"
 #include "ipc/ipc_message.h"
@@ -23,8 +22,7 @@
 // SafeBrowsing URL checks.
 // A MojoSafeBrowsingImpl instance is destructed when the Mojo message pipe is
 // disconnected or |resource_context_| is destructed.
-class MojoSafeBrowsingImpl : public mojom::SafeBrowsing,
-                             public base::SupportsUserData::Data {
+class MojoSafeBrowsingImpl : public mojom::SafeBrowsing {
  public:
   ~MojoSafeBrowsingImpl() override;
 
@@ -54,6 +52,10 @@
 
   void OnConnectionError();
 
+  // This is an instance of SafeBrowserUserData that is set as user-data on
+  // |resource_context_|. SafeBrowserUserData owns |this|.
+  const void* user_data_key_ = nullptr;
+
   mojo::BindingSet<mojom::SafeBrowsing> bindings_;
   scoped_refptr<UrlCheckerDelegate> delegate_;
   int render_process_id_ = MSG_ROUTING_NONE;
diff --git a/components/safe_browsing/db/BUILD.gn b/components/safe_browsing/db/BUILD.gn
index acda1f22..7cf0548 100644
--- a/components/safe_browsing/db/BUILD.gn
+++ b/components/safe_browsing/db/BUILD.gn
@@ -31,7 +31,6 @@
     ":hit_report",
     ":safebrowsing_proto",
     ":util",
-    ":v4_feature_list",  # Used by SafeBrowsingService
     "//components/safe_browsing/common:safe_browsing_prefs",
   ]
 }
@@ -135,17 +134,6 @@
   ]
 }
 
-static_library("v4_feature_list") {
-  sources = [
-    "v4_feature_list.cc",
-    "v4_feature_list.h",
-  ]
-  deps = [
-    "//base",
-    "//components/safe_browsing:features",
-  ]
-}
-
 static_library("v4_get_hash_protocol_manager") {
   sources = [
     "v4_get_hash_protocol_manager.cc",
@@ -176,7 +164,6 @@
     ":hit_report",
     ":safebrowsing_proto",
     ":v4_database",
-    ":v4_feature_list",
     ":v4_get_hash_protocol_manager",
     ":v4_protocol_manager_util",
     ":v4_update_protocol_manager",
diff --git a/components/safe_browsing/db/v4_feature_list.cc b/components/safe_browsing/db/v4_feature_list.cc
deleted file mode 100644
index 2213692..0000000
--- a/components/safe_browsing/db/v4_feature_list.cc
+++ /dev/null
@@ -1,24 +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/safe_browsing/db/v4_feature_list.h"
-
-#include "base/feature_list.h"
-#include "components/safe_browsing/features.h"
-
-namespace safe_browsing {
-
-namespace V4FeatureList {
-
-V4UsageStatus GetV4UsageStatus() {
-#if defined(SAFE_BROWSING_DB_LOCAL)
-  return V4UsageStatus::V4_ONLY;
-#else
-  return V4UsageStatus::V4_DISABLED;
-#endif
-}
-
-}  // namespace V4FeatureList
-
-}  // namespace safe_browsing
diff --git a/components/safe_browsing/db/v4_feature_list.h b/components/safe_browsing/db/v4_feature_list.h
deleted file mode 100644
index 5d7079a..0000000
--- a/components/safe_browsing/db/v4_feature_list.h
+++ /dev/null
@@ -1,34 +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_SAFE_BROWSING_DB_V4_FEATURE_LIST_H_
-#define COMPONENTS_SAFE_BROWSING_DB_V4_FEATURE_LIST_H_
-
-namespace safe_browsing {
-
-// Exposes methods to check whether a particular feature has been enabled
-// through Finch.
-namespace V4FeatureList {
-
-enum class V4UsageStatus {
-  // The V4 database manager is not even instantiated i.e. is diabled. All
-  // SafeBrowsing operations use PVer3 code.
-  V4_DISABLED,
-
-  // The V4 database manager is instantiated, and performs background updates,
-  // but all SafeBrowsing verdicts are returned using the PVer3 database.
-  V4_INSTANTIATED,
-
-  // Only the V4 database manager is instantiated, PVer3 database manager is
-  // not. All SafeBrowsing verdicts are returned using PVer4 database.
-  V4_ONLY
-};
-
-V4UsageStatus GetV4UsageStatus();
-
-}  // namespace V4FeatureList
-
-}  // namespace safe_browsing
-
-#endif  // COMPONENTS_SAFE_BROWSING_DB_V4_FEATURE_LIST_H_
diff --git a/components/safe_browsing/db/v4_local_database_manager.cc b/components/safe_browsing/db/v4_local_database_manager.cc
index 936689b..d40788f 100644
--- a/components/safe_browsing/db/v4_local_database_manager.cc
+++ b/components/safe_browsing/db/v4_local_database_manager.cc
@@ -18,7 +18,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
-#include "components/safe_browsing/db/v4_feature_list.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/components/safe_browsing/web_ui/safe_browsing_ui.cc b/components/safe_browsing/web_ui/safe_browsing_ui.cc
index 340eca0..60f878fd 100644
--- a/components/safe_browsing/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/web_ui/safe_browsing_ui.cc
@@ -59,13 +59,12 @@
 
 // static
 bool WebUIInfoSingleton::HasListener() {
-  return GetInstance()->has_test_listener_ ||
-         !GetInstance()->webui_instances_.empty();
+  return !GetInstance()->webui_instances_.empty();
 }
 
 void WebUIInfoSingleton::AddToClientDownloadRequestsSent(
     std::unique_ptr<ClientDownloadRequest> client_download_request) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -81,7 +80,7 @@
 
 void WebUIInfoSingleton::AddToClientDownloadResponsesReceived(
     std::unique_ptr<ClientDownloadResponse> client_download_response) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -98,7 +97,7 @@
 
 void WebUIInfoSingleton::AddToCSBRRsSent(
     std::unique_ptr<ClientSafeBrowsingReportRequest> csbrr) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -113,7 +112,7 @@
 
 void WebUIInfoSingleton::AddToPGEvents(
     const sync_pb::UserEventSpecifics& event) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -128,7 +127,7 @@
 
 int WebUIInfoSingleton::AddToPGPings(
     const LoginReputationClientRequest& request) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return -1;
 
   for (auto* webui_listener : webui_instances_)
@@ -142,7 +141,7 @@
 void WebUIInfoSingleton::AddToPGResponses(
     int token,
     const LoginReputationClientResponse& response) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   for (auto* webui_listener : webui_instances_)
@@ -157,7 +156,7 @@
 }
 
 void WebUIInfoSingleton::LogMessage(const std::string& message) {
-  if (!HasListener())
+  if (webui_instances_.empty())
     return;
 
   base::Time timestamp = base::Time::Now();
@@ -177,7 +176,7 @@
 
 void WebUIInfoSingleton::UnregisterWebUIInstance(SafeBrowsingUIHandler* webui) {
   base::Erase(webui_instances_, webui);
-  if (!HasListener()) {
+  if (webui_instances_.empty()) {
     ClearCSBRRsSent();
     ClearClientDownloadRequestsSent();
     ClearClientDownloadResponsesReceived();
diff --git a/components/safe_browsing/web_ui/safe_browsing_ui.h b/components/safe_browsing/web_ui/safe_browsing_ui.h
index 23d4515..dcd3e82 100644
--- a/components/safe_browsing/web_ui/safe_browsing_ui.h
+++ b/components/safe_browsing/web_ui/safe_browsing_ui.h
@@ -251,8 +251,6 @@
     return log_messages_;
   }
 
-  void AddListenerForTesting() { has_test_listener_ = true; }
-
  private:
   WebUIInfoSingleton();
   ~WebUIInfoSingleton();
@@ -303,9 +301,6 @@
   // The current referrer chain provider, if any. Can be nullptr.
   ReferrerChainProvider* referrer_chain_provider_ = nullptr;
 
-  // Whether there is a test listener.
-  bool has_test_listener_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(WebUIInfoSingleton);
 };
 
diff --git a/components/sessions/BUILD.gn b/components/sessions/BUILD.gn
index 78c52453..84d9062 100644
--- a/components/sessions/BUILD.gn
+++ b/components/sessions/BUILD.gn
@@ -59,6 +59,7 @@
 
     deps = [
       "//base",
+      "//ios/web/common",
     ]
   }
 }
diff --git a/components/sessions/content/content_record_password_state.cc b/components/sessions/content/content_record_password_state.cc
index 538450a..c9c9a92d 100644
--- a/components/sessions/content/content_record_password_state.cc
+++ b/components/sessions/content/content_record_password_state.cc
@@ -13,30 +13,44 @@
 // We stash an enum value in the first character of the string16 that is
 // associated with this key.
 const char kPasswordStateKey[] = "sessions_password_state";
-}
+
+class PasswordStateData : public base::SupportsUserData::Data {
+ public:
+  explicit PasswordStateData(
+      SerializedNavigationEntry::PasswordState password_state)
+      : password_state_(password_state) {}
+  ~PasswordStateData() override = default;
+
+  SerializedNavigationEntry::PasswordState password_state() const {
+    return password_state_;
+  }
+
+  // base::SupportsUserData::Data:
+  std::unique_ptr<Data> Clone() override {
+    return std::make_unique<PasswordStateData>(password_state_);
+  }
+
+ private:
+  const SerializedNavigationEntry::PasswordState password_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(PasswordStateData);
+};
+
+}  // namespace
 
 SerializedNavigationEntry::PasswordState GetPasswordStateFromNavigation(
     content::NavigationEntry* entry) {
-  base::string16 password_state_str;
-  if (!entry->GetExtraData(kPasswordStateKey, &password_state_str) ||
-      password_state_str.size() != 1) {
-    return SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN;
-  }
-
-  SerializedNavigationEntry::PasswordState state =
-      static_cast<SerializedNavigationEntry::PasswordState>(
-          password_state_str[0]);
-
-  DCHECK_GE(state, SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN);
-  DCHECK_LE(state, SerializedNavigationEntry::HAS_PASSWORD_FIELD);
-  return state;
+  PasswordStateData* data =
+      static_cast<PasswordStateData*>(entry->GetUserData(kPasswordStateKey));
+  return data ? data->password_state()
+              : SerializedNavigationEntry::PASSWORD_STATE_UNKNOWN;
 }
 
 void SetPasswordStateInNavigation(
     SerializedNavigationEntry::PasswordState state,
     content::NavigationEntry* entry) {
-  base::string16 password_state_str(1, static_cast<uint16_t>(state));
-  entry->SetExtraData(kPasswordStateKey, password_state_str);
+  entry->SetUserData(kPasswordStateKey,
+                     std::make_unique<PasswordStateData>(state));
 }
 
 }  // namespace sessions
diff --git a/components/sessions/content/content_serialized_navigation_builder_unittest.cc b/components/sessions/content/content_serialized_navigation_builder_unittest.cc
index 5fba63f5..363de14 100644
--- a/components/sessions/content/content_serialized_navigation_builder_unittest.cc
+++ b/components/sessions/content/content_serialized_navigation_builder_unittest.cc
@@ -26,24 +26,31 @@
 const char kExtendedInfoKey2[] = "Key 2";
 const char kExtendedInfoValue2[] = "Value 2";
 
+struct TestData : public base::SupportsUserData::Data {
+  explicit TestData(const std::string& string) : string(string) {}
+  ~TestData() override = default;
+
+  std::string string;
+};
+
 class TestExtendedInfoHandler : public ExtendedInfoHandler {
  public:
-  explicit TestExtendedInfoHandler(const std::string& key) : key_(key) {}
+  explicit TestExtendedInfoHandler(const char* key) : key_(key) {}
   ~TestExtendedInfoHandler() override {}
 
+  // ExtendedInfoHandler:
   std::string GetExtendedInfo(content::NavigationEntry* entry) const override {
-    base::string16 data;
-    entry->GetExtraData(key_, &data);
-    return base::UTF16ToASCII(data);
+    TestData* test_data = static_cast<TestData*>(entry->GetUserData(key_));
+    return test_data ? test_data->string : std::string();
   }
 
   void RestoreExtendedInfo(const std::string& info,
                            content::NavigationEntry* entry) override {
-    entry->SetExtraData(key_, base::ASCIIToUTF16(info));
+    entry->SetUserData(key_, std::make_unique<TestData>(info));
   }
 
  private:
-  std::string key_;
+  const char* key_;
 
   DISALLOW_COPY_AND_ASSIGN(TestExtendedInfoHandler);
 };
@@ -80,10 +87,10 @@
 }
 
 void SetExtendedInfoForTest(content::NavigationEntry* entry) {
-  entry->SetExtraData(kExtendedInfoKey1,
-                      base::ASCIIToUTF16(kExtendedInfoValue1));
-  entry->SetExtraData(kExtendedInfoKey2,
-                      base::ASCIIToUTF16(kExtendedInfoValue2));
+  entry->SetUserData(kExtendedInfoKey1,
+                     std::make_unique<TestData>(kExtendedInfoValue1));
+  entry->SetUserData(kExtendedInfoKey2,
+                     std::make_unique<TestData>(kExtendedInfoValue2));
 }
 
 }  // namespace
@@ -220,13 +227,14 @@
   EXPECT_EQ(test_data::kVirtualURL,
             new_navigation_entry->GetRedirectChain()[2]);
 
-  base::string16 extra_data;
-  EXPECT_TRUE(
-      new_navigation_entry->GetExtraData(kExtendedInfoKey1, &extra_data));
-  EXPECT_EQ(kExtendedInfoValue1, base::UTF16ToASCII(extra_data));
-  EXPECT_TRUE(
-      new_navigation_entry->GetExtraData(kExtendedInfoKey2, &extra_data));
-  EXPECT_EQ(kExtendedInfoValue2, base::UTF16ToASCII(extra_data));
+  TestData* test_data = static_cast<TestData*>(
+      new_navigation_entry->GetUserData(kExtendedInfoKey1));
+  ASSERT_TRUE(test_data);
+  EXPECT_EQ(kExtendedInfoValue1, test_data->string);
+  test_data = static_cast<TestData*>(
+      new_navigation_entry->GetUserData(kExtendedInfoKey2));
+  ASSERT_TRUE(test_data);
+  EXPECT_EQ(kExtendedInfoValue2, test_data->string);
 }
 
 TEST_F(ContentSerializedNavigationBuilderTest, SetPasswordState) {
diff --git a/components/sessions/ios/DEPS b/components/sessions/ios/DEPS
index 0fc0ddd..a5618a0 100644
--- a/components/sessions/ios/DEPS
+++ b/components/sessions/ios/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
+  "+ios/web/common",
   "+ios/web/public",
 ]
diff --git a/components/sessions/ios/ios_serialized_navigation_driver.cc b/components/sessions/ios/ios_serialized_navigation_driver.cc
index 5c53c21a..e9740dcd6 100644
--- a/components/sessions/ios/ios_serialized_navigation_driver.cc
+++ b/components/sessions/ios/ios_serialized_navigation_driver.cc
@@ -6,8 +6,8 @@
 
 #include "base/memory/singleton.h"
 #include "components/sessions/core/serialized_navigation_entry.h"
+#include "ios/web/common/referrer_util.h"
 #include "ios/web/public/referrer.h"
-#include "ios/web/public/referrer_util.h"
 
 namespace sessions {
 
diff --git a/components/sync/base/model_type.cc b/components/sync/base/model_type.cc
index e5f15af..4ea35bed 100644
--- a/components/sync/base/model_type.cc
+++ b/components/sync/base/model_type.cc
@@ -161,14 +161,14 @@
      sync_pb::EntitySpecifics::kExperimentsFieldNumber, 19},
 };
 
-static_assert(base::size(kModelTypeInfoMap) == MODEL_TYPE_COUNT,
-              "kModelTypeInfoMap should have MODEL_TYPE_COUNT elements");
+static_assert(base::size(kModelTypeInfoMap) == ModelType::NUM_ENTRIES,
+              "kModelTypeInfoMap should have ModelType::NUM_ENTRIES elements");
 
-static_assert(44 == syncer::MODEL_TYPE_COUNT,
+static_assert(44 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new type, update enum SyncModelTypes in enums.xml "
               "and suffix SyncModelType in histograms.xml.");
 
-static_assert(44 == syncer::MODEL_TYPE_COUNT,
+static_assert(44 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new type, update kAllocatorDumpNameWhitelist in "
               "base/trace_event/memory_infra_background_whitelist.cc.");
 
@@ -304,7 +304,7 @@
     case DEPRECATED_EXPERIMENTS:
       specifics->mutable_experiments();
       break;
-    case MODEL_TYPE_COUNT:
+    case ModelType::NUM_ENTRIES:
       NOTREACHED() << "No default field value for " << ModelTypeToString(type);
       break;
   }
@@ -354,7 +354,7 @@
 }
 
 ModelType GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
-  static_assert(44 == MODEL_TYPE_COUNT,
+  static_assert(44 == ModelType::NUM_ENTRIES,
                 "When adding new protocol types, the following type lookup "
                 "logic must be updated.");
   if (specifics.has_bookmark())
@@ -457,7 +457,7 @@
 }
 
 ModelTypeSet EncryptableUserTypes() {
-  static_assert(44 == MODEL_TYPE_COUNT,
+  static_assert(44 == ModelType::NUM_ENTRIES,
                 "If adding an unencryptable type, remove from "
                 "encryptable_user_types below.");
   ModelTypeSet encryptable_user_types = UserTypes();
@@ -502,7 +502,7 @@
   // This is used in serialization routines as well as for displaying debug
   // information.  Do not attempt to change these string values unless you know
   // what you're doing.
-  if (model_type >= UNSPECIFIED && model_type < MODEL_TYPE_COUNT)
+  if (model_type >= UNSPECIFIED && model_type < ModelType::NUM_ENTRIES)
     return kModelTypeInfoMap[model_type].model_type_string;
   NOTREACHED() << "No known extension for model type.";
   return "Invalid";
@@ -510,7 +510,7 @@
 
 const char* ModelTypeToHistogramSuffix(ModelType model_type) {
   DCHECK_GE(model_type, UNSPECIFIED);
-  DCHECK_LT(model_type, MODEL_TYPE_COUNT);
+  DCHECK_LT(model_type, ModelType::NUM_ENTRIES);
 
   // We use the same string that is used for notification types because they
   // satisfy all we need (being stable and explanatory).
@@ -525,13 +525,13 @@
 // changes to this list.
 int ModelTypeToHistogramInt(ModelType model_type) {
   DCHECK_GE(model_type, UNSPECIFIED);
-  DCHECK_LT(model_type, MODEL_TYPE_COUNT);
+  DCHECK_LT(model_type, ModelType::NUM_ENTRIES);
   return kModelTypeInfoMap[model_type].model_type_histogram_val;
 }
 
 int ModelTypeToStableIdentifier(ModelType model_type) {
   DCHECK_GE(model_type, UNSPECIFIED);
-  DCHECK_LT(model_type, MODEL_TYPE_COUNT);
+  DCHECK_LT(model_type, ModelType::NUM_ENTRIES);
   // Make sure the value is stable and positive.
   return ModelTypeToHistogramInt(model_type) + 1;
 }
@@ -645,7 +645,8 @@
 }
 
 bool IsRealDataType(ModelType model_type) {
-  return model_type >= FIRST_REAL_MODEL_TYPE && model_type < MODEL_TYPE_COUNT;
+  return model_type >= FIRST_REAL_MODEL_TYPE &&
+         model_type < ModelType::NUM_ENTRIES;
 }
 
 bool IsProxyType(ModelType model_type) {
diff --git a/components/sync/base/model_type.h b/components/sync/base/model_type.h
index 3253714..3bf14c1 100644
--- a/components/sync/base/model_type.h
+++ b/components/sync/base/model_type.h
@@ -156,7 +156,7 @@
   DEPRECATED_EXPERIMENTS,
   LAST_REAL_MODEL_TYPE = DEPRECATED_EXPERIMENTS,
 
-  MODEL_TYPE_COUNT,
+  NUM_ENTRIES,
 };
 
 using ModelTypeSet =
@@ -166,7 +166,7 @@
 
 inline ModelType ModelTypeFromInt(int i) {
   DCHECK_GE(i, 0);
-  DCHECK_LT(i, MODEL_TYPE_COUNT);
+  DCHECK_LT(i, ModelType::NUM_ENTRIES);
   return static_cast<ModelType>(i);
 }
 
diff --git a/components/sync/base/model_type_unittest.cc b/components/sync/base/model_type_unittest.cc
index 32acbe1..c318006 100644
--- a/components/sync/base/model_type_unittest.cc
+++ b/components/sync/base/model_type_unittest.cc
@@ -18,7 +18,7 @@
 class ModelTypeTest : public testing::Test {};
 
 TEST_F(ModelTypeTest, ModelTypeToValue) {
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     ModelType model_type = ModelTypeFromInt(i);
     base::ExpectStringValue(ModelTypeToString(model_type),
                             *ModelTypeToValue(model_type));
@@ -42,7 +42,7 @@
 
 TEST_F(ModelTypeTest, IsRealDataType) {
   EXPECT_FALSE(IsRealDataType(UNSPECIFIED));
-  EXPECT_FALSE(IsRealDataType(MODEL_TYPE_COUNT));
+  EXPECT_FALSE(IsRealDataType(ModelType::NUM_ENTRIES));
   EXPECT_FALSE(IsRealDataType(TOP_LEVEL_FOLDER));
   EXPECT_TRUE(IsRealDataType(FIRST_REAL_MODEL_TYPE));
   EXPECT_TRUE(IsRealDataType(BOOKMARKS));
@@ -54,7 +54,7 @@
 
 TEST_F(ModelTypeTest, IsProxyType) {
   EXPECT_FALSE(IsProxyType(BOOKMARKS));
-  EXPECT_FALSE(IsProxyType(MODEL_TYPE_COUNT));
+  EXPECT_FALSE(IsProxyType(ModelType::NUM_ENTRIES));
   EXPECT_TRUE(IsProxyType(PROXY_TABS));
 }
 
@@ -83,10 +83,10 @@
         << "Expected histogram values to be unique";
 
     // This is not necessary for the mapping to be valid, but most instances of
-    // UMA_HISTOGRAM that use this mapping specify MODEL_TYPE_COUNT as the
+    // UMA_HISTOGRAM that use this mapping specify ModelType::NUM_ENTRIES as the
     // maximum possible value.  If you break this assumption, you should update
     // those histograms.
-    EXPECT_LT(histogram_value, MODEL_TYPE_COUNT);
+    EXPECT_LT(histogram_value, ModelType::NUM_ENTRIES);
   }
 }
 
diff --git a/components/sync/base/sync_prefs.cc b/components/sync/base/sync_prefs.cc
index 3d412e5..3d1d6df1 100644
--- a/components/sync/base/sync_prefs.cc
+++ b/components/sync/base/sync_prefs.cc
@@ -408,7 +408,7 @@
     case SEND_TAB_TO_SELF:
     case NIGORI:
     case DEPRECATED_EXPERIMENTS:
-    case MODEL_TYPE_COUNT:
+    case ModelType::NUM_ENTRIES:
     case SESSIONS:
       break;
     case BOOKMARKS:
diff --git a/components/sync/driver/async_directory_type_controller.cc b/components/sync/driver/async_directory_type_controller.cc
index 8b07886..b9fe01617 100644
--- a/components/sync/driver/async_directory_type_controller.cc
+++ b/components/sync/driver/async_directory_type_controller.cc
@@ -214,7 +214,7 @@
   // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
   UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures2",
                             ModelTypeToHistogramInt(type()),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
 #define PER_DATA_TYPE_MACRO(type_str)                                    \
   UMA_HISTOGRAM_ENUMERATION("Sync." type_str "ConfigureFailure", result, \
                             MAX_CONFIGURE_RESULT);
diff --git a/components/sync/driver/backend_migrator.cc b/components/sync/driver/backend_migrator.cc
index 696eda69..648078e 100644
--- a/components/sync/driver/backend_migrator.cc
+++ b/components/sync/driver/backend_migrator.cc
@@ -118,7 +118,7 @@
 ModelTypeSet GetUnsyncedDataTypes(UserShare* user_share) {
   ReadTransaction trans(FROM_HERE, user_share);
   ModelTypeSet unsynced_data_types;
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     ModelType type = ModelTypeFromInt(i);
     sync_pb::DataTypeProgressMarker progress_marker;
     trans.GetDirectory()->GetDownloadProgress(type, &progress_marker);
diff --git a/components/sync/driver/backend_migrator_unittest.cc b/components/sync/driver/backend_migrator_unittest.cc
index 21f059f..53ba320 100644
--- a/components/sync/driver/backend_migrator_unittest.cc
+++ b/components/sync/driver/backend_migrator_unittest.cc
@@ -53,7 +53,7 @@
   // types as synced.
   void SetUnsyncedTypes(ModelTypeSet unsynced_types) {
     WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
-    for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+    for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
       ModelType type = ModelTypeFromInt(i);
       sync_pb::DataTypeProgressMarker progress_marker;
       if (!unsynced_types.Has(type)) {
diff --git a/components/sync/driver/data_type_manager_impl.cc b/components/sync/driver/data_type_manager_impl.cc
index 9dc92262..7416dee 100644
--- a/components/sync/driver/data_type_manager_impl.cc
+++ b/components/sync/driver/data_type_manager_impl.cc
@@ -262,7 +262,7 @@
       // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
       UMA_HISTOGRAM_ENUMERATION("Sync.ConfigureDataTypes",
                                 ModelTypeToHistogramInt(type),
-                                static_cast<int>(MODEL_TYPE_COUNT));
+                                static_cast<int>(ModelType::NUM_ENTRIES));
     }
   }
 
diff --git a/components/sync/driver/directory_data_type_controller.cc b/components/sync/driver/directory_data_type_controller.cc
index 85926dc..ae12bf1 100644
--- a/components/sync/driver/directory_data_type_controller.cc
+++ b/components/sync/driver/directory_data_type_controller.cc
@@ -75,8 +75,9 @@
 
 void DirectoryDataTypeController::GetStatusCounters(
     StatusCountersCallback callback) {
-  std::vector<int> num_entries_by_type(syncer::MODEL_TYPE_COUNT, 0);
-  std::vector<int> num_to_delete_entries_by_type(syncer::MODEL_TYPE_COUNT, 0);
+  std::vector<int> num_entries_by_type(syncer::ModelType::NUM_ENTRIES, 0);
+  std::vector<int> num_to_delete_entries_by_type(syncer::ModelType::NUM_ENTRIES,
+                                                 0);
   sync_service_->GetUserShare()->directory->CollectMetaHandleCounts(
       &num_entries_by_type, &num_to_delete_entries_by_type);
   syncer::StatusCounters counters;
diff --git a/components/sync/driver/frontend_data_type_controller.cc b/components/sync/driver/frontend_data_type_controller.cc
index 9d3be020..55a5c9b5 100644
--- a/components/sync/driver/frontend_data_type_controller.cc
+++ b/components/sync/driver/frontend_data_type_controller.cc
@@ -238,7 +238,7 @@
   // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
   UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures2",
                             ModelTypeToHistogramInt(type()),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
 #define PER_DATA_TYPE_MACRO(type_str)                                    \
   UMA_HISTOGRAM_ENUMERATION("Sync." type_str "ConfigureFailure", result, \
                             MAX_CONFIGURE_RESULT);
diff --git a/components/sync/driver/glue/sync_backend_host_core.cc b/components/sync/driver/glue/sync_backend_host_core.cc
index 59942dd..41ede0406 100644
--- a/components/sync/driver/glue/sync_backend_host_core.cc
+++ b/components/sync/driver/glue/sync_backend_host_core.cc
@@ -59,13 +59,13 @@
 
 void RecordPerModelTypeInvalidation(int model_type, bool is_grouped) {
   UMA_HISTOGRAM_ENUMERATION("Sync.InvalidationPerModelType", model_type,
-                            static_cast<int>(syncer::MODEL_TYPE_COUNT));
+                            static_cast<int>(syncer::ModelType::NUM_ENTRIES));
   if (!is_grouped) {
     // When recording metrics it's important to distinguish between
     // many/one case, since "many" aka grouped case is only common in
     // the deprecated implementation.
     UMA_HISTOGRAM_ENUMERATION("Sync.NonGroupedInvalidation", model_type,
-                              static_cast<int>(syncer::MODEL_TYPE_COUNT));
+                              static_cast<int>(syncer::ModelType::NUM_ENTRIES));
   }
 }
 
@@ -267,10 +267,10 @@
              << last_invalidation->second;
     redundant_invalidation = true;
     UMA_HISTOGRAM_ENUMERATION("Sync.RedundantInvalidationPerModelType", type,
-                              static_cast<int>(syncer::MODEL_TYPE_COUNT));
+                              static_cast<int>(syncer::ModelType::NUM_ENTRIES));
   } else {
     UMA_HISTOGRAM_ENUMERATION("Sync.NonRedundantInvalidationPerModelType", type,
-                              static_cast<int>(syncer::MODEL_TYPE_COUNT));
+                              static_cast<int>(syncer::ModelType::NUM_ENTRIES));
   }
 
   return !fcm_invalidation && redundant_invalidation;
@@ -299,7 +299,7 @@
         if (!is_grouped && !invalidation.is_unknown_version()) {
           UMA_HISTOGRAM_ENUMERATION("Sync.NonGroupedInvalidationKnownVersion",
                                     ModelTypeToHistogramInt(type),
-                                    static_cast<int>(MODEL_TYPE_COUNT));
+                                    static_cast<int>(ModelType::NUM_ENTRIES));
         }
         std::unique_ptr<InvalidationInterface> inv_adapter(
             new InvalidationAdapter(invalidation));
diff --git a/components/sync/driver/model_association_manager.cc b/components/sync/driver/model_association_manager.cc
index 18b3acb..8249e12 100644
--- a/components/sync/driver/model_association_manager.cc
+++ b/components/sync/driver/model_association_manager.cc
@@ -50,7 +50,7 @@
     SEND_TAB_TO_SELF, SECURITY_EVENTS};
 
 static_assert(base::size(kStartOrder) ==
-                  MODEL_TYPE_COUNT - FIRST_REAL_MODEL_TYPE,
+                  ModelType::NUM_ENTRIES - FIRST_REAL_MODEL_TYPE,
               "When adding a new type, update kStartOrder.");
 
 // The amount of time we wait for association to finish. If some types haven't
@@ -449,7 +449,7 @@
       // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
       UMA_HISTOGRAM_ENUMERATION("Sync.ConfigureFailed",
                                 ModelTypeToHistogramInt(dtc->type()),
-                                static_cast<int>(MODEL_TYPE_COUNT));
+                                static_cast<int>(ModelType::NUM_ENTRIES));
       StopDatatypeImpl(SyncError(FROM_HERE, SyncError::DATATYPE_ERROR,
                                  "Association timed out.", dtc->type()),
                        STOP_SYNC, dtc, base::DoNothing());
diff --git a/components/sync/driver/model_type_controller.cc b/components/sync/driver/model_type_controller.cc
index 7398c6ca..4dab5b0 100644
--- a/components/sync/driver/model_type_controller.cc
+++ b/components/sync/driver/model_type_controller.cc
@@ -292,7 +292,7 @@
   // defines quite a different order from the type() enum.
   UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures2",
                             ModelTypeToHistogramInt(type()),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
 }
 
 void ModelTypeController::RecordRunFailure() const {
@@ -302,7 +302,7 @@
   // defines quite a different order from the type() enum.
   UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures2",
                             ModelTypeToHistogramInt(type()),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
 }
 
 void ModelTypeController::OnDelegateStarted(
diff --git a/components/sync/driver/profile_sync_service.cc b/components/sync/driver/profile_sync_service.cc
index 9a4949b..12ec1095 100644
--- a/components/sync/driver/profile_sync_service.cc
+++ b/components/sync/driver/profile_sync_service.cc
@@ -1397,7 +1397,7 @@
       for (ModelType type : chosen_types) {
         UMA_HISTOGRAM_ENUMERATION("Sync.CustomSync2",
                                   ModelTypeToHistogramInt(type),
-                                  static_cast<int>(MODEL_TYPE_COUNT));
+                                  static_cast<int>(ModelType::NUM_ENTRIES));
       }
     }
   }
diff --git a/components/sync/driver/startup_controller.cc b/components/sync/driver/startup_controller.cc
index 0b7da17..8f6520543 100644
--- a/components/sync/driver/startup_controller.cc
+++ b/components/sync/driver/startup_controller.cc
@@ -166,7 +166,7 @@
   // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
   UMA_HISTOGRAM_ENUMERATION("Sync.Startup.TypeTriggeringInit",
                             ModelTypeToHistogramInt(type),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
   if (!start_up_time_.is_null()) {
     RecordTimeDeferred();
     UMA_HISTOGRAM_ENUMERATION("Sync.Startup.DeferredInitTrigger",
diff --git a/components/sync/driver/test_sync_service.cc b/components/sync/driver/test_sync_service.cc
index 05c1dad..44d1357 100644
--- a/components/sync/driver/test_sync_service.cc
+++ b/components/sync/driver/test_sync_service.cc
@@ -23,9 +23,9 @@
       /*num_server_conflicts=*/7, /*notifications_enabled=*/false,
       /*num_entries=*/0, /*sync_start_time=*/base::Time::Now(),
       /*poll_finish_time=*/base::Time::Now(),
-      /*num_entries_by_type=*/std::vector<int>(MODEL_TYPE_COUNT, 0),
+      /*num_entries_by_type=*/std::vector<int>(ModelType::NUM_ENTRIES, 0),
       /*num_to_delete_entries_by_type=*/
-      std::vector<int>(MODEL_TYPE_COUNT, 0),
+      std::vector<int>(ModelType::NUM_ENTRIES, 0),
       /*get_updates_origin=*/sync_pb::SyncEnums::UNKNOWN_ORIGIN,
       /*poll_interval=*/base::TimeDelta::FromMinutes(30),
       /*has_remaining_local_changes=*/false);
diff --git a/components/sync/engine/cycle/sync_cycle_snapshot.cc b/components/sync/engine/cycle/sync_cycle_snapshot.cc
index e307e2f..f3a4e73 100644
--- a/components/sync/engine/cycle/sync_cycle_snapshot.cc
+++ b/components/sync/engine/cycle/sync_cycle_snapshot.cc
@@ -33,8 +33,8 @@
       num_server_conflicts_(0),
       notifications_enabled_(false),
       num_entries_(0),
-      num_entries_by_type_(MODEL_TYPE_COUNT, 0),
-      num_to_delete_entries_by_type_(MODEL_TYPE_COUNT, 0),
+      num_entries_by_type_(ModelType::NUM_ENTRIES, 0),
+      num_to_delete_entries_by_type_(ModelType::NUM_ENTRIES, 0),
       has_remaining_local_changes_(false),
       is_initialized_(false) {}
 
@@ -106,7 +106,7 @@
 
   std::unique_ptr<base::DictionaryValue> counter_entries(
       new base::DictionaryValue());
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; i++) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; i++) {
     std::unique_ptr<base::DictionaryValue> type_entries(
         new base::DictionaryValue());
     type_entries->SetInteger("numEntries", num_entries_by_type_[i]);
diff --git a/components/sync/engine/cycle/sync_cycle_snapshot_unittest.cc b/components/sync/engine/cycle/sync_cycle_snapshot_unittest.cc
index 1fbe7d5..e312ba0 100644
--- a/components/sync/engine/cycle/sync_cycle_snapshot_unittest.cc
+++ b/components/sync/engine/cycle/sync_cycle_snapshot_unittest.cc
@@ -47,14 +47,15 @@
   const int kNumEncryptionConflicts = 1054;
   const int kNumHierarchyConflicts = 1055;
   const int kNumServerConflicts = 1057;
-  SyncCycleSnapshot snapshot(
-      model_neutral, download_progress_markers, kIsSilenced,
-      kNumEncryptionConflicts, kNumHierarchyConflicts, kNumServerConflicts,
-      false, 0, base::Time::Now(), base::Time::Now(),
-      std::vector<int>(MODEL_TYPE_COUNT, 0),
-      std::vector<int>(MODEL_TYPE_COUNT, 0), sync_pb::SyncEnums::UNKNOWN_ORIGIN,
-      /*poll_interval=*/base::TimeDelta::FromMinutes(30),
-      /*has_remaining_local_changes=*/false);
+  SyncCycleSnapshot snapshot(model_neutral, download_progress_markers,
+                             kIsSilenced, kNumEncryptionConflicts,
+                             kNumHierarchyConflicts, kNumServerConflicts, false,
+                             0, base::Time::Now(), base::Time::Now(),
+                             std::vector<int>(ModelType::NUM_ENTRIES, 0),
+                             std::vector<int>(ModelType::NUM_ENTRIES, 0),
+                             sync_pb::SyncEnums::UNKNOWN_ORIGIN,
+                             /*poll_interval=*/base::TimeDelta::FromMinutes(30),
+                             /*has_remaining_local_changes=*/false);
   std::unique_ptr<base::DictionaryValue> value(snapshot.ToValue());
   EXPECT_EQ(19u, value->size());
   ExpectDictIntegerValue(model_neutral.num_successful_commits, *value,
diff --git a/components/sync/engine/sync_backend_registrar_unittest.cc b/components/sync/engine/sync_backend_registrar_unittest.cc
index d5e2621b..d595f87 100644
--- a/components/sync/engine/sync_backend_registrar_unittest.cc
+++ b/components/sync/engine/sync_backend_registrar_unittest.cc
@@ -62,7 +62,7 @@
   }
 
   void ExpectHasProcessorsForTypes(ModelTypeSet types) {
-    for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+    for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
       ModelType model_type = ModelTypeFromInt(i);
       EXPECT_EQ(types.Has(model_type),
                 registrar_->IsTypeActivatedForTest(model_type));
diff --git a/components/sync/engine/sync_status.cc b/components/sync/engine/sync_status.cc
index e6b0c08..d1d55e2f 100644
--- a/components/sync/engine/sync_status.cc
+++ b/components/sync/engine/sync_status.cc
@@ -27,8 +27,8 @@
       crypto_has_pending_keys(false),
       has_keystore_key(false),
       passphrase_type(PassphraseType::IMPLICIT_PASSPHRASE),
-      num_entries_by_type(MODEL_TYPE_COUNT, 0),
-      num_to_delete_entries_by_type(MODEL_TYPE_COUNT, 0) {}
+      num_entries_by_type(ModelType::NUM_ENTRIES, 0),
+      num_to_delete_entries_by_type(ModelType::NUM_ENTRIES, 0) {}
 
 SyncStatus::SyncStatus(const SyncStatus& other) = default;
 
diff --git a/components/sync/engine_impl/cycle/sync_cycle.cc b/components/sync/engine_impl/cycle/sync_cycle.cc
index 0fc8384..785d313 100644
--- a/components/sync/engine_impl/cycle/sync_cycle.cc
+++ b/components/sync/engine_impl/cycle/sync_cycle.cc
@@ -27,7 +27,7 @@
 SyncCycleSnapshot SyncCycle::TakeSnapshotWithOrigin(
     sync_pb::SyncEnums::GetUpdatesOrigin get_updates_origin) const {
   ProgressMarkerMap download_progress_markers;
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     ModelType type(ModelTypeFromInt(i));
     const UpdateHandler* update_handler =
         context_->model_type_registry()->GetUpdateHandler(type);
@@ -43,8 +43,8 @@
   // an issue with USS types.
   syncable::Directory* dir = context_->directory();
 
-  std::vector<int> num_entries_by_type(MODEL_TYPE_COUNT, 0);
-  std::vector<int> num_to_delete_entries_by_type(MODEL_TYPE_COUNT, 0);
+  std::vector<int> num_entries_by_type(ModelType::NUM_ENTRIES, 0);
+  std::vector<int> num_to_delete_entries_by_type(ModelType::NUM_ENTRIES, 0);
   dir->CollectMetaHandleCounts(&num_entries_by_type,
                                &num_to_delete_entries_by_type);
 
diff --git a/components/sync/engine_impl/debug_info_event_listener.h b/components/sync/engine_impl/debug_info_event_listener.h
index e9cb21f..391b253 100644
--- a/components/sync/engine_impl/debug_info_event_listener.h
+++ b/components/sync/engine_impl/debug_info_event_listener.h
@@ -28,7 +28,7 @@
 // In order to track datatype association results, we need at least as many
 // entries as datatypes. Reserve additional space for other kinds of events that
 // are likely to happen during first sync or startup.
-const unsigned int kMaxEntries = MODEL_TYPE_COUNT + 10;
+const unsigned int kMaxEntries = ModelType::NUM_ENTRIES + 10;
 
 // Listens to events and records them in a queue. And passes the events to
 // syncer when requested.
diff --git a/components/sync/engine_impl/js_mutation_event_observer_unittest.cc b/components/sync/engine_impl/js_mutation_event_observer_unittest.cc
index 0159fea..49f2424 100644
--- a/components/sync/engine_impl/js_mutation_event_observer_unittest.cc
+++ b/components/sync/engine_impl/js_mutation_event_observer_unittest.cc
@@ -46,8 +46,8 @@
   // We don't test with passwords as that requires additional setup.
 
   // Build a list of example ChangeRecords.
-  ChangeRecord changes[MODEL_TYPE_COUNT];
-  for (int i = AUTOFILL_PROFILE; i < MODEL_TYPE_COUNT; ++i) {
+  ChangeRecord changes[ModelType::NUM_ENTRIES];
+  for (int i = AUTOFILL_PROFILE; i < ModelType::NUM_ENTRIES; ++i) {
     changes[i].id = i;
     switch (i % 3) {
       case 0:
@@ -67,13 +67,13 @@
   // starting from changes[i].
 
   // Set expectations for each data type.
-  for (int i = AUTOFILL_PROFILE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = AUTOFILL_PROFILE; i < ModelType::NUM_ENTRIES; ++i) {
     const std::string& model_type_str = ModelTypeToString(ModelTypeFromInt(i));
     base::DictionaryValue expected_details;
     expected_details.SetString("modelType", model_type_str);
     expected_details.SetString("writeTransactionId", "0");
     auto expected_changes = std::make_unique<base::ListValue>();
-    for (int j = i; j < MODEL_TYPE_COUNT; ++j) {
+    for (int j = i; j < ModelType::NUM_ENTRIES; ++j) {
       expected_changes->Append(changes[j].ToValue());
     }
     expected_details.Set("changes", std::move(expected_changes));
@@ -83,7 +83,7 @@
   }
 
   // Fire OnChangesApplied() for each data type.
-  for (int i = AUTOFILL_PROFILE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = AUTOFILL_PROFILE; i < ModelType::NUM_ENTRIES; ++i) {
     ChangeRecordList local_changes(std::begin(changes) + i, std::end(changes));
     js_mutation_event_observer_.OnChangesApplied(
         ModelTypeFromInt(i), 0, ImmutableChangeRecordList(&local_changes));
@@ -95,7 +95,7 @@
 TEST_F(JsMutationEventObserverTest, OnChangesComplete) {
   InSequence dummy;
 
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     base::DictionaryValue expected_details;
     expected_details.SetString("modelType",
                                ModelTypeToString(ModelTypeFromInt(i)));
@@ -104,7 +104,7 @@
                               HasDetailsAsDictionary(expected_details)));
   }
 
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     js_mutation_event_observer_.OnChangesComplete(ModelTypeFromInt(i));
   }
   PumpLoop();
diff --git a/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc b/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
index 6fda79b2..e44cf33 100644
--- a/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
+++ b/components/sync/engine_impl/js_sync_encryption_handler_observer_unittest.cc
@@ -113,7 +113,7 @@
   auto encrypted_type_values = std::make_unique<base::ListValue>();
   ModelTypeSet encrypted_types;
 
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     ModelType type = ModelTypeFromInt(i);
     encrypted_types.Put(type);
     encrypted_type_values->AppendString(ModelTypeToString(type));
diff --git a/components/sync/engine_impl/js_sync_manager_observer_unittest.cc b/components/sync/engine_impl/js_sync_manager_observer_unittest.cc
index 35309f85..fcb7d2d 100644
--- a/components/sync/engine_impl/js_sync_manager_observer_unittest.cc
+++ b/components/sync/engine_impl/js_sync_manager_observer_unittest.cc
@@ -61,13 +61,14 @@
 }
 
 TEST_F(JsSyncManagerObserverTest, OnSyncCycleCompleted) {
-  SyncCycleSnapshot snapshot(
-      ModelNeutralState(), ProgressMarkerMap(), false, 5, 2, 7, false, 0,
-      base::Time::Now(), base::Time::Now(),
-      std::vector<int>(MODEL_TYPE_COUNT, 0),
-      std::vector<int>(MODEL_TYPE_COUNT, 0), sync_pb::SyncEnums::UNKNOWN_ORIGIN,
-      /*poll_interval=*/base::TimeDelta::FromMinutes(30),
-      /*has_remaining_local_changes=*/false);
+  SyncCycleSnapshot snapshot(ModelNeutralState(), ProgressMarkerMap(), false, 5,
+                             2, 7, false, 0, base::Time::Now(),
+                             base::Time::Now(),
+                             std::vector<int>(ModelType::NUM_ENTRIES, 0),
+                             std::vector<int>(ModelType::NUM_ENTRIES, 0),
+                             sync_pb::SyncEnums::UNKNOWN_ORIGIN,
+                             /*poll_interval=*/base::TimeDelta::FromMinutes(30),
+                             /*has_remaining_local_changes=*/false);
   base::DictionaryValue expected_details;
   expected_details.Set("snapshot", snapshot.ToValue());
 
diff --git a/components/sync/engine_impl/model_type_registry.cc b/components/sync/engine_impl/model_type_registry.cc
index 0420cb5..ee4b09c 100644
--- a/components/sync/engine_impl/model_type_registry.cc
+++ b/components/sync/engine_impl/model_type_registry.cc
@@ -136,7 +136,7 @@
       // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
       UMA_HISTOGRAM_ENUMERATION("Sync.USSMigrationSuccess",
                                 ModelTypeToHistogramInt(type),
-                                static_cast<int>(MODEL_TYPE_COUNT));
+                                static_cast<int>(ModelType::NUM_ENTRIES));
       // If we succesfully migrated, purge the directory of data for the type.
       // Purging removes the directory's local copy of the data only.
       directory()->PurgeEntriesWithTypeIn(ModelTypeSet(type), ModelTypeSet(),
@@ -145,7 +145,7 @@
       // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
       UMA_HISTOGRAM_ENUMERATION("Sync.USSMigrationFailure",
                                 ModelTypeToHistogramInt(type),
-                                static_cast<int>(MODEL_TYPE_COUNT));
+                                static_cast<int>(ModelType::NUM_ENTRIES));
     }
 
     // Note that a partial failure may still contribute to the counts histogram.
diff --git a/components/sync/engine_impl/sync_manager_impl.cc b/components/sync/engine_impl/sync_manager_impl.cc
index 4473f84..093e6ae 100644
--- a/components/sync/engine_impl/sync_manager_impl.cc
+++ b/components/sync/engine_impl/sync_manager_impl.cc
@@ -148,7 +148,7 @@
       observing_network_connectivity_changes_(false),
       weak_ptr_factory_(this) {
   // Pre-fill |notification_info_map_|.
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     notification_info_map_.insert(
         std::make_pair(ModelTypeFromInt(i), NotificationInfo()));
   }
@@ -783,7 +783,7 @@
   LOG_IF(WARNING, !change_records_.empty())
       << "CALCULATE_CHANGES called with unapplied old changes.";
 
-  ChangeReorderBuffer change_buffers[MODEL_TYPE_COUNT];
+  ChangeReorderBuffer change_buffers[ModelType::NUM_ENTRIES];
 
   Cryptographer* crypto = directory()->GetCryptographer(trans);
   const syncable::ImmutableEntryKernelMutationMap& mutations =
@@ -812,7 +812,7 @@
   }
 
   ReadTransaction read_trans(GetUserShare(), trans);
-  for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
     if (!change_buffers[i].IsEmpty()) {
       if (change_buffers[i].GetAllChangesInTreeOrder(&read_trans,
                                                      &(change_records_[i]))) {
diff --git a/components/sync/engine_impl/syncer_proto_util.cc b/components/sync/engine_impl/syncer_proto_util.cc
index db55ed5..2902f7e5 100644
--- a/components/sync/engine_impl/syncer_proto_util.cc
+++ b/components/sync/engine_impl/syncer_proto_util.cc
@@ -368,7 +368,7 @@
           "Sync.PostedDataTypeGetUpdatesRequest",
           ModelTypeToHistogramInt(GetModelTypeFromSpecificsFieldNumber(
               progress_marker.data_type_id())),
-          static_cast<int>(MODEL_TYPE_COUNT));
+          static_cast<int>(ModelType::NUM_ENTRIES));
     }
   }
 
diff --git a/components/sync/model/data_type_error_handler_impl.cc b/components/sync/model/data_type_error_handler_impl.cc
index af4b3b68..b05e98f 100644
--- a/components/sync/model/data_type_error_handler_impl.cc
+++ b/components/sync/model/data_type_error_handler_impl.cc
@@ -25,7 +25,7 @@
   // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
   UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures2",
                             ModelTypeToHistogramInt(error.model_type()),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
   ui_thread_->PostTask(error.location(), base::BindOnce(sync_callback_, error));
 }
 
diff --git a/components/sync/model_impl/blocking_model_type_store_impl.cc b/components/sync/model_impl/blocking_model_type_store_impl.cc
index c95024cc..5bc511c8 100644
--- a/components/sync/model_impl/blocking_model_type_store_impl.cc
+++ b/components/sync/model_impl/blocking_model_type_store_impl.cc
@@ -242,7 +242,7 @@
   DCHECK_EQ(write_batch_impl->GetModelType(), type_);
   UMA_HISTOGRAM_ENUMERATION("Sync.ModelTypeStoreCommitCount",
                             ModelTypeToHistogramInt(type_),
-                            static_cast<int>(MODEL_TYPE_COUNT));
+                            static_cast<int>(ModelType::NUM_ENTRIES));
   return backend_->WriteModifications(
       LevelDbWriteBatch::ToLevelDbWriteBatch(std::move(write_batch_impl)));
 }
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.cc b/components/sync/model_impl/client_tag_based_model_type_processor.cc
index 649b67d..f7940d9 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.cc
@@ -398,7 +398,7 @@
     // Ignore changes that don't actually change anything.
     UMA_HISTOGRAM_ENUMERATION("Sync.ModelTypeRedundantPut",
                               ModelTypeToHistogramInt(type_),
-                              static_cast<int>(MODEL_TYPE_COUNT));
+                              static_cast<int>(ModelType::NUM_ENTRIES));
     return;
   }
 
@@ -1137,7 +1137,7 @@
     storage_keys_to_untrack.push_back(storage_key);
     UMA_HISTOGRAM_ENUMERATION("Sync.ModelTypeOrphanMetadata",
                               ModelTypeToHistogramInt(type_),
-                              static_cast<int>(MODEL_TYPE_COUNT));
+                              static_cast<int>(ModelType::NUM_ENTRIES));
   }
 
   if (storage_keys_to_untrack.empty()) {
diff --git a/components/sync/protocol/proto_value_conversions_unittest.cc b/components/sync/protocol/proto_value_conversions_unittest.cc
index ea2176fd..d53a888f 100644
--- a/components/sync/protocol/proto_value_conversions_unittest.cc
+++ b/components/sync/protocol/proto_value_conversions_unittest.cc
@@ -60,7 +60,7 @@
 
 DEFINE_SPECIFICS_TO_VALUE_TEST(encrypted)
 
-static_assert(44 == syncer::MODEL_TYPE_COUNT,
+static_assert(44 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new field, add a DEFINE_SPECIFICS_TO_VALUE_TEST "
               "for your field below, and optionally a test for the specific "
               "conversions.");
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index 56c45ce9..7469b54 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -367,7 +367,7 @@
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::EntitySpecifics& proto) {
-  static_assert(44 == MODEL_TYPE_COUNT,
+  static_assert(44 == ModelType::NUM_ENTRIES,
                 "When adding a new protocol type, you will likely need to add "
                 "it here as well.");
   VISIT(encrypted);
diff --git a/components/sync/syncable/directory.cc b/components/sync/syncable/directory.cc
index 1c477bc..8bd6110 100644
--- a/components/sync/syncable/directory.cc
+++ b/components/sync/syncable/directory.cc
@@ -828,7 +828,7 @@
       base::StringPrintf("sync/0x%" PRIXPTR, reinterpret_cast<uintptr_t>(this));
 
   size_t kernel_memory_usage;
-  size_t model_type_entry_count[MODEL_TYPE_COUNT] = {0};
+  size_t model_type_entry_count[ModelType::NUM_ENTRIES] = {0};
   {
     using base::trace_event::EstimateMemoryUsage;
 
@@ -860,7 +860,7 @@
   }
 
   // Similar to UploadModelTypeEntryCount()
-  for (size_t i = FIRST_REAL_MODEL_TYPE; i != MODEL_TYPE_COUNT; ++i) {
+  for (size_t i = FIRST_REAL_MODEL_TYPE; i != ModelType::NUM_ENTRIES; ++i) {
     ModelType model_type = static_cast<ModelType>(i);
     std::string notification_type;
     if (RealModelTypeToNotificationType(model_type, &notification_type)) {
@@ -1114,7 +1114,7 @@
                                               std::vector<int64_t>* result) {
   result->clear();
   ScopedKernelLock lock(this);
-  for (int i = UNSPECIFIED; i < MODEL_TYPE_COUNT; ++i) {
+  for (int i = UNSPECIFIED; i < ModelType::NUM_ENTRIES; ++i) {
     const ModelType type = ModelTypeFromInt(i);
     if (server_types.Has(type)) {
       std::copy(kernel_->unapplied_update_metahandles[type].begin(),
diff --git a/components/sync/syncable/directory.h b/components/sync/syncable/directory.h
index fb340d4..26b70b61 100644
--- a/components/sync/syncable/directory.h
+++ b/components/sync/syncable/directory.h
@@ -99,14 +99,14 @@
     size_t EstimateMemoryUsage() const;
 
     // Last sync timestamp fetched from the server.
-    sync_pb::DataTypeProgressMarker download_progress[MODEL_TYPE_COUNT];
+    sync_pb::DataTypeProgressMarker download_progress[ModelType::NUM_ENTRIES];
     // Sync-side transaction version per data type. Monotonically incremented
     // when updating native model. A copy is also saved in native model.
     // Later out-of-sync models can be detected and fixed by comparing
     // transaction versions of sync model and native model.
     // TODO(hatiaol): implement detection and fixing of out-of-sync models.
     //                Bug 154858.
-    int64_t transaction_version[MODEL_TYPE_COUNT];
+    int64_t transaction_version[ModelType::NUM_ENTRIES];
     // The store birthday we were given by the server. Contents are opaque to
     // the client.
     std::string store_birthday;
@@ -115,7 +115,7 @@
     // ChipBag defined in sync.proto. It can contains null characters.
     std::string bag_of_chips;
     // The per-datatype context.
-    sync_pb::DataTypeContext datatype_context[MODEL_TYPE_COUNT];
+    sync_pb::DataTypeContext datatype_context[ModelType::NUM_ENTRIES];
   };
 
   // What the Directory needs on initialization to create itself and its Kernel.
@@ -197,7 +197,7 @@
 
     // 3 in-memory indices on bits used extremely frequently by the syncer.
     // |unapplied_update_metahandles| is keyed by the server model type.
-    MetahandleSet unapplied_update_metahandles[MODEL_TYPE_COUNT];
+    MetahandleSet unapplied_update_metahandles[ModelType::NUM_ENTRIES];
     MetahandleSet unsynced_metahandles;
     // Contains metahandles that are most likely dirty (though not
     // necessarily).  Dirtyness is confirmed in TakeSnapshotForSaveChanges().
diff --git a/components/sync/syncable/nigori_util.cc b/components/sync/syncable/nigori_util.cc
index c4c62ce..215c635 100644
--- a/components/sync/syncable/nigori_util.cc
+++ b/components/sync/syncable/nigori_util.cc
@@ -214,7 +214,7 @@
              << " already match, dropping change.";
     UMA_HISTOGRAM_ENUMERATION("Sync.ModelTypeRedundantPut",
                               ModelTypeToHistogramInt(type),
-                              static_cast<int>(MODEL_TYPE_COUNT));
+                              static_cast<int>(ModelType::NUM_ENTRIES));
     return true;
   }
 
@@ -250,7 +250,7 @@
                                     bool encrypt_everything,
                                     sync_pb::NigoriSpecifics* nigori) {
   nigori->set_encrypt_everything(encrypt_everything);
-  static_assert(44 == MODEL_TYPE_COUNT,
+  static_assert(44 == ModelType::NUM_ENTRIES,
                 "If adding an encryptable type, update handling below.");
   nigori->set_encrypt_bookmarks(encrypted_types.Has(BOOKMARKS));
   nigori->set_encrypt_preferences(encrypted_types.Has(PREFERENCES));
@@ -287,7 +287,7 @@
     return ModelTypeSet::All();
 
   ModelTypeSet encrypted_types;
-  static_assert(44 == MODEL_TYPE_COUNT,
+  static_assert(44 == ModelType::NUM_ENTRIES,
                 "If adding an encryptable type, update handling below.");
   if (nigori.encrypt_bookmarks())
     encrypted_types.Put(BOOKMARKS);
diff --git a/components/sync/syncable/parent_child_index.cc b/components/sync/syncable/parent_child_index.cc
index c3701af..96735b4 100644
--- a/components/sync/syncable/parent_child_index.cc
+++ b/components/sync/syncable/parent_child_index.cc
@@ -41,8 +41,8 @@
 
 ParentChildIndex::ParentChildIndex() {
   // Pre-allocate these two vectors to the number of model types.
-  model_type_root_ids_.resize(MODEL_TYPE_COUNT);
-  type_root_child_sets_.resize(MODEL_TYPE_COUNT);
+  model_type_root_ids_.resize(ModelType::NUM_ENTRIES);
+  type_root_child_sets_.resize(ModelType::NUM_ENTRIES);
 }
 
 ParentChildIndex::~ParentChildIndex() {}
diff --git a/components/sync_bookmarks/bookmark_model_associator.cc b/components/sync_bookmarks/bookmark_model_associator.cc
index 958a6ffa6..8c09407 100644
--- a/components/sync_bookmarks/bookmark_model_associator.cc
+++ b/components/sync_bookmarks/bookmark_model_associator.cc
@@ -966,9 +966,10 @@
       context->set_native_model_sync_state(IN_SYNC);
     } else {
       // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
-      UMA_HISTOGRAM_ENUMERATION("Sync.LocalModelOutOfSync",
-                                ModelTypeToHistogramInt(syncer::BOOKMARKS),
-                                static_cast<int>(syncer::MODEL_TYPE_COUNT));
+      UMA_HISTOGRAM_ENUMERATION(
+          "Sync.LocalModelOutOfSync",
+          ModelTypeToHistogramInt(syncer::BOOKMARKS),
+          static_cast<int>(syncer::ModelType::NUM_ENTRIES));
 
       // Clear version on bookmark model so that we only report error once.
       bookmark_model_->SetNodeSyncTransactionVersion(
diff --git a/components/test/data/update_client/updatecheck_reply_1.xml b/components/test/data/update_client/updatecheck_reply_1.xml
deleted file mode 100644
index b6ff48f..0000000
--- a/components/test/data/update_client/updatecheck_reply_1.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
-    <updatecheck status='ok'>
-      <urls>
-        <url codebase='http://localhost/download/'/>
-      </urls>
-      <manifest version='1.0' prodversionmin='11.0.1.0'>
-        <packages>
-          <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/>
-        </packages>
-      </manifest>
-      <actions>
-        <action run='this'/>
-      </actions>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_4.xml b/components/test/data/update_client/updatecheck_reply_4.xml
deleted file mode 100644
index c2d44625..0000000
--- a/components/test/data/update_client/updatecheck_reply_4.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <daystart elapsed_days='3383' />
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
-    <updatecheck status='ok'>
-      <urls>
-        <url codebase='http://localhost/download/'/>
-      </urls>
-      <manifest version='1.0' prodversionmin='11.0.1.0'>
-        <packages>
-          <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/>
-        </packages>
-      </manifest>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_noupdate.xml b/components/test/data/update_client/updatecheck_reply_noupdate.xml
deleted file mode 100644
index 1e18fa1..0000000
--- a/components/test/data/update_client/updatecheck_reply_noupdate.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
-    <updatecheck status='noupdate'>
-      <actions>
-        <action run='this'/>
-      </actions>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_parse_error.xml b/components/test/data/update_client/updatecheck_reply_parse_error.xml
deleted file mode 100644
index 43efe93..0000000
--- a/components/test/data/update_client/updatecheck_reply_parse_error.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.0'>
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf'></app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_unknownapp.xml b/components/test/data/update_client/updatecheck_reply_unknownapp.xml
deleted file mode 100644
index f708ec4..0000000
--- a/components/test/data/update_client/updatecheck_reply_unknownapp.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf' status="error-unknownApplication"/>
-</response>
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
index 06cb620..4a99ebf5 100644
--- a/components/update_client/BUILD.gn
+++ b/components/update_client/BUILD.gn
@@ -67,14 +67,10 @@
     "protocol_parser.h",
     "protocol_parser_json.cc",
     "protocol_parser_json.h",
-    "protocol_parser_xml.cc",
-    "protocol_parser_xml.h",
     "protocol_serializer.cc",
     "protocol_serializer.h",
     "protocol_serializer_json.cc",
     "protocol_serializer_json.h",
-    "protocol_serializer_xml.cc",
-    "protocol_serializer_xml.h",
     "request_sender.cc",
     "request_sender.h",
     "task.h",
@@ -123,7 +119,6 @@
     "//components/version_info:version_info",
     "//courgette:courgette_lib",
     "//crypto",
-    "//third_party/libxml",
     "//url",
   ]
 
@@ -179,15 +174,10 @@
     "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx",
     "//components/test/data/update_client/runaction_test_win.crx3",
     "//components/test/data/update_client/updatecheck_reply_1.json",
-    "//components/test/data/update_client/updatecheck_reply_1.xml",
     "//components/test/data/update_client/updatecheck_reply_4.json",
-    "//components/test/data/update_client/updatecheck_reply_4.xml",
     "//components/test/data/update_client/updatecheck_reply_noupdate.json",
-    "//components/test/data/update_client/updatecheck_reply_noupdate.xml",
     "//components/test/data/update_client/updatecheck_reply_parse_error.json",
-    "//components/test/data/update_client/updatecheck_reply_parse_error.xml",
     "//components/test/data/update_client/updatecheck_reply_unknownapp.json",
-    "//components/test/data/update_client/updatecheck_reply_unknownapp.xml",
   ]
   outputs = [
     "{{bundle_resources_dir}}/" +
@@ -204,10 +194,8 @@
     "persisted_data_unittest.cc",
     "ping_manager_unittest.cc",
     "protocol_parser_json_unittest.cc",
-    "protocol_parser_xml_unittest.cc",
     "protocol_serializer_json_unittest.cc",
     "protocol_serializer_unittest.cc",
-    "protocol_serializer_xml_unittest.cc",
     "request_sender_unittest.cc",
     "update_checker_unittest.cc",
     "update_client_unittest.cc",
@@ -240,7 +228,6 @@
     "//services/service_manager/public/cpp/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
-    "//third_party/libxml",
     "//third_party/re2",
   ]
 }
diff --git a/components/update_client/DEPS b/components/update_client/DEPS
index 0ac27fd..0a397d6 100644
--- a/components/update_client/DEPS
+++ b/components/update_client/DEPS
@@ -8,10 +8,8 @@
   "+components/version_info",
   "+courgette",
   "+crypto",
-  "+libxml",
   "+mojo",
   "+services/service_manager/public",
-  "+third_party/libxml",
   "+third_party/re2",
   "+third_party/zlib",
 ]
diff --git a/components/update_client/ping_manager_unittest.cc b/components/update_client/ping_manager_unittest.cc
index b48a905e..60d078c 100644
--- a/components/update_client/ping_manager_unittest.cc
+++ b/components/update_client/ping_manager_unittest.cc
@@ -54,8 +54,6 @@
   scoped_refptr<TestConfigurator> config_;
   scoped_refptr<PingManager> ping_manager_;
 
-  bool use_JSON_ = false;
-
   int error_ = -1;
   std::string response_;
 
@@ -71,8 +69,6 @@
 }
 
 void PingManagerTest::SetUp() {
-  use_JSON_ = GetParam();
-  config_->SetUseJSON(use_JSON_);
   ping_manager_ = base::MakeRefCounted<PingManager>(config_);
 }
 
@@ -138,7 +134,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -174,25 +169,6 @@
       EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
       EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
       EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
-    } else {
-      constexpr char regex[] =
-          R"(<\?xml version="1\.0" encoding="UTF-8"\?>)"
-          R"(<request protocol="3\.1" )"
-          R"(dedup="cr" acceptformat="crx2,crx3" extra="foo" )"
-          R"(sessionid="{[-\w]{36}}" requestid="{[-\w]{36}}" )"
-          R"(updater="fake_prodid" updaterversion="30\.0" prodversion="30\.0" )"
-          R"(lang="fake_lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" )"
-          R"((wow64="1" )?)"
-          R"(updaterchannel="fake_channel_string" )"
-          R"(prodchannel="fake_channel_string">)"
-          R"(<hw physmemory="[0-9]+"/>)"
-          R"(<os platform="Fake Operating System" arch="[,-.\w]+" )"
-          R"(version="[-.\w]+"( sp="[\s\w]+")?/>)"
-          R"(<app appid="abc" version="1\.0">)"
-          R"(<event eventresult="1" eventtype="3" )"
-          R"(nextversion="2\.0" previousversion="1\.0"/></app></request>)";
-      EXPECT_TRUE(RE2::FullMatch(msg, regex)) << msg;
-    }
 
     // Check the ping request does not carry the specific extra request headers.
     const auto headers = std::get<1>(interceptor->GetRequests()[0]);
@@ -219,7 +195,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -231,13 +206,6 @@
       EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
       EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
       EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
-    } else {
-      constexpr char regex[] =
-          R"(<app appid="abc" version="1\.0">)"
-          R"(<event eventresult="0" eventtype="3" )"
-          R"(nextversion="2\.0" previousversion="1\.0"/></app>)";
-      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
-    }
     interceptor->Reset();
   }
 
@@ -267,7 +235,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -288,16 +255,6 @@
       EXPECT_EQ(-1, event.FindKey("extracode1")->GetInt());
       EXPECT_EQ("next fp", event.FindKey("nextfp")->GetString());
       EXPECT_EQ("prev fp", event.FindKey("previousfp")->GetString());
-    } else {
-      constexpr char regex[] =
-          R"(<app appid="abc" version="1\.0">)"
-          R"(<event differrorcat="4" differrorcode="20" )"
-          R"(diffextracode1="-10" diffresult="0" errorcat="1" errorcode="2" )"
-          R"(eventresult="0" eventtype="3" extracode1="-1" nextfp="next fp" )"
-          R"(nextversion="2\.0" previousfp="prev fp" previousversion="1\.0"/>)"
-          R"(</app>)";
-      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
-    }
     interceptor->Reset();
   }
 
@@ -318,7 +275,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -329,13 +285,6 @@
       EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
       EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
       EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
-    } else {
-      constexpr char regex[] =
-          R"(<app appid="abc" version="1\.0">)"
-          R"(<event eventresult="0" eventtype="3" previousversion="1\.0"/>)"
-          R"(</app>)";
-      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
-    }
     interceptor->Reset();
   }
 
@@ -353,7 +302,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -365,13 +313,6 @@
       EXPECT_EQ(4, event.FindKey("eventtype")->GetInt());
       EXPECT_EQ("1.2.3.4", event.FindKey("previousversion")->GetString());
       EXPECT_EQ("0", event.FindKey("nextversion")->GetString());
-    } else {
-      constexpr char regex[] =
-          R"(<app appid="abc" version="1\.2\.3\.4">)"
-          R"(<event eventresult="1" eventtype="4" )"
-          R"(nextversion="0" previousversion="1\.2\.3\.4"/></app>)";
-      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
-    }
     interceptor->Reset();
   }
 
@@ -418,7 +359,6 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(msg);
       ASSERT_TRUE(root);
       const auto* request = root->FindKey("request");
@@ -471,27 +411,6 @@
         EXPECT_EQ(9007199254740991, event.FindKey("total")->GetDouble());
         EXPECT_EQ("http://host3/path3", event.FindKey("url")->GetString());
       }
-    } else {
-      constexpr char regex[] =
-          R"(<app appid="abc" version="1\.0">)"
-          R"(<event eventresult="1" eventtype="3" )"
-          R"(nextversion="2\.0" previousversion="1\.0"/>)"
-          R"(<event download_time_ms="987" )"
-          R"(downloaded="123" downloader="direct" )"
-          R"(errorcode="-1" eventresult="0" eventtype="14" )"
-          R"(nextversion="2\.0" previousversion="1\.0" total="456" )"
-          R"(url="http://host1/path1"/>)"
-          R"(<event download_time_ms="9870" downloaded="1230" )"
-          R"(downloader="bits" )"
-          R"(eventresult="1" eventtype="14" nextversion="2\.0" )"
-          R"(previousversion="1\.0" total="4560" url="http://host2/path2"/>)"
-          R"(<event download_time_ms="9007199254740990" )"
-          R"(downloaded="9007199254740992" downloader="bits" )"
-          R"(eventresult="1" eventtype="14" nextversion="2.0" )"
-          R"(previousversion="1.0" total="9007199254740991" )"
-          R"(url="http://host3/path3"/></app>)";
-      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
-    }
     interceptor->Reset();
   }
 }
diff --git a/components/update_client/protocol_handler.cc b/components/update_client/protocol_handler.cc
index a6b4999..aa547b4 100644
--- a/components/update_client/protocol_handler.cc
+++ b/components/update_client/protocol_handler.cc
@@ -4,22 +4,10 @@
 
 #include "components/update_client/protocol_handler.h"
 #include "components/update_client/protocol_parser_json.h"
-#include "components/update_client/protocol_parser_xml.h"
 #include "components/update_client/protocol_serializer_json.h"
-#include "components/update_client/protocol_serializer_xml.h"
 
 namespace update_client {
 
-std::unique_ptr<ProtocolParser> ProtocolHandlerFactoryXml::CreateParser()
-    const {
-  return std::make_unique<ProtocolParserXml>();
-}
-
-std::unique_ptr<ProtocolSerializer>
-ProtocolHandlerFactoryXml::CreateSerializer() const {
-  return std::make_unique<ProtocolSerializerXml>();
-}
-
 std::unique_ptr<ProtocolParser> ProtocolHandlerFactoryJSON::CreateParser()
     const {
   return std::make_unique<ProtocolParserJSON>();
diff --git a/components/update_client/protocol_handler.h b/components/update_client/protocol_handler.h
index cb8e97e..57c8cfd 100644
--- a/components/update_client/protocol_handler.h
+++ b/components/update_client/protocol_handler.h
@@ -27,13 +27,6 @@
   DISALLOW_COPY_AND_ASSIGN(ProtocolHandlerFactory);
 };
 
-class ProtocolHandlerFactoryXml final : public ProtocolHandlerFactory {
- public:
-  // Overrides for ProtocolHandlerFactory.
-  std::unique_ptr<ProtocolParser> CreateParser() const override;
-  std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
-};
-
 class ProtocolHandlerFactoryJSON final : public ProtocolHandlerFactory {
  public:
   // Overrides for ProtocolHandlerFactory.
diff --git a/components/update_client/protocol_parser.cc b/components/update_client/protocol_parser.cc
index cea8744..7f280c56 100644
--- a/components/update_client/protocol_parser.cc
+++ b/components/update_client/protocol_parser.cc
@@ -4,7 +4,6 @@
 
 #include "components/update_client/protocol_parser.h"
 #include "base/strings/stringprintf.h"
-#include "components/update_client/protocol_parser_xml.h"
 
 namespace update_client {
 
@@ -53,8 +52,4 @@
   return DoParse(response, &results_);
 }
 
-std::unique_ptr<ProtocolParser> ProtocolParser::Create() {
-  return std::make_unique<ProtocolParserXml>();
-}
-
 }  // namespace update_client
diff --git a/components/update_client/protocol_parser_xml.cc b/components/update_client/protocol_parser_xml.cc
deleted file mode 100644
index ace73c1b..0000000
--- a/components/update_client/protocol_parser_xml.cc
+++ /dev/null
@@ -1,422 +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.
-
-#include "components/update_client/protocol_parser_xml.h"
-
-#include <stddef.h>
-
-#include <algorithm>
-#include <cstdint>
-#include <memory>
-#include <vector>
-
-#include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/version.h"
-#include "components/update_client/protocol_definition.h"
-#include "libxml/tree.h"
-#include "third_party/libxml/chromium/libxml_utils.h"
-
-namespace update_client {
-
-// Checks whether a given node's name matches |expected_name|.
-static bool TagNameEquals(const xmlNode* node, const char* expected_name) {
-  return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
-}
-
-// Returns child nodes of |root| with name |name|.
-static std::vector<xmlNode*> GetChildren(xmlNode* root, const char* name) {
-  std::vector<xmlNode*> result;
-  for (xmlNode* child = root->children; child != nullptr; child = child->next) {
-    if (!TagNameEquals(child, name)) {
-      continue;
-    }
-    result.push_back(child);
-  }
-  return result;
-}
-
-// Returns the value of a named attribute, or the empty string.
-static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
-  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
-  for (xmlAttr* attr = node->properties; attr != nullptr; attr = attr->next) {
-    if (!xmlStrcmp(attr->name, name) && attr->children &&
-        attr->children->content) {
-      return std::string(
-          reinterpret_cast<const char*>(attr->children->content));
-    }
-  }
-  return std::string();
-}
-
-// Returns the value of a named attribute, or nullptr .
-static std::unique_ptr<std::string> GetAttributePtr(
-    xmlNode* node,
-    const char* attribute_name) {
-  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
-  for (xmlAttr* attr = node->properties; attr != nullptr; attr = attr->next) {
-    if (!xmlStrcmp(attr->name, name) && attr->children &&
-        attr->children->content) {
-      return std::make_unique<std::string>(
-          reinterpret_cast<const char*>(attr->children->content));
-    }
-  }
-  return nullptr;
-}
-
-// This is used for the xml parser to report errors. This assumes the context
-// is a pointer to a std::string where the error message should be appended.
-static void XmlErrorFunc(void* context, const char* message, ...) {
-  va_list args;
-  va_start(args, message);
-  std::string* error = static_cast<std::string*>(context);
-  base::StringAppendV(error, message, args);
-  va_end(args);
-}
-
-// Utility class for cleaning up the xml document when leaving a scope.
-class ScopedXmlDocument {
- public:
-  explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
-  ~ScopedXmlDocument() {
-    if (document_)
-      xmlFreeDoc(document_);
-  }
-
-  xmlDocPtr get() { return document_; }
-
- private:
-  xmlDocPtr document_;
-};
-
-// Parses the <package> tag.
-bool ParsePackageTag(xmlNode* package,
-                     ProtocolParser::Result* result,
-                     std::string* error) {
-  ProtocolParser::Result::Manifest::Package p;
-  p.name = GetAttribute(package, "name");
-  if (p.name.empty()) {
-    *error = "Missing name for package.";
-    return false;
-  }
-
-  p.namediff = GetAttribute(package, "namediff");
-
-  // package_fingerprint is optional. It identifies the package, preferably
-  // with a modified sha256 hash of the package in hex format.
-  p.fingerprint = GetAttribute(package, "fp");
-
-  p.hash_sha256 = GetAttribute(package, "hash_sha256");
-  int64_t size = 0;
-  if (base::StringToInt64(GetAttribute(package, "size"), &size) && size >= 0) {
-    p.size = size;
-  }
-
-  p.hashdiff_sha256 = GetAttribute(package, "hashdiff_sha256");
-  int64_t sizediff = 0;
-  if (base::StringToInt64(GetAttribute(package, "sizediff"), &sizediff) &&
-      sizediff >= 0) {
-    p.sizediff = sizediff;
-  }
-
-  result->manifest.packages.push_back(p);
-
-  return true;
-}
-
-// Parses the <manifest> tag.
-bool ParseManifestTag(xmlNode* manifest,
-                      ProtocolParser::Result* result,
-                      std::string* error) {
-  // Get the version.
-  result->manifest.version = GetAttribute(manifest, "version");
-  if (result->manifest.version.empty()) {
-    *error = "Missing version for manifest.";
-    return false;
-  }
-  base::Version version(result->manifest.version);
-  if (!version.IsValid()) {
-    *error = "Invalid version: '";
-    *error += result->manifest.version;
-    *error += "'.";
-    return false;
-  }
-
-  // Get the minimum browser version (not required).
-  result->manifest.browser_min_version =
-      GetAttribute(manifest, "prodversionmin");
-  if (result->manifest.browser_min_version.length()) {
-    base::Version browser_min_version(result->manifest.browser_min_version);
-    if (!browser_min_version.IsValid()) {
-      *error = "Invalid prodversionmin: '";
-      *error += result->manifest.browser_min_version;
-      *error += "'.";
-      return false;
-    }
-  }
-
-  // Get the <packages> node.
-  std::vector<xmlNode*> packages = GetChildren(manifest, "packages");
-  if (packages.empty()) {
-    *error = "Missing packages tag on manifest.";
-    return false;
-  }
-
-  // Parse each of the <package> tags.
-  std::vector<xmlNode*> package = GetChildren(packages[0], "package");
-  for (size_t i = 0; i != package.size(); ++i) {
-    if (!ParsePackageTag(package[i], result, error))
-      return false;
-  }
-
-  return true;
-}
-
-// Parses the <urls> tag and its children in the <updatecheck>.
-bool ParseUrlsTag(xmlNode* urls,
-                  ProtocolParser::Result* result,
-                  std::string* error) {
-  // Get the url nodes.
-  std::vector<xmlNode*> url = GetChildren(urls, "url");
-  if (url.empty()) {
-    *error = "Missing url tags on urls.";
-    return false;
-  }
-
-  // Get the list of urls for full and diff updates.
-  for (size_t i = 0; i != url.size(); ++i) {
-    const GURL crx_url(GetAttribute(url[i], "codebase"));
-    if (crx_url.is_valid())
-      result->crx_urls.push_back(crx_url);
-    const GURL crx_diffurl(GetAttribute(url[i], "codebasediff"));
-    if (crx_diffurl.is_valid())
-      result->crx_diffurls.push_back(crx_diffurl);
-  }
-
-  // Expect at least one url for full update.
-  if (result->crx_urls.empty()) {
-    *error = "Missing valid url for full update.";
-    return false;
-  }
-
-  return true;
-}
-
-// Parses the <actions> tag. It picks up the "run" attribute of the first
-// "action" element in "actions".
-void ParseActionsTag(xmlNode* updatecheck, ProtocolParser::Result* result) {
-  std::vector<xmlNode*> actions = GetChildren(updatecheck, "actions");
-  if (actions.empty())
-    return;
-
-  std::vector<xmlNode*> action = GetChildren(actions.front(), "action");
-  if (action.empty())
-    return;
-
-  result->action_run = GetAttribute(action.front(), "run");
-}
-
-// Parses the <updatecheck> tag.
-bool ParseUpdateCheckTag(xmlNode* updatecheck,
-                         ProtocolParser::Result* result,
-                         std::string* error) {
-  // Read the |status| attribute.
-  result->status = GetAttribute(updatecheck, "status");
-  if (result->status.empty()) {
-    *error = "Missing status on updatecheck node";
-    return false;
-  }
-
-  if (result->status == "noupdate") {
-    ParseActionsTag(updatecheck, result);
-    return true;
-  }
-
-  if (result->status == "ok") {
-    std::vector<xmlNode*> urls = GetChildren(updatecheck, "urls");
-    if (urls.empty()) {
-      *error = "Missing urls on updatecheck.";
-      return false;
-    }
-
-    if (!ParseUrlsTag(urls[0], result, error)) {
-      return false;
-    }
-
-    std::vector<xmlNode*> manifests = GetChildren(updatecheck, "manifest");
-    if (manifests.empty()) {
-      *error = "Missing manifest on updatecheck.";
-      return false;
-    }
-
-    ParseActionsTag(updatecheck, result);
-    return ParseManifestTag(manifests[0], result, error);
-  }
-
-  // Return the |updatecheck| element status as a parsing error.
-  *error = result->status;
-  return false;
-}
-
-// Parses a single <app> tag.
-bool ParseAppTag(xmlNode* app,
-                 ProtocolParser::Result* result,
-                 std::string* error) {
-  // Read cohort information.
-  auto cohort = GetAttributePtr(app, "cohort");
-  static const char* attrs[] = {ProtocolParser::Result::kCohort,
-                                ProtocolParser::Result::kCohortHint,
-                                ProtocolParser::Result::kCohortName};
-  for (auto* attr : attrs) {
-    auto value = GetAttributePtr(app, attr);
-    if (value)
-      result->cohort_attrs.insert({attr, *value});
-  }
-
-  // Read the crx id.
-  result->extension_id = GetAttribute(app, "appid");
-  if (result->extension_id.empty()) {
-    *error = "Missing appid on app node";
-    return false;
-  }
-
-  // Read the |status| attribute for the app.
-  // If the status is one of the defined app status error literals, then return
-  // it in the result as if it were an updatecheck status, then stop parsing,
-  // and return success.
-  result->status = GetAttribute(app, "status");
-  if (result->status == "restricted" ||
-      result->status == "error-unknownApplication" ||
-      result->status == "error-invalidAppId")
-    return true;
-
-  // If the status was not handled above and the status is not "ok", then
-  // this must be a status literal that that the parser does not know about.
-  if (!result->status.empty() && result->status != "ok") {
-    *error = "Unknown app status";
-    return false;
-  }
-
-  // Get the <updatecheck> tag.
-  DCHECK(result->status.empty() || result->status == "ok");
-  std::vector<xmlNode*> updates = GetChildren(app, "updatecheck");
-  if (updates.empty()) {
-    *error = "Missing updatecheck on app.";
-    return false;
-  }
-
-  return ParseUpdateCheckTag(updates[0], result, error);
-}
-
-// An update response looks like this:
-//
-// <?xml version="1.0" encoding="UTF-8"?>
-//  <response protocol="3.0" server="prod">
-//    <daystart elapsed_seconds="56508"/>
-//    <app appid="{430FD4D0-B729-4F61-AA34-91526481799D}" status="ok">
-//      <updatecheck status="noupdate"/>
-//      <ping status="ok"/>
-//    </app>
-//    <app appid="{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}" status="ok">
-//      <updatecheck status="ok">
-//        <urls>
-//          <url codebase="http://host/edgedl/chrome/install/782.112/"
-//          <url codebasediff="http://fallback/chrome/diff/782.112/"/>
-//        </urls>
-//        <manifest version="13.0.782.112" prodversionmin="2.0.143.0">
-//          <packages>
-//            <package name="component.crx"
-//                     namediff="diff_1.2.3.4.crx"
-//                     fp="1.123"
-//                     hash_sha256="9830b4245c4..." size="23963192"
-//                     hashdiff_sha256="cfb6caf3d0..." sizediff="101"/>
-//          </packages>
-//        </manifest>
-//      </updatecheck>
-//      <ping status="ok"/>
-//    </app>
-//  </response>
-//
-// The <daystart> tag contains a "elapsed_seconds" attribute which refers to
-// the server's notion of how many seconds it has been since midnight.
-//
-// The "appid" attribute of the <app> tag refers to the unique id of the
-// extension. The "codebase" attribute of the <updatecheck> tag is the url to
-// fetch the updated crx file, and the "prodversionmin" attribute refers to
-// the minimum version of the chrome browser that the update applies to.
-//
-// The diff data members correspond to the differential update package, if
-// a differential update is specified in the response.
-bool ProtocolParserXml::DoParse(const std::string& response_xml,
-                                Results* results) {
-  DCHECK(results);
-
-  if (response_xml.empty()) {
-    ParseError("Empty xml");
-    return false;
-  }
-
-  std::string xml_errors;
-  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
-
-  // Start up the xml parser with the manifest_xml contents.
-  ScopedXmlDocument document(
-      xmlParseDoc(reinterpret_cast<const xmlChar*>(response_xml.c_str())));
-  if (!document.get()) {
-    ParseError("%s", xml_errors.c_str());
-    return false;
-  }
-
-  xmlNode* root = xmlDocGetRootElement(document.get());
-  if (!root) {
-    ParseError("Missing root node");
-    return false;
-  }
-
-  if (!TagNameEquals(root, "response")) {
-    ParseError("Missing response tag");
-    return false;
-  }
-
-  // Check for the response "protocol" attribute.
-  const auto protocol = GetAttribute(root, "protocol");
-  if (protocol != kProtocolVersion) {
-    ParseError(
-        "Missing/incorrect protocol on response tag "
-        "(expected '%s', found '%s')",
-        kProtocolVersion, protocol.c_str());
-    return false;
-  }
-
-  // Parse the first <daystart> if it is present.
-  std::vector<xmlNode*> daystarts = GetChildren(root, "daystart");
-  if (!daystarts.empty()) {
-    xmlNode* first = daystarts[0];
-    std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
-    int parsed_elapsed = kNoDaystart;
-    if (base::StringToInt(elapsed_seconds, &parsed_elapsed))
-      results->daystart_elapsed_seconds = parsed_elapsed;
-    std::string elapsed_days = GetAttribute(first, "elapsed_days");
-    parsed_elapsed = kNoDaystart;
-    if (base::StringToInt(elapsed_days, &parsed_elapsed))
-      results->daystart_elapsed_days = parsed_elapsed;
-  }
-
-  // Parse each of the <app> tags.
-  std::vector<xmlNode*> apps = GetChildren(root, "app");
-  for (size_t i = 0; i != apps.size(); ++i) {
-    Result result;
-    std::string error;
-    if (ParseAppTag(apps[i], &result, &error))
-      results->list.push_back(result);
-    else
-      ParseError("%s", error.c_str());
-  }
-
-  return true;
-}
-
-}  // namespace update_client
diff --git a/components/update_client/protocol_parser_xml.h b/components/update_client/protocol_parser_xml.h
deleted file mode 100644
index 773ac96..0000000
--- a/components/update_client/protocol_parser_xml.h
+++ /dev/null
@@ -1,30 +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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_XML_H_
-#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_XML_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "components/update_client/protocol_parser.h"
-
-namespace update_client {
-
-// Parses responses for the update protocol version 3.
-// (https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md)
-class ProtocolParserXml final : public ProtocolParser {
- public:
-  ProtocolParserXml() = default;
-
- private:
-  // Overrides for ProtocolParser.
-  bool DoParse(const std::string& response_xml, Results* results) override;
-
-  DISALLOW_COPY_AND_ASSIGN(ProtocolParserXml);
-};
-
-}  // namespace update_client
-
-#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_XML_H_
diff --git a/components/update_client/protocol_parser_xml_unittest.cc b/components/update_client/protocol_parser_xml_unittest.cc
deleted file mode 100644
index 28f2b71..0000000
--- a/components/update_client/protocol_parser_xml_unittest.cc
+++ /dev/null
@@ -1,489 +0,0 @@
-// Copyright 2013 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/update_client/protocol_parser_xml.h"
-
-#include <memory>
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace update_client {
-
-const char* kValidXml =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "       <url codebasediff='http://diff.example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-const char* valid_xml_with_hash =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx' hash_sha256='1234'"
-    "             hashdiff_sha256='5678'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-const char* valid_xml_with_invalid_sizes =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='1' size='1234'/>"
-    "         <package name='2' size='9223372036854775807'/>"
-    "         <package name='3' size='-1234'/>"
-    "         <package name='4' />"
-    "         <package name='5' size='-a'/>"
-    "         <package name='6' size='-123467890123456789'/>"
-    "         <package name='7' size='123467890123456789012'/>"
-    "         <package name='8' sizediff='1234'/>"
-    "         <package name='9' sizediff='9223372036854775807'/>"
-    "         <package name='10' sizediff='-1234'/>"
-    "         <package name='11' sizediff='-a'/>"
-    "         <package name='12' sizediff='-123467890123456789'/>"
-    "         <package name='13' sizediff='123467890123456789012'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-const char* kInvalidValidXmlMissingCodebase =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebasediff='http://diff.example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package namediff='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-const char* kInvalidValidXmlMissingManifest =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-const char* kMissingAppId =
-    "<?xml version='1.0'?>"
-    "<response protocol='3.1'>"
-    " <app>"
-    "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
-    "               version='1.2.3.4'/>"
-    " </app>"
-    "</response>";
-
-const char* kInvalidCodebase =
-    "<?xml version='1.0'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345' status='ok'>"
-    "  <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
-    "               version='1.2.3.4'/>"
-    " </app>"
-    "</response>";
-
-const char* kMissingVersion =
-    "<?xml version='1.0'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345' status='ok'>"
-    "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'/>"
-    " </app>"
-    "</response>";
-
-const char* kInvalidVersion =
-    "<?xml version='1.0'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345' status='ok'>"
-    "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
-    "               version='1.2.3.a'/>"
-    " </app>"
-    "</response>";
-
-// The v3 version of the protocol is not using namespaces. However, the parser
-// must be able to parse responses that include namespaces.
-const char* kUsesNamespacePrefix =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<g:response xmlns:g='http://www.google.com/update2/response' "
-    "protocol='3.1'>"
-    " <g:app appid='12345'>"
-    "   <g:updatecheck status='ok'>"
-    "     <g:urls>"
-    "       <g:url codebase='http://example.com/'/>"
-    "     </g:urls>"
-    "     <g:manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <g:packages>"
-    "         <g:package name='extension_1_2_3_4.crx'/>"
-    "       </g:packages>"
-    "     </g:manifest>"
-    "   </g:updatecheck>"
-    " </g:app>"
-    "</g:response>";
-
-// Includes unrelated <app> tags from other xml namespaces - this should
-// not cause problems.
-const char* kSimilarTagnames =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response xmlns:a='http://a' protocol='3.1'>"
-    " <a:app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </a:app>"
-    " <b:app appid='xyz' xmlns:b='http://b'>"
-    "   <updatecheck status='noupdate'/>"
-    " </b:app>"
-    "</response>";
-
-// Includes a <daystart> tag.
-const char* kWithDaystart =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <daystart elapsed_seconds='456'/>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Indicates no updates available - this should not be a parse error.
-const char* kNoUpdate =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "  <updatecheck status='noupdate'/>"
-    " </app>"
-    "</response>";
-
-// Includes two <app> tags, one with an error.
-const char* kTwoAppsOneError =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='aaaaaaaa' status='error-unknownApplication'>"
-    "  <updatecheck status='error-internal'/>"
-    " </app>"
-    " <app appid='bbbbbbbb'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Includes two <app> tags, both of which set the cohort.
-const char* kTwoAppsSetCohort =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='aaaaaaaa' cohort='1:2q3/'>"
-    "  <updatecheck status='noupdate'/>"
-    " </app>"
-    " <app appid='bbbbbbbb' cohort='1:33z@0.33' cohortname='cname'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Includes a run action for an update check with status='ok'.
-const char* kUpdateCheckStatusOkWithRunAction =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='ok'>"
-    "     <urls>"
-    "       <url codebase='http://example.com/'/>"
-    "       <url codebasediff='http://diff.example.com/'/>"
-    "     </urls>"
-    "     <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
-    "       <packages>"
-    "         <package name='extension_1_2_3_4.crx'/>"
-    "       </packages>"
-    "     </manifest>"
-    "     <actions>"
-    "       <action run='this'/>"
-    "     </actions>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Includes a run action for an update check with status='noupdate'.
-const char* kUpdateCheckStatusNoUpdateWithRunAction =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345'>"
-    "   <updatecheck status='noupdate'>"
-    "     <actions>"
-    "       <action run='this'/>"
-    "     </actions>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Includes a run action for an update check with status='error'.
-const char* kUpdateCheckStatusErrorWithRunAction =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='12345' status='ok'>"
-    "  <updatecheck status='error-osnotsupported'>"
-    "     <actions>"
-    "       <action run='this'/>"
-    "     </actions>"
-    "   </updatecheck>"
-    " </app>"
-    "</response>";
-
-// Includes four <app> tags with status different than "ok".
-const char* kAppsStatusError =
-    "<?xml version='1.0' encoding='UTF-8'?>"
-    "<response protocol='3.1'>"
-    " <app appid='aaaaaaaa' status='error-unknownApplication'>"
-    "  <updatecheck status='error-internal'/>"
-    " </app>"
-    " <app appid='bbbbbbbb' status='restricted'>"
-    "  <updatecheck status='error-internal'/>"
-    " </app>"
-    " <app appid='cccccccc' status='error-invalidAppId'>"
-    "  <updatecheck status='error-internal'/>"
-    " </app>"
-    " <app appid='dddddddd' status='foobar'>"
-    "  <updatecheck status='error-internal'/>"
-    " </app>"
-    "</response>";
-
-TEST(UpdateClientProtocolParserXmlTest, Parse) {
-  const auto parser = std::make_unique<ProtocolParserXml>();
-
-  // Test parsing of a number of invalid xml cases
-  EXPECT_FALSE(parser->Parse(std::string()));
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kMissingAppId));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kInvalidCodebase));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kMissingVersion));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kInvalidVersion));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kInvalidValidXmlMissingCodebase));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  EXPECT_TRUE(parser->Parse(kInvalidValidXmlMissingManifest));
-  EXPECT_TRUE(parser->results().list.empty());
-  EXPECT_FALSE(parser->errors().empty());
-
-  // Parse some valid XML, and check that all params came out as expected
-  EXPECT_TRUE(parser->Parse(kValidXml));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_EQ(1u, parser->results().list.size());
-  const ProtocolParser::Result* first_result = &parser->results().list[0];
-  EXPECT_STREQ("ok", first_result->status.c_str());
-  EXPECT_EQ(1u, first_result->crx_urls.size());
-  EXPECT_EQ(GURL("http://example.com/"), first_result->crx_urls[0]);
-  EXPECT_EQ(GURL("http://diff.example.com/"), first_result->crx_diffurls[0]);
-  EXPECT_EQ("1.2.3.4", first_result->manifest.version);
-  EXPECT_EQ("2.0.143.0", first_result->manifest.browser_min_version);
-  EXPECT_EQ(1u, first_result->manifest.packages.size());
-  EXPECT_EQ("extension_1_2_3_4.crx", first_result->manifest.packages[0].name);
-
-  // Parse some xml that uses namespace prefixes.
-  EXPECT_TRUE(parser->Parse(kUsesNamespacePrefix));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_TRUE(parser->Parse(kSimilarTagnames));
-  EXPECT_TRUE(parser->errors().empty());
-
-  // Parse xml with hash value
-  EXPECT_TRUE(parser->Parse(valid_xml_with_hash));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  first_result = &parser->results().list[0];
-  EXPECT_FALSE(first_result->manifest.packages.empty());
-  EXPECT_EQ("1234", first_result->manifest.packages[0].hash_sha256);
-  EXPECT_EQ("5678", first_result->manifest.packages[0].hashdiff_sha256);
-
-  // Parse xml with package size value
-  EXPECT_TRUE(parser->Parse(valid_xml_with_invalid_sizes));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  first_result = &parser->results().list[0];
-  EXPECT_FALSE(first_result->manifest.packages.empty());
-  EXPECT_EQ(1234, first_result->manifest.packages[0].size);
-  EXPECT_EQ(9223372036854775807, first_result->manifest.packages[1].size);
-  EXPECT_EQ(0, first_result->manifest.packages[2].size);
-  EXPECT_EQ(0, first_result->manifest.packages[3].size);
-  EXPECT_EQ(0, first_result->manifest.packages[4].size);
-  EXPECT_EQ(0, first_result->manifest.packages[5].size);
-  EXPECT_EQ(0, first_result->manifest.packages[5].size);
-  EXPECT_EQ(1234, first_result->manifest.packages[7].sizediff);
-  EXPECT_EQ(9223372036854775807, first_result->manifest.packages[8].sizediff);
-  EXPECT_EQ(0, first_result->manifest.packages[9].sizediff);
-  EXPECT_EQ(0, first_result->manifest.packages[10].sizediff);
-  EXPECT_EQ(0, first_result->manifest.packages[11].sizediff);
-  EXPECT_EQ(0, first_result->manifest.packages[12].sizediff);
-
-  // Parse xml with a <daystart> element.
-  EXPECT_TRUE(parser->Parse(kWithDaystart));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  EXPECT_EQ(parser->results().daystart_elapsed_seconds, 456);
-
-  // Parse a no-update response.
-  EXPECT_TRUE(parser->Parse(kNoUpdate));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  first_result = &parser->results().list[0];
-  EXPECT_STREQ("noupdate", first_result->status.c_str());
-  EXPECT_EQ(first_result->extension_id, "12345");
-  EXPECT_EQ(first_result->manifest.version, "");
-
-  // Parse xml with one error and one success <app> tag.
-  EXPECT_TRUE(parser->Parse(kTwoAppsOneError));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_EQ(2u, parser->results().list.size());
-  first_result = &parser->results().list[0];
-  EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
-  EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
-  EXPECT_TRUE(first_result->manifest.version.empty());
-  const ProtocolParser::Result* second_result = &parser->results().list[1];
-  EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
-  EXPECT_STREQ("ok", second_result->status.c_str());
-  EXPECT_EQ("1.2.3.4", second_result->manifest.version);
-
-  // Parse xml with two apps setting the cohort info.
-  EXPECT_TRUE(parser->Parse(kTwoAppsSetCohort));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_EQ(2u, parser->results().list.size());
-  first_result = &parser->results().list[0];
-  EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
-  EXPECT_NE(first_result->cohort_attrs.find("cohort"),
-            first_result->cohort_attrs.end());
-  EXPECT_EQ(first_result->cohort_attrs.find("cohort")->second, "1:2q3/");
-  EXPECT_EQ(first_result->cohort_attrs.find("cohortname"),
-            first_result->cohort_attrs.end());
-  EXPECT_EQ(first_result->cohort_attrs.find("cohorthint"),
-            first_result->cohort_attrs.end());
-  second_result = &parser->results().list[1];
-  EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
-  EXPECT_NE(second_result->cohort_attrs.find("cohort"),
-            second_result->cohort_attrs.end());
-  EXPECT_EQ(second_result->cohort_attrs.find("cohort")->second, "1:33z@0.33");
-  EXPECT_NE(second_result->cohort_attrs.find("cohortname"),
-            second_result->cohort_attrs.end());
-  EXPECT_EQ(second_result->cohort_attrs.find("cohortname")->second, "cname");
-  EXPECT_EQ(second_result->cohort_attrs.find("cohorthint"),
-            second_result->cohort_attrs.end());
-
-  EXPECT_TRUE(parser->Parse(kUpdateCheckStatusOkWithRunAction));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  first_result = &parser->results().list[0];
-  EXPECT_STREQ("ok", first_result->status.c_str());
-  EXPECT_EQ(first_result->extension_id, "12345");
-  EXPECT_STREQ("this", first_result->action_run.c_str());
-
-  EXPECT_TRUE(parser->Parse(kUpdateCheckStatusNoUpdateWithRunAction));
-  EXPECT_TRUE(parser->errors().empty());
-  EXPECT_FALSE(parser->results().list.empty());
-  first_result = &parser->results().list[0];
-  EXPECT_STREQ("noupdate", first_result->status.c_str());
-  EXPECT_EQ(first_result->extension_id, "12345");
-  EXPECT_STREQ("this", first_result->action_run.c_str());
-
-  EXPECT_TRUE(parser->Parse(kUpdateCheckStatusErrorWithRunAction));
-  EXPECT_FALSE(parser->errors().empty());
-  EXPECT_TRUE(parser->results().list.empty());
-
-  EXPECT_TRUE(parser->Parse(kAppsStatusError));
-  EXPECT_STREQ("Unknown app status", parser->errors().c_str());
-  EXPECT_EQ(3u, parser->results().list.size());
-  first_result = &parser->results().list[0];
-  EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
-  EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
-  EXPECT_TRUE(first_result->manifest.version.empty());
-  second_result = &parser->results().list[1];
-  EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
-  EXPECT_STREQ("restricted", second_result->status.c_str());
-  EXPECT_TRUE(second_result->manifest.version.empty());
-  const ProtocolParser::Result* third_result = &parser->results().list[2];
-  EXPECT_EQ(third_result->extension_id, "cccccccc");
-  EXPECT_STREQ("error-invalidAppId", third_result->status.c_str());
-  EXPECT_TRUE(third_result->manifest.version.empty());
-}
-
-}  // namespace update_client
diff --git a/components/update_client/protocol_serializer_xml.cc b/components/update_client/protocol_serializer_xml.cc
deleted file mode 100644
index 5cf61e4c..0000000
--- a/components/update_client/protocol_serializer_xml.cc
+++ /dev/null
@@ -1,200 +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.
-
-#include "components/update_client/protocol_serializer_xml.h"
-
-#include <memory>
-
-#include "base/strings/stringprintf.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "components/update_client/updater_state.h"
-
-namespace update_client {
-
-std::string ProtocolSerializerXml::Serialize(
-    const protocol_request::Request& request) const {
-  std::string msg;
-  base::StringAppendF(&msg,
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
-                      "<request protocol=\"%s\"",
-                      kProtocolVersion);
-
-  // Constant information for this updater.
-  base::StringAppendF(&msg, " dedup=\"cr\" acceptformat=\"crx2,crx3\"");
-
-  if (!request.additional_attributes.empty()) {
-    for (const auto& attr : request.additional_attributes) {
-      base::StringAppendF(&msg, " %s=\"%s\"", attr.first.c_str(),
-                          attr.second.c_str());
-    }
-  }
-
-  // Session id and request id.
-  base::StringAppendF(&msg, " sessionid=\"%s\" requestid=\"%s\"",
-                      request.session_id.c_str(), request.request_id.c_str());
-
-  // Chrome version and platform information.
-  base::StringAppendF(&msg,
-                      " updater=\"%s\" updaterversion=\"%s\" prodversion=\"%s\""
-                      " lang=\"%s\" os=\"%s\" arch=\"%s\" nacl_arch=\"%s\"",
-                      request.updatername.c_str(),
-                      request.updaterversion.c_str(),
-                      request.prodversion.c_str(), request.lang.c_str(),
-                      request.operating_system.c_str(), request.arch.c_str(),
-                      request.nacl_arch.c_str());
-#if defined(OS_WIN)
-  if (request.is_wow64)
-    base::StringAppendF(&msg, " wow64=\"%d\"", request.is_wow64);
-#endif  // OS_WIN
-  if (!request.updaterchannel.empty()) {
-    base::StringAppendF(&msg, " updaterchannel=\"%s\"",
-                        request.updaterchannel.c_str());
-  }
-  if (!request.prodchannel.empty()) {
-    base::StringAppendF(&msg, " prodchannel=\"%s\"",
-                        request.prodchannel.c_str());
-  }
-  if (!request.dlpref.empty())
-    base::StringAppendF(&msg, " dlpref=\"%s\"", request.dlpref.c_str());
-
-  if (request.domain_joined) {
-    base::StringAppendF(&msg, " %s=\"%d\"", UpdaterState::kIsEnterpriseManaged,
-                        *request.domain_joined);
-  }
-  base::StringAppendF(&msg, ">");
-
-  // HW platform information.
-  base::StringAppendF(&msg, "<hw physmemory=\"%d\"/>", request.hw.physmemory);
-
-  // OS version and platform information.
-  base::StringAppendF(&msg, "<os platform=\"%s\" arch=\"%s\"",
-                      request.os.platform.c_str(), request.os.arch.c_str());
-  if (!request.os.version.empty())
-    base::StringAppendF(&msg, " version=\"%s\"", request.os.version.c_str());
-  if (!request.os.service_pack.empty())
-    base::StringAppendF(&msg, " sp=\"%s\"", request.os.service_pack.c_str());
-  base::StringAppendF(&msg, "/>");
-
-#if defined(GOOGLE_CHROME_BUILD)
-  if (request.updater) {
-    const auto& updater = *request.updater;
-    base::StringAppendF(&msg, "<updater name=\"%s\"", updater.name.c_str());
-    if (!updater.version.empty())
-      base::StringAppendF(&msg, " version=\"%s\"", updater.version.c_str());
-    if (updater.last_checked)
-      base::StringAppendF(&msg, " lastchecked=\"%d\"", *updater.last_checked);
-    if (updater.last_started)
-      base::StringAppendF(&msg, " laststarted=\"%d\"", *updater.last_started);
-    base::StringAppendF(
-        &msg,
-        " ismachine=\"%d\" autoupdatecheckenabled=\"%d\" updatepolicy=\"%d\"/>",
-        updater.is_machine, updater.autoupdate_check_enabled,
-        updater.update_policy);
-  }
-#endif
-
-  for (const auto& app : request.apps) {
-    // Begin <app> attributes.
-    base::StringAppendF(&msg, "<app appid=\"%s\"", app.app_id.c_str());
-    base::StringAppendF(&msg, " version=\"%s\"", app.version.c_str());
-    if (!app.brand_code.empty())
-      base::StringAppendF(&msg, " brand=\"%s\"", app.brand_code.c_str());
-    if (!app.install_source.empty()) {
-      base::StringAppendF(&msg, " installsource=\"%s\"",
-                          app.install_source.c_str());
-    }
-    if (!app.install_location.empty()) {
-      base::StringAppendF(&msg, " installedby=\"%s\"",
-                          app.install_location.c_str());
-    }
-    for (const auto& attr : app.installer_attributes) {
-      base::StringAppendF(&msg, " %s=\"%s\"", attr.first.c_str(),
-                          attr.second.c_str());
-    }
-    if (!app.cohort.empty())
-      base::StringAppendF(&msg, " cohort=\"%s\"", app.cohort.c_str());
-    if (!app.cohort_name.empty())
-      base::StringAppendF(&msg, " cohortname=\"%s\"", app.cohort_name.c_str());
-    if (!app.cohort_hint.empty())
-      base::StringAppendF(&msg, " cohorthint=\"%s\"", app.cohort_hint.c_str());
-    if (app.enabled)
-      base::StringAppendF(&msg, " enabled=\"%d\"", *app.enabled ? 1 : 0);
-    base::StringAppendF(&msg, ">");
-    // End <app> attributes.
-
-    if (app.disabled_reasons) {
-      for (const int disabled_reason : *app.disabled_reasons)
-        base::StringAppendF(&msg, "<disabled reason=\"%d\"/>", disabled_reason);
-    }
-
-    if (app.update_check) {
-      base::StringAppendF(&msg, "<updatecheck");
-      if (app.update_check->is_update_disabled)
-        base::StringAppendF(&msg, " updatedisabled=\"true\"");
-      base::StringAppendF(&msg, "/>");
-    }
-
-    if (app.ping) {
-      base::StringAppendF(&msg, "<ping");
-
-      // Output "ad" or "a" only if the this app has been seen 'active'.
-      if (app.ping->date_last_active) {
-        base::StringAppendF(&msg, " ad=\"%d\"", *app.ping->date_last_active);
-      } else if (app.ping->days_since_last_active_ping) {
-        base::StringAppendF(&msg, " a=\"%d\"",
-                            *app.ping->days_since_last_active_ping);
-      }
-
-      // Output "rd" if valid or "r" as a last resort roll call metric.
-      if (app.ping->date_last_roll_call)
-        base::StringAppendF(&msg, " rd=\"%d\"", *app.ping->date_last_roll_call);
-      else
-        base::StringAppendF(&msg, " r=\"%d\"",
-                            app.ping->days_since_last_roll_call);
-      if (!app.ping->ping_freshness.empty())
-        base::StringAppendF(&msg, " ping_freshness=\"%s\"",
-                            app.ping->ping_freshness.c_str());
-
-      base::StringAppendF(&msg, "/>");  // End <ping>.
-    }
-
-    if (!app.fingerprint.empty()) {
-      base::StringAppendF(&msg,
-                          "<packages>"
-                          "<package fp=\"%s\"/>"
-                          "</packages>",
-                          app.fingerprint.c_str());
-    }
-
-    if (app.events) {
-      for (const auto& event : *app.events) {
-        DCHECK(event.is_dict());
-        DCHECK(!event.DictEmpty());
-        base::StringAppendF(&msg, "<event");
-        const auto& attrs = event.DictItems();
-        for (auto it = attrs.begin(); it != attrs.end(); ++it) {
-          base::StringAppendF(&msg, " %s=", (*it).first.c_str());
-          const auto& value = (*it).second;
-          if (value.is_string())
-            base::StringAppendF(&msg, "\"%s\"", value.GetString().c_str());
-          else if (value.is_int())
-            base::StringAppendF(&msg, "\"%d\"", value.GetInt());
-          else if (value.is_double())
-            base::StringAppendF(&msg, "\"%.0f\"", value.GetDouble());
-          else
-            NOTREACHED();
-        }
-        base::StringAppendF(&msg, "/>");
-      }
-    }
-
-    base::StringAppendF(&msg, "</app>");
-  }
-
-  base::StringAppendF(&msg, "</request>");
-  return msg;
-}
-
-}  // namespace update_client
diff --git a/components/update_client/protocol_serializer_xml.h b/components/update_client/protocol_serializer_xml.h
deleted file mode 100644
index 48a47c2..0000000
--- a/components/update_client/protocol_serializer_xml.h
+++ /dev/null
@@ -1,28 +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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_XML_H_
-#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_XML_H_
-
-#include <string>
-
-#include "components/update_client/protocol_serializer.h"
-
-namespace update_client {
-
-class ProtocolSerializerXml final : public ProtocolSerializer {
- public:
-  ProtocolSerializerXml() = default;
-
-  // Overrides for ProtocolSerializer.
-  std::string Serialize(
-      const protocol_request::Request& request) const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ProtocolSerializerXml);
-};
-
-}  // namespace update_client
-
-#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_XML_H_
diff --git a/components/update_client/protocol_serializer_xml_unittest.cc b/components/update_client/protocol_serializer_xml_unittest.cc
deleted file mode 100644
index 95bee49d..0000000
--- a/components/update_client/protocol_serializer_xml_unittest.cc
+++ /dev/null
@@ -1,91 +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.
-
-#include <memory>
-#include <string>
-
-#include "components/update_client/protocol_serializer.h"
-#include "components/update_client/protocol_serializer_xml.h"
-#include "components/update_client/updater_state.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/re2/src/re2/re2.h"
-
-using std::string;
-
-namespace update_client {
-
-TEST(SerializeRequestXml, Serialize) {
-  // When no updater state is provided, then check that the elements and
-  // attributes related to the updater state are not serialized.
-  const auto request =
-      std::make_unique<ProtocolSerializerXml>()->Serialize(MakeProtocolRequest(
-          "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0", "lang",
-          "channel", "OS", "cacheable", {{"extra", "params"}}, nullptr, {}));
-  constexpr char regex[] =
-      R"(<\?xml version="1\.0" encoding="UTF-8"\?>)"
-      R"(<request protocol="3\.1" )"
-      R"(dedup="cr" acceptformat="crx2,crx3" extra="params" )"
-      R"(sessionid="{[-\w]{36}}" requestid="{[-\w]{36}}" )"
-      R"(updater="prod_id" updaterversion="1\.0" prodversion="1\.0" )"
-      R"(lang="lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" (wow64="1" )?)"
-      R"(updaterchannel="channel" prodchannel="channel" dlpref="cacheable">)"
-      R"(<hw physmemory="\d+"/>)"
-      R"(<os platform="OS" arch="[,-.\w]+" version="[-.\w]+"( sp="[\s\w]+")?/>)"
-      R"(</request>)";
-  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
-}
-
-TEST(SerializeRequestXml, DownloadPreference) {
-  // Verifies that an empty |download_preference| is not serialized.
-  const auto serializer = std::make_unique<ProtocolSerializerXml>();
-  auto request = serializer->Serialize(
-      MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
-                          "", "", "", {}, nullptr, {}));
-  EXPECT_FALSE(RE2::PartialMatch(request, " dlpref=")) << request;
-
-  // Verifies that |download_preference| is serialized.
-  request = serializer->Serialize(
-      MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
-                          "", "", "cacheable", {}, nullptr, {}));
-  EXPECT_TRUE(RE2::PartialMatch(request, R"( dlpref="cacheable")")) << request;
-}
-
-// When present, updater state attributes are only serialized for Google builds,
-// except the |domainjoined| attribute, which is serialized in all cases.
-TEST(SerializeRequestXml, UpdaterStateAttributes) {
-  const auto serializer = std::make_unique<ProtocolSerializerXml>();
-  UpdaterState::Attributes attributes;
-  attributes["ismachine"] = "1";
-  attributes["domainjoined"] = "1";
-  attributes["name"] = "Omaha";
-  attributes["version"] = "1.2.3.4";
-  attributes["laststarted"] = "1";
-  attributes["lastchecked"] = "2";
-  attributes["autoupdatecheckenabled"] = "0";
-  attributes["updatepolicy"] = "-1";
-  const auto request = serializer->Serialize(MakeProtocolRequest(
-      "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0", "lang",
-      "channel", "OS", "cacheable", {{"extra", "params"}}, &attributes, {}));
-  constexpr char regex[] =
-      R"(<\?xml version="1\.0" encoding="UTF-8"\?>)"
-      R"(<request protocol="3\.1" )"
-      R"(dedup="cr" acceptformat="crx2,crx3" extra="params" )"
-      R"(sessionid="{[-\w]{36}}" requestid="{[-\w]{36}}" )"
-      R"(updater="prod_id" updaterversion="1\.0" prodversion="1\.0" )"
-      R"(lang="lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" (wow64="1" )?)"
-      R"(updaterchannel="channel" prodchannel="channel" dlpref="cacheable" )"
-      R"(domainjoined="1">)"
-      R"(<hw physmemory="\d+"/>)"
-      R"(<os platform="OS" arch="[,-.\w]+" version="[-.\w]+"( sp="[\s\w]+")?/>)"
-#if defined(GOOGLE_CHROME_BUILD)
-      R"(<updater name="Omaha" version="1\.2\.3\.4" lastchecked="2" )"
-      R"(laststarted="1" ismachine="1" autoupdatecheckenabled="0" )"
-      R"(updatepolicy="-1"/>)"
-#endif  // GOOGLE_CHROME_BUILD
-      R"(</request>)";
-
-  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
-}
-
-}  // namespace update_client
diff --git a/components/update_client/request_sender_unittest.cc b/components/update_client/request_sender_unittest.cc
index 7d5debbc..e1e16d8 100644
--- a/components/update_client/request_sender_unittest.cc
+++ b/components/update_client/request_sender_unittest.cc
@@ -130,7 +130,7 @@
 TEST_P(RequestSenderTest, RequestSendSuccess) {
   EXPECT_TRUE(
       post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
-                                       test_file("updatecheck_reply_1.xml")));
+                                       test_file("updatecheck_reply_1.json")));
 
   const bool is_foreground = GetParam();
   request_sender_->Send(
@@ -154,10 +154,7 @@
 
   // Check the response post conditions.
   EXPECT_EQ(0, error_);
-  EXPECT_TRUE(base::StartsWith(response_,
-                               "<?xml version='1.0' encoding='UTF-8'?>",
-                               base::CompareCase::SENSITIVE));
-  EXPECT_EQ(505ul, response_.size());
+  EXPECT_EQ(419ul, response_.size());
 
   // Check the interactivity header value.
   const auto extra_request_headers =
@@ -242,7 +239,7 @@
 TEST_F(RequestSenderTest, RequestSendCupError) {
   EXPECT_TRUE(
       post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
-                                       test_file("updatecheck_reply_1.xml")));
+                                       test_file("updatecheck_reply_1.json")));
 
   const std::vector<GURL> urls = {GURL(kUrl1)};
   request_sender_ = std::make_unique<RequestSender>(config_);
diff --git a/components/update_client/test_configurator.cc b/components/update_client/test_configurator.cc
index 7820f3b..2fc9e24a 100644
--- a/components/update_client/test_configurator.cc
+++ b/components/update_client/test_configurator.cc
@@ -37,7 +37,6 @@
       ondemand_time_(0),
       enabled_cup_signing_(false),
       enabled_component_updates_(true),
-      use_JSON_(false),
       connector_(connector_factory_.CreateConnector()),
       unzip_service_(
           connector_factory_.RegisterInstance(unzip::mojom::kServiceName)),
@@ -183,10 +182,6 @@
   app_guid_ = app_guid;
 }
 
-void TestConfigurator::SetUseJSON(bool use_JSON) {
-  use_JSON_ = use_JSON;
-}
-
 PrefService* TestConfigurator::GetPrefService() const {
   return nullptr;
 }
@@ -209,9 +204,7 @@
 
 std::unique_ptr<ProtocolHandlerFactory>
 TestConfigurator::GetProtocolHandlerFactory() const {
-  if (use_JSON_)
-    return std::make_unique<ProtocolHandlerFactoryJSON>();
-  return std::make_unique<ProtocolHandlerFactoryXml>();
+  return std::make_unique<ProtocolHandlerFactoryJSON>();
 }
 
 RecoveryCRXElevator TestConfigurator::GetRecoveryCRXElevator() const {
diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc
index c74a51f..ac4fa08a 100644
--- a/components/update_client/update_checker_unittest.cc
+++ b/components/update_client/update_checker_unittest.cc
@@ -109,10 +109,7 @@
 
 }  // namespace
 
-using UpdateCheckTestParams =
-    std::tuple<bool /*is_foreground*/, bool /*use_JSON*/>;
-
-class UpdateCheckerTest : public testing::TestWithParam<UpdateCheckTestParams> {
+class UpdateCheckerTest : public testing::TestWithParam<bool> {
  public:
   UpdateCheckerTest();
   ~UpdateCheckerTest() override;
@@ -149,7 +146,6 @@
 
   scoped_refptr<UpdateContext> update_context_;
 
-  bool use_JSON_ = false;
   bool is_foreground_ = false;
 
  private:
@@ -161,10 +157,8 @@
   DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest);
 };
 
-// This test is parameterized for |is_foreground and |use_JSON|.
-INSTANTIATE_TEST_SUITE_P(Parameterized,
-                         UpdateCheckerTest,
-                         testing::Combine(testing::Bool(), testing::Bool()));
+// This test is parameterized for |is_foreground|.
+INSTANTIATE_TEST_SUITE_P(Parameterized, UpdateCheckerTest, testing::Bool());
 
 UpdateCheckerTest::UpdateCheckerTest()
     : scoped_task_environment_(
@@ -174,10 +168,9 @@
 }
 
 void UpdateCheckerTest::SetUp() {
-  std::tie(is_foreground_, use_JSON_) = GetParam();
+  is_foreground_ = GetParam();
 
   config_ = base::MakeRefCounted<TestConfigurator>();
-  config_->SetUseJSON(use_JSON_);
 
   pref_ = std::make_unique<TestingPrefServiceSimple>();
   activity_data_service_ = std::make_unique<ActivityDataServiceTest>();
@@ -258,8 +251,7 @@
 TEST_P(UpdateCheckerTest, UpdateCheckSuccess) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                          : "updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -282,55 +274,54 @@
       << post_interceptor_->GetRequestsAsString();
 
   // Sanity check the request.
-  const auto& request = post_interceptor_->GetRequestBody(0);
-  if (use_JSON_) {
-    const auto root = base::JSONReader::Read(request);
-    ASSERT_TRUE(root);
-    const auto* request = root->FindKey("request");
-    ASSERT_TRUE(request);
-    EXPECT_TRUE(request->FindKey("@os"));
-    EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
-    EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
-    EXPECT_TRUE(request->FindKey("arch"));
-    EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
-    EXPECT_EQ("params", request->FindKey("extra")->GetString());
-    EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
-    EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
-    EXPECT_TRUE(request->FindKey("nacl_arch"));
-    EXPECT_EQ("fake_channel_string",
-              request->FindKey("prodchannel")->GetString());
-    EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
-    EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
-    EXPECT_TRUE(request->FindKey("requestid"));
-    EXPECT_TRUE(request->FindKey("sessionid"));
-    EXPECT_EQ("1", request->FindKey("testrequest")->GetString());
-    EXPECT_EQ("fake_channel_string",
-              request->FindKey("updaterchannel")->GetString());
-    EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+  const auto root =
+      base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+  ASSERT_TRUE(root);
+  const auto* request = root->FindKey("request");
+  ASSERT_TRUE(request);
+  EXPECT_TRUE(request->FindKey("@os"));
+  EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+  EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+  EXPECT_TRUE(request->FindKey("arch"));
+  EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+  EXPECT_EQ("params", request->FindKey("extra")->GetString());
+  EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+  EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+  EXPECT_TRUE(request->FindKey("nacl_arch"));
+  EXPECT_EQ("fake_channel_string",
+            request->FindKey("prodchannel")->GetString());
+  EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+  EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+  EXPECT_TRUE(request->FindKey("requestid"));
+  EXPECT_TRUE(request->FindKey("sessionid"));
+  EXPECT_EQ("1", request->FindKey("testrequest")->GetString());
+  EXPECT_EQ("fake_channel_string",
+            request->FindKey("updaterchannel")->GetString());
+  EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
 
-    // No "dlpref" is sent by default.
-    EXPECT_FALSE(request->FindKey("dlpref"));
+  // No "dlpref" is sent by default.
+  EXPECT_FALSE(request->FindKey("dlpref"));
 
-    EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
-    EXPECT_EQ("Fake Operating System",
-              request->FindPath({"os", "platform"})->GetString());
-    EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+  EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+  EXPECT_EQ("Fake Operating System",
+            request->FindPath({"os", "platform"})->GetString());
+  EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
 
-    const auto& app = request->FindKey("app")->GetList()[0];
-    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
-    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
-    EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
-    if (is_foreground_)
-      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
-    EXPECT_EQ("some_ap", app.FindKey("ap")->GetString());
-    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
-    EXPECT_TRUE(app.FindKey("updatecheck"));
-    EXPECT_TRUE(app.FindKey("ping"));
-    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
-    EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
-                         ->GetList()[0]
-                         .FindKey("fp")
-                         ->GetString());
+  const auto& app = request->FindKey("app")->GetList()[0];
+  EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+  EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+  EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+  if (is_foreground_)
+    EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+  EXPECT_EQ("some_ap", app.FindKey("ap")->GetString());
+  EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+  EXPECT_TRUE(app.FindKey("updatecheck"));
+  EXPECT_TRUE(app.FindKey("ping"));
+  EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+  EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                       ->GetList()[0]
+                       .FindKey("fp")
+                       ->GetString());
 
 #if (OS_WIN)
     EXPECT_TRUE(request->FindKey("domainjoined"));
@@ -343,40 +334,6 @@
     EXPECT_TRUE(updater->FindKey("updatepolicy")->is_int());
 #endif  // GOOGLE_CHROME_BUILD
 #endif  // OS_WINDOWS
-  } else {
-    EXPECT_THAT(request, testing::HasSubstr(
-                             R"(request protocol="3.1" dedup="cr" )"
-                             R"(acceptformat="crx2,crx3" extra="params" )"
-                             R"(testrequest="1")"));
-    // The request must not contain any "dlpref" in the default case.
-    EXPECT_THAT(request, testing::Not(testing::HasSubstr(R"( dlpref=")")));
-    EXPECT_THAT(
-        request,
-        testing::HasSubstr(
-            std::string(R"(<app appid=")") + kUpdateItemId +
-            R"(" version="0.9" brand="TEST")" +
-            (is_foreground_ ? R"( installsource="ondemand")" : "") +
-            R"( ap="some_ap" enabled="1"><updatecheck/><ping r="-2"/>)"));
-    EXPECT_THAT(request,
-                testing::HasSubstr(
-                    R"(<packages><package fp="fp1"/></packages></app>)"));
-    EXPECT_THAT(request, testing::HasSubstr("<hw physmemory="));
-
-    // Tests that the product id is injected correctly from the configurator.
-    EXPECT_THAT(request, testing::HasSubstr(
-                             R"( updater="fake_prodid" updaterversion="30.0")"
-                             R"( prodversion="30.0" )"));
-    // Tests that there is a sessionid attribute.
-    EXPECT_THAT(request, testing::HasSubstr(" sessionid="));
-#if (OS_WIN)
-    EXPECT_THAT(request, testing::HasSubstr(" domainjoined="));
-#if defined(GOOGLE_CHROME_BUILD)
-    // Check the Omaha updater state data in the request.
-    EXPECT_THAT(request, testing::HasSubstr("<updater "));
-    EXPECT_THAT(request, testing::HasSubstr(R"( name="Omaha" )"));
-#endif  // GOOGLE_CHROME_BUILD
-#endif  // OS_WINDOWS
-  }
 
   // Sanity check the arguments of the callback after parsing.
   EXPECT_EQ(ErrorCategory::kNone, error_category_);
@@ -411,8 +368,7 @@
 TEST_P(UpdateCheckerTest, UpdateCheckInvalidAp) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                          : "updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -431,7 +387,6 @@
   RunThreads();
 
   const auto request = post_interceptor_->GetRequestBody(0);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -449,30 +404,12 @@
                          ->GetList()[0]
                          .FindKey("fp")
                          ->GetString());
-  } else {
-    if (is_foreground_) {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" brand="TEST" )"
-                               R"(installsource="ondemand" enabled="1">)"
-                               R"(<updatecheck/><ping r="-2"/>)"));
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" brand="TEST" enabled="1">)"
-                               R"(<updatecheck/><ping r="-2"/>)"));
-    }
-    EXPECT_THAT(request,
-                testing::HasSubstr(
-                    R"(<packages><package fp="fp1"/></packages></app>)"));
-  }
 }
 
 TEST_P(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                          : "updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
 
   config_->SetBrand("TOOLONG");   // Sets an invalid brand code.
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
@@ -489,7 +426,6 @@
 
   const auto request = post_interceptor_->GetRequestBody(0);
 
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -506,22 +442,6 @@
                          ->GetList()[0]
                          .FindKey("fp")
                          ->GetString());
-  } else {
-    if (is_foreground_) {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" installsource="ondemand" )"
-                               R"(enabled="1"><updatecheck/><ping r="-2"/>)"));
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" enabled="1">)" +
-                               R"(<updatecheck/><ping r="-2"/>)"));
-    }
-    EXPECT_THAT(request,
-                testing::HasSubstr(
-                    R"(<packages><package fp="fp1"/></packages></app>)"));
-  }
 }
 
 // Simulates a 403 server response error.
@@ -553,8 +473,7 @@
 TEST_P(UpdateCheckerTest, UpdateCheckDownloadPreference) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                          : "updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
 
   config_->SetDownloadPreference(string("cacheable"));
 
@@ -572,14 +491,10 @@
 
   // The request must contain dlpref="cacheable".
   const auto request = post_interceptor_->GetRequestBody(0);
-  if (use_JSON_) {
     const auto root = base::JSONReader().Read(request);
     ASSERT_TRUE(root);
     EXPECT_EQ("cacheable",
               root->FindKey("request")->FindKey("dlpref")->GetString());
-  } else {
-    EXPECT_THAT(request, testing::HasSubstr(R"( dlpref="cacheable")"));
-  }
 }
 
 // This test is checking that an update check signed with CUP fails, since there
@@ -588,8 +503,7 @@
 TEST_P(UpdateCheckerTest, UpdateCheckCupError) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                          : "updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
 
   config_->SetEnabledCupSigning(true);
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
@@ -611,7 +525,6 @@
 
   // Sanity check the request.
   const auto& request = post_interceptor_->GetRequestBody(0);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -629,24 +542,6 @@
                          .FindKey("fp")
                          ->GetString());
 
-  } else {
-    if (is_foreground_) {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(<app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" brand="TEST" )"
-                               R"(installsource="ondemand" enabled="1">)"
-                               R"(<updatecheck/><ping r="-2"/>)"));
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(<app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" brand="TEST" enabled="1">)"
-                               R"(<updatecheck/><ping r="-2"/>)"));
-    }
-    EXPECT_THAT(request,
-                testing::HasSubstr(
-                    R"(<packages><package fp="fp1"/></packages></app>)"));
-  }
-
   // Expect an error since the response is not trusted.
   EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
   EXPECT_EQ(-10000, error_);
@@ -680,8 +575,7 @@
 // Tests that the PersistedData will get correctly update and reserialize
 // the elapsed_days value.
 TEST_P(UpdateCheckerTest, UpdateCheckLastRollCall) {
-  const char* filename =
-      use_JSON_ ? "updatecheck_reply_4.json" : "updatecheck_reply_4.xml";
+  const char* filename = "updatecheck_reply_4.json";
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
@@ -714,7 +608,6 @@
   ASSERT_EQ(2, post_interceptor_->GetCount())
       << post_interceptor_->GetRequestsAsString();
 
-  if (use_JSON_) {
     const auto root1 =
         base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
     ASSERT_TRUE(root1);
@@ -726,17 +619,10 @@
     const auto& app2 = root2->FindKey("request")->FindKey("app")->GetList()[0];
     EXPECT_EQ(3383, app2.FindPath({"ping", "rd"})->GetInt());
     EXPECT_TRUE(app2.FindPath({"ping", "ping_freshness"})->is_string());
-  } else {
-    EXPECT_THAT(post_interceptor_->GetRequestBody(0),
-                testing::HasSubstr(R"(<ping r="5")"));
-    EXPECT_THAT(post_interceptor_->GetRequestBody(1),
-                testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
-  }
 }
 
 TEST_P(UpdateCheckerTest, UpdateCheckLastActive) {
-  const char* filename =
-      use_JSON_ ? "updatecheck_reply_4.json" : "updatecheck_reply_4.xml";
+  const char* filename = "updatecheck_reply_4.json";
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
@@ -788,7 +674,6 @@
   ASSERT_EQ(3, post_interceptor_->GetCount())
       << post_interceptor_->GetRequestsAsString();
 
-  if (use_JSON_) {
     {
       const auto root =
           base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
@@ -814,15 +699,6 @@
       EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
       EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
     }
-  } else {
-    EXPECT_THAT(post_interceptor_->GetRequestBody(0),
-                testing::HasSubstr(R"(<ping a="10" r="-2"/>)"));
-    EXPECT_THAT(
-        post_interceptor_->GetRequestBody(1),
-        testing::HasSubstr(R"(<ping ad="3383" rd="3383" ping_freshness=)"));
-    EXPECT_THAT(post_interceptor_->GetRequestBody(2),
-                testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
-  }
 }
 
 TEST_P(UpdateCheckerTest, UpdateCheckInstallSource) {
@@ -840,34 +716,26 @@
           config_->test_url_loader_factory());
       EXPECT_TRUE(post_interceptor->ExpectRequest(
           std::make_unique<PartialMatch>("updatecheck"),
-          test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                              : "updatecheck_reply_1.xml")));
+          test_file("updatecheck_reply_1.json")));
       update_checker_->CheckForUpdates(
           update_context_->session_id, {kUpdateItemId}, components, {}, false,
           base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                          base::Unretained(this)));
       RunThreads();
       const auto& request = post_interceptor->GetRequestBody(0);
-      if (use_JSON_) {
         const auto root = base::JSONReader::Read(request);
         ASSERT_TRUE(root);
         const auto& app =
             root->FindKey("request")->FindKey("app")->GetList()[0];
         EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
         EXPECT_FALSE(app.FindKey("installedby"));
-      } else {
-        EXPECT_THAT(request, testing::HasSubstr(R"(installsource="ondemand")"));
-        EXPECT_THAT(request,
-                    testing::Not(testing::HasSubstr(R"(installedby=)")));
-      }
     }
     {
       auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
           config_->test_url_loader_factory());
       EXPECT_TRUE(post_interceptor->ExpectRequest(
           std::make_unique<PartialMatch>("updatecheck"),
-          test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                              : "updatecheck_reply_1.xml")));
+          test_file("updatecheck_reply_1.json")));
       crx_component->install_source = "sideload";
       crx_component->install_location = "policy";
       component->set_crx_component(*crx_component);
@@ -877,17 +745,12 @@
                          base::Unretained(this)));
       RunThreads();
       const auto& request = post_interceptor->GetRequestBody(0);
-      if (use_JSON_) {
         const auto root = base::JSONReader::Read(request);
         ASSERT_TRUE(root);
         const auto& app =
             root->FindKey("request")->FindKey("app")->GetList()[0];
         EXPECT_EQ("sideload", app.FindKey("installsource")->GetString());
         EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
-      } else {
-        EXPECT_THAT(request, testing::HasSubstr(R"(installsource="sideload")"));
-        EXPECT_THAT(request, testing::HasSubstr(R"(installedby="policy")"));
-      }
     }
     return;
   }
@@ -898,30 +761,24 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_FALSE(app.FindKey("installsource"));
-    } else {
-      EXPECT_THAT(request, testing::Not(testing::HasSubstr("installsource=")));
-    }
   }
   {
     auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     crx_component->install_source = "webstore";
     crx_component->install_location = "external";
     component->set_crx_component(*crx_component);
@@ -931,16 +788,11 @@
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ("webstore", app.FindKey("installsource")->GetString());
       EXPECT_EQ("external", app.FindKey("installedby")->GetString());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(installsource="webstore")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(installedby="external")"));
-    }
   }
 }
 
@@ -958,24 +810,18 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_FALSE(app.FindKey("disabled"));
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="1")"));
-      EXPECT_THAT(request, testing::Not(testing::HasSubstr("<disabled")));
-    }
   }
 
   {
@@ -985,24 +831,18 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_FALSE(app.FindKey("disabled"));
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="1")"));
-      EXPECT_THAT(request, testing::Not(testing::HasSubstr("<disabled")));
-    }
   }
 
   {
@@ -1012,15 +852,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1028,10 +866,6 @@
       const auto& disabled = app.FindKey("disabled")->GetList();
       EXPECT_EQ(1u, disabled.size());
       EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="0")"));
-    }
   }
   {
     crx_component->disabled_reasons = {1};
@@ -1040,15 +874,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1056,10 +888,6 @@
       const auto& disabled = app.FindKey("disabled")->GetList();
       EXPECT_EQ(1u, disabled.size());
       EXPECT_EQ(1, disabled[0].FindKey("reason")->GetInt());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="1")"));
-    }
   }
 
   {
@@ -1069,15 +897,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1087,12 +913,6 @@
       EXPECT_EQ(4, disabled[0].FindKey("reason")->GetInt());
       EXPECT_EQ(8, disabled[1].FindKey("reason")->GetInt());
       EXPECT_EQ(16, disabled[2].FindKey("reason")->GetInt());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="4")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="8")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="16")"));
-    }
   }
 
   {
@@ -1102,15 +922,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1121,13 +939,6 @@
       EXPECT_EQ(4, disabled[1].FindKey("reason")->GetInt());
       EXPECT_EQ(8, disabled[2].FindKey("reason")->GetInt());
       EXPECT_EQ(16, disabled[3].FindKey("reason")->GetInt());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="0")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="4")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="8")"));
-      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="16")"));
-    }
   }
 }
 
@@ -1154,15 +965,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1170,12 +979,6 @@
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
-    } else {
-      EXPECT_THAT(
-          request,
-          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
-    }
   }
   {
     // Tests the scenario where:
@@ -1188,15 +991,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, false,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1204,12 +1005,6 @@
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_TRUE(app.FindPath({"updatecheck", "updatedisabled"})->GetBool());
-    } else {
-      EXPECT_THAT(request, testing::HasSubstr(
-                               std::string(R"(<app appid=")") + kUpdateItemId +
-                               R"(" version="0.9" enabled="1">)" +
-                               R"(<updatecheck updatedisabled="true"/>)"));
-    }
   }
   {
     // Tests the scenario where:
@@ -1222,15 +1017,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, true,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1238,12 +1031,6 @@
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
-    } else {
-      EXPECT_THAT(
-          request,
-          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
-    }
   }
   {
     // Tests the scenario where:
@@ -1256,15 +1043,13 @@
         config_->test_url_loader_factory());
     EXPECT_TRUE(post_interceptor->ExpectRequest(
         std::make_unique<PartialMatch>("updatecheck"),
-        test_file(use_JSON_ ? "updatecheck_reply_1.json"
-                            : "updatecheck_reply_1.xml")));
+        test_file("updatecheck_reply_1.json")));
     update_checker_->CheckForUpdates(
         update_context_->session_id, {kUpdateItemId}, components, {}, true,
         base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
                        base::Unretained(this)));
     RunThreads();
     const auto& request = post_interceptor->GetRequestBody(0);
-    if (use_JSON_) {
       const auto root = base::JSONReader::Read(request);
       ASSERT_TRUE(root);
       const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1272,20 +1057,13 @@
       EXPECT_EQ("0.9", app.FindKey("version")->GetString());
       EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
       EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
-    } else {
-      EXPECT_THAT(
-          request,
-          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
-    }
   }
 }
 
 TEST_P(UpdateCheckerTest, NoUpdateActionRun) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_noupdate.json"
-                          : "updatecheck_reply_noupdate.xml")));
+      test_file("updatecheck_reply_noupdate.json")));
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
   IdToComponentPtrMap components;
@@ -1316,8 +1094,7 @@
 TEST_P(UpdateCheckerTest, UpdatePauseResume) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_noupdate.json"
-                          : "updatecheck_reply_noupdate.xml")));
+      test_file("updatecheck_reply_noupdate.json")));
   post_interceptor_->url_job_request_ready_callback(base::BindOnce(
       [](URLLoaderPostInterceptor* post_interceptor) {
         post_interceptor->Resume();
@@ -1340,7 +1117,6 @@
   RunThreads();
 
   const auto& request = post_interceptor_->GetRequestBody(0);
-  if (use_JSON_) {
     const auto root = base::JSONReader::Read(request);
     ASSERT_TRUE(root);
     const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
@@ -1355,15 +1131,6 @@
                          ->GetList()[0]
                          .FindKey("fp")
                          ->GetString());
-  } else {
-    EXPECT_THAT(request, testing::HasSubstr(
-                             std::string(R"(<app appid=")") + kUpdateItemId +
-                             R"(" version="0.9" brand="TEST" enabled="1">)" +
-                             R"(<updatecheck/><ping r="-2"/>)"));
-    EXPECT_THAT(request,
-                testing::HasSubstr(
-                    R"(<packages><package fp="fp1"/></packages></app>)"));
-  }
 }
 
 // Tests that an update checker object and its underlying SimpleURLLoader can
@@ -1374,7 +1141,7 @@
 
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file("updatecheck_reply_1.json")));
   post_interceptor_->url_job_request_ready_callback(base::BindOnce(
       [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
       std::move(quit_closure)));
@@ -1396,8 +1163,7 @@
 TEST_P(UpdateCheckerTest, ParseErrorProtocolVersionMismatch) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_parse_error.json"
-                          : "updatecheck_reply_parse_error.xml")));
+      test_file("updatecheck_reply_parse_error.json")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -1426,8 +1192,7 @@
 TEST_P(UpdateCheckerTest, ParseErrorAppStatusErrorUnknownApplication) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file(use_JSON_ ? "updatecheck_reply_unknownapp.json"
-                          : "updatecheck_reply_unknownapp.xml")));
+      test_file("updatecheck_reply_unknownapp.json")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc
index 20e2dd6..6b40ab0 100644
--- a/components/update_client/update_client_unittest.cc
+++ b/components/update_client/update_client_unittest.cc
@@ -31,7 +31,7 @@
 #include "components/update_client/network.h"
 #include "components/update_client/persisted_data.h"
 #include "components/update_client/ping_manager.h"
-#include "components/update_client/protocol_parser.h"
+#include "components/update_client/protocol_handler.h"
 #include "components/update_client/test_configurator.h"
 #include "components/update_client/test_installer.h"
 #include "components/update_client/update_checker.h"
@@ -3453,19 +3453,21 @@
       EXPECT_EQ(4u, ids_to_check.size());
 
       const std::string update_response =
-          R"(<?xml version="1.0" encoding="UTF-8"?>)"
-          R"(<response protocol="3.1">)"
-          R"(<app appid="jebgalgnebhfojomionfpkfelancnnkf")"
-          R"( status="error-unknownApplication"/>)"
-          R"(<app appid="abagagagagagagagagagagagagagagag")"
-          R"( status="restricted"/>)"
-          R"(<app appid="ihfokbkgjpifnbbojhneepfflplebdkc")"
-          R"( status="error-invalidAppId"/>)"
-          R"(<app appid="gjpmebpgbhcamgdgjcmnjfhggjpgcimm")"
-          R"( status="error-foobarApp"/>)"
-          R"(</response>)";
+          ")]}'"
+          R"({"response": {)"
+          R"( "protocol": "3.1",)"
+          R"( "app": [)"
+          R"({"appid": "jebgalgnebhfojomionfpkfelancnnkf",)"
+          R"( "status": "error-unknownApplication"},)"
+          R"({"appid": "abagagagagagagagagagagagagagagag",)"
+          R"( "status": "restricted"},)"
+          R"({"appid": "ihfokbkgjpifnbbojhneepfflplebdkc",)"
+          R"( "status": "error-invalidAppId"},)"
+          R"({"appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm",)"
+          R"( "status": "error-foobarApp"})"
+          R"(]}})";
 
-      const auto parser = ProtocolParser::Create();
+      const auto parser = ProtocolHandlerFactoryJSON().CreateParser();
       EXPECT_TRUE(parser->Parse(update_response));
 
       base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 499504c..abdfcd6f 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -847,6 +847,35 @@
       child_resource_provider, child_context_provider);
 }
 
+// Create two quads of specified colors on half-pixel boundaries.
+void CreateTestAxisAlignedQuads(const gfx::Rect& rect,
+                                SkColor front_color,
+                                SkColor back_color,
+                                bool needs_blending,
+                                bool force_aa_off,
+                                RenderPass* pass) {
+  gfx::Transform front_quad_to_target_transform;
+  front_quad_to_target_transform.Translate(50, 50);
+  front_quad_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
+                                       0.5f + 1.0f / (rect.height() * 2.0f));
+  SharedQuadState* front_shared_state = CreateTestSharedQuadState(
+      front_quad_to_target_transform, rect, pass, gfx::RRectF());
+
+  auto* front = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+  front->SetAll(front_shared_state, rect, rect, needs_blending, front_color,
+                force_aa_off);
+
+  gfx::Transform back_quad_to_target_transform;
+  back_quad_to_target_transform.Translate(25.5f, 25.5f);
+  back_quad_to_target_transform.Scale(0.5f, 0.5f);
+  SharedQuadState* back_shared_state = CreateTestSharedQuadState(
+      back_quad_to_target_transform, rect, pass, gfx::RRectF());
+
+  auto* back = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+  back->SetAll(back_shared_state, rect, rect, needs_blending, back_color,
+               force_aa_off);
+}
+
 using RendererTypes =
     ::testing::Types<GLRenderer,
                      SoftwareRenderer,
@@ -3146,8 +3175,8 @@
 }
 
 // This test tests that anti-aliasing works for axis aligned quads.
-// Anti-aliasing is only supported in the gl renderer.
-TYPED_TEST(GLOnlyRendererPixelTest, AxisAligned) {
+// Anti-aliasing is only supported in the gl and skia renderers.
+TYPED_TEST(GLCapableRendererPixelTest, AxisAligned) {
   gfx::Rect rect(this->device_viewport_size_);
 
   int id = 1;
@@ -3155,24 +3184,8 @@
   std::unique_ptr<RenderPass> pass =
       CreateTestRenderPass(id, rect, transform_to_root);
 
-  gfx::Transform red_quad_to_target_transform;
-  red_quad_to_target_transform.Translate(50, 50);
-  red_quad_to_target_transform.Scale(0.5f + 1.0f / (rect.width() * 2.0f),
-                                     0.5f + 1.0f / (rect.height() * 2.0f));
-  SharedQuadState* red_shared_state = CreateTestSharedQuadState(
-      red_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* red = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  red->SetNew(red_shared_state, rect, rect, SK_ColorRED, false);
-
-  gfx::Transform yellow_quad_to_target_transform;
-  yellow_quad_to_target_transform.Translate(25.5f, 25.5f);
-  yellow_quad_to_target_transform.Scale(0.5f, 0.5f);
-  SharedQuadState* yellow_shared_state = CreateTestSharedQuadState(
-      yellow_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* yellow = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  yellow->SetNew(yellow_shared_state, rect, rect, SK_ColorYELLOW, false);
+  CreateTestAxisAlignedQuads(rect, SK_ColorRED, SK_ColorYELLOW, false, false,
+                             pass.get());
 
   gfx::Transform blue_quad_to_target_transform;
   SharedQuadState* blue_shared_state = CreateTestSharedQuadState(
@@ -3352,6 +3365,29 @@
       cc::ExactPixelComparator(false)));
 }
 
+// This test tests that forcing anti-aliasing off works as expected while
+// blending is still enabled.
+// Anti-aliasing is only supported in the gl and skia renderers.
+TYPED_TEST(GLCapableRendererPixelTest, BlendingWithoutAntiAliasing) {
+  gfx::Rect rect(this->device_viewport_size_);
+
+  int id = 1;
+  gfx::Transform transform_to_root;
+  std::unique_ptr<RenderPass> pass =
+      CreateTestRenderPass(id, rect, transform_to_root);
+
+  CreateTestAxisAlignedQuads(rect, 0x800000FF, 0x8000FF00, true, true,
+                             pass.get());
+
+  RenderPassList pass_list;
+  pass_list.push_back(std::move(pass));
+
+  EXPECT_TRUE(this->RunPixelTest(
+      &pass_list,
+      base::FilePath(FILE_PATH_LITERAL("translucent_quads_no_aa.png")),
+      cc::ExactPixelComparator(false)));
+}
+
 // Trilinear filtering is only supported in the gl renderer.
 TYPED_TEST(GLCapableRendererPixelTest, TrilinearFiltering) {
   gfx::Rect viewport_rect(this->device_viewport_size_);
@@ -4558,25 +4594,8 @@
   std::unique_ptr<RenderPass> pass =
       CreateTestRenderPass(id, rect, transform_to_root);
 
-  gfx::Transform dark_gray_quad_to_target_transform;
-  dark_gray_quad_to_target_transform.Translate(50, 50);
-  dark_gray_quad_to_target_transform.Scale(
-      0.5f + 1.0f / (rect.width() * 2.0f),
-      0.5f + 1.0f / (rect.height() * 2.0f));
-  SharedQuadState* dark_gray_shared_state = CreateTestSharedQuadState(
-      dark_gray_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* dark_gray = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  dark_gray->SetNew(dark_gray_shared_state, rect, rect, 0x10444444, false);
-
-  gfx::Transform light_gray_quad_to_target_transform;
-  light_gray_quad_to_target_transform.Translate(25.5f, 25.5f);
-  light_gray_quad_to_target_transform.Scale(0.5f, 0.5f);
-  SharedQuadState* light_gray_shared_state = CreateTestSharedQuadState(
-      light_gray_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* light_gray = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  light_gray->SetNew(light_gray_shared_state, rect, rect, 0x10CCCCCC, false);
+  CreateTestAxisAlignedQuads(rect, 0x10444444, 0x10CCCCCC, true, false,
+                             pass.get());
 
   gfx::Transform bg_quad_to_target_transform;
   SharedQuadState* bg_shared_state = CreateTestSharedQuadState(
@@ -4616,25 +4635,8 @@
   std::unique_ptr<RenderPass> pass =
       CreateTestRenderPass(id, rect, transform_to_root);
 
-  gfx::Transform dark_gray_quad_to_target_transform;
-  dark_gray_quad_to_target_transform.Translate(50, 50);
-  dark_gray_quad_to_target_transform.Scale(
-      0.5f + 1.0f / (rect.width() * 2.0f),
-      0.5f + 1.0f / (rect.height() * 2.0f));
-  SharedQuadState* dark_gray_shared_state = CreateTestSharedQuadState(
-      dark_gray_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* dark_gray = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  dark_gray->SetNew(dark_gray_shared_state, rect, rect, 0x10444444, false);
-
-  gfx::Transform light_gray_quad_to_target_transform;
-  light_gray_quad_to_target_transform.Translate(25.5f, 25.5f);
-  light_gray_quad_to_target_transform.Scale(0.5f, 0.5f);
-  SharedQuadState* light_gray_shared_state = CreateTestSharedQuadState(
-      light_gray_quad_to_target_transform, rect, pass.get(), gfx::RRectF());
-
-  auto* light_gray = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
-  light_gray->SetNew(light_gray_shared_state, rect, rect, 0x10CCCCCC, false);
+  CreateTestAxisAlignedQuads(rect, 0x10444444, 0x10CCCCCC, true, false,
+                             pass.get());
 
   gfx::Transform bg_quad_to_target_transform;
   SharedQuadState* bg_shared_state = CreateTestSharedQuadState(
diff --git a/components/viz/service/main/viz_compositor_thread_runner.cc b/components/viz/service/main/viz_compositor_thread_runner.cc
index 076546e..bc76bac 100644
--- a/components/viz/service/main/viz_compositor_thread_runner.cc
+++ b/components/viz/service/main/viz_compositor_thread_runner.cc
@@ -49,10 +49,7 @@
   auto thread = std::make_unique<base::Thread>(kThreadName);
 
   base::Thread::Options thread_options;
-#if defined(OS_WIN)
-  // Windows needs a UI message loop for child HWND.
-  thread_options.message_loop_type = base::MessageLoop::TYPE_UI;
-#elif defined(USE_OZONE)
+#if defined(USE_OZONE)
   // We may need a non-default message loop type for the platform surface.
   thread_options.message_loop_type =
       ui::OzonePlatform::GetInstance()->GetMessageLoopTypeForGpu();
diff --git a/components/viz/test/data/translucent_quads_no_aa.png b/components/viz/test/data/translucent_quads_no_aa.png
new file mode 100644
index 0000000..9df8374
--- /dev/null
+++ b/components/viz/test/data/translucent_quads_no_aa.png
Binary files differ
diff --git a/content/browser/accessibility/site_per_process_accessibility_browsertest.cc b/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
index 241e5fa6..be09e2e 100644
--- a/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
+++ b/content/browser/accessibility/site_per_process_accessibility_browsertest.cc
@@ -136,8 +136,9 @@
   EXPECT_EQ(ax_child_frame_root->PlatformGetParent(), ax_iframe);
 }
 
+// TODO(aboxhall): Flaky test, discuss with dmazzoni
 IN_PROC_BROWSER_TEST_F(MAYBE_SitePerProcessAccessibilityBrowserTest,
-                       TwoCrossSiteNavigations) {
+                       DISABLED_TwoCrossSiteNavigations) {
   // Enable full accessibility for all current and future WebContents.
   BrowserAccessibilityState::GetInstance()->EnableAccessibility();
 
diff --git a/content/browser/download/mhtml_generation_browsertest.cc b/content/browser/download/mhtml_generation_browsertest.cc
index 38ea178..97edcac6 100644
--- a/content/browser/download/mhtml_generation_browsertest.cc
+++ b/content/browser/download/mhtml_generation_browsertest.cc
@@ -96,29 +96,54 @@
 // static
 int FindTrackingDelegate::global_request_id = 0;
 
-}  // namespace
+const char kTestData[] =
+    "Sample Text to write on a generated MHTML "
+    "file for tests to validate whether the implementation is able to access "
+    "and write to the file.";
 
-// This Mock injects our overwritten interface, running the callback
-// SerializeAsMHTMLResponse and immediately disconnecting the message pipe.
-class MockMhtmlFileWriter : public mojom::MhtmlFileWriter {
+class MockWriterBase : public mojom::MhtmlFileWriter {
  public:
-  explicit MockMhtmlFileWriter() : binding_(this) {}
-
-  ~MockMhtmlFileWriter() override {}
+  MockWriterBase() : binding_(this) {}
+  ~MockWriterBase() override {}
 
   void BindRequest(mojo::ScopedInterfaceEndpointHandle handle) {
     binding_.Bind(mojom::MhtmlFileWriterAssociatedRequest(std::move(handle)));
   }
 
+ protected:
+  void SendResponse(SerializeAsMHTMLCallback callback) {
+    std::vector<std::string> dummy_digests;
+    base::TimeDelta dummy_time_delta = base::TimeDelta::FromMilliseconds(100);
+    std::move(callback).Run(mojom::MhtmlSaveStatus::kSuccess, dummy_digests,
+                            dummy_time_delta);
+  }
+
   void WriteDataToDestinationFile(base::File& destination_file) {
-    const char kTestData[] =
-        "Sample Text to write on generated MHTML "
-        "file to verify it has been written to.";
     base::ScopedAllowBlockingForTesting allow_blocking;
     destination_file.WriteAtCurrentPos(kTestData, strlen(kTestData));
     destination_file.Close();
   }
 
+  void WriteDataToProducerPipe(
+      mojo::ScopedDataPipeProducerHandle producer_pipe) {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    uint32_t size = strlen(kTestData);
+    producer_pipe->WriteData(kTestData, &size, MOJO_WRITE_DATA_FLAG_NONE);
+    producer_pipe.reset();
+  }
+
+  mojo::AssociatedBinding<mojom::MhtmlFileWriter> binding_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockWriterBase);
+};
+
+// This Mock injects our overwritten interface, running the callback
+// SerializeAsMHTMLResponse and immediately disconnecting the message pipe.
+class RespondAndDisconnectMockWriter : public MockWriterBase {
+ public:
+  RespondAndDisconnectMockWriter() {}
+
   void SerializeAsMHTML(mojom::SerializeAsMHTMLParamsPtr params,
                         SerializeAsMHTMLCallback callback) override {
     // Upon using the overridden mock interface implementation, this will be
@@ -174,36 +199,73 @@
     //   then accepts the error notification and invokes the connection error
     //   handler, guaranteeing its execution before (3).
 
-    // Write a valid MHTML file to destination_file, since we are not
-    // actively running a serialization pipeline in the mock implementation.
-    WriteDataToDestinationFile(params->destination_file);
+    bool compute_contents_hash = params->output_handle->is_producer_handle();
 
-    std::vector<std::string> dummy_digests;
-    base::TimeDelta dummy_time_delta = base::TimeDelta::Max();
-    std::move(callback).Run(mojom::MhtmlSaveStatus::kSuccess, dummy_digests,
-                            dummy_time_delta);
+    // Write a valid MHTML file to its respective handle, since we are not
+    // actively running a serialization pipeline in the mock implementation.
+    if (compute_contents_hash) {
+      WriteDataToProducerPipe(
+          std::move(params->output_handle->get_producer_handle()));
+    } else {
+      WriteDataToDestinationFile(params->output_handle->get_file_handle());
+    }
+
+    SendResponse(std::move(callback));
 
     // Close the message pipe connection to invoke the connection error
     // callback. The connection error handler from here will finalize
     // the Job and attempt to call MHTMLGenerationManager::Job::CloseFile
     // a second time. If this situation is handled correctly, the
     // browser file should be invalidated and idempotent.
-    binding_.Unbind();
+    if (!compute_contents_hash) {
+      binding_.Unbind();
+      return;
+    }
+
+    // In the case we are using a data pipe to stream serialized MHTML data,
+    // we must ensure the write complete notification arrives before the
+    // connection error notification, otherwise the Browser will report
+    // an MhtmlSaveStatus != kSuccess. We can guarantee this by potentially
+    // running tasks after each watcher invocation to send notifications that
+    // it has been completed. We need at least two tasks to guarantee this,
+    // as there can be at most two watcher invocations to write a block of
+    // data smaller than the data pipe buffer to file.
+    download::GetDownloadTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce(&RespondAndDisconnectMockWriter::TaskX,
+                                  base::Unretained(this)));
   }
 
- private:
-  mojo::AssociatedBinding<mojom::MhtmlFileWriter> binding_;
+  void TaskX() {
+    download::GetDownloadTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce(&RespondAndDisconnectMockWriter::TaskY,
+                                  base::Unretained(this)));
+  }
 
-  DISALLOW_COPY_AND_ASSIGN(MockMhtmlFileWriter);
+  void TaskY() {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&RespondAndDisconnectMockWriter::TaskZ,
+                       base::Unretained(this)));
+  }
+
+  void TaskZ() { binding_.Unbind(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RespondAndDisconnectMockWriter);
 };
 
-class MHTMLGenerationTest : public ContentBrowserTest {
+}  // namespace
+
+class MHTMLGenerationTest : public ContentBrowserTest,
+                            public testing::WithParamInterface<bool> {
  public:
   MHTMLGenerationTest()
       : has_mhtml_callback_run_(false),
         file_size_(0),
         well_formedness_check_(true) {}
 
+  enum TaskOrder { WriteThenRespond, RespondThenWrite };
+
  protected:
   void SetUpOnMainThread() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -211,19 +273,34 @@
     ContentBrowserTest::SetUpOnMainThread();
   }
 
-  void GenerateMHTML(const base::FilePath& path, const GURL& url) {
-    GenerateMHTML(MHTMLGenerationParams(path), url);
+  void OverrideInterface(MockWriterBase* mock_writer) {
+    blink::AssociatedInterfaceProvider* remote_interfaces =
+        shell()
+            ->web_contents()
+            ->GetMainFrame()
+            ->GetRemoteAssociatedInterfaces();
+    remote_interfaces->OverrideBinderForTesting(
+        mojom::MhtmlFileWriter::Name_,
+        base::BindRepeating(&MockWriterBase::BindRequest,
+                            base::Unretained(mock_writer)));
   }
 
-  void GenerateMHTML(const MHTMLGenerationParams& params, const GURL& url) {
+  void GenerateMHTML(base::FilePath& path, const GURL& url) {
+    MHTMLGenerationParams params(path);
+    GenerateMHTML(params, url);
+  }
+
+  void GenerateMHTML(MHTMLGenerationParams& params, const GURL& url) {
     NavigateToURL(shell(), url);
     GenerateMHTMLForCurrentPage(params);
   }
 
-  void GenerateMHTMLForCurrentPage(const MHTMLGenerationParams& params) {
+  void GenerateMHTMLForCurrentPage(MHTMLGenerationParams& params) {
     base::RunLoop run_loop;
     histogram_tester_.reset(new base::HistogramTester());
 
+    params.compute_contents_hash = GetParam();
+
     shell()->web_contents()->GenerateMHTML(
         params, base::BindOnce(&MHTMLGenerationTest::MHTMLGenerated,
                                base::Unretained(this), run_loop.QuitClosure()));
@@ -254,6 +331,8 @@
     shell()->web_contents()->SetDelegate(old_delegate);
   }
 
+  void TwoStepSyncTestFor(const TaskOrder order);
+
   int64_t ReadFileSizeFromDisk(base::FilePath path) {
     base::ScopedAllowBlockingForTesting allow_blocking;
     int64_t file_size;
@@ -263,7 +342,7 @@
 
   void TestOriginalVsSavedPage(
       const GURL& url,
-      const MHTMLGenerationParams params,
+      MHTMLGenerationParams params,
       int expected_number_of_frames,
       const std::vector<std::string>& expected_substrings,
       const std::vector<std::string>& forbidden_substrings_in_saved_page,
@@ -352,7 +431,7 @@
 // Note that the actual content of the file is not tested, the purpose of this
 // test is to ensure we were successful in creating the MHTML data from the
 // renderer.
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTML) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateMHTML) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
@@ -377,22 +456,18 @@
 }
 
 // Regression test for the crash/race from https://crbug.com/612098.
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLAndCloseConnection) {
-  MockMhtmlFileWriter mock_writer;
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateMHTMLAndCloseConnection) {
+  RespondAndDisconnectMockWriter mock_writer;
 
   NavigateToURL(shell(), embedded_test_server()->GetURL("/simple_page.html"));
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
-  blink::AssociatedInterfaceProvider* remote_interfaces =
-      shell()->web_contents()->GetMainFrame()->GetRemoteAssociatedInterfaces();
-  remote_interfaces->OverrideBinderForTesting(
-      mojom::MhtmlFileWriter::Name_,
-      base::BindRepeating(&MockMhtmlFileWriter::BindRequest,
-                          base::Unretained(&mock_writer)));
-
+  OverrideInterface(&mock_writer);
   DisableWellformednessCheck();
-  GenerateMHTMLForCurrentPage(MHTMLGenerationParams(path));
+
+  MHTMLGenerationParams params(path);
+  GenerateMHTMLForCurrentPage(params);
 
   // Verify the file has some contents written to it.
   EXPECT_GT(ReadFileSizeFromDisk(path), 100);
@@ -406,7 +481,7 @@
 #else
 #define MAYBE_InvalidPath InvalidPath
 #endif
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, MAYBE_InvalidPath) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, MAYBE_InvalidPath) {
   base::FilePath path(FILE_PATH_LITERAL("/invalid/file/path"));
 
   GenerateMHTML(path, embedded_test_server()->GetURL(
@@ -423,7 +498,7 @@
 // Tests that MHTML generated using the default 'quoted-printable' encoding does
 // not contain the 'binary' Content-Transfer-Encoding header, and generates
 // base64 encoding for the image part.
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateNonBinaryMHTMLWithImage) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateNonBinaryMHTMLWithImage) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test_binary.mht"));
 
@@ -447,7 +522,7 @@
 // Tests that MHTML generated using the binary encoding contains the 'binary'
 // Content-Transfer-Encoding header, and does not contain any base64 encoded
 // parts.
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateBinaryMHTMLWithImage) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateBinaryMHTMLWithImage) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test_binary.mht"));
 
@@ -471,7 +546,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLIgnoreNoStore) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateMHTMLIgnoreNoStore) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
@@ -502,7 +577,7 @@
   ViewedMHTMLContainsNoStoreContent
 #endif
 
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest,
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest,
                        MAYBE_ViewedMHTMLContainsNoStoreContent) {
   // Generate MHTML.
   base::FilePath path(temp_dir_.GetPath());
@@ -545,7 +620,7 @@
 };
 
 // Test for crbug.com/538766.
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationSitePerProcessTest, GenerateMHTML) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationSitePerProcessTest, GenerateMHTML) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
@@ -571,7 +646,7 @@
   EXPECT_THAT(mhtml, ContainsRegex("Content-Location:.*/title1.html"));
 }
 
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, RemovePopupOverlay) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, RemovePopupOverlay) {
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
@@ -593,7 +668,7 @@
   EXPECT_THAT(mhtml, Not(HasSubstr("class=3D\"modal")));
 }
 
-IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLWithExtraData) {
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateMHTMLWithExtraData) {
   const char kFakeSignalData1[] = "FakeSignalData1";
   const char kFakeSignalData2[] = "OtherMockDataForSignals";
   const char kFakeContentType[] = "text/plain";
@@ -602,7 +677,6 @@
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
   GURL url(embedded_test_server()->GetURL("/page_with_image.html"));
-  MHTMLGenerationParams params(path);
 
   // Place the extra data we need into the web contents user data.
   std::string content_type(kFakeContentType);
@@ -619,7 +693,7 @@
   extra_parts->AddExtraMHTMLPart(content_type, content_location, extra_headers,
                                  kFakeSignalData2);
   EXPECT_EQ(extra_parts->size(), 2);
-  GenerateMHTML(params, url);
+  GenerateMHTML(path, url);
 
   EXPECT_TRUE(has_mhtml_callback_run());
 
@@ -634,4 +708,147 @@
   EXPECT_THAT(mhtml, HasSubstr(kFakeSignalData2));
 }
 
+IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest, GenerateMHTMLWithMultipleFrames) {
+  base::FilePath path(temp_dir_.GetPath());
+  path = path.Append(FILE_PATH_LITERAL("test.mht"));
+
+  const std::string kContentURLs[] = {
+      "Content-Location:.*/page_with_image.html",
+      "Content-Location:.*/page_with_popup.html",
+      "Content-Location:.*/page_with_frameset.html",
+      "Content-Location:.*/page_with_allowfullscreen_frame.html",
+      "Content-Location:.*/page_with_iframe_and_link.html"};
+
+  MHTMLGenerationParams params(path);
+  TestOriginalVsSavedPage(
+      embedded_test_server()->GetURL("/page_with_multiple_iframes.html"),
+      params, 11 /* expected number of frames */, std::vector<std::string>(),
+      std::vector<std::string>());
+
+  // Test whether generation was successful.
+  EXPECT_GT(file_size(), 0);  // Verify the size reported by the callback.
+  EXPECT_GT(ReadFileSizeFromDisk(path), 100);  // Verify the actual file size.
+
+  std::string mhtml;
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    ASSERT_TRUE(base::ReadFileToString(path, &mhtml));
+  }
+
+  // Expect all frames in the .html are included in the generated file.
+  for (const auto& regex : kContentURLs)
+    EXPECT_THAT(mhtml, ContainsRegex(regex));
+}
+
+// Tests for the synchronization logic that waits for both the Mojo
+// response and the data pipe closure to consider a frame serialization done.
+// This is only relevant when a Mojo data pipe is being used (hash computation
+// case) and is skipped if writing directly to file.
+namespace {
+class OrderedTaskMockWriter : public MockWriterBase {
+ public:
+  explicit OrderedTaskMockWriter(MHTMLGenerationTest::TaskOrder order)
+      : order_(order) {}
+
+  void SerializeAsMHTML(mojom::SerializeAsMHTMLParamsPtr params,
+                        SerializeAsMHTMLCallback callback) override {
+    DCHECK(params->output_handle->is_producer_handle());
+    DCHECK(params->output_handle->get_producer_handle()->is_valid());
+
+    switch (order_) {
+      case MHTMLGenerationTest::TaskOrder::RespondThenWrite:
+        delayed_callback_ = base::BindOnce(
+            &OrderedTaskMockWriter::WriteDataToProducerPipe,
+            base::Unretained(this),
+            std::move(params->output_handle->get_producer_handle()));
+        SendResponse(std::move(callback));
+        PostClosure();
+        break;
+      case MHTMLGenerationTest::TaskOrder::WriteThenRespond:
+        delayed_callback_ =
+            base::BindOnce(&OrderedTaskMockWriter::SendResponse,
+                           base::Unretained(this), std::move(callback));
+        WriteDataToProducerPipe(
+            std::move(params->output_handle->get_producer_handle()));
+        // For this case, we must post to the download sequence first to
+        // ensure we run the closure after the write operation completes.
+        download::GetDownloadTaskRunner()->PostTask(
+            FROM_HERE, base::BindOnce(&OrderedTaskMockWriter::PostClosure,
+                                      base::Unretained(this)));
+        break;
+    }
+  }
+
+  // Posts the quit closure to the UI thread to unblock the serialization Job
+  // after receiving the first task complete notification.
+  void PostClosure() {
+    base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
+                             std::move(first_run_loop_closure_));
+  }
+
+  base::OnceClosure first_run_loop_closure_;
+  base::OnceClosure delayed_callback_;
+
+ private:
+  MHTMLGenerationTest::TaskOrder order_;
+
+  DISALLOW_COPY_AND_ASSIGN(OrderedTaskMockWriter);
+};
+}  // namespace
+
+void MHTMLGenerationTest::TwoStepSyncTestFor(
+    const MHTMLGenerationTest::TaskOrder order) {
+  OrderedTaskMockWriter mock_writer(order);
+
+  base::FilePath path(temp_dir_.GetPath());
+  path = path.Append(FILE_PATH_LITERAL("test.mht"));
+
+  MHTMLGenerationParams params(path);
+
+  OverrideInterface(&mock_writer);
+
+  base::RunLoop first_run_loop;
+  base::RunLoop second_run_loop;
+
+  params.compute_contents_hash = true;
+  mock_writer.first_run_loop_closure_ = first_run_loop.QuitWhenIdleClosure();
+
+  shell()->web_contents()->GenerateMHTML(
+      params,
+      base::BindOnce(&MHTMLGenerationTest::MHTMLGenerated,
+                     base::Unretained(this), second_run_loop.QuitClosure()));
+
+  // Run serialization pipeline until stalled.
+  first_run_loop.Run();
+  ASSERT_FALSE(has_mhtml_callback_run())
+      << "MHTML generation complete but should be waiting on operation.";
+
+  // Run stalled task and block until MHTML generation completes.
+  DCHECK(mock_writer.delayed_callback_);
+  std::move(mock_writer.delayed_callback_).Run();
+  second_run_loop.Run();
+
+  ASSERT_TRUE(has_mhtml_callback_run())
+      << "MHTML generation has not been complete despite unblocking the Job.";
+
+  // Verify the file has some contents written to it.
+  EXPECT_GT(ReadFileSizeFromDisk(path), 100);
+  // Verify the reported file size matches the file written to disk.
+  EXPECT_EQ(ReadFileSizeFromDisk(path), file_size_);
+}
+
+// These tests do not depend on the parameter declared by the
+// MHTMLGenerationTest test suite, so we only want to run them once.
+IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLButDelayWrite) {
+  TwoStepSyncTestFor(TaskOrder::RespondThenWrite);
+}
+
+IN_PROC_BROWSER_TEST_F(MHTMLGenerationTest, GenerateMHTMLButDelayResponse) {
+  TwoStepSyncTestFor(TaskOrder::WriteThenRespond);
+}
+
+INSTANTIATE_TEST_SUITE_P(MHTMLGenerationTest,
+                         MHTMLGenerationTest,
+                         ::testing::Bool());
+
 }  // namespace content
diff --git a/content/browser/download/mhtml_generation_manager.cc b/content/browser/download/mhtml_generation_manager.cc
index b529f36..9e524c5 100644
--- a/content/browser/download/mhtml_generation_manager.cc
+++ b/content/browser/download/mhtml_generation_manager.cc
@@ -37,6 +37,10 @@
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 namespace {
+// Callback to notify the UI thread that writing to the MHTML file is complete.
+using MHTMLWriteCompleteCallback =
+    base::RepeatingCallback<void(content::mojom::MhtmlSaveStatus)>;
+
 const char kContentLocation[] = "Content-Location: ";
 const char kContentType[] = "Content-Type: ";
 int kInvalidFileSize = -1;
@@ -46,6 +50,26 @@
   content::mojom::MhtmlSaveStatus save_status;
   int64_t file_size;
 };
+
+base::File CreateMHTMLFile(const base::FilePath& file_path) {
+  DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
+
+  // SECURITY NOTE: A file descriptor to the file created below will be passed
+  // to multiple renderer processes which (in out-of-process iframes mode) can
+  // act on behalf of separate web principals.  Therefore it is important to
+  // only allow writing to the file and forbid reading from the file (as this
+  // would allow reading content generated by other renderers / other web
+  // principals).
+  uint32_t file_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
+
+  base::File browser_file(file_path, file_flags);
+  if (!browser_file.IsValid()) {
+    DLOG(ERROR) << "Failed to create file to save MHTML at: "
+                << file_path.value();
+  }
+  return browser_file;
+}
+
 }  // namespace
 
 namespace content {
@@ -74,16 +98,20 @@
       GenerateMHTMLCallback callback);
   ~Job();
 
-  // Writes the MHTML footer to the file and closes it.
+  // Writes the MHTML footer to the file and closes it. It also receives the
+  // SimpleWatcher instance used to watch the data pipe for safe destruction on
+  // the IO thread.
   //
   // Note: The same |boundary| marker must be used for all "boundaries" -- in
   // the header, parts and footer -- that belong to the same MHTML document (see
   // also rfc1341, section 7.2.1, "boundary" description).
-  static CloseFileResult FinalizeAndCloseFileOnFileThread(
+  static CloseFileResult FinalizeOnFileThread(
       mojom::MhtmlSaveStatus save_status,
       const std::string& boundary,
       base::File file,
-      const std::vector<MHTMLExtraDataPart>& extra_data_parts);
+      const std::vector<MHTMLExtraDataPart>& extra_data_parts,
+      std::unique_ptr<mojo::SimpleWatcher> watcher);
+
   void AddFrame(RenderFrameHost* render_frame_host);
 
   // If we have any extra MHTML parts to write out, write them into the file
@@ -94,7 +122,7 @@
       base::File& file,
       const std::vector<MHTMLExtraDataPart>& extra_data_parts);
 
-  // Writes the footer into the MHTML file.  Returns false for faiulre.
+  // Writes the footer into the MHTML file.  Returns false for failure.
   static bool WriteFooter(const std::string& boundary, base::File& file);
 
   // Called on the UI thread when the file that should hold the MHTML data has
@@ -105,6 +133,28 @@
   // or an error occurred while creating a new file.
   void OnFinished(const CloseFileResult& close_file_result);
 
+  // Starts watching a handle on the file thread. Instantiates a new instance
+  // of |watcher_| upon call.
+  void BeginWatchingHandle(MHTMLWriteCompleteCallback callback);
+
+  // Writes data from the consumer handle to the new MHTML file. Only done
+  // with on the fly hash computation.
+  // Bound to the data pipe watcher and called upon notification of write
+  // completion to producer pipe sent to the Renderer.
+  // TODO(https://crbug.com/915966): Eventually simplify this implementation
+  // with a DataPipeDrainer once error signalling is implemented there.
+  void WriteMHTMLToDisk(MHTMLWriteCompleteCallback callback,
+                        MojoResult result,
+                        const mojo::HandleSignalsState& state);
+
+  // Destroys |watcher_| instance and notifies UI thread of write completion.
+  void OnWriteComplete(MHTMLWriteCompleteCallback callback,
+                       mojom::MhtmlSaveStatus save_status);
+
+  // Notifies Job of frame write completion and sends request to next render
+  // frame if the response was blocked by the write operation.
+  void DoneWritingToDisk(mojom::MhtmlSaveStatus save_status);
+
   // Called when the message pipe to the renderer is disconnected.
   void OnConnectionError();
 
@@ -116,13 +166,15 @@
       base::TimeDelta renderer_main_thread_time);
 
   // Records newly serialized resource digests into
-  // |digests_of_already_serialized_uris_|, and continues sending serialization
-  // requests to the next frame if there are more frames to be serialized.
-  // Returns MhtmlSaveStatus::kSuccess or a specific error status.
-  mojom::MhtmlSaveStatus RecordDigestsAndContinue(
+  // |digests_of_already_serialized_uris_|.
+  void RecordDigests(
       const std::vector<std::string>& digests_of_uris_of_serialized_resources);
 
-  // Packs up the current status of the MHTML file saving into a Mojo
+  // Continues sending serialization requests to the next frame if ready and
+  // there are more frames to be serialized.
+  void MaybeSendToNextRenderFrame(mojom::MhtmlSaveStatus save_status);
+
+  // Packs up the current status of the MHTML file save operation into a Mojo
   // struct to send to the renderer process.
   mojom::SerializeAsMHTMLParamsPtr CreateMojoParams();
 
@@ -131,11 +183,11 @@
   // specific error status.
   mojom::MhtmlSaveStatus SendToNextRenderFrame();
 
-  // Indicates if more calls to SendToNextRenderFrame are needed.
-  // This check is necessary to prevent a race condition between the
-  // Renderer and Browser where the Job is deleted before the response
-  // is received.
-  bool IsDone() const;
+  // Indicates if the writing operation on the IO thread is complete, and
+  // we have received a response from the Renderer.
+  // This check is necessary to provide synchronization between file writing
+  // operations and MHTML serialization.
+  bool CurrentFrameDone() const;
 
   // Called on the UI thread when a job has been finished.
   void Finalize(mojom::MhtmlSaveStatus save_status);
@@ -145,6 +197,8 @@
   // negative in case of errors).
   void CloseFile(mojom::MhtmlSaveStatus save_status);
 
+  // Marks the Job as completed, preventing any further notifications from the
+  // Renderer. This prevents the race/crash from https://crbug.com/612098.
   void MarkAsFinished();
 
   void ReportRendererMainThreadTime(base::TimeDelta renderer_main_thread_time);
@@ -193,9 +247,20 @@
   // Any extra data parts that should be emitted into the output MHTML.
   std::vector<MHTMLExtraDataPart> extra_data_parts_;
 
-  // MHTML File Writer pointer to keep the variable alive.
+  // MHTMLFileWriter instance for the frame being currently serialized.
   mojom::MhtmlFileWriterAssociatedPtr writer_;
 
+  // Watcher to detect new data written to |mhtml_data_consumer_|.
+  // This is instantiated and destroyed in the download sequence for each frame.
+  std::unique_ptr<mojo::SimpleWatcher> watcher_;
+
+  // Consumer handle for data pipe streaming.
+  mojo::ScopedDataPipeConsumerHandle mhtml_data_consumer_;
+
+  // Indicates whether there is currently data being streamed from the Renderer.
+  // Not used when the renderer is writing directly to file.
+  bool waiting_on_data_streaming_;
+
   base::WeakPtrFactory<Job> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(Job);
@@ -211,6 +276,7 @@
       salt_(base::GenerateGUID()),
       callback_(std::move(callback)),
       is_finished_(false),
+      waiting_on_data_streaming_(false),
       weak_factory_(this) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -236,12 +302,13 @@
 
   base::PostTaskAndReplyWithResult(
       download::GetDownloadTaskRunner().get(), FROM_HERE,
-      base::BindOnce(&CreateFile, params.file_path),
+      base::BindOnce(&CreateMHTMLFile, params.file_path),
       base::BindOnce(&Job::OnFileAvailable, weak_factory_.GetWeakPtr()));
 }
 
 MHTMLGenerationManager::Job::~Job() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!watcher_);
 }
 
 mojom::SerializeAsMHTMLParamsPtr
@@ -253,9 +320,6 @@
   mojo_params->mhtml_popup_overlay_removal = params_.remove_popup_overlay;
   mojo_params->mhtml_problem_detection = params_.use_page_problem_detectors;
 
-  // File::Duplicate() creates a reference to this file for use in the Renderer.
-  mojo_params->destination_file = browser_file_.Duplicate();
-
   // Tell the renderer to skip (= deduplicate) already covered MHTML parts.
   mojo_params->salt = salt_;
   mojo_params->digests_of_uris_to_skip.assign(
@@ -279,28 +343,138 @@
 
   // Bind Mojo interface to the RenderFrame
   rfh->GetRemoteAssociatedInterfaces()->GetInterface(&writer_);
-  auto callback = base::BindOnce(&Job::SerializeAsMHTMLResponse,
-                                 weak_factory_.GetWeakPtr());
 
   // Safe, as |writer_| is owned by this Job instance.
   auto error_callback =
       base::BindOnce(&Job::OnConnectionError, base::Unretained(this));
   writer_.set_connection_error_handler(std::move(error_callback));
 
-  // Send a Mojo request asking to serialize the frame.
+  mojom::SerializeAsMHTMLParamsPtr params(CreateMojoParams());
+
+  // Initialize method of file writing depending on |compute_contents_hash|
+  // flag.
+  params->output_handle = mojom::MhtmlOutputHandle::New();
+  if (params_.compute_contents_hash) {
+    // Create and set up the data pipe.
+    mojo::ScopedDataPipeProducerHandle producer;
+    if (mojo::CreateDataPipe(nullptr, &producer, &mhtml_data_consumer_) !=
+        MOJO_RESULT_OK) {
+      DLOG(ERROR) << "Failed to create Mojo Data Pipe.";
+      return mojom::MhtmlSaveStatus::kStreamingError;
+    }
+    MHTMLWriteCompleteCallback write_complete_callback = base::BindRepeating(
+        &Job::DoneWritingToDisk, weak_factory_.GetWeakPtr());
+    download::GetDownloadTaskRunner().get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&Job::BeginWatchingHandle, base::Unretained(this),
+                       std::move(write_complete_callback)));
+    waiting_on_data_streaming_ = true;
+    params->output_handle->set_producer_handle(std::move(producer));
+  } else {
+    // File::Duplicate() creates a reference to this file for use in the
+    // Renderer.
+    params->output_handle->set_file_handle(browser_file_.Duplicate());
+  }
+
+  // Send a Mojo request to Renderer to serialize its frame.
   DCHECK_EQ(FrameTreeNode::kFrameTreeNodeInvalidId,
             frame_tree_node_id_of_busy_frame_);
   frame_tree_node_id_of_busy_frame_ = frame_tree_node_id;
-  writer_->SerializeAsMHTML(CreateMojoParams(), std::move(callback));
+
+  auto response_callback = base::BindOnce(&Job::SerializeAsMHTMLResponse,
+                                          weak_factory_.GetWeakPtr());
+  writer_->SerializeAsMHTML(std::move(params), std::move(response_callback));
 
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("page-serialization", "WaitingOnRenderer",
                                     this, "frame tree node id",
-                                    frame_tree_node_id);
+                                    frame_tree_node_id_of_busy_frame_);
   DCHECK(wait_on_renderer_start_time_.is_null());
   wait_on_renderer_start_time_ = base::TimeTicks::Now();
   return mojom::MhtmlSaveStatus::kSuccess;
 }
 
+void MHTMLGenerationManager::Job::BeginWatchingHandle(
+    MHTMLWriteCompleteCallback callback) {
+  DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
+
+  DCHECK(!watcher_);
+  watcher_ = std::make_unique<mojo::SimpleWatcher>(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC,
+      download::GetDownloadTaskRunner());
+
+  // base::Unretained is safe, as |this| owns |mhtml_data_consumer_|, which
+  // is responsible for invoking |watcher_| callbacks.
+  if (watcher_->Watch(
+          mhtml_data_consumer_.get(),
+          MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+          MOJO_WATCH_CONDITION_SATISFIED,
+          base::BindRepeating(&Job::WriteMHTMLToDisk, base::Unretained(this),
+                              callback)) != MOJO_RESULT_OK) {
+    DLOG(ERROR) << "Failed to strap watcher to consumer handle.";
+    OnWriteComplete(callback, mojom::MhtmlSaveStatus::kStreamingError);
+  }
+}
+
+void MHTMLGenerationManager::Job::WriteMHTMLToDisk(
+    MHTMLWriteCompleteCallback callback,
+    MojoResult result,
+    const mojo::HandleSignalsState& state) {
+  DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
+  DCHECK_NE(result, MOJO_RESULT_FAILED_PRECONDITION);
+
+  // Begin consumer data pipe handle read and file write loop.
+  char buffer[1024];
+  while (result == MOJO_RESULT_OK && state.readable()) {
+    uint32_t num_bytes = sizeof(buffer);
+    result = mhtml_data_consumer_->ReadData(&buffer, &num_bytes,
+                                            MOJO_READ_DATA_FLAG_NONE);
+
+    if (result == MOJO_RESULT_OK &&
+        browser_file_.WriteAtCurrentPos(buffer, num_bytes) < 0) {
+      DLOG(ERROR) << "Error writing to file handle.";
+      OnWriteComplete(std::move(callback),
+                      mojom::MhtmlSaveStatus::kFileWritingError);
+      return;
+    }
+  }
+
+  if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION &&
+      result != MOJO_RESULT_SHOULD_WAIT) {
+    DLOG(ERROR) << "Error streaming MHTML data to the Browser.";
+    OnWriteComplete(std::move(callback),
+                    mojom::MhtmlSaveStatus::kStreamingError);
+    return;
+  }
+
+  // Only notify successful write completion if peer handle is closed without
+  // any errors.
+  if (state.peer_closed())
+    OnWriteComplete(std::move(callback), mojom::MhtmlSaveStatus::kSuccess);
+}
+
+void MHTMLGenerationManager::Job::OnWriteComplete(
+    MHTMLWriteCompleteCallback callback,
+    mojom::MhtmlSaveStatus save_status) {
+  DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
+
+  watcher_.reset();
+  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
+                           base::BindOnce(std::move(callback), save_status));
+}
+
+void MHTMLGenerationManager::Job::DoneWritingToDisk(
+    mojom::MhtmlSaveStatus save_status) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // If the Job has prematurely finalized and marked as finished, make this
+  // response no-op.
+  if (is_finished_)
+    return;
+
+  waiting_on_data_streaming_ = false;
+  MaybeSendToNextRenderFrame(save_status);
+}
+
 void MHTMLGenerationManager::Job::OnConnectionError() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // If message pipe end closes, then it is an unexpected crash.
@@ -353,6 +527,11 @@
   is_finished_ = true;
   writer_.reset();
 
+  // Additionally, |watcher_| may also invoke DoneWritingToDisk() from
+  // the download sequence, potentially calling this twice. We cannot disable
+  // |watcher_| notifications similar to |writer_|, since it exists in
+  // the download sequence, so we handle the case in DoneWritingToDisk().
+
   TRACE_EVENT_NESTABLE_ASYNC_INSTANT0("page-serialization", "JobFinished",
                                       this);
 
@@ -404,24 +583,18 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!mhtml_boundary_marker_.empty());
 
-  if (!browser_file_.IsValid()) {
-    // Only update the status if that won't hide an earlier error.
-    if (save_status == mojom::MhtmlSaveStatus::kSuccess)
-      save_status = mojom::MhtmlSaveStatus::kFileWritingError;
-    OnFinished(CloseFileResult(save_status, -1));
-    return;
-  }
+  // Only update the status if that won't hide an earlier error.
+  if (!browser_file_.IsValid() &&
+      save_status == mojom::MhtmlSaveStatus::kSuccess)
+    save_status = mojom::MhtmlSaveStatus::kFileWritingError;
 
   // If no previous error occurred the boundary should be sent.
   base::PostTaskAndReplyWithResult(
       download::GetDownloadTaskRunner().get(), FROM_HERE,
-      base::BindOnce(
-          &MHTMLGenerationManager::Job::FinalizeAndCloseFileOnFileThread,
-          save_status,
-          (save_status == mojom::MhtmlSaveStatus::kSuccess
-               ? mhtml_boundary_marker_
-               : std::string()),
-          std::move(browser_file_), std::move(extra_data_parts_)),
+      base::BindOnce(&MHTMLGenerationManager::Job::FinalizeOnFileThread,
+                     save_status, mhtml_boundary_marker_,
+                     std::move(browser_file_), std::move(extra_data_parts_),
+                     std::move(watcher_)),
       base::BindOnce(&Job::OnFinished, weak_factory_.GetWeakPtr()));
 }
 
@@ -437,25 +610,14 @@
 
   frame_tree_node_id_of_busy_frame_ = FrameTreeNode::kFrameTreeNodeInvalidId;
 
-  // If the renderer succeeded, update the status.
-  if (save_status == mojom::MhtmlSaveStatus::kSuccess) {
-    save_status =
-        RecordDigestsAndContinue(digests_of_uris_of_serialized_resources);
-  }
+  // If the renderer succeeded, update the resource digests.
+  if (save_status == mojom::MhtmlSaveStatus::kSuccess)
+    RecordDigests(digests_of_uris_of_serialized_resources);
 
-  // If there was a failure (either from the renderer or from the job) then
-  // terminate the job and return.
-  if (save_status != mojom::MhtmlSaveStatus::kSuccess) {
-    Finalize(save_status);
-    return;
-  }
-
-  // Otherwise report completion if the job is done.
-  if (IsDone())
-    Finalize(mojom::MhtmlSaveStatus::kSuccess);
+  MaybeSendToNextRenderFrame(save_status);
 }
 
-mojom::MhtmlSaveStatus MHTMLGenerationManager::Job::RecordDigestsAndContinue(
+void MHTMLGenerationManager::Job::RecordDigests(
     const std::vector<std::string>& digests_of_uris_of_serialized_resources) {
   DCHECK(!wait_on_renderer_start_time_.is_null());
   base::TimeDelta renderer_wait_time =
@@ -477,20 +639,35 @@
   digests_of_already_serialized_uris_.insert(
       digests_of_uris_of_serialized_resources.begin(),
       digests_of_uris_of_serialized_resources.end());
-
-  // Report success if all frames have been processed.
-  if (pending_frame_tree_node_ids_.empty())
-    return mojom::MhtmlSaveStatus::kSuccess;
-
-  return SendToNextRenderFrame();
 }
 
-bool MHTMLGenerationManager::Job::IsDone() const {
+void MHTMLGenerationManager::Job::MaybeSendToNextRenderFrame(
+    mojom::MhtmlSaveStatus save_status) {
+  // If current operation is successful and there are more frames to process,
+  // let save status depend on the result of sending the next request.
+  if (save_status == mojom::MhtmlSaveStatus::kSuccess &&
+      !pending_frame_tree_node_ids_.empty() && CurrentFrameDone()) {
+    save_status = SendToNextRenderFrame();
+  }
+
+  // If there was a failure (either from the renderer or from the job) then
+  // terminate the job and return.
+  if (save_status != mojom::MhtmlSaveStatus::kSuccess) {
+    Finalize(save_status);
+    return;
+  }
+
+  // Otherwise report completion if there are no more frames to process
+  // and Job is done processing the current frame.
+  if (pending_frame_tree_node_ids_.empty() && CurrentFrameDone())
+    Finalize(mojom::MhtmlSaveStatus::kSuccess);
+}
+
+bool MHTMLGenerationManager::Job::CurrentFrameDone() const {
   bool waiting_for_response_from_renderer =
       frame_tree_node_id_of_busy_frame_ !=
       FrameTreeNode::kFrameTreeNodeInvalidId;
-  bool no_more_requests_to_send = pending_frame_tree_node_ids_.empty();
-  return !waiting_for_response_from_renderer && no_more_requests_to_send;
+  return !waiting_for_response_from_renderer && !waiting_on_data_streaming_;
 }
 
 void MHTMLGenerationManager::Job::Finalize(mojom::MhtmlSaveStatus save_status) {
@@ -511,13 +688,16 @@
 }
 
 // static
-CloseFileResult MHTMLGenerationManager::Job::FinalizeAndCloseFileOnFileThread(
+CloseFileResult MHTMLGenerationManager::Job::FinalizeOnFileThread(
     mojom::MhtmlSaveStatus save_status,
     const std::string& boundary,
     base::File file,
-    const std::vector<MHTMLExtraDataPart>& extra_data_parts) {
+    const std::vector<MHTMLExtraDataPart>& extra_data_parts,
+    std::unique_ptr<mojo::SimpleWatcher> watcher) {
   DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
 
+  watcher.reset();
+
   // If no previous error occurred the boundary should have been provided.
   if (save_status == mojom::MhtmlSaveStatus::kSuccess) {
     TRACE_EVENT0("page-serialization",
@@ -620,24 +800,4 @@
   Job::StartNewJob(web_contents, params, std::move(callback));
 }
 
-// static
-base::File MHTMLGenerationManager::CreateFile(const base::FilePath& file_path) {
-  DCHECK(download::GetDownloadTaskRunner()->RunsTasksInCurrentSequence());
-
-  // SECURITY NOTE: A file descriptor to the file created below will be passed
-  // to multiple renderer processes which (in out-of-process iframes mode) can
-  // act on behalf of separate web principals.  Therefore it is important to
-  // only allow writing to the file and forbid reading from the file (as this
-  // would allow reading content generated by other renderers / other web
-  // principals).
-  uint32_t file_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
-
-  base::File browser_file(file_path, file_flags);
-  if (!browser_file.IsValid()) {
-    DLOG(ERROR) << "Failed to create file to save MHTML at: "
-                << file_path.value();
-  }
-  return browser_file;
-}
-
 }  // namespace content
diff --git a/content/browser/download/mhtml_generation_manager.h b/content/browser/download/mhtml_generation_manager.h
index 9cfa5c1..fc79bd2 100644
--- a/content/browser/download/mhtml_generation_manager.h
+++ b/content/browser/download/mhtml_generation_manager.h
@@ -10,16 +10,11 @@
 #include <set>
 #include <string>
 
-#include "base/files/file.h"
 #include "base/macros.h"
 #include "base/memory/singleton.h"
 #include "base/process/process.h"
 #include "content/public/common/mhtml_generation_params.h"
 
-namespace base {
-class FilePath;
-}
-
 namespace content {
 
 class WebContents;
@@ -45,9 +40,6 @@
                  const MHTMLGenerationParams& params,
                  GenerateMHTMLCallback callback);
 
-  // Called on the file thread to create a new file for MHTML serialization.
-  static base::File CreateFile(const base::FilePath& file_path);
-
  private:
   friend struct base::DefaultSingletonTraits<MHTMLGenerationManager>;
   class Job;
diff --git a/content/browser/frame_host/navigation_controller_android.cc b/content/browser/frame_host/navigation_controller_android.cc
index 1baf7aa..519b640 100644
--- a/content/browser/frame_host/navigation_controller_android.cc
+++ b/content/browser/frame_host/navigation_controller_android.cc
@@ -9,6 +9,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/callback.h"
+#include "base/containers/flat_map.h"
 #include "base/strings/string16.h"
 #include "content/browser/frame_host/navigation_controller_impl.h"
 #include "content/browser/frame_host/navigation_entry_impl.h"
@@ -30,6 +31,8 @@
 
 namespace {
 
+const char kMapDataKey[] = "map_data_key";
+
 // static
 static base::android::ScopedJavaLocalRef<jobject>
 JNI_NavigationControllerImpl_CreateJavaNavigationEntry(
@@ -71,6 +74,36 @@
                                                              index));
 }
 
+class MapData : public base::SupportsUserData::Data {
+ public:
+  MapData() = default;
+  ~MapData() override = default;
+
+  static MapData* Get(content::NavigationEntry* entry) {
+    MapData* map_data = static_cast<MapData*>(entry->GetUserData(kMapDataKey));
+    if (map_data)
+      return map_data;
+    auto map_data_ptr = std::make_unique<MapData>();
+    map_data = map_data_ptr.get();
+    entry->SetUserData(kMapDataKey, std::move(map_data_ptr));
+    return map_data;
+  }
+
+  base::flat_map<std::string, base::string16>& map() { return map_; }
+
+  // base::SupportsUserData::Data:
+  std::unique_ptr<Data> Clone() override {
+    std::unique_ptr<MapData> clone = std::make_unique<MapData>();
+    clone->map_ = map_;
+    return clone;
+  }
+
+ private:
+  base::flat_map<std::string, base::string16> map_;
+
+  DISALLOW_COPY_AND_ASSIGN(MapData);
+};
+
 }  // namespace
 
 namespace content {
@@ -389,9 +422,11 @@
     return ScopedJavaLocalRef<jstring>();
 
   std::string key = base::android::ConvertJavaStringToUTF8(env, jkey);
-  base::string16 value;
-  navigation_controller_->GetEntryAtIndex(index)->GetExtraData(key, &value);
-  return ConvertUTF16ToJavaString(env, value);
+  MapData* map_data =
+      MapData::Get(navigation_controller_->GetEntryAtIndex(index));
+  auto iter = map_data->map().find(key);
+  return ConvertUTF16ToJavaString(
+      env, iter == map_data->map().end() ? base::string16() : iter->second);
 }
 
 void NavigationControllerAndroid::SetEntryExtraData(
@@ -405,7 +440,9 @@
 
   std::string key = base::android::ConvertJavaStringToUTF8(env, jkey);
   base::string16 value = base::android::ConvertJavaStringToUTF16(env, jvalue);
-  navigation_controller_->GetEntryAtIndex(index)->SetExtraData(key, value);
+  MapData* map_data =
+      MapData::Get(navigation_controller_->GetEntryAtIndex(index));
+  map_data->map()[key] = value;
 }
 
 jboolean NavigationControllerAndroid::IsEntryMarkedToBeSkipped(
diff --git a/content/browser/frame_host/navigation_entry_impl.cc b/content/browser/frame_host/navigation_entry_impl.cc
index ef34341f..85482344 100644
--- a/content/browser/frame_host/navigation_entry_impl.cc
+++ b/content/browser/frame_host/navigation_entry_impl.cc
@@ -604,24 +604,6 @@
   return can_load_local_resources_;
 }
 
-void NavigationEntryImpl::SetExtraData(const std::string& key,
-                                       const base::string16& data) {
-  extra_data_[key] = data;
-}
-
-bool NavigationEntryImpl::GetExtraData(const std::string& key,
-                                       base::string16* data) {
-  auto iter = extra_data_.find(key);
-  if (iter == extra_data_.end())
-    return false;
-  *data = iter->second;
-  return true;
-}
-
-void NavigationEntryImpl::ClearExtraData(const std::string& key) {
-  extra_data_.erase(key);
-}
-
 std::unique_ptr<NavigationEntryImpl> NavigationEntryImpl::Clone() const {
   return NavigationEntryImpl::CloneAndReplace(nullptr, false, nullptr, nullptr);
 }
@@ -669,7 +651,7 @@
   // ResetForCommit: frame_tree_node_id_
   copy->has_user_gesture_ = has_user_gesture_;
   // ResetForCommit: reload_type_
-  copy->extra_data_ = extra_data_;
+  copy->CloneDataFrom(*this);
   copy->replaced_entry_data_ = replaced_entry_data_;
   copy->should_skip_on_back_forward_ui_ = should_skip_on_back_forward_ui_;
 
diff --git a/content/browser/frame_host/navigation_entry_impl.h b/content/browser/frame_host/navigation_entry_impl.h
index 105f3ac..f7f3b75f 100644
--- a/content/browser/frame_host/navigation_entry_impl.h
+++ b/content/browser/frame_host/navigation_entry_impl.h
@@ -139,10 +139,6 @@
   base::Time GetTimestamp() override;
   void SetCanLoadLocalResources(bool allow) override;
   bool GetCanLoadLocalResources() override;
-  void SetExtraData(const std::string& key,
-                    const base::string16& data) override;
-  bool GetExtraData(const std::string& key, base::string16* data) override;
-  void ClearExtraData(const std::string& key) override;
   void SetHttpStatusCode(int http_status_code) override;
   int GetHttpStatusCode() override;
   void SetRedirectChain(const std::vector<GURL>& redirects) override;
@@ -532,11 +528,6 @@
   // Determine if the navigation was started within a context menu.
   bool started_from_context_menu_;
 
-  // Used to store extra data to support browser features. This member is not
-  // persisted, unless specific data is taken out/put back in at save/restore
-  // time (see TabNavigation for an example of this).
-  std::map<std::string, base::string16> extra_data_;
-
   // Set to true if the navigation controller gets notified about a SSL error
   // for a pending navigation. Defaults to false.
   bool ssl_error_;
diff --git a/content/browser/frame_host/navigation_entry_impl_unittest.cc b/content/browser/frame_host/navigation_entry_impl_unittest.cc
index 3d7e8f9..4bd938e 100644
--- a/content/browser/frame_host/navigation_entry_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_entry_impl_unittest.cc
@@ -317,27 +317,6 @@
   EXPECT_EQ(now, entry1_->GetTimestamp());
 }
 
-// Test extra data stored in the navigation entry.
-TEST_F(NavigationEntryTest, NavigationEntryExtraData) {
-  base::string16 test_data = ASCIIToUTF16("my search terms");
-  base::string16 output;
-  entry1_->SetExtraData("search_terms", test_data);
-
-  EXPECT_FALSE(entry1_->GetExtraData("non_existent_key", &output));
-  EXPECT_EQ(ASCIIToUTF16(""), output);
-  EXPECT_TRUE(entry1_->GetExtraData("search_terms", &output));
-  EXPECT_EQ(test_data, output);
-  // Data is cleared.
-  entry1_->ClearExtraData("search_terms");
-  // Content in |output| is not modified if data is not present at the key.
-  EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output));
-  EXPECT_EQ(test_data, output);
-  // Using an empty string shows that the data is not present in the map.
-  base::string16 output2;
-  EXPECT_FALSE(entry1_->GetExtraData("search_terms", &output2));
-  EXPECT_EQ(ASCIIToUTF16(""), output2);
-}
-
 #if defined(OS_ANDROID)
 // Test that content URIs correctly show the file display name as the title.
 TEST_F(NavigationEntryTest, NavigationEntryContentUri) {
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 64bb4b9..8ffc145 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -55,12 +55,22 @@
   }
 }
 
-ui::WebScopedInputEvent ScaleEvent(const WebInputEvent& event, double scale) {
+std::unique_ptr<InputEvent> ScaleEvent(const WebInputEvent& event,
+                                       double scale,
+                                       const ui::LatencyInfo latency_info) {
   std::unique_ptr<blink::WebInputEvent> event_in_viewport =
       ui::ScaleWebInputEvent(event, scale);
-  if (event_in_viewport)
-    return ui::WebScopedInputEvent(event_in_viewport.release());
-  return ui::WebInputEventTraits::Clone(event);
+  if (event_in_viewport) {
+    ui::LatencyInfo scaled_latency_info(latency_info);
+    scaled_latency_info.set_scroll_update_delta(
+        latency_info.scroll_update_delta() * scale);
+    return std::make_unique<InputEvent>(
+        ui::WebScopedInputEvent(event_in_viewport.release()),
+        scaled_latency_info);
+  }
+
+  return std::make_unique<InputEvent>(ui::WebInputEventTraits::Clone(event),
+                                      latency_info);
 }
 
 }  // namespace
@@ -520,8 +530,8 @@
     return;
   }
 
-  std::unique_ptr<InputEvent> event = std::make_unique<InputEvent>(
-      ScaleEvent(input_event, device_scale_factor_), latency_info);
+  std::unique_ptr<InputEvent> event =
+      ScaleEvent(input_event, device_scale_factor_, latency_info);
   if (WebInputEventTraits::ShouldBlockEventStream(input_event)) {
     TRACE_EVENT_INSTANT0("input", "InputEventSentBlocking",
                          TRACE_EVENT_SCOPE_THREAD);
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
index 33662f3c..fd04742 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
@@ -1204,115 +1204,4 @@
   EXPECT_TRUE(HistogramSizeEq("Event.Latency.EndToEnd.TouchpadPinch", 1));
 }
 
-TEST_F(RenderWidgetHostLatencyTrackerTest, TestScrollUpdateAverageLag) {
-  // Simulate a simple situation that events at every 10ms and start at t=15ms,
-  // frame swaps at every 10ms too and start at t=20ms.
-  base::TimeTicks event_time =
-      base::TimeTicks() + base::TimeDelta::FromMilliseconds(5);
-  base::TimeTicks frame_time =
-      base::TimeTicks() + base::TimeDelta::FromMilliseconds(10);
-  {
-    // ScrollBegin
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    touch_latency.set_scroll_update_delta(10);
-    event_time += base::TimeDelta::FromMilliseconds(10);  // 15ms
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
-        event_time, 1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);  // 20ms
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency,
-                                   false /* rendering_on_main */, frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // Send 101 ScrollUpdate events to verify that there is 1 AverageLag record
-  // per 1 second.
-  const int kUpdates = 101;
-  for (int i = 0; i < kUpdates; i++) {
-    // ScrollUpdate
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    const int sign = (i < kUpdates / 2) ? 1 : -1;
-    touch_latency.set_scroll_update_delta(sign * 10);
-    event_time += base::TimeDelta::FromMilliseconds(10);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, event_time,
-        1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency, false /*rendering_on_main*/,
-                                   frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // ScrollBegin report_time is at 20ms, so the next ScrollUpdate report_time is
-  // at 1020ms. The last event_time that finish this report should be later than
-  // 1020ms.
-  EXPECT_EQ(event_time,
-            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1025));
-  EXPECT_EQ(frame_time,
-            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1030));
-
-  // ScrollBegin AverageLag are the area between the event original component
-  // (time=15ms, delta=10px) to the frame swap time (time=20ms, expect finger
-  // position at delta=15px). The AverageLag scaled to 1 second is
-  // (5ms*10px+5ms*5px/2)/5ms = 12.5px.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
-              ElementsAre(Bucket(12, 1)));
-  // This ScrollUpdate AverageLag are calculated as the finger uniformly scroll
-  // 10px each frame. For each frame, the Lag on the last frame start is 5px,
-  // and Lag on this frame swap is 15px, so the AverageLag in 1 second is 10px.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
-              ElementsAre(Bucket(10, 1)));
-  ResetHistograms();
-
-  {
-    // Send another ScrollBegin to end the unfinished ScrollUpdate report.
-    SyntheticWebTouchEvent touch;
-    touch.PressPoint(0, 0);
-    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
-
-    event_time += base::TimeDelta::FromMilliseconds(10);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
-        event_time, 1);
-    touch_latency.AddLatencyNumberWithTimestamp(
-        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
-        1);
-
-    frame_time += base::TimeDelta::FromMilliseconds(10);
-    AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, frame_time);
-    AddRenderingScheduledComponent(&touch_latency,
-                                   false /* rendering_on_main */, frame_time);
-    tracker()->OnInputEvent(touch, &touch_latency);
-    tracker()->OnInputEventAck(touch, &touch_latency,
-                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-    viz_tracker()->OnGpuSwapBuffersCompleted(touch_latency);
-  }
-  // The last frame's lag is 8.75px and truncated to 8.
-  EXPECT_THAT(histogram_tester().GetAllSamples(
-                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
-              ElementsAre(Bucket(8, 1)));
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
index 69eb47a..38c4daf 100644
--- a/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
+++ b/content/browser/renderer_host/pepper/content_browser_pepper_host_factory.cc
@@ -110,25 +110,29 @@
       return std::unique_ptr<ppapi::host::ResourceHost>(new PepperFileRefHost(
           host_, instance, resource, file_system, internal_path));
     }
-    case PpapiHostMsg_TCPSocket_Create::ID: {
-      ppapi::TCPSocketVersion version;
-      if (!ppapi::UnpackMessage<PpapiHostMsg_TCPSocket_Create>(message,
-                                                               &version) ||
-          version == ppapi::TCP_SOCKET_VERSION_PRIVATE) {
-        return std::unique_ptr<ppapi::host::ResourceHost>();
-      }
+  }
 
-      return CreateNewTCPSocket(instance, resource, version);
-    }
-    case PpapiHostMsg_UDPSocket_Create::ID: {
-      if (CanCreateSocket()) {
+  // Socket interfaces.
+  if (GetPermissions().HasPermission(ppapi::PERMISSION_SOCKET)) {
+    switch (message.type()) {
+      case PpapiHostMsg_TCPSocket_Create::ID: {
+        ppapi::TCPSocketVersion version;
+        if (!ppapi::UnpackMessage<PpapiHostMsg_TCPSocket_Create>(message,
+                                                                 &version) ||
+            version == ppapi::TCP_SOCKET_VERSION_PRIVATE) {
+          return nullptr;
+        }
+        if (!CanCreateSocket())
+          return nullptr;
+        return CreateNewTCPSocket(instance, resource, version);
+      }
+      case PpapiHostMsg_UDPSocket_Create::ID: {
+        if (!CanCreateSocket())
+          return nullptr;
         scoped_refptr<ppapi::host::ResourceMessageFilter> udp_socket(
             new PepperUDPSocketMessageFilter(host_, instance, false));
-        return std::unique_ptr<ppapi::host::ResourceHost>(
-            new ppapi::host::MessageFilterHost(host_->GetPpapiHost(), instance,
-                                               resource, udp_socket));
-      } else {
-        return std::unique_ptr<ppapi::host::ResourceHost>();
+        return std::make_unique<ppapi::host::MessageFilterHost>(
+            host_->GetPpapiHost(), instance, resource, udp_socket);
       }
     }
   }
diff --git a/content/browser/resources/appcache/appcache_internals.css b/content/browser/resources/appcache/appcache_internals.css
index 0844ed5..b2fe53f3 100644
--- a/content/browser/resources/appcache/appcache_internals.css
+++ b/content/browser/resources/appcache/appcache_internals.css
@@ -68,9 +68,17 @@
 
 .appcache-info-template-file-url {
   width: 200px;
-  height:25px;
+  height: 25px;
 }
 
 .appcache-info-template-file-url {
   color: rgb(60, 102, 221);
 }
+
+.appcache-details {
+  text-align: start;
+}
+
+.appcache-details td {
+  width: 80px;
+}
diff --git a/content/browser/resources/appcache/appcache_internals.html b/content/browser/resources/appcache/appcache_internals.html
index 45ce6bb..7dfd489 100644
--- a/content/browser/resources/appcache/appcache_internals.html
+++ b/content/browser/resources/appcache/appcache_internals.html
@@ -80,19 +80,31 @@
         </div>
       </div>
 
-      <div id="appcache-info-template" jsselect="$this.items">
-        <span>
-          <a href="#" class="appcache-info-template-file-url"
-                      jscontent="fileUrl" jsvalues=".responseId:responseId">
-          </a>
-        </span>
-        <span jscontent="totalSize"></span>
-        =
-        <span jscontent="responseSize"></span> (response)
-        +
-        <span jscontent="paddingSize"></span> (padding)
-        <span jscontent="properties"></span>
-      </div>
+      <table id="appcache-info-template">
+        <thead>
+          <tr>
+            <th>File URL</th>
+            <th>Total Size</th>
+            <th>Response</th>
+            <th>Padding</th>
+            <th>Properties</th>
+          </tr>
+        </thead>
+        <tbody class="appcache-details"
+               jsvalues=".manifestURL:$manifestURL;.groupId:$groupId;">
+          <tr jsselect="items">
+            <td>
+              <a href="#" class="appcache-info-template-file-url"
+                          jscontent="fileUrl" jsvalues=".responseId:responseId">
+              </a>
+            </td>
+            <td jscontent="totalSize"></td>
+            <td jscontent="responseSize"></td>
+            <td jscontent="paddingSize"></td>
+            <td jscontent="properties"></td>
+          </tr>
+        </tbody>
+      </table>
     </div>
     <h1>Application Cache</h1>
     <div class="content">
diff --git a/content/common/download/mhtml_file_writer.mojom b/content/common/download/mhtml_file_writer.mojom
index 619f02a..04c3b71 100644
--- a/content/common/download/mhtml_file_writer.mojom
+++ b/content/common/download/mhtml_file_writer.mojom
@@ -33,13 +33,22 @@
 
   // A render process needed for the serialization of one of the page's frame is
   // no more. Determined by the browser.
-  kRenderProcessExited
+  kRenderProcessExited,
+
+  // There is a problem reading or writing serialized data to the Data Pipe.
+  // Determined by either browser or renderer.
+  kStreamingError
+};
+
+union MhtmlOutputHandle {
+  // Destination file handle.
+  mojo_base.mojom.File file_handle;
+
+  // Data pipe producer handle for on-the-fly hashing.
+  handle<data_pipe_producer> producer_handle;
 };
 
 struct SerializeAsMHTMLParams {
-  // Destination file handle.
-  mojo_base.mojom.File destination_file;
-
   // MHTML boundary marker / MIME multipart boundary maker. The same
   // |mhtml_boundary_marker| should be used for serialization of each frame.
   string mhtml_boundary_marker;
@@ -67,10 +76,13 @@
 
   // Salt used for |digests_of_uris_to_skip|.
   string salt;
+
+  // Destination handle to write MHTML data to.
+  MhtmlOutputHandle output_handle;
 };
 
 // Serialize target frame and its resources into MHTML and write it into the
-// provided destination file handle.  Note that when serializing multiple
+// provided output handle.  Note that when serializing multiple
 // frames, one needs to serialize the *main* frame first (the main frame
 // needs to go first according to RFC2557 + the main frame will trigger
 // generation of the MHTML header).
diff --git a/content/public/browser/navigation_entry.h b/content/public/browser/navigation_entry.h
index ea0573d..e3c6f60f 100644
--- a/content/public/browser/navigation_entry.h
+++ b/content/public/browser/navigation_entry.h
@@ -13,6 +13,7 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
+#include "base/supports_user_data.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
@@ -34,9 +35,9 @@
 // required to recreate a browsing state. This includes some opaque binary
 // state as provided by the WebContents as well as some clear text title and
 // URL which is used for our user interface.
-class NavigationEntry {
+class NavigationEntry : public base::SupportsUserData {
  public:
-  virtual ~NavigationEntry() {}
+  ~NavigationEntry() override {}
 
   CONTENT_EXPORT static std::unique_ptr<NavigationEntry> Create();
 
@@ -193,15 +194,6 @@
   virtual void SetCanLoadLocalResources(bool allow) = 0;
   virtual bool GetCanLoadLocalResources() = 0;
 
-  // Set extra data on this NavigationEntry according to the specified |key|.
-  // This data is not persisted by default.
-  virtual void SetExtraData(const std::string& key,
-                            const base::string16& data) = 0;
-  // If present, fills the |data| present at the specified |key|.
-  virtual bool GetExtraData(const std::string& key, base::string16* data) = 0;
-  // Removes the data at the specified |key|.
-  virtual void ClearExtraData(const std::string& key) = 0;
-
   // The status code of the last known successful navigation.  If
   // GetHttpStatusCode() returns 0 that means that either:
   //
diff --git a/content/public/common/mhtml_generation_params.h b/content/public/common/mhtml_generation_params.h
index d1fe446..479109f 100644
--- a/content/public/common/mhtml_generation_params.h
+++ b/content/public/common/mhtml_generation_params.h
@@ -28,9 +28,9 @@
   // Run page problem detectors while generating MTHML if true.
   bool use_page_problem_detectors = false;
 
-  // Whether to use Mojo for the MHTML serialization pipeline.
+  // Whether to compute the hash of the contents while saving the MHTML file.
   // This is triggered by the feature flag kOnTheFlyMhtmlHashComputation.
-  bool use_mojo_for_mhtml_serialization = false;
+  bool compute_contents_hash = false;
 };
 
 }  // namespace content
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index b9ff933..09c06057 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -397,6 +397,8 @@
     "media_stream_utils.cc",
     "menu_item_builder.cc",
     "menu_item_builder.h",
+    "mhtml_handle_writer.cc",
+    "mhtml_handle_writer.h",
     "mojo/blink_interface_provider_impl.cc",
     "mojo/blink_interface_provider_impl.h",
     "mojo/blink_interface_registry_impl.cc",
diff --git a/content/renderer/dom_automation_controller.cc b/content/renderer/dom_automation_controller.cc
index 4e7f80bf36..bf231fc 100644
--- a/content/renderer/dom_automation_controller.cc
+++ b/content/renderer/dom_automation_controller.cc
@@ -36,8 +36,10 @@
     return;
 
   v8::Local<v8::Object> global = context->Global();
-  global->Set(gin::StringToV8(isolate, "domAutomationController"),
-              controller.ToV8());
+  global
+      ->Set(context, gin::StringToV8(isolate, "domAutomationController"),
+            controller.ToV8())
+      .Check();
 }
 
 DomAutomationController::DomAutomationController(RenderFrame* render_frame)
@@ -72,8 +74,10 @@
     return;
 
   v8::Local<v8::Object> global = context->Global();
-  global->Set(gin::StringToV8(isolate, "domAutomationController"),
-              controller.ToV8());
+  global
+      ->Set(context, gin::StringToV8(isolate, "domAutomationController"),
+            controller.ToV8())
+      .Check();
 }
 
 bool DomAutomationController::SendMsg(const gin::Arguments& args) {
diff --git a/content/renderer/gpu_benchmarking_extension.cc b/content/renderer/gpu_benchmarking_extension.cc
index 232c323c..b16d0b13 100644
--- a/content/renderer/gpu_benchmarking_extension.cc
+++ b/content/renderer/gpu_benchmarking_extension.cc
@@ -544,7 +544,10 @@
     return;
 
   v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
-  chrome->Set(gin::StringToV8(isolate, "gpuBenchmarking"), controller.ToV8());
+  chrome
+      ->Set(context, gin::StringToV8(isolate, "gpuBenchmarking"),
+            controller.ToV8())
+      .Check();
 }
 
 GpuBenchmarking::GpuBenchmarking(RenderFrameImpl* frame)
diff --git a/content/renderer/mhtml_handle_writer.cc b/content/renderer/mhtml_handle_writer.cc
new file mode 100644
index 0000000..c6eb1c0
--- /dev/null
+++ b/content/renderer/mhtml_handle_writer.cc
@@ -0,0 +1,176 @@
+// 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/renderer/mhtml_handle_writer.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "content/common/download/mhtml_file_writer.mojom.h"
+#include "content/public/renderer/render_thread.h"
+#include "third_party/blink/public/platform/web_thread_safe_data.h"
+
+namespace content {
+
+MHTMLHandleWriter::MHTMLHandleWriter(
+    scoped_refptr<base::TaskRunner> main_thread_task_runner,
+    MHTMLWriteCompleteCallback callback)
+    : main_thread_task_runner_(std::move(main_thread_task_runner)),
+      callback_(std::move(callback)) {}
+
+MHTMLHandleWriter::~MHTMLHandleWriter() {}
+
+void MHTMLHandleWriter::WriteContents(
+    std::vector<blink::WebThreadSafeData> mhtml_contents) {
+  TRACE_EVENT_ASYNC_BEGIN0("page-serialization",
+                           "Writing MHTML contents to handle", this);
+  DCHECK(mhtml_write_start_time_.is_null());
+  mhtml_write_start_time_ = base::TimeTicks::Now();
+
+  WriteContentsImpl(std::move(mhtml_contents));
+}
+
+void MHTMLHandleWriter::Finish(mojom::MhtmlSaveStatus save_status) {
+  DCHECK(!RenderThread::IsMainThread())
+      << "Should not run in the main renderer thread";
+
+  // Only record UMA if WriteContents has been called.
+  if (!mhtml_write_start_time_.is_null()) {
+    TRACE_EVENT_ASYNC_END0("page-serialization",
+                           "WriteContentsImpl (MHTMLHandleWriter)", this);
+    base::TimeDelta mhtml_write_time =
+        base::TimeTicks::Now() - mhtml_write_start_time_;
+    UMA_HISTOGRAM_TIMES(
+        "PageSerialization.MhtmlGeneration.WriteToDiskTime.SingleFrame",
+        mhtml_write_time);
+  }
+
+  Close();
+
+  main_thread_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback_), save_status));
+  delete this;
+}
+
+MHTMLFileHandleWriter::MHTMLFileHandleWriter(
+    scoped_refptr<base::TaskRunner> main_thread_task_runner,
+    MHTMLWriteCompleteCallback callback,
+    base::File file)
+    : MHTMLHandleWriter(std::move(main_thread_task_runner),
+                        std::move(callback)),
+      file_(std::move(file)) {}
+
+MHTMLFileHandleWriter::~MHTMLFileHandleWriter() {}
+
+void MHTMLFileHandleWriter::WriteContentsImpl(
+    std::vector<blink::WebThreadSafeData> mhtml_contents) {
+  mojom::MhtmlSaveStatus save_status = mojom::MhtmlSaveStatus::kSuccess;
+  for (const blink::WebThreadSafeData& data : mhtml_contents) {
+    if (!data.IsEmpty() &&
+        file_.WriteAtCurrentPos(data.Data(), data.size()) < 0) {
+      save_status = mojom::MhtmlSaveStatus::kFileWritingError;
+      break;
+    }
+  }
+  Finish(save_status);
+}
+
+void MHTMLFileHandleWriter::Close() {
+  file_.Close();
+}
+
+MHTMLProducerHandleWriter::MHTMLProducerHandleWriter(
+    scoped_refptr<base::TaskRunner> main_thread_task_runner,
+    MHTMLWriteCompleteCallback callback,
+    mojo::ScopedDataPipeProducerHandle producer)
+    : MHTMLHandleWriter(std::move(main_thread_task_runner),
+                        std::move(callback)),
+      producer_(std::move(producer)),
+      current_block_(0),
+      write_position_(0) {}
+
+void MHTMLProducerHandleWriter::WriteContentsImpl(
+    std::vector<blink::WebThreadSafeData> mhtml_contents) {
+  DCHECK(mhtml_contents_.empty());
+  mhtml_contents_ = std::move(mhtml_contents);
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+
+  task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&MHTMLProducerHandleWriter::BeginWatchingHandle,
+                                base::Unretained(this)));
+}
+
+MHTMLProducerHandleWriter::~MHTMLProducerHandleWriter() {}
+
+void MHTMLProducerHandleWriter::Close() {
+  producer_.reset();
+}
+
+void MHTMLProducerHandleWriter::BeginWatchingHandle() {
+  // mojo::SimpleWatcher's constructor by default gets a reference ptr
+  // to the current SequencedTaskRunner if one is not specified, keeping
+  // the current SequencedTaskRunner's lifetime bound to |watcher_|.
+  watcher_ = std::make_unique<mojo::SimpleWatcher>(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC);
+  // Using base::Unretained is safe, as |this| owns |watcher_|.
+  watcher_->Watch(
+      producer_.get(),
+      MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      MOJO_WATCH_CONDITION_SATISFIED,
+      base::BindRepeating(&MHTMLProducerHandleWriter::TryWritingContents,
+                          base::Unretained(this)));
+}
+
+// TODO(https://crbug.com/915966): This can be simplified with usage
+// of BlockingCopyToString once error signalling is implemented and
+// updated with usage of base::span instead of std::string.
+void MHTMLProducerHandleWriter::TryWritingContents(
+    MojoResult result,
+    const mojo::HandleSignalsState& state) {
+  if (result != MOJO_RESULT_OK) {
+    DLOG(ERROR)
+        << "Error receiving notifications from producer handle watcher.";
+    Finish(mojom::MhtmlSaveStatus::kStreamingError);
+    return;
+  }
+
+  while (true) {
+    const blink::WebThreadSafeData& data = mhtml_contents_.at(current_block_);
+
+    // If there is no more data in this block, continue to next block or
+    // finish.
+    uint32_t num_bytes = data.size() - write_position_;
+    if (num_bytes == 0) {
+      write_position_ = 0;
+      if (++current_block_ >= mhtml_contents_.size()) {
+        Finish(mojom::MhtmlSaveStatus::kSuccess);
+        return;
+      }
+      continue;
+    }
+
+    result = producer_->WriteData(data.Data() + write_position_, &num_bytes,
+                                  MOJO_WRITE_DATA_FLAG_NONE);
+
+    // Break out of loop early if write was not successful to avoid
+    // incrementing the write position incorrectly.
+    if (result != MOJO_RESULT_OK)
+      break;
+
+    // Reaching this indicates a successful write.
+    write_position_ += num_bytes;
+    DCHECK(write_position_ <= data.size());
+  }
+
+  if (result != MOJO_RESULT_SHOULD_WAIT) {
+    Finish(mojom::MhtmlSaveStatus::kStreamingError);
+  }
+
+  // Buffer is full, return to automatically re-arm the watcher.
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/renderer/mhtml_handle_writer.h b/content/renderer/mhtml_handle_writer.h
new file mode 100644
index 0000000..6a44851
--- /dev/null
+++ b/content/renderer/mhtml_handle_writer.h
@@ -0,0 +1,120 @@
+// 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_RENDERER_MHTML_HANDLE_WRITER_H_
+#define CONTENT_RENDERER_MHTML_HANDLE_WRITER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/files/file.h"
+#include "content/common/download/mhtml_file_writer.mojom-forward.h"
+
+namespace blink {
+class WebThreadSafeData;
+}
+
+namespace content {
+
+// TODO(https://crbug.com/915966): This class needs unit tests.
+
+// Handle wrapper for MHTML serialization to abstract the handle which data
+// is written to. This is instantiated on the heap and is responsible for
+// destroying itself after completing its write operation.
+// Should only live in blocking sequenced threads.
+class MHTMLHandleWriter {
+ public:
+  using MHTMLWriteCompleteCallback =
+      base::OnceCallback<void(mojom::MhtmlSaveStatus)>;
+
+  MHTMLHandleWriter(scoped_refptr<base::TaskRunner> main_thread_task_runner,
+                    MHTMLWriteCompleteCallback callback);
+  virtual ~MHTMLHandleWriter();
+
+  void WriteContents(std::vector<blink::WebThreadSafeData> mhtml_contents);
+
+  // Finalizes the writing operation, recording the UMA, closing the handle,
+  // and deleting itself.
+  void Finish(mojom::MhtmlSaveStatus save_status);
+
+ protected:
+  virtual void WriteContentsImpl(
+      std::vector<blink::WebThreadSafeData> mhtml_contents) = 0;
+
+  virtual void Close() = 0;
+
+ private:
+  base::TimeTicks mhtml_write_start_time_;
+
+  scoped_refptr<base::TaskRunner> main_thread_task_runner_;
+  MHTMLWriteCompleteCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(MHTMLHandleWriter);
+};
+
+// Wraps a base::File target to write MHTML contents to.
+// This implementation immediately finishes after writing all MHTML contents
+// to the file handle.
+class MHTMLFileHandleWriter : public MHTMLHandleWriter {
+ public:
+  MHTMLFileHandleWriter(scoped_refptr<base::TaskRunner> main_thread_task_runner,
+                        MHTMLWriteCompleteCallback callback,
+                        base::File file);
+  ~MHTMLFileHandleWriter() override;
+
+ protected:
+  // Writes the serialized and encoded MHTML data from WebThreadSafeData
+  // instances directly to the file handle passed from the Browser.
+  void WriteContentsImpl(
+      std::vector<blink::WebThreadSafeData> mhtml_contents) override;
+
+  void Close() override;
+
+ private:
+  base::File file_;
+
+  DISALLOW_COPY_AND_ASSIGN(MHTMLFileHandleWriter);
+};
+
+// Wraps a mojo::ScopedDataPipeProducerHandle target to write MHTML contents to.
+// This implementation does not immediately finish and destroy itself due to
+// the limited size of the data pipe buffer. We must ensure all data is
+// written to the handle before finishing the write operation.
+class MHTMLProducerHandleWriter : public MHTMLHandleWriter {
+ public:
+  MHTMLProducerHandleWriter(
+      scoped_refptr<base::TaskRunner> main_thread_task_runner,
+      MHTMLWriteCompleteCallback callback,
+      mojo::ScopedDataPipeProducerHandle producer);
+  ~MHTMLProducerHandleWriter() override;
+
+ protected:
+  // Creates a new SequencedTaskRunner to dispatch |watcher_| invocations on.
+  void WriteContentsImpl(
+      std::vector<blink::WebThreadSafeData> mhtml_contents) override;
+
+  void Close() override;
+
+ private:
+  void BeginWatchingHandle();
+
+  // Writes the serialized and encoded MHTML data from WebThreadSafeData
+  // instances to producer while possible.
+  void TryWritingContents(MojoResult result,
+                          const mojo::HandleSignalsState& state);
+
+  mojo::ScopedDataPipeProducerHandle producer_;
+
+  std::vector<blink::WebThreadSafeData> mhtml_contents_;
+  std::unique_ptr<mojo::SimpleWatcher> watcher_;
+
+  size_t current_block_;
+  size_t write_position_;
+
+  DISALLOW_COPY_AND_ASSIGN(MHTMLProducerHandleWriter);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MHTML_HANDLE_WRITER_H_
\ No newline at end of file
diff --git a/content/renderer/pepper/plugin_module.cc b/content/renderer/pepper/plugin_module.cc
index b03756a..ebabd9b 100644
--- a/content/renderer/pepper/plugin_module.cc
+++ b/content/renderer/pepper/plugin_module.cc
@@ -402,6 +402,7 @@
 #include "ppapi/thunk/interfaces_ppb_private_pdf.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev_channel.h"
+#include "ppapi/thunk/interfaces_ppb_public_socket.h"
 #include "ppapi/thunk/interfaces_ppb_public_stable.h"
 
 #undef PROXIED_IFACE
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index c067241..6a61a8b 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -128,6 +128,7 @@
 #include "content/renderer/media/stream/media_stream_device_observer.h"
 #include "content/renderer/media/stream/user_media_client_impl.h"
 #include "content/renderer/media/webrtc/rtc_peer_connection_handler.h"
+#include "content/renderer/mhtml_handle_writer.h"
 #include "content/renderer/mojo/blink_interface_registry_impl.h"
 #include "content/renderer/navigation_client.h"
 #include "content/renderer/navigation_state.h"
@@ -694,29 +695,52 @@
   return request.HttpMethod().Utf8() == "POST";
 }
 
-// Writes to file the serialized and encoded MHTML data from WebThreadSafeData
-// instances.
-mojom::MhtmlSaveStatus WriteMHTMLToDisk(
-    std::vector<WebThreadSafeData> mhtml_contents,
-    base::File file) {
-  TRACE_EVENT0("page-serialization", "WriteMHTMLToDisk (RenderFrameImpl)");
-  SCOPED_UMA_HISTOGRAM_TIMER(
-      "PageSerialization.MhtmlGeneration.WriteToDiskTime.SingleFrame");
-  DCHECK(!RenderThread::IsMainThread())
-      << "Should not run in the main renderer thread";
-  mojom::MhtmlSaveStatus save_status = mojom::MhtmlSaveStatus::kSuccess;
-  for (const WebThreadSafeData& data : mhtml_contents) {
-    if (!data.IsEmpty() &&
-        file.WriteAtCurrentPos(data.Data(), data.size()) < 0) {
-      save_status = mojom::MhtmlSaveStatus::kFileWritingError;
-      break;
+// Delegate responsible for determining the handle writing implementation by
+// instantiating an MHTMLHandleWriter on the heap respective to the passed in
+// MHTMLSerializationParams. This transfers ownership of the handle to the
+// new MHTMLHandleWriter.
+class MHTMLHandleWriterDelegate {
+ public:
+  MHTMLHandleWriterDelegate(
+      mojom::SerializeAsMHTMLParams& params,
+      MHTMLHandleWriter::MHTMLWriteCompleteCallback callback,
+      scoped_refptr<base::TaskRunner> main_thread_task_runner) {
+    // Handle must be instantiated.
+    DCHECK(params.output_handle);
+
+    if (params.output_handle->is_file_handle()) {
+      handle_ = new MHTMLFileHandleWriter(
+          std::move(main_thread_task_runner), std::move(callback),
+          std::move(params.output_handle->get_file_handle()));
+    } else {
+      handle_ = new MHTMLProducerHandleWriter(
+          std::move(main_thread_task_runner), std::move(callback),
+          std::move(params.output_handle->get_producer_handle()));
     }
   }
-  // Explicitly close |file| here to make sure to include any flush operations
-  // in the UMA metric.
-  file.Close();
-  return save_status;
-}
+
+  void WriteContents(std::vector<WebThreadSafeData> mhtml_contents) {
+    // Using base::Unretained is safe, as calls to WriteContents() always
+    // deletes |handle| upon Finish().
+    base::PostTaskWithTraits(
+        FROM_HERE, {base::MayBlock()},
+        base::BindOnce(&MHTMLHandleWriter::WriteContents,
+                       base::Unretained(handle_), std::move(mhtml_contents)));
+  }
+
+  // Within the context of the delegate, only for premature write finish.
+  void Finish(mojom::MhtmlSaveStatus save_status) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {base::MayBlock()},
+        base::BindOnce(&MHTMLHandleWriter::Finish, base::Unretained(handle_),
+                       save_status));
+  }
+
+ private:
+  MHTMLHandleWriter* handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(MHTMLHandleWriterDelegate);
+};
 
 FaviconURL::IconType ToFaviconType(blink::WebIconURL::Type type) {
   switch (type) {
@@ -6594,7 +6618,7 @@
   // Note: the MHTML footer is written by the browser process, after the last
   // frame is serialized by a renderer process.
 
-  // Note: we assume RenderFrameImpl::OnWriteMHTMLToDiskComplete and the rest of
+  // Note: we assume RenderFrameImpl::OnWriteMHTMLComplete and the rest of
   // this function will be fast enough to not need to be accounted for in this
   // metric.
   base::TimeDelta main_thread_use_time = base::TimeTicks::Now() - start_time;
@@ -6602,30 +6626,27 @@
       "PageSerialization.MhtmlGeneration.RendererMainThreadTime.SingleFrame",
       main_thread_use_time);
 
+  MHTMLHandleWriterDelegate handle_delegate(
+      *params,
+      base::BindOnce(&RenderFrameImpl::OnWriteMHTMLComplete,
+                     weak_factory_.GetWeakPtr(), std::move(callback),
+                     std::move(serialized_resources_uri_digests),
+                     main_thread_use_time),
+      GetTaskRunner(blink::TaskType::kInternalDefault));
+
   if (save_status == mojom::MhtmlSaveStatus::kSuccess && has_some_data) {
-    base::PostTaskWithTraitsAndReplyWithResult(
-        FROM_HERE, {base::MayBlock()},
-        base::BindOnce(&WriteMHTMLToDisk, std::move(mhtml_contents),
-                       std::move(params->destination_file)),
-        base::BindOnce(&RenderFrameImpl::OnWriteMHTMLToDiskComplete,
-                       weak_factory_.GetWeakPtr(), std::move(callback),
-                       std::move(serialized_resources_uri_digests),
-                       main_thread_use_time));
+    handle_delegate.WriteContents(mhtml_contents);
   } else {
-    params->destination_file.Close();
-    OnWriteMHTMLToDiskComplete(std::move(callback),
-                               std::move(serialized_resources_uri_digests),
-                               main_thread_use_time, save_status);
+    handle_delegate.Finish(save_status);
   }
 }
 
-void RenderFrameImpl::OnWriteMHTMLToDiskComplete(
+void RenderFrameImpl::OnWriteMHTMLComplete(
     SerializeAsMHTMLCallback callback,
     std::unordered_set<std::string> serialized_resources_uri_digests,
     base::TimeDelta main_thread_use_time,
     mojom::MhtmlSaveStatus save_status) {
-  TRACE_EVENT1("page-serialization",
-               "RenderFrameImpl::OnWriteMHTMLToDiskComplete",
+  TRACE_EVENT1("page-serialization", "RenderFrameImpl::OnWriteMHTMLComplete",
                "frame save status", save_status);
   DCHECK(RenderThread::IsMainThread())
       << "Must run in the main renderer thread";
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index bf2ed251c..1f6e37c 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1170,8 +1170,8 @@
 #endif
 
   // Callback scheduled from SerializeAsMHTML for when writing serialized
-  // MHTML to file has been completed in the file thread.
-  void OnWriteMHTMLToDiskComplete(
+  // MHTML to the handle has been completed in the file thread.
+  void OnWriteMHTMLComplete(
       SerializeAsMHTMLCallback callback,
       std::unordered_set<std::string> serialized_resources_uri_digests,
       base::TimeDelta main_thread_use_time,
diff --git a/content/renderer/skia_benchmarking_extension.cc b/content/renderer/skia_benchmarking_extension.cc
index 0ad993d7..e125a2d 100644
--- a/content/renderer/skia_benchmarking_extension.cc
+++ b/content/renderer/skia_benchmarking_extension.cc
@@ -125,7 +125,10 @@
     return;
 
   v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
-  chrome->Set(gin::StringToV8(isolate, "skiaBenchmarking"), controller.ToV8());
+  chrome
+      ->Set(context, gin::StringToV8(isolate, "skiaBenchmarking"),
+            controller.ToV8())
+      .Check();
 }
 
 // static
@@ -277,21 +280,31 @@
   skia::BenchmarkingCanvas benchmarking_canvas(&canvas);
   picture->picture->playback(&benchmarking_canvas);
 
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Array> op_times =
       v8::Array::New(isolate, benchmarking_canvas.CommandCount());
   for (size_t i = 0; i < benchmarking_canvas.CommandCount(); ++i) {
-    op_times->Set(i, v8::Number::New(isolate, benchmarking_canvas.GetTime(i)));
+    op_times
+        ->Set(context, i,
+              v8::Number::New(isolate, benchmarking_canvas.GetTime(i)))
+        .Check();
   }
 
   v8::Local<v8::Object> result = v8::Object::New(isolate);
-  result->Set(v8::String::NewFromUtf8(isolate, "total_time",
-                                      v8::NewStringType::kInternalized)
-                  .ToLocalChecked(),
-              v8::Number::New(isolate, total_time.InMillisecondsF()));
-  result->Set(v8::String::NewFromUtf8(isolate, "cmd_times",
-                                      v8::NewStringType::kInternalized)
-                  .ToLocalChecked(),
-              op_times);
+  result
+      ->Set(context,
+            v8::String::NewFromUtf8(isolate, "total_time",
+                                    v8::NewStringType::kInternalized)
+                .ToLocalChecked(),
+            v8::Number::New(isolate, total_time.InMillisecondsF()))
+      .Check();
+  result
+      ->Set(context,
+            v8::String::NewFromUtf8(isolate, "cmd_times",
+                                    v8::NewStringType::kInternalized)
+                .ToLocalChecked(),
+            op_times)
+      .Check();
 
   args->Return(result);
 }
@@ -306,15 +319,22 @@
   if (!picture.get())
     return;
 
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> result = v8::Object::New(isolate);
-  result->Set(v8::String::NewFromUtf8(isolate, "width",
-                                      v8::NewStringType::kInternalized)
-                  .ToLocalChecked(),
-              v8::Number::New(isolate, picture->layer_rect.width()));
-  result->Set(v8::String::NewFromUtf8(isolate, "height",
-                                      v8::NewStringType::kInternalized)
-                  .ToLocalChecked(),
-              v8::Number::New(isolate, picture->layer_rect.height()));
+  result
+      ->Set(context,
+            v8::String::NewFromUtf8(isolate, "width",
+                                    v8::NewStringType::kInternalized)
+                .ToLocalChecked(),
+            v8::Number::New(isolate, picture->layer_rect.width()))
+      .Check();
+  result
+      ->Set(context,
+            v8::String::NewFromUtf8(isolate, "height",
+                                    v8::NewStringType::kInternalized)
+                .ToLocalChecked(),
+            v8::Number::New(isolate, picture->layer_rect.height()))
+      .Check();
 
   args->Return(result);
 }
diff --git a/content/renderer/stats_collection_controller.cc b/content/renderer/stats_collection_controller.cc
index 8a62df4a..894c1fb 100644
--- a/content/renderer/stats_collection_controller.cc
+++ b/content/renderer/stats_collection_controller.cc
@@ -42,8 +42,10 @@
   if (controller.IsEmpty())
     return;
   v8::Local<v8::Object> global = context->Global();
-  global->Set(gin::StringToV8(isolate, "statsCollectionController"),
-              controller.ToV8());
+  global
+      ->Set(context, gin::StringToV8(isolate, "statsCollectionController"),
+            controller.ToV8())
+      .Check();
 }
 
 StatsCollectionController::StatsCollectionController() {}
diff --git a/content/renderer/web_ui_extension.cc b/content/renderer/web_ui_extension.cc
index 2e37ad5..5af8770 100644
--- a/content/renderer/web_ui_extension.cc
+++ b/content/renderer/web_ui_extension.cc
@@ -74,16 +74,20 @@
   v8::Context::Scope context_scope(context);
 
   v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
-  chrome->Set(
-      gin::StringToSymbol(isolate, "send"),
-      gin::CreateFunctionTemplate(isolate, base::Bind(&WebUIExtension::Send))
-          ->GetFunction(context)
-          .ToLocalChecked());
-  chrome->Set(gin::StringToSymbol(isolate, "getVariableValue"),
-              gin::CreateFunctionTemplate(
-                  isolate, base::Bind(&WebUIExtension::GetVariableValue))
-                  ->GetFunction(context)
-                  .ToLocalChecked());
+  chrome
+      ->Set(context, gin::StringToSymbol(isolate, "send"),
+            gin::CreateFunctionTemplate(isolate,
+                                        base::Bind(&WebUIExtension::Send))
+                ->GetFunction(context)
+                .ToLocalChecked())
+      .Check();
+  chrome
+      ->Set(context, gin::StringToSymbol(isolate, "getVariableValue"),
+            gin::CreateFunctionTemplate(
+                isolate, base::Bind(&WebUIExtension::GetVariableValue))
+                ->GetFunction(context)
+                .ToLocalChecked())
+      .Check();
 }
 
 // static
diff --git a/content/test/data/page_with_multiple_iframes.html b/content/test/data/page_with_multiple_iframes.html
new file mode 100644
index 0000000..0f7e155
--- /dev/null
+++ b/content/test/data/page_with_multiple_iframes.html
@@ -0,0 +1,11 @@
+<html>
+<head></head>
+<body>
+  <p>This page has several iframes. Yay for iframes!</p>
+  <p><iframe src="page_with_image.html" id="test_iframe_1"></iframe></p>
+  <p><iframe src="page_with_popup.html" id="test_iframe_2"></iframe></p>
+  <p><iframe src="page_with_frameset.html" id="test_iframe_3"></iframe></p>
+  <p><iframe src="page_with_allowfullscreen_frame.html" id="test_iframe_4"></iframe></p>
+  <p><iframe src="page_with_iframe_and_link.html" id="test_iframe_5"></iframe></p>
+</body>
+</html>
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json
index 4acd353..e9928d24 100644
--- a/extensions/common/api/_manifest_features.json
+++ b/extensions/common/api/_manifest_features.json
@@ -139,7 +139,10 @@
   "file_handlers": [
     {
       "channel": "stable",
-      "extension_types": ["platform_app"]
+      "extension_types": [
+        "platform_app",
+        "hosted_app"  // This should be removed when bookmark apps have migrated off hosted apps.
+      ]
     }, {
       "channel": "stable",
       "extension_types": [ "extension"],
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 81b0a3c..c40aded 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -408,6 +408,8 @@
     "Invalid value for 'file_filters[*]'.";
 const char kInvalidFileHandlers[] =
     "Invalid value for 'file_handlers'.";
+const char kInvalidFileHandlersHostedAppsNotSupported[] =
+    "Hosted apps do not support file handlers";
 const char kInvalidFileHandlersTooManyTypesAndExtensions[] =
     "Too many MIME and extension file_handlers have been declared.";
 const char kInvalidFileHandlerExtension[] =
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 5132846..3b45fc0 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -329,6 +329,7 @@
 extern const char kInvalidFileFiltersList[];
 extern const char kInvalidFileFilterValue[];
 extern const char kInvalidFileHandlers[];
+extern const char kInvalidFileHandlersHostedAppsNotSupported[];
 extern const char kInvalidFileHandlersTooManyTypesAndExtensions[];
 extern const char kInvalidFileHandlerExtension[];
 extern const char kInvalidFileHandlerExtensionElement[];
diff --git a/extensions/common/manifest_handlers/file_handler_info.cc b/extensions/common/manifest_handlers/file_handler_info.cc
index a33e848..bad091bb 100644
--- a/extensions/common/manifest_handlers/file_handler_info.cc
+++ b/extensions/common/manifest_handlers/file_handler_info.cc
@@ -173,6 +173,17 @@
 }
 
 bool FileHandlersParser::Parse(Extension* extension, base::string16* error) {
+  // Don't load file handlers for hosted_apps unless they're also bookmark apps.
+  // This check can be removed when bookmark apps are migrated off hosted apps,
+  // and hosted_apps should be removed from the list of valid extension types
+  // for "file_handling" in extensions/common/api/_manifest_features.json.
+  if (extension->is_hosted_app() && !extension->from_bookmark()) {
+    extension->AddInstallWarning(
+        InstallWarning(errors::kInvalidFileHandlersHostedAppsNotSupported,
+                       keys::kFileHandlers));
+    return true;
+  }
+
   std::unique_ptr<FileHandlers> info(new FileHandlers);
   const base::Value* all_handlers = nullptr;
   if (!extension->manifest()->GetDictionary(keys::kFileHandlers,
diff --git a/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc b/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
index 75c336a6..aacce84 100644
--- a/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
+++ b/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
@@ -2,7 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <vector>
+
 #include "base/stl_util.h"
+#include "extensions/common/install_warning.h"
+#include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/file_handler_info.h"
 #include "extensions/common/manifest_test.h"
@@ -82,4 +86,44 @@
   ASSERT_TRUE(handlers == NULL);
 }
 
+TEST_F(FileHandlersManifestTest, HostedNotBookmarkApp) {
+  // This should load successfully but have the file handlers ignored.
+  scoped_refptr<const Extension> extension = LoadAndExpectSuccess(
+      "file_handlers_valid_hosted_app.json", extensions::Manifest::INTERNAL);
+
+  ASSERT_TRUE(extension);
+
+  std::vector<InstallWarning> expected_warnings;
+  expected_warnings.push_back(
+      InstallWarning(errors::kInvalidFileHandlersHostedAppsNotSupported));
+  EXPECT_EQ(expected_warnings, extension->install_warnings());
+
+  EXPECT_TRUE(extension->is_hosted_app());
+  EXPECT_FALSE(extension->from_bookmark());
+
+  const FileHandlersInfo* handlers =
+      FileHandlers::GetFileHandlers(extension.get());
+  EXPECT_FALSE(handlers);
+}
+
+TEST_F(FileHandlersManifestTest, HostedBookmarkApp) {
+  // This should load successfully with file handlers.
+  scoped_refptr<const Extension> extension =
+      LoadAndExpectSuccess("file_handlers_valid_hosted_app.json",
+                           extensions::Manifest::Location::INTERNAL,
+                           extensions::Extension::FROM_BOOKMARK);
+
+  ASSERT_TRUE(extension);
+  EXPECT_TRUE(extension->install_warnings().empty());
+
+  // Check we're a hosted app and a bookmark app.
+  EXPECT_TRUE(extension->is_hosted_app());
+  EXPECT_TRUE(extension->from_bookmark());
+
+  const FileHandlersInfo* handlers =
+      FileHandlers::GetFileHandlers(extension.get());
+  ASSERT_TRUE(handlers);
+  EXPECT_GE(handlers->size(), 1u);
+}
+
 }  // namespace extensions
diff --git a/extensions/test/data/manifest_tests/file_handlers_valid_hosted_app.json b/extensions/test/data/manifest_tests/file_handlers_valid_hosted_app.json
new file mode 100644
index 0000000..e4fea5d
--- /dev/null
+++ b/extensions/test/data/manifest_tests/file_handlers_valid_hosted_app.json
@@ -0,0 +1,32 @@
+{
+  "name": "test",
+  "manifest_version": 2,
+  "description": "App with file_handlers manifest.",
+  "version": "1",
+  "app": {
+    "launch": {
+      "web_url": "/"
+    }
+  },
+  "file_handlers": {
+    "text": {
+      "types": [
+        "text/*"
+      ]
+    },
+    "image": {
+      "types": [
+        "image/*"
+      ],
+      "extensions": [
+        ".png",
+        ".gif"
+      ],
+      "verb": "add_to"
+    },
+    "directories": {
+      "extensions": ["*/*"],
+      "include_directories": true
+    }
+  }
+}
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index e597994..5ca2706 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -4,10 +4,30 @@
 #include "gpu/config/gpu_finch_features.h"
 
 #if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/string_split.h"
 #include "ui/gl/android/android_surface_control_compat.h"
 #endif
 
 namespace features {
+namespace {
+
+#if defined(OS_ANDROID)
+bool FieldIsInBlacklist(const char* current_value, std::string blacklist_str) {
+  std::vector<std::string> blacklist = base::SplitString(
+      blacklist_str, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  for (const std::string& value : blacklist) {
+    if (value == current_value)
+      return true;
+  }
+
+  return false;
+}
+#endif
+
+}  // namespace
+
 #if defined(OS_ANDROID)
 // Use android AImageReader when playing videos with MediaPlayer.
 const base::Feature kAImageReaderMediaPlayer{"AImageReaderMediaPlayer",
@@ -80,8 +100,26 @@
 
 #if defined(OS_ANDROID)
 bool IsAndroidSurfaceControlEnabled() {
-  return base::FeatureList::IsEnabled(kAndroidSurfaceControl) &&
-         gl::SurfaceControl::IsSupported();
+  if (!gl::SurfaceControl::IsSupported())
+    return false;
+
+  if (!base::FeatureList::IsEnabled(kAndroidSurfaceControl))
+    return false;
+
+  if (FieldIsInBlacklist(base::android::BuildInfo::GetInstance()->model(),
+                         base::GetFieldTrialParamValueByFeature(
+                             kAndroidSurfaceControl, "blacklisted_models"))) {
+    return false;
+  }
+
+  if (FieldIsInBlacklist(
+          base::android::BuildInfo::GetInstance()->android_build_id(),
+          base::GetFieldTrialParamValueByFeature(kAndroidSurfaceControl,
+                                                 "blacklisted_build_ids"))) {
+    return false;
+  }
+
+  return true;
 }
 #endif
 
diff --git a/gpu/ipc/service/direct_composition_surface_win.cc b/gpu/ipc/service/direct_composition_surface_win.cc
index c4a7a6c..b219525 100644
--- a/gpu/ipc/service/direct_composition_surface_win.cc
+++ b/gpu/ipc/service/direct_composition_surface_win.cc
@@ -16,6 +16,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/trace_event/trace_event.h"
 #include "base/win/scoped_handle.h"
@@ -1053,6 +1054,21 @@
       D3D11_TEXTURE2D_DESC texture_desc = {};
       base::debug::Alias(&texture_desc);
       image_dxgi->texture()->GetDesc(&texture_desc);
+      static crash_reporter::CrashKeyString<32> texture_size_key(
+          "texture-size");
+      texture_size_key.Set(
+          gfx::Size(texture_desc.Width, texture_desc.Height).ToString());
+      static crash_reporter::CrashKeyString<16> texture_array_size_key(
+          "texture-array-size");
+      texture_array_size_key.Set(
+          base::StringPrintf("%d", texture_desc.ArraySize));
+      static crash_reporter::CrashKeyString<16> texture_bind_flags_key(
+          "texture-bind-flags");
+      texture_bind_flags_key.Set(
+          base::StringPrintf("%#x", texture_desc.BindFlags));
+      static crash_reporter::CrashKeyString<16> texture_usage_key(
+          "texture-usage-key");
+      texture_usage_key.Set(base::StringPrintf("%#x", texture_desc.Usage));
       base::debug::DumpWithoutCrashing();
       return false;
     }
diff --git a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index 94771a4..63fb1ae 100644
--- a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -43,7 +43,7 @@
  protected:
   // Returns the collection of default datatypes.
   std::vector<syncer::ModelType> DefaultDatatypes() {
-    static_assert(44 == syncer::MODEL_TYPE_COUNT,
+    static_assert(44 == syncer::ModelType::NUM_ENTRIES,
                   "When adding a new type, you probably want to add it here as "
                   "well (assuming it is already enabled).");
 
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index 525af18..c91a33b 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -145,6 +145,7 @@
     "//ios/public/provider/chrome/browser/voice",
     "//ios/third_party/material_components_ios",
     "//ios/web",
+    "//ios/web/common",
     "//ios/web/public",
     "//third_party/google_toolbox_for_mac",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 178ddfe..696e64e 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -189,10 +189,10 @@
 #include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
 #include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
 #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
+#include "ios/web/common/referrer_util.h"
 #include "ios/web/public/features.h"
 #include "ios/web/public/navigation_item.h"
 #import "ios/web/public/navigation_manager.h"
-#include "ios/web/public/referrer_util.h"
 #include "ios/web/public/url_scheme_util.h"
 #include "ios/web/public/user_agent.h"
 #include "ios/web/public/web_client.h"
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index e8f2786..1456faa 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -48,6 +48,7 @@
     "//ios/chrome/browser/ui/util:util",
     "//ios/net",
     "//ios/web",
+    "//ios/web/common",
     "//ui/base",
     "//url",
   ]
diff --git a/ios/chrome/browser/web/image_fetch_tab_helper.mm b/ios/chrome/browser/web/image_fetch_tab_helper.mm
index ec069fab..685fc9e 100644
--- a/ios/chrome/browser/web/image_fetch_tab_helper.mm
+++ b/ios/chrome/browser/web/image_fetch_tab_helper.mm
@@ -12,8 +12,8 @@
 #include "base/task/post_task.h"
 #include "base/values.h"
 #include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
+#include "ios/web/common/referrer_util.h"
 #include "ios/web/public/browser_state.h"
-#include "ios/web/public/referrer_util.h"
 #import "ios/web/public/web_state/navigation_context.h"
 #include "ios/web/public/web_task_traits.h"
 #include "ios/web/public/web_thread.h"
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index ef60a30..d19dcea3 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -358,7 +358,6 @@
   sources = [
     "public/crw_session_certificate_policy_cache_storage_unittest.mm",
     "public/origin_util_unittest.mm",
-    "public/referrer_util_unittest.cc",
     "public/serializable_user_data_manager_unittest.mm",
     "public/ssl_status_unittest.cc",
     "public/user_agent_unittest.mm",
@@ -379,6 +378,7 @@
     "//ios/net",
     "//ios/testing:ocmock_support",
     "//ios/web",
+    "//ios/web/common",
     "//ios/web/find_in_page",
     "//ios/web/interstitials",
     "//ios/web/navigation",
diff --git a/ios/web/common/BUILD.gn b/ios/web/common/BUILD.gn
index d146abf..4bac33b 100644
--- a/ios/web/common/BUILD.gn
+++ b/ios/web/common/BUILD.gn
@@ -9,6 +9,8 @@
     "crw_content_view.h",
     "crw_web_view_content_view.h",
     "crw_web_view_content_view.mm",
+    "referrer_util.cc",
+    "referrer_util.h",
     "url_util.cc",
     "url_util.h",
   ]
@@ -16,6 +18,8 @@
   deps = [
     "//base",
     "//ios/web/public:features",
+    "//ios/web/public:referrer",
+    "//net",
     "//url",
   ]
 
@@ -29,10 +33,15 @@
   testonly = true
   deps = [
     ":common",
+    "//base",
+    "//ios/web/public:referrer",
+    "//net",
     "//testing/gtest",
+    "//url",
   ]
 
   sources = [
+    "referrer_util_unittest.cc",
     "url_util_unittest.cc",
   ]
 }
diff --git a/ios/web/common/DEPS b/ios/web/common/DEPS
index f07d4508..a57d2fc 100644
--- a/ios/web/common/DEPS
+++ b/ios/web/common/DEPS
@@ -2,6 +2,7 @@
   # common interfaces cannot depend on private web code.
   "-ios/web",
   "+ios/web/common",
+  "+ios/web/public",
 ]
 
 specific_include_rules = {
diff --git a/ios/web/public/referrer_util.cc b/ios/web/common/referrer_util.cc
similarity index 94%
rename from ios/web/public/referrer_util.cc
rename to ios/web/common/referrer_util.cc
index f572293..01bf0dc 100644
--- a/ios/web/public/referrer_util.cc
+++ b/ios/web/common/referrer_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/web/public/referrer_util.h"
+#include "ios/web/common/referrer_util.h"
 
 #include "base/logging.h"
 #include "ios/web/public/referrer.h"
@@ -10,9 +10,8 @@
 
 namespace web {
 
-std::string ReferrerHeaderValueForNavigation(
-    const GURL& destination,
-    const web::Referrer& referrer) {
+std::string ReferrerHeaderValueForNavigation(const GURL& destination,
+                                             const web::Referrer& referrer) {
   bool is_downgrade = referrer.url.SchemeIsCryptographic() &&
                       !destination.SchemeIsCryptographic();
   switch (referrer.policy) {
diff --git a/ios/web/public/referrer_util.h b/ios/web/common/referrer_util.h
similarity index 81%
rename from ios/web/public/referrer_util.h
rename to ios/web/common/referrer_util.h
index bcbdcbef..49ef082 100644
--- a/ios/web/public/referrer_util.h
+++ b/ios/web/common/referrer_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_WEB_PUBLIC_REFERRER_UTIL_H_
-#define IOS_WEB_PUBLIC_REFERRER_UTIL_H_
+#ifndef IOS_WEB_COMMON_REFERRER_UTIL_H_
+#define IOS_WEB_COMMON_REFERRER_UTIL_H_
 
 #include <string>
 
@@ -17,9 +17,8 @@
 // Returns the string that should be sent as the Referer header value for
 // navigating to |destination| from the given referrer, taking the referrer
 // policy into account. Returns an empty string if no Referer should be sent.
-std::string ReferrerHeaderValueForNavigation(
-    const GURL& destination,
-    const web::Referrer& referrer);
+std::string ReferrerHeaderValueForNavigation(const GURL& destination,
+                                             const web::Referrer& referrer);
 
 // Returns the policy that should be used to process subsequent forwards, if
 // any.
@@ -36,4 +35,4 @@
 
 }  // namespace web
 
-#endif  // IOS_WEB_PUBLIC_REFERRER_UTIL_H_
+#endif  // IOS_WEB_COMMON_REFERRER_UTIL_H_
diff --git a/ios/web/public/referrer_util_unittest.cc b/ios/web/common/referrer_util_unittest.cc
similarity index 99%
rename from ios/web/public/referrer_util_unittest.cc
rename to ios/web/common/referrer_util_unittest.cc
index 2330ec80..a8cca5a0 100644
--- a/ios/web/public/referrer_util_unittest.cc
+++ b/ios/web/common/referrer_util_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/web/public/referrer_util.h"
+#include "ios/web/common/referrer_util.h"
 
 #include "base/stl_util.h"
 #include "ios/web/public/referrer.h"
diff --git a/ios/web/public/BUILD.gn b/ios/web/public/BUILD.gn
index 690ebbe..4a0e104 100644
--- a/ios/web/public/BUILD.gn
+++ b/ios/web/public/BUILD.gn
@@ -7,6 +7,7 @@
 source_set("public") {
   public_deps = [
     ":features",
+    ":referrer",
     ":user_agent",
     "//net",
     "//services/network/public/cpp",
@@ -45,9 +46,6 @@
     "navigation_manager.h",
     "origin_util.h",
     "origin_util.mm",
-    "referrer.h",
-    "referrer_util.cc",
-    "referrer_util.h",
     "reload_type.h",
     "security_style.h",
     "serializable_user_data_manager.h",
@@ -133,3 +131,16 @@
 
   configs += [ "//build/config/compiler:enable_arc" ]
 }
+
+source_set("referrer") {
+  deps = [
+    "//base",
+    "//url",
+  ]
+
+  sources = [
+    "referrer.h",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
diff --git a/ios/web/shell/test/earl_grey/shell_actions.h b/ios/web/shell/test/earl_grey/shell_actions.h
index 9081ede..b9f7b1e 100644
--- a/ios/web/shell/test/earl_grey/shell_actions.h
+++ b/ios/web/shell/test/earl_grey/shell_actions.h
@@ -7,9 +7,10 @@
 
 #include <string>
 
-#import <EarlGrey/EarlGrey.h>
+#import <Foundation/Foundation.h>
 
-#include "ios/web/public/test/element_selector.h"
+@class ElementSelector;
+@protocol GREYAction;
 
 namespace web {
 
diff --git a/ios/web/shell/test/earl_grey/shell_actions.mm b/ios/web/shell/test/earl_grey/shell_actions.mm
index b1dadb1..f7c33c5 100644
--- a/ios/web/shell/test/earl_grey/shell_actions.mm
+++ b/ios/web/shell/test/earl_grey/shell_actions.mm
@@ -4,6 +4,8 @@
 
 #import "ios/web/shell/test/earl_grey/shell_actions.h"
 
+#import <EarlGrey/EarlGrey.h>
+
 #import "ios/web/public/test/earl_grey/web_view_actions.h"
 #include "ios/web/public/test/element_selector.h"
 #import "ios/web/shell/test/app/web_shell_test_util.h"
diff --git a/ios/web/web_state/BUILD.gn b/ios/web/web_state/BUILD.gn
index b5d82d3..ebfd098 100644
--- a/ios/web/web_state/BUILD.gn
+++ b/ios/web/web_state/BUILD.gn
@@ -153,6 +153,7 @@
   deps = [
     "//base",
     "//components/url_formatter",
+    "//ios/web/common",
     "//ios/web/public",
   ]
 
diff --git a/ios/web/web_state/context_menu_params_utils.mm b/ios/web/web_state/context_menu_params_utils.mm
index 52e4c542..d376851 100644
--- a/ios/web/web_state/context_menu_params_utils.mm
+++ b/ios/web/web_state/context_menu_params_utils.mm
@@ -6,7 +6,7 @@
 
 #include "base/strings/sys_string_conversions.h"
 #include "components/url_formatter/url_formatter.h"
-#include "ios/web/public/referrer_util.h"
+#include "ios/web/common/referrer_util.h"
 #import "ios/web/web_state/context_menu_constants.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
diff --git a/ios/web/web_state/context_menu_params_utils_unittest.mm b/ios/web/web_state/context_menu_params_utils_unittest.mm
index 76a65684..293cd23 100644
--- a/ios/web/web_state/context_menu_params_utils_unittest.mm
+++ b/ios/web/web_state/context_menu_params_utils_unittest.mm
@@ -6,7 +6,7 @@
 
 #include "base/strings/sys_string_conversions.h"
 #include "components/url_formatter/url_formatter.h"
-#include "ios/web/public/referrer_util.h"
+#include "ios/web/common/referrer_util.h"
 #import "ios/web/public/web_state/context_menu_params.h"
 #import "ios/web/web_state/context_menu_constants.h"
 #import "net/base/mac/url_conversions.h"
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 9007019..6974017 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -45,6 +45,7 @@
 #import "ios/web/browsing_data/browsing_data_remover_observer.h"
 #import "ios/web/common/crw_content_view.h"
 #import "ios/web/common/crw_web_view_content_view.h"
+#include "ios/web/common/referrer_util.h"
 #include "ios/web/common/url_util.h"
 #import "ios/web/find_in_page/find_in_page_manager_impl.h"
 #include "ios/web/history_state_util.h"
@@ -66,7 +67,6 @@
 #import "ios/web/public/navigation_item.h"
 #import "ios/web/public/navigation_manager.h"
 #include "ios/web/public/referrer.h"
-#include "ios/web/public/referrer_util.h"
 #include "ios/web/public/ssl_status.h"
 #import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_client.h"
diff --git a/ios/web/web_state/web_state_unittest.mm b/ios/web/web_state/web_state_unittest.mm
index c8b68d4..97ebd47 100644
--- a/ios/web/web_state/web_state_unittest.mm
+++ b/ios/web/web_state/web_state_unittest.mm
@@ -334,11 +334,6 @@
 // Verifies that large session can be restored. SlimNavigationManagder has max
 // session size limit of |wk_navigation_util::kMaxSessionSize|.
 TEST_P(WebStateTest, RestoreLargeSession) {
-// TODO(crbug.com/946898): This test is failing on device with slim-nav enabled.
-#if !TARGET_IPHONE_SIMULATOR
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled())
-    return;
-#endif
   // Create session storage with large number of items.
   const int kItemCount = 150;
   NSMutableArray<CRWNavigationItemStorage*>* item_storages =
@@ -355,6 +350,7 @@
   CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
   session_storage.itemStorages = item_storages;
   auto web_state = WebState::CreateWithStorageSession(params, session_storage);
+  web_state->SetKeepRenderProcessAlive(true);
   WebState* web_state_ptr = web_state.get();
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
   // TODO(crbug.com/873729): The session will not be restored until
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index dde1433..66e8c0c 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -20,17 +20,6 @@
   ]
 }
 
-if (is_chromeos && use_v4lplugin) {
-  generate_stubs("libv4l2_stubs") {
-    extra_header = "v4l2/v4l2_stub_header.fragment"
-    sigs = [ "v4l2/v4l2.sig" ]
-    output_name = "v4l2/v4l2_stubs"
-    deps = [
-      "//base",
-    ]
-  }
-}
-
 component("gpu") {
   output_name = "media_gpu"
 
@@ -77,17 +66,12 @@
     "gpu_video_decode_accelerator_factory.h",
     "gpu_video_encode_accelerator_factory.cc",
     "gpu_video_encode_accelerator_factory.h",
-    "image_processor.cc",
-    "image_processor.h",
-    "image_processor_factory.cc",
-    "image_processor_factory.h",
-    "libyuv_image_processor.cc",
-    "libyuv_image_processor.h",
   ]
 
   public_deps = [
     ":buildflags",
     ":common",
+    ":image_processor",
     "//base",
     "//gpu",
     "//media",
@@ -187,49 +171,8 @@
     }
   }
 
-  if (use_v4lplugin) {
-    deps += [ ":libv4l2_stubs" ]
-  }
-
   if (use_v4l2_codec) {
-    sources += [
-      "v4l2/generic_v4l2_device.cc",
-      "v4l2/generic_v4l2_device.h",
-      "v4l2/v4l2_decode_surface.cc",
-      "v4l2/v4l2_decode_surface.h",
-      "v4l2/v4l2_decode_surface_handler.h",
-      "v4l2/v4l2_device.cc",
-      "v4l2/v4l2_device.h",
-      "v4l2/v4l2_h264_accelerator.cc",
-      "v4l2/v4l2_h264_accelerator.h",
-      "v4l2/v4l2_image_processor.cc",
-      "v4l2/v4l2_image_processor.h",
-      "v4l2/v4l2_jpeg_encode_accelerator.cc",
-      "v4l2/v4l2_jpeg_encode_accelerator.h",
-      "v4l2/v4l2_mjpeg_decode_accelerator.cc",
-      "v4l2/v4l2_mjpeg_decode_accelerator.h",
-      "v4l2/v4l2_slice_video_decode_accelerator.cc",
-      "v4l2/v4l2_slice_video_decode_accelerator.h",
-      "v4l2/v4l2_video_decode_accelerator.cc",
-      "v4l2/v4l2_video_decode_accelerator.h",
-      "v4l2/v4l2_video_encode_accelerator.cc",
-      "v4l2/v4l2_video_encode_accelerator.h",
-      "v4l2/v4l2_vp8_accelerator.cc",
-      "v4l2/v4l2_vp8_accelerator.h",
-      "v4l2/v4l2_vp9_accelerator.cc",
-      "v4l2/v4l2_vp9_accelerator.h",
-    ]
-
-    libs = [
-      "EGL",
-      "GLESv2",
-    ]
-    if (current_cpu == "arm") {
-      sources += [
-        "v4l2/tegra_v4l2_device.cc",
-        "v4l2/tegra_v4l2_device.h",
-      ]
-    }
+    public_deps += [ "//media/gpu/v4l2" ]
   }
 
   if (use_vaapi) {
@@ -347,6 +290,45 @@
   ]
 }
 
+source_set("image_processor") {
+  defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
+  sources = [
+    "image_processor_factory.cc",
+    "libyuv_image_processor.cc",
+    "libyuv_image_processor.h",
+  ]
+
+  public_deps = [
+    ":image_processor_common",
+  ]
+
+  deps = [
+    "//third_party/libyuv",
+    "//ui/gl",
+  ]
+
+  if (use_v4l2_codec) {
+    deps += [ "//media/gpu/v4l2" ]
+  }
+}
+
+source_set("image_processor_common") {
+  defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
+  sources = [
+    "image_processor.cc",
+    "image_processor.h",
+    "image_processor_factory.h",
+  ]
+
+  public_deps = [
+    ":buildflags",
+    ":common",
+    "//base",
+    "//media",
+    "//ui/gfx/geometry",
+  ]
+}
+
 # TODO(watk): Run this on bots. http://crbug.com/461437
 if (is_win || is_android || use_v4l2_codec || use_vaapi) {
   test("video_decode_accelerator_unittest") {
@@ -603,7 +585,7 @@
     deps += [ "//media/gpu/vaapi:unit_test" ]
   }
   if (use_v4l2_codec) {
-    sources += [ "v4l2/v4l2_device_unittest.cc" ]
+    deps += [ "//media/gpu/v4l2:unit_test" ]
   }
   if (use_v4l2_codec || use_vaapi) {
     sources += [ "vp8_decoder_unittest.cc" ]
diff --git a/media/gpu/v4l2/BUILD.gn b/media/gpu/v4l2/BUILD.gn
new file mode 100644
index 0000000..adc3078
--- /dev/null
+++ b/media/gpu/v4l2/BUILD.gn
@@ -0,0 +1,98 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//build/config/ui.gni")
+import("//media/gpu/args.gni")
+import("//testing/test.gni")
+import("//tools/generate_stubs/rules.gni")
+import("//ui/gl/features.gni")
+import("//ui/ozone/ozone.gni")
+
+assert(use_v4l2_codec)
+
+if (use_v4lplugin) {
+  generate_stubs("libv4l2_stubs") {
+    extra_header = "v4l2_stub_header.fragment"
+    sigs = [ "v4l2.sig" ]
+    output_name = "v4l2_stubs"
+    deps = [
+      "//base",
+    ]
+  }
+}
+
+source_set("v4l2") {
+  defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
+  sources = [
+    "generic_v4l2_device.cc",
+    "generic_v4l2_device.h",
+    "v4l2_decode_surface.cc",
+    "v4l2_decode_surface.h",
+    "v4l2_decode_surface_handler.h",
+    "v4l2_device.cc",
+    "v4l2_device.h",
+    "v4l2_h264_accelerator.cc",
+    "v4l2_h264_accelerator.h",
+    "v4l2_image_processor.cc",
+    "v4l2_image_processor.h",
+    "v4l2_jpeg_encode_accelerator.cc",
+    "v4l2_jpeg_encode_accelerator.h",
+    "v4l2_mjpeg_decode_accelerator.cc",
+    "v4l2_mjpeg_decode_accelerator.h",
+    "v4l2_slice_video_decode_accelerator.cc",
+    "v4l2_slice_video_decode_accelerator.h",
+    "v4l2_video_decode_accelerator.cc",
+    "v4l2_video_decode_accelerator.h",
+    "v4l2_video_encode_accelerator.cc",
+    "v4l2_video_encode_accelerator.h",
+    "v4l2_vp8_accelerator.cc",
+    "v4l2_vp8_accelerator.h",
+    "v4l2_vp9_accelerator.cc",
+    "v4l2_vp9_accelerator.h",
+  ]
+
+  libs = [
+    "EGL",
+    "GLESv2",
+  ]
+
+  if (current_cpu == "arm") {
+    sources += [
+      "tegra_v4l2_device.cc",
+      "tegra_v4l2_device.h",
+    ]
+  }
+
+  configs += [ "//third_party/libyuv:libyuv_config" ]
+
+  deps = [
+    "//base",
+    "//gpu/ipc/service",
+    "//media",
+    "//media/gpu:buildflags",
+    "//media/gpu:common",
+    "//media/gpu:image_processor_common",
+    "//third_party/libyuv",
+    "//ui/gfx/geometry",
+    "//ui/ozone",
+  ]
+
+  if (use_v4lplugin) {
+    deps += [ ":libv4l2_stubs" ]
+  }
+}
+
+source_set("unit_test") {
+  testonly = true
+  sources = [
+    "v4l2_device_unittest.cc",
+  ]
+  deps = [
+    ":v4l2",
+    "//testing/gtest",
+    "//ui/gfx:test_support",
+    "//ui/gl",
+  ]
+}
diff --git a/media/learning/impl/BUILD.gn b/media/learning/impl/BUILD.gn
index 638014f..1400eee1 100644
--- a/media/learning/impl/BUILD.gn
+++ b/media/learning/impl/BUILD.gn
@@ -35,8 +35,8 @@
     "random_number_generator.h",
     "random_tree_trainer.cc",
     "random_tree_trainer.h",
-    "target_distribution.cc",
-    "target_distribution.h",
+    "target_histogram.cc",
+    "target_histogram.h",
     "training_algorithm.h",
     "voting_ensemble.cc",
     "voting_ensemble.h",
@@ -69,7 +69,7 @@
     "one_hot_unittest.cc",
     "random_number_generator_unittest.cc",
     "random_tree_trainer_unittest.cc",
-    "target_distribution_unittest.cc",
+    "target_histogram_unittest.cc",
     "test_random_number_generator.cc",
     "test_random_number_generator.h",
   ]
diff --git a/media/learning/impl/distribution_reporter.cc b/media/learning/impl/distribution_reporter.cc
index 7643bbd..9213028 100644
--- a/media/learning/impl/distribution_reporter.cc
+++ b/media/learning/impl/distribution_reporter.cc
@@ -32,8 +32,8 @@
  public:
   RegressionReporter(const LearningTask& task) : DistributionReporter(task) {}
 
-  void OnPrediction(TargetDistribution observed,
-                    TargetDistribution predicted) override {
+  void OnPrediction(TargetHistogram observed,
+                    TargetHistogram predicted) override {
     DCHECK_EQ(task().target_description.ordering,
               LearningTask::Ordering::kNumeric);
     DCHECK(!task().uma_hacky_confusion_matrix.empty());
@@ -97,7 +97,7 @@
 DistributionReporter::~DistributionReporter() = default;
 
 Model::PredictionCB DistributionReporter::GetPredictionCallback(
-    TargetDistribution observed) {
+    TargetHistogram observed) {
   return base::BindOnce(&DistributionReporter::OnPrediction,
                         weak_factory_.GetWeakPtr(), observed);
 }
diff --git a/media/learning/impl/distribution_reporter.h b/media/learning/impl/distribution_reporter.h
index b47ce61..aa2ef6e 100644
--- a/media/learning/impl/distribution_reporter.h
+++ b/media/learning/impl/distribution_reporter.h
@@ -14,7 +14,7 @@
 #include "base/optional.h"
 #include "media/learning/common/learning_task.h"
 #include "media/learning/impl/model.h"
-#include "media/learning/impl/target_distribution.h"
+#include "media/learning/impl/target_histogram.h"
 
 namespace media {
 namespace learning {
@@ -31,8 +31,7 @@
 
   // Returns a prediction CB that will be compared to |observed|.  |observed| is
   // the total number of counts that we observed.
-  virtual Model::PredictionCB GetPredictionCallback(
-      TargetDistribution observed);
+  virtual Model::PredictionCB GetPredictionCallback(TargetHistogram observed);
 
   // Set the subset of features that is being used to train the model.  This is
   // used for feature importance measuremnts.
@@ -49,8 +48,8 @@
   const LearningTask& task() const { return task_; }
 
   // Implemented by subclasses to report a prediction.
-  virtual void OnPrediction(TargetDistribution observed,
-                            TargetDistribution predicted) = 0;
+  virtual void OnPrediction(TargetHistogram observed,
+                            TargetHistogram predicted) = 0;
 
   const base::Optional<std::set<int>>& feature_indices() const {
     return feature_indices_;
diff --git a/media/learning/impl/distribution_reporter_unittest.cc b/media/learning/impl/distribution_reporter_unittest.cc
index 3815d5c..7908c4e4 100644
--- a/media/learning/impl/distribution_reporter_unittest.cc
+++ b/media/learning/impl/distribution_reporter_unittest.cc
@@ -33,13 +33,13 @@
   const TargetValue Zero(0);
   const TargetValue One(1);
 
-  TargetDistribution observed;
+  TargetHistogram observed;
   // Observe an average of 2 / 3.
   observed[Zero] = 100;
   observed[One] = 200;
   auto cb = reporter_->GetPredictionCallback(observed);
 
-  TargetDistribution predicted;
+  TargetHistogram predicted;
   // Predict an average of 5 / 9.
   predicted[Zero] = 40;
   predicted[One] = 50;
diff --git a/media/learning/impl/extra_trees_trainer_unittest.cc b/media/learning/impl/extra_trees_trainer_unittest.cc
index ad07000..d9e1897 100644
--- a/media/learning/impl/extra_trees_trainer_unittest.cc
+++ b/media/learning/impl/extra_trees_trainer_unittest.cc
@@ -55,7 +55,7 @@
   TrainingData empty;
   auto model = Train(task_, empty);
   EXPECT_NE(model.get(), nullptr);
-  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetDistribution());
+  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetHistogram());
 }
 
 TEST_P(ExtraTreesTest, FisherIrisDataset) {
@@ -67,8 +67,7 @@
   // Verify predictions on the training set, just for sanity.
   size_t num_correct = 0;
   for (const LabelledExample& example : training_data) {
-    TargetDistribution distribution =
-        model->PredictDistribution(example.features);
+    TargetHistogram distribution = model->PredictDistribution(example.features);
     TargetValue predicted_value;
     if (distribution.FindSingularMax(&predicted_value) &&
         predicted_value == example.target_value) {
@@ -102,8 +101,7 @@
   auto model = Train(task_, training_data);
 
   // The singular max should be example_1.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   TargetValue predicted_value;
   EXPECT_TRUE(distribution.FindSingularMax(&predicted_value));
   EXPECT_EQ(predicted_value, example_1.target_value);
@@ -135,8 +133,7 @@
   auto model = Train(task_, training_data);
 
   // Make sure that the results are in the right range.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   EXPECT_GT(distribution.Average(), example_1.target_value.value() * 0.95);
   EXPECT_LT(distribution.Average(), example_1.target_value.value() * 1.05);
   distribution = model->PredictDistribution(example_2.features);
@@ -194,10 +191,10 @@
   // the data is separable, it probably should be exact.
   for (auto& r_example : r_examples) {
     const FeatureVector& fv = r_example.features;
-    TargetDistribution c_dist = c_model->PredictDistribution(fv);
+    TargetHistogram c_dist = c_model->PredictDistribution(fv);
     EXPECT_LE(c_dist.Average(), r_example.target_value.value() * 1.05);
     EXPECT_GE(c_dist.Average(), r_example.target_value.value() * 0.95);
-    TargetDistribution r_dist = r_model->PredictDistribution(fv);
+    TargetHistogram r_dist = r_model->PredictDistribution(fv);
     EXPECT_LE(r_dist.Average(), r_example.target_value.value() * 1.05);
     EXPECT_GE(r_dist.Average(), r_example.target_value.value() * 0.95);
   }
diff --git a/media/learning/impl/learning_task_controller_impl.cc b/media/learning/impl/learning_task_controller_impl.cc
index fb1540e..fc4597a 100644
--- a/media/learning/impl/learning_task_controller_impl.cc
+++ b/media/learning/impl/learning_task_controller_impl.cc
@@ -85,10 +85,9 @@
 
   // Once we have a model, see if we'd get |example| correct.
   if (model_ && reporter_) {
-    TargetDistribution predicted =
-        model_->PredictDistribution(example.features);
+    TargetHistogram predicted = model_->PredictDistribution(example.features);
 
-    TargetDistribution observed;
+    TargetHistogram observed;
     observed += example.target_value;
     reporter_->GetPredictionCallback(observed).Run(predicted);
   }
diff --git a/media/learning/impl/learning_task_controller_impl_unittest.cc b/media/learning/impl/learning_task_controller_impl_unittest.cc
index 5c05b0a..1b5f6c5 100644
--- a/media/learning/impl/learning_task_controller_impl_unittest.cc
+++ b/media/learning/impl/learning_task_controller_impl_unittest.cc
@@ -28,8 +28,8 @@
     }
 
    protected:
-    void OnPrediction(TargetDistribution observed,
-                      TargetDistribution predicted) override {
+    void OnPrediction(TargetHistogram observed,
+                      TargetHistogram predicted) override {
       num_reported_++;
       if (observed == predicted)
         num_correct_++;
@@ -46,9 +46,9 @@
     FakeModel(TargetValue target) : target_(target) {}
 
     // Model
-    TargetDistribution PredictDistribution(
+    TargetHistogram PredictDistribution(
         const FeatureVector& features) override {
-      TargetDistribution dist;
+      TargetHistogram dist;
       dist += target_;
       return dist;
     }
diff --git a/media/learning/impl/lookup_table_trainer.cc b/media/learning/impl/lookup_table_trainer.cc
index 4c698c7..57ebdbbc 100644
--- a/media/learning/impl/lookup_table_trainer.cc
+++ b/media/learning/impl/lookup_table_trainer.cc
@@ -19,17 +19,16 @@
   }
 
   // Model
-  TargetDistribution PredictDistribution(
-      const FeatureVector& instance) override {
+  TargetHistogram PredictDistribution(const FeatureVector& instance) override {
     auto iter = buckets_.find(instance);
     if (iter == buckets_.end())
-      return TargetDistribution();
+      return TargetHistogram();
 
     return iter->second;
   }
 
  private:
-  std::map<FeatureVector, TargetDistribution> buckets_;
+  std::map<FeatureVector, TargetHistogram> buckets_;
 };
 
 LookupTableTrainer::LookupTableTrainer() = default;
diff --git a/media/learning/impl/lookup_table_trainer_unittest.cc b/media/learning/impl/lookup_table_trainer_unittest.cc
index 323d69d..4761874 100644
--- a/media/learning/impl/lookup_table_trainer_unittest.cc
+++ b/media/learning/impl/lookup_table_trainer_unittest.cc
@@ -37,7 +37,7 @@
   TrainingData empty;
   std::unique_ptr<Model> model = Train(task_, empty);
   EXPECT_NE(model.get(), nullptr);
-  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetDistribution());
+  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetHistogram());
 }
 
 TEST_F(LookupTableTrainerTest, UniformTrainingDataWorks) {
@@ -51,8 +51,7 @@
 
   // The tree should produce a distribution for one value (our target), which
   // has |n_examples| counts.
-  TargetDistribution distribution =
-      model->PredictDistribution(example.features);
+  TargetHistogram distribution = model->PredictDistribution(example.features);
   EXPECT_EQ(distribution.size(), 1u);
   EXPECT_EQ(distribution[example.target_value], n_examples);
 }
@@ -66,8 +65,7 @@
   std::unique_ptr<Model> model = Train(task_, training_data);
 
   // Each value should have a distribution with one target value with one count.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   EXPECT_NE(model.get(), nullptr);
   EXPECT_EQ(distribution.size(), 1u);
   EXPECT_EQ(distribution[example_1.target_value], 1u);
@@ -104,8 +102,7 @@
 
   // Each example should have a distribution that selects the right value.
   for (const auto& example : training_data) {
-    TargetDistribution distribution =
-        model->PredictDistribution(example.features);
+    TargetHistogram distribution = model->PredictDistribution(example.features);
     TargetValue singular_max;
     EXPECT_TRUE(distribution.FindSingularMax(&singular_max));
     EXPECT_EQ(singular_max, example.target_value);
@@ -122,8 +119,7 @@
   EXPECT_NE(model.get(), nullptr);
 
   // Each value should have a distribution with two targets with one count each.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   EXPECT_EQ(distribution.size(), 2u);
   EXPECT_EQ(distribution[example_1.target_value], 1u);
   EXPECT_EQ(distribution[example_2.target_value], 1u);
@@ -143,7 +139,7 @@
   training_data.push_back(example_2);
 
   std::unique_ptr<Model> model = Train(task_, training_data);
-  TargetDistribution distribution =
+  TargetHistogram distribution =
       model->PredictDistribution(FeatureVector({FeatureValue(789)}));
   // OOV data should return an empty distribution (nominal).
   EXPECT_EQ(distribution.size(), 0u);
@@ -160,7 +156,7 @@
   training_data.push_back(example_2);
 
   std::unique_ptr<Model> model = Train(task_, training_data);
-  TargetDistribution distribution =
+  TargetHistogram distribution =
       model->PredictDistribution(FeatureVector({FeatureValue(123)}));
   double avg = distribution.Average();
   const double expected =
diff --git a/media/learning/impl/model.h b/media/learning/impl/model.h
index 0950b6c..4236687 100644
--- a/media/learning/impl/model.h
+++ b/media/learning/impl/model.h
@@ -8,7 +8,7 @@
 #include "base/component_export.h"
 #include "media/learning/common/labelled_example.h"
 #include "media/learning/impl/model.h"
-#include "media/learning/impl/target_distribution.h"
+#include "media/learning/impl/target_histogram.h"
 
 namespace media {
 namespace learning {
@@ -19,11 +19,11 @@
 class COMPONENT_EXPORT(LEARNING_IMPL) Model {
  public:
   // Callback for asynchronous predictions.
-  using PredictionCB = base::OnceCallback<void(TargetDistribution predicted)>;
+  using PredictionCB = base::OnceCallback<void(TargetHistogram predicted)>;
 
   virtual ~Model() = default;
 
-  virtual TargetDistribution PredictDistribution(
+  virtual TargetHistogram PredictDistribution(
       const FeatureVector& instance) = 0;
 
   // TODO(liberato): Consider adding an async prediction helper.
diff --git a/media/learning/impl/one_hot.cc b/media/learning/impl/one_hot.cc
index b8dab81..c3e8285 100644
--- a/media/learning/impl/one_hot.cc
+++ b/media/learning/impl/one_hot.cc
@@ -111,7 +111,7 @@
     : converter_(std::move(converter)), model_(std::move(model)) {}
 ConvertingModel::~ConvertingModel() = default;
 
-TargetDistribution ConvertingModel::PredictDistribution(
+TargetHistogram ConvertingModel::PredictDistribution(
     const FeatureVector& instance) {
   FeatureVector converted_instance = converter_->Convert(instance);
   return model_->PredictDistribution(converted_instance);
diff --git a/media/learning/impl/one_hot.h b/media/learning/impl/one_hot.h
index 0a3f479..b48b66e 100644
--- a/media/learning/impl/one_hot.h
+++ b/media/learning/impl/one_hot.h
@@ -67,8 +67,7 @@
   ~ConvertingModel() override;
 
   // Model
-  TargetDistribution PredictDistribution(
-      const FeatureVector& instance) override;
+  TargetHistogram PredictDistribution(const FeatureVector& instance) override;
 
  private:
   std::unique_ptr<OneHotConverter> converter_;
diff --git a/media/learning/impl/random_tree_trainer.cc b/media/learning/impl/random_tree_trainer.cc
index 88e1b97..c45b757 100644
--- a/media/learning/impl/random_tree_trainer.cc
+++ b/media/learning/impl/random_tree_trainer.cc
@@ -40,8 +40,7 @@
         split_point_(split_point) {}
 
   // Model
-  TargetDistribution PredictDistribution(
-      const FeatureVector& features) override {
+  TargetHistogram PredictDistribution(const FeatureVector& features) override {
     // Figure out what feature value we should use for the split.
     FeatureValue f;
     switch (ordering_) {
@@ -59,16 +58,16 @@
 
     // If we've never seen this feature value, then return nothing.
     if (iter == children_.end())
-      return TargetDistribution();
+      return TargetHistogram();
 
     return iter->second->PredictDistribution(features);
   }
 
-  TargetDistribution PredictDistributionWithMissingValues(
+  TargetHistogram PredictDistributionWithMissingValues(
       const FeatureVector& features) {
-    TargetDistribution total;
+    TargetHistogram total;
     for (auto& child_pair : children_) {
-      TargetDistribution predicted =
+      TargetHistogram predicted =
           child_pair.second->PredictDistribution(features);
       // TODO(liberato): Normalize?  Weight?
       total += predicted;
@@ -102,19 +101,26 @@
     for (size_t idx : training_idx)
       distribution_ += training_data[idx];
 
-    // Note that we don't treat numeric targets any differently.  We want to
-    // weight the leaf by the number of examples, so replacing it with an
-    // average would just introduce rounding errors.  One might as well take the
-    // average of the final distribution.
+    // Each leaf gets one vote.
+    // See https://en.wikipedia.org/wiki/Bootstrap_aggregating .  TL;DR: the
+    // individual trees should average (regression) or vote (classification).
+    //
+    // TODO(liberato): It's unclear that a leaf should get to vote with an
+    // entire distribution; we might want to take the max for kUnordered here.
+    // If so, then we might also want to Average() for kNumeric targets, though
+    // in that case, the results would be the same anyway.  That's not, of
+    // course, guaranteed for all methods of converting |distribution_| into a
+    // numeric prediction.  In general, we should provide a single estimate.
+    distribution_.Normalize();
   }
 
   // TreeNode
-  TargetDistribution PredictDistribution(const FeatureVector&) override {
+  TargetHistogram PredictDistribution(const FeatureVector&) override {
     return distribution_;
   }
 
  private:
-  TargetDistribution distribution_;
+  TargetHistogram distribution_;
 };
 
 RandomTreeTrainer::RandomTreeTrainer(RandomNumberGenerator* rng)
@@ -297,7 +303,8 @@
 
   // Find the split's feature values and construct the training set for each.
   // I think we want to iterate on the underlying vector, and look up the int in
-  // the training data directly.
+  // the training data directly.  |total_weight| will hold the total weight of
+  // all examples that come into this node.
   double total_weight = 0.;
   for (size_t idx : training_idx) {
     const LabelledExample& example = training_data[idx];
@@ -324,7 +331,7 @@
 
     Split::BranchInfo& branch_info = iter->second;
     branch_info.training_idx.push_back(idx);
-    branch_info.target_distribution += example;
+    branch_info.target_histogram += example;
   }
 
   // Figure out how good / bad this split is.
@@ -340,18 +347,22 @@
   return split;
 }
 
-void RandomTreeTrainer::ComputeSplitScore_Nominal(Split* split,
-                                                  double total_weight) {
+void RandomTreeTrainer::ComputeSplitScore_Nominal(
+    Split* split,
+    double total_incoming_weight) {
   // Compute the nats given that we're at this node.
   split->nats_remaining = 0;
   for (auto& info_iter : split->branch_infos) {
     Split::BranchInfo& branch_info = info_iter.second;
 
-    const double total_counts = branch_info.target_distribution.total_counts();
+    // |weight_along_branch| is the total weight of examples that would follow
+    // this branch in the tree.
+    const double weight_along_branch =
+        branch_info.target_histogram.total_counts();
     // |p_branch| is the probability of following this branch.
-    const double p_branch = total_counts / total_weight;
-    for (auto& iter : branch_info.target_distribution) {
-      double p = iter.second / total_counts;
+    const double p_branch = weight_along_branch / total_incoming_weight;
+    for (auto& iter : branch_info.target_histogram) {
+      double p = iter.second / total_incoming_weight;
       // p*log(p) is the expected nats if the answer is |iter|.  We multiply
       // that by the probability of being in this bucket at all.
       split->nats_remaining -= (p * log(p)) * p_branch;
@@ -359,25 +370,29 @@
   }
 }
 
-void RandomTreeTrainer::ComputeSplitScore_Numeric(Split* split,
-                                                  double total_weight) {
+void RandomTreeTrainer::ComputeSplitScore_Numeric(
+    Split* split,
+    double total_incoming_weight) {
   // Compute the nats given that we're at this node.
   split->nats_remaining = 0;
   for (auto& info_iter : split->branch_infos) {
     Split::BranchInfo& branch_info = info_iter.second;
 
-    const double total_counts = branch_info.target_distribution.total_counts();
+    // |weight_along_branch| is the total weight of examples that would follow
+    // this branch in the tree.
+    const double weight_along_branch =
+        branch_info.target_histogram.total_counts();
     // |p_branch| is the probability of following this branch.
-    const double p_branch = total_counts / total_weight;
+    const double p_branch = weight_along_branch / total_incoming_weight;
 
     // Compute the average at this node.  Note that we have no idea if the leaf
     // node would actually use an average, but really it should match.  It would
-    // be really nice if we could compute the value (or TargetDistribution) as
+    // be really nice if we could compute the value (or TargetHistogram) as
     // part of computing the split, and have somebody just hand that target
     // distribution to the leaf if it ends up as one.
-    double average = branch_info.target_distribution.Average();
+    double average = branch_info.target_histogram.Average();
 
-    for (auto& iter : branch_info.target_distribution) {
+    for (auto& iter : branch_info.target_histogram) {
       // Compute the squared error for all |iter.second| counts that each have a
       // value of |iter.first|, when this leaf approximates them as |average|.
       double sq_err = (iter.first.value() - average) *
diff --git a/media/learning/impl/random_tree_trainer.h b/media/learning/impl/random_tree_trainer.h
index 3383e55..81646983 100644
--- a/media/learning/impl/random_tree_trainer.h
+++ b/media/learning/impl/random_tree_trainer.h
@@ -20,7 +20,7 @@
 namespace media {
 namespace learning {
 
-// Trains RandomTree decision tree classifier (doesn't handle regression).
+// Trains RandomTree decision tree classifier / regressor.
 //
 // Decision trees, including RandomTree, classify instances as follows.  Each
 // non-leaf node is marked with a feature number |i|.  The value of the |i|-th
@@ -71,9 +71,9 @@
 // See https://en.wikipedia.org/wiki/Random_forest for information.  Note that
 // this is just a single tree, not the whole forest.
 //
-// TODO(liberato): Right now, it not-so-randomly selects from the entire set.
-// TODO(liberato): consider PRF or other simplified approximations.
-// TODO(liberato): separate Model and TrainingAlgorithm.  This is the latter.
+// Note that this variant chooses split points randomly, as described by the
+// ExtraTrees algorithm.  This is slightly different than RandomForest, which
+// chooses split points to improve the split's score.
 class COMPONENT_EXPORT(LEARNING_IMPL) RandomTreeTrainer
     : public TrainingAlgorithm,
       public HasRandomNumberGenerator {
@@ -135,7 +135,7 @@
       // branch of the split.
       // This is a flat_map since we're likely to have a very small (e.g.,
       // "true / "false") number of targets.
-      TargetDistribution target_distribution;
+      TargetHistogram target_histogram;
     };
 
     // [feature value at this split] = info about which examples take this
@@ -158,12 +158,13 @@
                        const std::vector<size_t>& training_idx,
                        int index);
 
-  // Fill in |nats_remaining| for |split| for a nominal target.  |total_weight|
-  // is the total weight of all instances coming into this split.
-  void ComputeSplitScore_Nominal(Split* split, double total_weight);
+  // Fill in |nats_remaining| for |split| for a nominal target.
+  // |total_incoming_weight| is the total weight of all instances coming into
+  // the node that we're splitting.
+  void ComputeSplitScore_Nominal(Split* split, double total_incoming_weight);
 
   // Fill in |nats_remaining| for |split| for a numeric target.
-  void ComputeSplitScore_Numeric(Split* split, double total_weight);
+  void ComputeSplitScore_Numeric(Split* split, double total_incoming_weight);
 
   // Compute the split point for |training_data| for a nominal feature.
   FeatureValue FindSplitPoint_Nominal(size_t index,
diff --git a/media/learning/impl/random_tree_trainer_unittest.cc b/media/learning/impl/random_tree_trainer_unittest.cc
index 6af9e94..f9face03 100644
--- a/media/learning/impl/random_tree_trainer_unittest.cc
+++ b/media/learning/impl/random_tree_trainer_unittest.cc
@@ -55,7 +55,7 @@
   TrainingData empty;
   std::unique_ptr<Model> model = Train(task_, empty);
   EXPECT_NE(model.get(), nullptr);
-  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetDistribution());
+  EXPECT_EQ(model->PredictDistribution(FeatureVector()), TargetHistogram());
 }
 
 TEST_P(RandomTreeTest, UniformTrainingDataWorks) {
@@ -69,11 +69,10 @@
   std::unique_ptr<Model> model = Train(task_, training_data);
 
   // The tree should produce a distribution for one value (our target), which
-  // has |n_examples| counts.
-  TargetDistribution distribution =
-      model->PredictDistribution(example.features);
+  // has one count.
+  TargetHistogram distribution = model->PredictDistribution(example.features);
   EXPECT_EQ(distribution.size(), 1u);
-  EXPECT_EQ(distribution[example.target_value], n_examples);
+  EXPECT_EQ(distribution[example.target_value], 1.0);
 }
 
 TEST_P(RandomTreeTest, SimpleSeparableTrainingData) {
@@ -86,8 +85,7 @@
   std::unique_ptr<Model> model = Train(task_, training_data);
 
   // Each value should have a distribution with one target value with one count.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   EXPECT_NE(model.get(), nullptr);
   EXPECT_EQ(distribution.size(), 1u);
   EXPECT_EQ(distribution[example_1.target_value], 1u);
@@ -129,8 +127,7 @@
 
   // Each example should have a distribution that selects the right value.
   for (const LabelledExample& example : training_data) {
-    TargetDistribution distribution =
-        model->PredictDistribution(example.features);
+    TargetHistogram distribution = model->PredictDistribution(example.features);
     TargetValue singular_max;
     EXPECT_TRUE(distribution.FindSingularMax(&singular_max));
     EXPECT_EQ(singular_max, example.target_value);
@@ -147,17 +144,16 @@
   std::unique_ptr<Model> model = Train(task_, training_data);
   EXPECT_NE(model.get(), nullptr);
 
-  // Each value should have a distribution with two targets with one count each.
-  TargetDistribution distribution =
-      model->PredictDistribution(example_1.features);
+  // Each value should have a distribution with two targets with equal counts.
+  TargetHistogram distribution = model->PredictDistribution(example_1.features);
   EXPECT_EQ(distribution.size(), 2u);
-  EXPECT_EQ(distribution[example_1.target_value], 1u);
-  EXPECT_EQ(distribution[example_2.target_value], 1u);
+  EXPECT_EQ(distribution[example_1.target_value], 0.5);
+  EXPECT_EQ(distribution[example_2.target_value], 0.5);
 
   distribution = model->PredictDistribution(example_2.features);
   EXPECT_EQ(distribution.size(), 2u);
-  EXPECT_EQ(distribution[example_1.target_value], 1u);
-  EXPECT_EQ(distribution[example_2.target_value], 1u);
+  EXPECT_EQ(distribution[example_1.target_value], 0.5);
+  EXPECT_EQ(distribution[example_2.target_value], 0.5);
 }
 
 TEST_P(RandomTreeTest, UnknownFeatureValueHandling) {
@@ -202,7 +198,7 @@
   std::unique_ptr<Model> model = Train(task_, training_data);
   for (size_t i = 0; i < 4; i++) {
     // Get a prediction for the |i|-th feature value.
-    TargetDistribution distribution = model->PredictDistribution(
+    TargetHistogram distribution = model->PredictDistribution(
         FeatureVector({FeatureValue(i * feature_mult)}));
     // The distribution should have one count that should be correct.  If
     // the feature isn't split four times, then some feature value will have too
diff --git a/media/learning/impl/target_distribution.cc b/media/learning/impl/target_distribution.cc
deleted file mode 100644
index 2fe27173..0000000
--- a/media/learning/impl/target_distribution.cc
+++ /dev/null
@@ -1,118 +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.
-
-#include "media/learning/impl/target_distribution.h"
-
-#include <sstream>
-
-namespace media {
-namespace learning {
-
-TargetDistribution::TargetDistribution() = default;
-
-TargetDistribution::TargetDistribution(const TargetDistribution& rhs) = default;
-
-TargetDistribution::TargetDistribution(TargetDistribution&& rhs) = default;
-
-TargetDistribution::~TargetDistribution() = default;
-
-TargetDistribution& TargetDistribution::operator=(
-    const TargetDistribution& rhs) = default;
-
-TargetDistribution& TargetDistribution::operator=(TargetDistribution&& rhs) =
-    default;
-
-bool TargetDistribution::operator==(const TargetDistribution& rhs) const {
-  return rhs.total_counts() == total_counts() && rhs.counts_ == counts_;
-}
-
-TargetDistribution& TargetDistribution::operator+=(
-    const TargetDistribution& rhs) {
-  for (auto& rhs_pair : rhs.counts())
-    counts_[rhs_pair.first] += rhs_pair.second;
-
-  return *this;
-}
-
-TargetDistribution& TargetDistribution::operator+=(const TargetValue& rhs) {
-  counts_[rhs]++;
-  return *this;
-}
-
-TargetDistribution& TargetDistribution::operator+=(
-    const LabelledExample& example) {
-  counts_[example.target_value] += example.weight;
-  return *this;
-}
-
-size_t TargetDistribution::operator[](const TargetValue& value) const {
-  auto iter = counts_.find(value);
-  if (iter == counts_.end())
-    return 0;
-
-  return iter->second;
-}
-
-size_t& TargetDistribution::operator[](const TargetValue& value) {
-  return counts_[value];
-}
-
-bool TargetDistribution::FindSingularMax(TargetValue* value_out,
-                                         size_t* counts_out) const {
-  if (!counts_.size())
-    return false;
-
-  size_t unused_counts;
-  if (!counts_out)
-    counts_out = &unused_counts;
-
-  auto iter = counts_.begin();
-  *value_out = iter->first;
-  *counts_out = iter->second;
-  bool singular_max = true;
-  for (iter++; iter != counts_.end(); iter++) {
-    if (iter->second > *counts_out) {
-      *value_out = iter->first;
-      *counts_out = iter->second;
-      singular_max = true;
-    } else if (iter->second == *counts_out) {
-      // If this turns out to be the max, then it's not singular.
-      singular_max = false;
-    }
-  }
-
-  return singular_max;
-}
-
-double TargetDistribution::Average() const {
-  double total_value = 0.;
-  size_t total_counts = 0;
-  for (auto& iter : counts_) {
-    total_value += iter.first.value() * iter.second;
-    total_counts += iter.second;
-  }
-
-  if (!total_counts)
-    return 0.;
-
-  return total_value / total_counts;
-}
-
-std::string TargetDistribution::ToString() const {
-  std::ostringstream ss;
-  ss << "[";
-  for (auto& entry : counts_)
-    ss << " " << entry.first << ":" << entry.second;
-  ss << " ]";
-
-  return ss.str();
-}
-
-std::ostream& operator<<(std::ostream& out,
-                         const media::learning::TargetDistribution& dist) {
-  return out << dist.ToString();
-}
-
-}  // namespace learning
-}  // namespace media
diff --git a/media/learning/impl/target_distribution.h b/media/learning/impl/target_distribution.h
deleted file mode 100644
index 3f4a9cd..0000000
--- a/media/learning/impl/target_distribution.h
+++ /dev/null
@@ -1,94 +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 MEDIA_LEARNING_IMPL_TARGET_DISTRIBUTION_H_
-#define MEDIA_LEARNING_IMPL_TARGET_DISTRIBUTION_H_
-
-#include <ostream>
-#include <string>
-
-#include "base/component_export.h"
-#include "base/containers/flat_map.h"
-#include "base/macros.h"
-#include "media/learning/common/labelled_example.h"
-#include "media/learning/common/value.h"
-
-namespace media {
-namespace learning {
-
-// TargetDistribution of target values.
-class COMPONENT_EXPORT(LEARNING_IMPL) TargetDistribution {
- private:
-  // We use a flat_map since this will often have only one or two TargetValues,
-  // such as "true" or "false".
-  using DistributionMap = base::flat_map<TargetValue, size_t>;
-
- public:
-  TargetDistribution();
-  TargetDistribution(const TargetDistribution& rhs);
-  TargetDistribution(TargetDistribution&& rhs);
-  ~TargetDistribution();
-
-  TargetDistribution& operator=(const TargetDistribution& rhs);
-  TargetDistribution& operator=(TargetDistribution&& rhs);
-
-  bool operator==(const TargetDistribution& rhs) const;
-
-  // Add |rhs| to our counts.
-  TargetDistribution& operator+=(const TargetDistribution& rhs);
-
-  // Increment |rhs| by one.
-  TargetDistribution& operator+=(const TargetValue& rhs);
-
-  // Increment the distribution by |example|'s target value and weight.
-  TargetDistribution& operator+=(const LabelledExample& example);
-
-  // Return the number of counts for |value|.
-  size_t operator[](const TargetValue& value) const;
-  size_t& operator[](const TargetValue& value);
-
-  // Return the total counts in the map.
-  size_t total_counts() const {
-    size_t total = 0.;
-    for (auto& entry : counts_)
-      total += entry.second;
-    return total;
-  }
-
-  DistributionMap::const_iterator begin() const { return counts_.begin(); }
-
-  DistributionMap::const_iterator end() const { return counts_.end(); }
-
-  // Return the number of buckets in the distribution.
-  // TODO(liberato): Do we want this?
-  size_t size() const { return counts_.size(); }
-
-  // Find the singular value with the highest counts, and copy it into
-  // |value_out| and (optionally) |counts_out|.  Returns true if there is a
-  // singular maximum, else returns false with the out params undefined.
-  bool FindSingularMax(TargetValue* value_out,
-                       size_t* counts_out = nullptr) const;
-
-  // Return the average value of the entries in this distribution.  Of course,
-  // this only makes sense if the TargetValues can be interpreted as numeric.
-  double Average() const;
-
-  std::string ToString() const;
-
- private:
-  const DistributionMap& counts() const { return counts_; }
-
-  // [value] == counts
-  DistributionMap counts_;
-
-  // Allow copy and assign.
-};
-
-COMPONENT_EXPORT(LEARNING_IMPL)
-std::ostream& operator<<(std::ostream& out, const TargetDistribution& dist);
-
-}  // namespace learning
-}  // namespace media
-
-#endif  // MEDIA_LEARNING_IMPL_TARGET_DISTRIBUTION_H_
diff --git a/media/learning/impl/target_distribution_unittest.cc b/media/learning/impl/target_distribution_unittest.cc
deleted file mode 100644
index 1c7564a..0000000
--- a/media/learning/impl/target_distribution_unittest.cc
+++ /dev/null
@@ -1,164 +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.
-
-#include "media/learning/impl/target_distribution.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-namespace learning {
-
-class TargetDistributionTest : public testing::Test {
- public:
-  TargetDistributionTest() : value_1(123), value_2(456), value_3(789) {}
-
-  TargetDistribution distribution_;
-
-  TargetValue value_1;
-  const size_t counts_1 = 100;
-
-  TargetValue value_2;
-  const size_t counts_2 = 10;
-
-  TargetValue value_3;
-};
-
-TEST_F(TargetDistributionTest, EmptyTargetDistributionHasZeroCounts) {
-  EXPECT_EQ(distribution_.total_counts(), 0u);
-}
-
-TEST_F(TargetDistributionTest, AddingCountsWorks) {
-  distribution_[value_1] = counts_1;
-  EXPECT_EQ(distribution_.total_counts(), counts_1);
-  EXPECT_EQ(distribution_[value_1], counts_1);
-  distribution_[value_1] += counts_1;
-  EXPECT_EQ(distribution_.total_counts(), counts_1 * 2u);
-  EXPECT_EQ(distribution_[value_1], counts_1 * 2u);
-}
-
-TEST_F(TargetDistributionTest, MultipleValuesAreSeparate) {
-  distribution_[value_1] = counts_1;
-  distribution_[value_2] = counts_2;
-  EXPECT_EQ(distribution_.total_counts(), counts_1 + counts_2);
-  EXPECT_EQ(distribution_[value_1], counts_1);
-  EXPECT_EQ(distribution_[value_2], counts_2);
-}
-
-TEST_F(TargetDistributionTest, AddingTargetValues) {
-  distribution_ += value_1;
-  EXPECT_EQ(distribution_.total_counts(), 1u);
-  EXPECT_EQ(distribution_[value_1], 1u);
-  EXPECT_EQ(distribution_[value_2], 0u);
-
-  distribution_ += value_1;
-  EXPECT_EQ(distribution_.total_counts(), 2u);
-  EXPECT_EQ(distribution_[value_1], 2u);
-  EXPECT_EQ(distribution_[value_2], 0u);
-
-  distribution_ += value_2;
-  EXPECT_EQ(distribution_.total_counts(), 3u);
-  EXPECT_EQ(distribution_[value_1], 2u);
-  EXPECT_EQ(distribution_[value_2], 1u);
-}
-
-TEST_F(TargetDistributionTest, AddingTargetDistributions) {
-  distribution_[value_1] = counts_1;
-
-  TargetDistribution rhs;
-  rhs[value_2] = counts_2;
-
-  distribution_ += rhs;
-
-  EXPECT_EQ(distribution_.total_counts(), counts_1 + counts_2);
-  EXPECT_EQ(distribution_[value_1], counts_1);
-  EXPECT_EQ(distribution_[value_2], counts_2);
-}
-
-TEST_F(TargetDistributionTest, FindSingularMaxFindsTheSingularMax) {
-  distribution_[value_1] = counts_1;
-  distribution_[value_2] = counts_2;
-  ASSERT_TRUE(counts_1 > counts_2);
-
-  TargetValue max_value(0);
-  size_t max_counts = 0;
-  EXPECT_TRUE(distribution_.FindSingularMax(&max_value, &max_counts));
-  EXPECT_EQ(max_value, value_1);
-  EXPECT_EQ(max_counts, counts_1);
-}
-
-TEST_F(TargetDistributionTest,
-       FindSingularMaxFindsTheSingularMaxAlternateOrder) {
-  // Switch the order, to handle sorting in different directions.
-  distribution_[value_1] = counts_2;
-  distribution_[value_2] = counts_1;
-  ASSERT_TRUE(counts_1 > counts_2);
-
-  TargetValue max_value(0);
-  size_t max_counts = 0;
-  EXPECT_TRUE(distribution_.FindSingularMax(&max_value, &max_counts));
-  EXPECT_EQ(max_value, value_2);
-  EXPECT_EQ(max_counts, counts_1);
-}
-
-TEST_F(TargetDistributionTest, FindSingularMaxReturnsFalsForNonSingularMax) {
-  distribution_[value_1] = counts_1;
-  distribution_[value_2] = counts_1;
-
-  TargetValue max_value(0);
-  size_t max_counts = 0;
-  EXPECT_FALSE(distribution_.FindSingularMax(&max_value, &max_counts));
-}
-
-TEST_F(TargetDistributionTest, FindSingularMaxIgnoresNonSingularNonMax) {
-  distribution_[value_1] = counts_1;
-  // |value_2| and |value_3| are tied, but not the max.
-  distribution_[value_2] = counts_2;
-  distribution_[value_3] = counts_2;
-  ASSERT_TRUE(counts_1 > counts_2);
-
-  TargetValue max_value(0);
-  size_t max_counts = 0;
-  EXPECT_TRUE(distribution_.FindSingularMax(&max_value, &max_counts));
-  EXPECT_EQ(max_value, value_1);
-  EXPECT_EQ(max_counts, counts_1);
-}
-
-TEST_F(TargetDistributionTest, FindSingularMaxDoesntRequireCounts) {
-  distribution_[value_1] = counts_1;
-
-  TargetValue max_value(0);
-  EXPECT_TRUE(distribution_.FindSingularMax(&max_value));
-  EXPECT_EQ(max_value, value_1);
-}
-
-TEST_F(TargetDistributionTest, EqualDistributionsCompareAsEqual) {
-  distribution_[value_1] = counts_1;
-  TargetDistribution distribution_2;
-  distribution_2[value_1] = counts_1;
-
-  EXPECT_TRUE(distribution_ == distribution_2);
-}
-
-TEST_F(TargetDistributionTest, UnequalDistributionsCompareAsNotEqual) {
-  distribution_[value_1] = counts_1;
-  TargetDistribution distribution_2;
-  distribution_2[value_2] = counts_2;
-
-  EXPECT_FALSE(distribution_ == distribution_2);
-}
-
-TEST_F(TargetDistributionTest, WeightedLabelledExamplesCountCorrectly) {
-  LabelledExample example = {{}, value_1};
-  example.weight = counts_1;
-  distribution_ += example;
-
-  TargetDistribution distribution_2;
-  for (size_t i = 0; i < counts_1; i++)
-    distribution_2 += value_1;
-
-  EXPECT_EQ(distribution_, distribution_2);
-}
-
-}  // namespace learning
-}  // namespace media
diff --git a/media/learning/impl/target_histogram.cc b/media/learning/impl/target_histogram.cc
new file mode 100644
index 0000000..ad1a1f2
--- /dev/null
+++ b/media/learning/impl/target_histogram.cc
@@ -0,0 +1,121 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/learning/impl/target_histogram.h"
+
+#include <sstream>
+
+namespace media {
+namespace learning {
+
+TargetHistogram::TargetHistogram() = default;
+
+TargetHistogram::TargetHistogram(const TargetHistogram& rhs) = default;
+
+TargetHistogram::TargetHistogram(TargetHistogram&& rhs) = default;
+
+TargetHistogram::~TargetHistogram() = default;
+
+TargetHistogram& TargetHistogram::operator=(const TargetHistogram& rhs) =
+    default;
+
+TargetHistogram& TargetHistogram::operator=(TargetHistogram&& rhs) = default;
+
+bool TargetHistogram::operator==(const TargetHistogram& rhs) const {
+  return rhs.total_counts() == total_counts() && rhs.counts_ == counts_;
+}
+
+TargetHistogram& TargetHistogram::operator+=(const TargetHistogram& rhs) {
+  for (auto& rhs_pair : rhs.counts())
+    counts_[rhs_pair.first] += rhs_pair.second;
+
+  return *this;
+}
+
+TargetHistogram& TargetHistogram::operator+=(const TargetValue& rhs) {
+  counts_[rhs]++;
+  return *this;
+}
+
+TargetHistogram& TargetHistogram::operator+=(const LabelledExample& example) {
+  counts_[example.target_value] += example.weight;
+  return *this;
+}
+
+double TargetHistogram::operator[](const TargetValue& value) const {
+  auto iter = counts_.find(value);
+  if (iter == counts_.end())
+    return 0;
+
+  return iter->second;
+}
+
+double& TargetHistogram::operator[](const TargetValue& value) {
+  return counts_[value];
+}
+
+bool TargetHistogram::FindSingularMax(TargetValue* value_out,
+                                      double* counts_out) const {
+  if (!counts_.size())
+    return false;
+
+  double unused_counts;
+  if (!counts_out)
+    counts_out = &unused_counts;
+
+  auto iter = counts_.begin();
+  *value_out = iter->first;
+  *counts_out = iter->second;
+  bool singular_max = true;
+  for (iter++; iter != counts_.end(); iter++) {
+    if (iter->second > *counts_out) {
+      *value_out = iter->first;
+      *counts_out = iter->second;
+      singular_max = true;
+    } else if (iter->second == *counts_out) {
+      // If this turns out to be the max, then it's not singular.
+      singular_max = false;
+    }
+  }
+
+  return singular_max;
+}
+
+double TargetHistogram::Average() const {
+  double total_value = 0.;
+  double total_counts = 0;
+  for (auto& iter : counts_) {
+    total_value += iter.first.value() * iter.second;
+    total_counts += iter.second;
+  }
+
+  if (!total_counts)
+    return 0.;
+
+  return total_value / total_counts;
+}
+
+void TargetHistogram::Normalize() {
+  double total = total_counts();
+  for (auto& iter : counts_)
+    iter.second /= total;
+}
+
+std::string TargetHistogram::ToString() const {
+  std::ostringstream ss;
+  ss << "[";
+  for (auto& entry : counts_)
+    ss << " " << entry.first << ":" << entry.second;
+  ss << " ]";
+
+  return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         const media::learning::TargetHistogram& dist) {
+  return out << dist.ToString();
+}
+
+}  // namespace learning
+}  // namespace media
diff --git a/media/learning/impl/target_histogram.h b/media/learning/impl/target_histogram.h
new file mode 100644
index 0000000..cb8de2b6
--- /dev/null
+++ b/media/learning/impl/target_histogram.h
@@ -0,0 +1,98 @@
+// 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 MEDIA_LEARNING_IMPL_TARGET_HISTOGRAM_H_
+#define MEDIA_LEARNING_IMPL_TARGET_HISTOGRAM_H_
+
+#include <ostream>
+#include <string>
+
+#include "base/component_export.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "media/learning/common/labelled_example.h"
+#include "media/learning/common/value.h"
+
+namespace media {
+namespace learning {
+
+// Histogram of target values that allows fractional counts.
+class COMPONENT_EXPORT(LEARNING_IMPL) TargetHistogram {
+ private:
+  // We use a flat_map since this will often have only one or two TargetValues,
+  // such as "true" or "false".
+  using CountMap = base::flat_map<TargetValue, double>;
+
+ public:
+  TargetHistogram();
+  TargetHistogram(const TargetHistogram& rhs);
+  TargetHistogram(TargetHistogram&& rhs);
+  ~TargetHistogram();
+
+  TargetHistogram& operator=(const TargetHistogram& rhs);
+  TargetHistogram& operator=(TargetHistogram&& rhs);
+
+  bool operator==(const TargetHistogram& rhs) const;
+
+  // Add |rhs| to our counts.
+  TargetHistogram& operator+=(const TargetHistogram& rhs);
+
+  // Increment |rhs| by one.
+  TargetHistogram& operator+=(const TargetValue& rhs);
+
+  // Increment the histogram by |example|'s target value and weight.
+  TargetHistogram& operator+=(const LabelledExample& example);
+
+  // Return the number of counts for |value|.
+  double operator[](const TargetValue& value) const;
+  double& operator[](const TargetValue& value);
+
+  // Return the total counts in the map.
+  double total_counts() const {
+    double total = 0.;
+    for (auto& entry : counts_)
+      total += entry.second;
+    return total;
+  }
+
+  CountMap::const_iterator begin() const { return counts_.begin(); }
+
+  CountMap::const_iterator end() const { return counts_.end(); }
+
+  // Return the number of buckets in the histogram.
+  // TODO(liberato): Do we want this?
+  size_t size() const { return counts_.size(); }
+
+  // Find the singular value with the highest counts, and copy it into
+  // |value_out| and (optionally) |counts_out|.  Returns true if there is a
+  // singular maximum, else returns false with the out params undefined.
+  bool FindSingularMax(TargetValue* value_out,
+                       double* counts_out = nullptr) const;
+
+  // Return the average value of the entries in this histogram.  Of course,
+  // this only makes sense if the TargetValues can be interpreted as numeric.
+  double Average() const;
+
+  // Normalize the histogram so that it has one total count, unless it's
+  // empty.  It will continue to have zero in that case.
+  void Normalize();
+
+  std::string ToString() const;
+
+ private:
+  const CountMap& counts() const { return counts_; }
+
+  // [value] == counts
+  CountMap counts_;
+
+  // Allow copy and assign.
+};
+
+COMPONENT_EXPORT(LEARNING_IMPL)
+std::ostream& operator<<(std::ostream& out, const TargetHistogram& dist);
+
+}  // namespace learning
+}  // namespace media
+
+#endif  // MEDIA_LEARNING_IMPL_TARGET_HISTOGRAM_H_
diff --git a/media/learning/impl/target_histogram_unittest.cc b/media/learning/impl/target_histogram_unittest.cc
new file mode 100644
index 0000000..5ba36e0
--- /dev/null
+++ b/media/learning/impl/target_histogram_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/learning/impl/target_histogram.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace learning {
+
+class TargetHistogramTest : public testing::Test {
+ public:
+  TargetHistogramTest() : value_1(123), value_2(456), value_3(789) {}
+
+  TargetHistogram histogram_;
+
+  TargetValue value_1;
+  const size_t counts_1 = 100;
+
+  TargetValue value_2;
+  const size_t counts_2 = 10;
+
+  TargetValue value_3;
+};
+
+TEST_F(TargetHistogramTest, EmptyTargetHistogramHasZeroCounts) {
+  EXPECT_EQ(histogram_.total_counts(), 0u);
+}
+
+TEST_F(TargetHistogramTest, AddingCountsWorks) {
+  histogram_[value_1] = counts_1;
+  EXPECT_EQ(histogram_.total_counts(), counts_1);
+  EXPECT_EQ(histogram_[value_1], counts_1);
+  histogram_[value_1] += counts_1;
+  EXPECT_EQ(histogram_.total_counts(), counts_1 * 2u);
+  EXPECT_EQ(histogram_[value_1], counts_1 * 2u);
+}
+
+TEST_F(TargetHistogramTest, MultipleValuesAreSeparate) {
+  histogram_[value_1] = counts_1;
+  histogram_[value_2] = counts_2;
+  EXPECT_EQ(histogram_.total_counts(), counts_1 + counts_2);
+  EXPECT_EQ(histogram_[value_1], counts_1);
+  EXPECT_EQ(histogram_[value_2], counts_2);
+}
+
+TEST_F(TargetHistogramTest, AddingTargetValues) {
+  histogram_ += value_1;
+  EXPECT_EQ(histogram_.total_counts(), 1u);
+  EXPECT_EQ(histogram_[value_1], 1u);
+  EXPECT_EQ(histogram_[value_2], 0u);
+
+  histogram_ += value_1;
+  EXPECT_EQ(histogram_.total_counts(), 2u);
+  EXPECT_EQ(histogram_[value_1], 2u);
+  EXPECT_EQ(histogram_[value_2], 0u);
+
+  histogram_ += value_2;
+  EXPECT_EQ(histogram_.total_counts(), 3u);
+  EXPECT_EQ(histogram_[value_1], 2u);
+  EXPECT_EQ(histogram_[value_2], 1u);
+}
+
+TEST_F(TargetHistogramTest, AddingTargetHistograms) {
+  histogram_[value_1] = counts_1;
+
+  TargetHistogram rhs;
+  rhs[value_2] = counts_2;
+
+  histogram_ += rhs;
+
+  EXPECT_EQ(histogram_.total_counts(), counts_1 + counts_2);
+  EXPECT_EQ(histogram_[value_1], counts_1);
+  EXPECT_EQ(histogram_[value_2], counts_2);
+}
+
+TEST_F(TargetHistogramTest, FindSingularMaxFindsTheSingularMax) {
+  histogram_[value_1] = counts_1;
+  histogram_[value_2] = counts_2;
+  ASSERT_TRUE(counts_1 > counts_2);
+
+  TargetValue max_value(0);
+  double max_counts = 0;
+  EXPECT_TRUE(histogram_.FindSingularMax(&max_value, &max_counts));
+  EXPECT_EQ(max_value, value_1);
+  EXPECT_EQ(max_counts, counts_1);
+}
+
+TEST_F(TargetHistogramTest, FindSingularMaxFindsTheSingularMaxAlternateOrder) {
+  // Switch the order, to handle sorting in different directions.
+  histogram_[value_1] = counts_2;
+  histogram_[value_2] = counts_1;
+  ASSERT_TRUE(counts_1 > counts_2);
+
+  TargetValue max_value(0);
+  double max_counts = 0;
+  EXPECT_TRUE(histogram_.FindSingularMax(&max_value, &max_counts));
+  EXPECT_EQ(max_value, value_2);
+  EXPECT_EQ(max_counts, counts_1);
+}
+
+TEST_F(TargetHistogramTest, FindSingularMaxReturnsFalsForNonSingularMax) {
+  histogram_[value_1] = counts_1;
+  histogram_[value_2] = counts_1;
+
+  TargetValue max_value(0);
+  double max_counts = 0;
+  EXPECT_FALSE(histogram_.FindSingularMax(&max_value, &max_counts));
+}
+
+TEST_F(TargetHistogramTest, FindSingularMaxIgnoresNonSingularNonMax) {
+  histogram_[value_1] = counts_1;
+  // |value_2| and |value_3| are tied, but not the max.
+  histogram_[value_2] = counts_2;
+  histogram_[value_3] = counts_2;
+  ASSERT_TRUE(counts_1 > counts_2);
+
+  TargetValue max_value(0);
+  double max_counts = 0;
+  EXPECT_TRUE(histogram_.FindSingularMax(&max_value, &max_counts));
+  EXPECT_EQ(max_value, value_1);
+  EXPECT_EQ(max_counts, counts_1);
+}
+
+TEST_F(TargetHistogramTest, FindSingularMaxDoesntRequireCounts) {
+  histogram_[value_1] = counts_1;
+
+  TargetValue max_value(0);
+  EXPECT_TRUE(histogram_.FindSingularMax(&max_value));
+  EXPECT_EQ(max_value, value_1);
+}
+
+TEST_F(TargetHistogramTest, EqualDistributionsCompareAsEqual) {
+  histogram_[value_1] = counts_1;
+  TargetHistogram histogram_2;
+  histogram_2[value_1] = counts_1;
+
+  EXPECT_TRUE(histogram_ == histogram_2);
+}
+
+TEST_F(TargetHistogramTest, UnequalDistributionsCompareAsNotEqual) {
+  histogram_[value_1] = counts_1;
+  TargetHistogram histogram_2;
+  histogram_2[value_2] = counts_2;
+
+  EXPECT_FALSE(histogram_ == histogram_2);
+}
+
+TEST_F(TargetHistogramTest, WeightedLabelledExamplesCountCorrectly) {
+  LabelledExample example = {{}, value_1};
+  example.weight = counts_1;
+  histogram_ += example;
+
+  TargetHistogram histogram_2;
+  for (size_t i = 0; i < counts_1; i++)
+    histogram_2 += value_1;
+
+  EXPECT_EQ(histogram_, histogram_2);
+}
+
+TEST_F(TargetHistogramTest, Normalize) {
+  histogram_[value_1] = counts_1;
+  histogram_[value_2] = counts_2;
+  histogram_.Normalize();
+  EXPECT_EQ(histogram_[value_1],
+            counts_1 / static_cast<double>(counts_1 + counts_2));
+  EXPECT_EQ(histogram_[value_2],
+            counts_2 / static_cast<double>(counts_1 + counts_2));
+}
+
+TEST_F(TargetHistogramTest, NormalizeEmptyDistribution) {
+  // Normalizing an empty distribution should result in an empty distribution.
+  histogram_.Normalize();
+  EXPECT_EQ(histogram_.total_counts(), 0);
+}
+
+}  // namespace learning
+}  // namespace media
diff --git a/media/learning/impl/voting_ensemble.cc b/media/learning/impl/voting_ensemble.cc
index 667e7399..ef07b1c 100644
--- a/media/learning/impl/voting_ensemble.cc
+++ b/media/learning/impl/voting_ensemble.cc
@@ -12,9 +12,9 @@
 
 VotingEnsemble::~VotingEnsemble() = default;
 
-TargetDistribution VotingEnsemble::PredictDistribution(
+TargetHistogram VotingEnsemble::PredictDistribution(
     const FeatureVector& instance) {
-  TargetDistribution distribution;
+  TargetHistogram distribution;
 
   for (auto iter = models_.begin(); iter != models_.end(); iter++)
     distribution += (*iter)->PredictDistribution(instance);
diff --git a/media/learning/impl/voting_ensemble.h b/media/learning/impl/voting_ensemble.h
index 2b4bf11..7e0cba7 100644
--- a/media/learning/impl/voting_ensemble.h
+++ b/media/learning/impl/voting_ensemble.h
@@ -23,8 +23,7 @@
   ~VotingEnsemble() override;
 
   // Model
-  TargetDistribution PredictDistribution(
-      const FeatureVector& instance) override;
+  TargetHistogram PredictDistribution(const FeatureVector& instance) override;
 
  private:
   std::vector<std::unique_ptr<Model>> models_;
diff --git a/mojo/core/channel_mac.cc b/mojo/core/channel_mac.cc
index ea28ae3..0043335 100644
--- a/mojo/core/channel_mac.cc
+++ b/mojo/core/channel_mac.cc
@@ -117,6 +117,11 @@
                               size_t extra_header_size,
                               std::vector<PlatformHandle>* handles,
                               bool* deferred) override {
+    // TODO(https://crbug.com/946372): Remove when fixed.
+    static base::debug::CrashKeyString* error_crash_key =
+        base::debug::AllocateCrashKeyString("channel-mac-handles-error",
+                                            base::debug::CrashKeySize::Size64);
+
     // Validate the incoming handles. If validation fails, ensure they are
     // destroyed.
     std::vector<PlatformHandle> incoming_handles;
@@ -125,12 +130,14 @@
     if (extra_header_size <
         sizeof(Message::MachPortsExtraHeader) +
             (incoming_handles.size() * sizeof(Message::MachPortsEntry))) {
+      base::debug::SetCrashKeyString(error_crash_key, "extra_header_size");
       return false;
     }
 
     const auto* mach_ports_header =
         reinterpret_cast<const Message::MachPortsExtraHeader*>(extra_header);
     if (mach_ports_header->num_ports != incoming_handles.size()) {
+      base::debug::SetCrashKeyString(error_crash_key, "num_ports mismatch");
       return false;
     }
 
@@ -138,19 +145,29 @@
       auto type = static_cast<PlatformHandle::Type>(
           mach_ports_header->entries[i].mach_entry.type);
       if (type == PlatformHandle::Type::kNone) {
+        base::debug::SetCrashKeyString(
+            error_crash_key, base::StringPrintf("kNone handle #%d", i));
         return false;
       } else if (type == PlatformHandle::Type::kFd &&
                  incoming_handles[i].is_mach_send()) {
         int fd = fileport_makefd(incoming_handles[i].GetMachSendRight().get());
         if (fd < 0) {
+          base::debug::SetCrashKeyString(
+              error_crash_key,
+              base::StringPrintf("fileport_makefd %d -%d #%d", fd, errno, i));
           return false;
         }
         incoming_handles[i] = PlatformHandle(base::ScopedFD(fd));
       } else if (type != incoming_handles[i].type()) {
+        base::debug::SetCrashKeyString(
+            error_crash_key,
+            base::StringPrintf("handle mismatch %d != -%d #%d", type,
+                               incoming_handles[i].type(), i));
         return false;
       }
     }
 
+    base::debug::ClearCrashKeyString(error_crash_key);
     *handles = std::move(incoming_handles);
     return true;
   }
@@ -251,7 +268,7 @@
       MACH_LOG(ERROR, kr) << "mach_msg send handshake";
 
       base::AutoLock lock(write_lock_);
-      OnWriteErrorLocked(Error::kConnectionFailed, kr);
+      OnWriteErrorLocked(Error::kConnectionFailed);
       return;
     }
 
@@ -388,7 +405,7 @@
               fileport_makeport(handle.GetFD().get(), &descriptor->name);
           if (kr != KERN_SUCCESS) {
             MACH_LOG(ERROR, kr) << "fileport_makeport";
-            OnWriteErrorLocked(Error::kDisconnected, kr);
+            OnWriteErrorLocked(Error::kDisconnected);
             return false;
           }
           descriptor->disposition = MACH_MSG_TYPE_MOVE_SEND;
@@ -397,7 +414,7 @@
         default:
           NOTREACHED() << "Unsupported handle type "
                        << static_cast<int>(handle.type());
-          OnWriteErrorLocked(Error::kDisconnected, 0xbad04adl);
+          OnWriteErrorLocked(Error::kDisconnected);
       }
     }
 
@@ -441,7 +458,7 @@
         MACH_LOG_IF(ERROR, kr != MACH_SEND_INVALID_DEST, kr) << "mach_msg send";
         send_buffer_contains_message_ = false;
         mach_msg_destroy(header);
-        OnWriteErrorLocked(Error::kDisconnected, kr);
+        OnWriteErrorLocked(Error::kDisconnected);
       }
       return false;
     }
@@ -623,21 +640,19 @@
     size_t ignored;
     DispatchResult result = TryDispatchMessage(payload, &ignored);
     if (result != DispatchResult::kOK) {
+      // TODO(https://crbug.com/946372): Remove when fixed.
+      static auto* error_crash_key = base::debug::AllocateCrashKeyString(
+          "channel-mac-try-dispatch", base::debug::CrashKeySize::Size32);
+      base::debug::SetCrashKeyString(error_crash_key,
+                                     base::StringPrintf("%d", result));
+      base::debug::DumpWithoutCrashing();
       OnError(Error::kReceivedMalformedData);
       return;
     }
   }
 
   // Marks the channel as unaccepting of new messages and shuts it down.
-  void OnWriteErrorLocked(Error error, uint32_t error_code) {
-    // TODO(https://crbug.com/946372): Remove when fixed.
-    static base::debug::CrashKeyString* error_crash_key =
-        base::debug::AllocateCrashKeyString("channel-mac-write-error",
-                                            base::debug::CrashKeySize::Size32);
-    base::debug::SetCrashKeyString(error_crash_key,
-                                   base::StringPrintf("0x%x", error_code));
-    base::debug::DumpWithoutCrashing();
-
+  void OnWriteErrorLocked(Error error) {
     reject_writes_ = true;
     io_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&ChannelMac::OnError, this, error));
diff --git a/mojo/public/cpp/bindings/lib/serialization_util.h b/mojo/public/cpp/bindings/lib/serialization_util.h
index a7a99b3..51eb5f23 100644
--- a/mojo/public/cpp/bindings/lib/serialization_util.h
+++ b/mojo/public/cpp/bindings/lib/serialization_util.h
@@ -77,62 +77,6 @@
   return false;
 }
 
-template <typename T>
-struct HasSetUpContextMethod {
-  template <typename U>
-  static char Test(decltype(U::SetUpContext) *);
-  template <typename U>
-  static int Test(...);
-  static const bool value = sizeof(Test<T>(0)) == sizeof(char);
-
- private:
-  EnsureTypeIsComplete<T> check_t_;
-};
-
-template <typename Traits,
-          bool has_context = HasSetUpContextMethod<Traits>::value>
-struct CustomContextHelper;
-
-template <typename Traits>
-struct CustomContextHelper<Traits, true> {
-  template <typename MaybeConstUserType>
-  static void* SetUp(MaybeConstUserType& input, SerializationContext* context) {
-    return Traits::SetUpContext(input);
-  }
-
-  template <typename MaybeConstUserType>
-  static void TearDown(MaybeConstUserType& input, void* custom_context) {
-    Traits::TearDownContext(input, custom_context);
-  }
-};
-
-template <typename Traits>
-struct CustomContextHelper<Traits, false> {
-  template <typename MaybeConstUserType>
-  static void* SetUp(MaybeConstUserType& input, SerializationContext* context) {
-    return nullptr;
-  }
-
-  template <typename MaybeConstUserType>
-  static void TearDown(MaybeConstUserType& input, void* custom_context) {
-    DCHECK(!custom_context);
-  }
-};
-
-template <typename ReturnType, typename ParamType, typename InputUserType>
-ReturnType CallWithContext(ReturnType (*f)(ParamType, void*),
-                           InputUserType&& input,
-                           void* context) {
-  return f(std::forward<InputUserType>(input), context);
-}
-
-template <typename ReturnType, typename ParamType, typename InputUserType>
-ReturnType CallWithContext(ReturnType (*f)(ParamType),
-                           InputUserType&& input,
-                           void* context) {
-  return f(std::forward<InputUserType>(input));
-}
-
 template <typename T, typename MaybeConstUserType>
 struct HasGetBeginMethod {
   template <typename U>
diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h
index 1fe6b87..d11a0c7 100644
--- a/mojo/public/cpp/bindings/lib/string_serialization.h
+++ b/mojo/public/cpp/bindings/lib/string_serialization.h
@@ -29,12 +29,9 @@
     if (CallIsNullIfExists<Traits>(input))
       return;
 
-    void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
-    const size_t size = CallWithContext(Traits::GetSize, input, custom_context);
-    writer->Allocate(size, buffer);
-    memcpy((*writer)->storage(),
-           CallWithContext(Traits::GetData, input, custom_context), size);
-    CustomContextHelper<Traits>::TearDown(input, custom_context);
+    auto r = Traits::GetUTF8(input);
+    writer->Allocate(r.size(), buffer);
+    memcpy((*writer)->storage(), r.data(), r.size());
   }
 
   static bool Deserialize(String_Data* input,
diff --git a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
index 71b758c..540a154 100644
--- a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
+++ b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
@@ -4,43 +4,9 @@
 
 #include "mojo/public/cpp/bindings/string_traits_wtf.h"
 
-#include <string.h>
-
-#include "base/logging.h"
-#include "mojo/public/cpp/bindings/lib/array_internal.h"
 #include "mojo/public/cpp/bindings/string_data_view.h"
-#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
 
 namespace mojo {
-namespace {
-
-struct UTF8AdaptorInfo {
-  explicit UTF8AdaptorInfo(const WTF::String& input) : utf8_adaptor(input) {
-#if DCHECK_IS_ON()
-    original_size_in_bytes = input.CharactersSizeInBytes();
-#endif
-  }
-
-  ~UTF8AdaptorInfo() {}
-
-  WTF::StringUTF8Adaptor utf8_adaptor;
-
-#if DCHECK_IS_ON()
-  // For sanity check only.
-  size_t original_size_in_bytes;
-#endif
-};
-
-UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) {
-  UTF8AdaptorInfo* adaptor = static_cast<UTF8AdaptorInfo*>(context);
-
-#if DCHECK_IS_ON()
-  DCHECK_EQ(adaptor->original_size_in_bytes, input.CharactersSizeInBytes());
-#endif
-  return adaptor;
-}
-
-}  // namespace
 
 // static
 void StringTraits<WTF::String>::SetToNull(WTF::String* output) {
@@ -52,26 +18,9 @@
 }
 
 // static
-void* StringTraits<WTF::String>::SetUpContext(const WTF::String& input) {
-  return new UTF8AdaptorInfo(input);
-}
-
-// static
-void StringTraits<WTF::String>::TearDownContext(const WTF::String& input,
-                                                void* context) {
-  delete ToAdaptor(input, context);
-}
-
-// static
-size_t StringTraits<WTF::String>::GetSize(const WTF::String& input,
-                                          void* context) {
-  return ToAdaptor(input, context)->utf8_adaptor.length();
-}
-
-// static
-const char* StringTraits<WTF::String>::GetData(const WTF::String& input,
-                                               void* context) {
-  return ToAdaptor(input, context)->utf8_adaptor.Data();
+WTF::StringUTF8Adaptor StringTraits<WTF::String>::GetUTF8(
+    const WTF::String& input) {
+  return WTF::StringUTF8Adaptor(input);
 }
 
 // static
diff --git a/mojo/public/cpp/bindings/string_traits.h b/mojo/public/cpp/bindings/string_traits.h
index 165c9fa..bd25647 100644
--- a/mojo/public/cpp/bindings/string_traits.h
+++ b/mojo/public/cpp/bindings/string_traits.h
@@ -12,8 +12,7 @@
 // This must be specialized for any type |T| to be serialized/deserialized as
 // a mojom string.
 //
-// Imagine you want to specialize it for CustomString, usually you need to
-// implement:
+// An example specialization for CustomString:
 //
 //   template <T>
 //   struct StringTraits<CustomString> {
@@ -21,31 +20,14 @@
 //     static bool IsNull(const CustomString& input);
 //     static void SetToNull(CustomString* output);
 //
-//     static size_t GetSize(const CustomString& input);
-//     static const char* GetData(const CustomString& input);
+//     // This doesn't need to be a base::StringPiece; it simply needs to be a
+//     // type that exposes a data() method that returns a pointer to the UTF-8
+//     // bytes and a size() method that returns the length of the UTF-8 bytes.
+//     static std::span<char> GetUTF8(const CustomString& input);
 //
 //     // The caller guarantees that |!input.is_null()|.
 //     static bool Read(StringDataView input, CustomString* output);
 //   };
-//
-// In some cases, you may need to do conversion before you can return the size
-// and data as 8-bit characters for serialization. (For example, CustomString is
-// UTF-16 string). In that case, you can add two optional methods:
-//
-//   static void* SetUpContext(const CustomString& input);
-//   static void TearDownContext(const CustomString& input, void* context);
-//
-// And then you append a second parameter, void* context, to GetSize() and
-// GetData():
-//
-//   static size_t GetSize(const CustomString& input, void* context);
-//   static const char* GetData(const CustomString& input, void* context);
-//
-// If a CustomString instance is not null, the serialization code will call
-// SetUpContext() at the beginning, and pass the resulting context pointer to
-// GetSize()/GetData(). After serialization is done, it calls TearDownContext()
-// so that you can do any necessary cleanup.
-//
 template <typename T>
 struct StringTraits {
   static_assert(internal::AlwaysFalse<T>::value,
diff --git a/mojo/public/cpp/bindings/string_traits_stl.h b/mojo/public/cpp/bindings/string_traits_stl.h
index f6cc8ad09..cb3c5fb 100644
--- a/mojo/public/cpp/bindings/string_traits_stl.h
+++ b/mojo/public/cpp/bindings/string_traits_stl.h
@@ -23,9 +23,7 @@
     output->clear();
   }
 
-  static size_t GetSize(const std::string& input) { return input.size(); }
-
-  static const char* GetData(const std::string& input) { return input.data(); }
+  static const std::string& GetUTF8(const std::string& input) { return input; }
 
   static bool Read(StringDataView input, std::string* output) {
     output->assign(input.storage(), input.size());
diff --git a/mojo/public/cpp/bindings/string_traits_string_piece.h b/mojo/public/cpp/bindings/string_traits_string_piece.h
index af6be89..7855379 100644
--- a/mojo/public/cpp/bindings/string_traits_string_piece.h
+++ b/mojo/public/cpp/bindings/string_traits_string_piece.h
@@ -12,7 +12,7 @@
 
 template <>
 struct StringTraits<base::StringPiece> {
-  static bool IsNull(const base::StringPiece& input) {
+  static bool IsNull(base::StringPiece input) {
     // base::StringPiece is always converted to non-null mojom string. We could
     // have let StringPiece containing a null data pointer map to null mojom
     // string, but StringPiece::empty() returns true in this case. It seems
@@ -26,11 +26,7 @@
     output->set(nullptr, 0);
   }
 
-  static size_t GetSize(const base::StringPiece& input) { return input.size(); }
-
-  static const char* GetData(const base::StringPiece& input) {
-    return input.data();
-  }
+  static base::StringPiece GetUTF8(base::StringPiece input) { return input; }
 
   static bool Read(StringDataView input, base::StringPiece* output) {
     output->set(input.storage(), input.size());
diff --git a/mojo/public/cpp/bindings/string_traits_wtf.h b/mojo/public/cpp/bindings/string_traits_wtf.h
index 51601d1a..662fff5 100644
--- a/mojo/public/cpp/bindings/string_traits_wtf.h
+++ b/mojo/public/cpp/bindings/string_traits_wtf.h
@@ -7,6 +7,7 @@
 
 #include "mojo/public/cpp/bindings/lib/bindings_internal.h"
 #include "mojo/public/cpp/bindings/string_traits.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace mojo {
@@ -16,12 +17,7 @@
   static bool IsNull(const WTF::String& input) { return input.IsNull(); }
   static void SetToNull(WTF::String* output);
 
-  static void* SetUpContext(const WTF::String& input);
-  static void TearDownContext(const WTF::String& input, void* context);
-
-  static size_t GetSize(const WTF::String& input, void* context);
-
-  static const char* GetData(const WTF::String& input, void* context);
+  static WTF::StringUTF8Adaptor GetUTF8(const WTF::String& input);
 
   static bool Read(StringDataView input, WTF::String* output);
 };
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
index 5571e84..5f11278 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
@@ -17,12 +17,10 @@
                         SerializationContext* context) {
     if (CallIsNullIfExists<Traits>(input))
       return;
-    void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
     {{struct_macros.serialize(
           struct, struct.name ~ " struct",
-          "CallWithContext(Traits::%s, input, custom_context)", "(*output)",
+          "Traits::%s(input)", "(*output)",
           "buffer", "context", True)|indent(2)}}
-    CustomContextHelper<Traits>::TearDown(input, custom_context);
   }
 
   static bool Deserialize({{data_type}}* input,
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
index 4e39774..3e98ede 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
@@ -18,8 +18,6 @@
          writer->data()->set_null();
       return;
     }
-    void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
-
     if (!inlined)
       writer->Allocate(buffer);
 
@@ -28,16 +26,15 @@
     // TODO(azani): Handle unknown and objects.
     // Set the not-null flag.
     result->size = kUnionDataSize;
-    result->tag = CallWithContext(Traits::GetTag, input, custom_context);
+    result->tag = Traits::GetTag(input);
     switch (result->tag) {
 {%- for field in union.fields %}
 {%-   set name = field.name %}
 {%-   set kind = field.kind %}
 {%-   set serializer_type = kind|unmapped_type_for_serializer %}
       case {{data_view}}::Tag::{{field.name|upper}}: {
-        decltype(CallWithContext(Traits::{{name}}, input, custom_context))
-            in_{{name}} = CallWithContext(Traits::{{name}}, input,
-                                          custom_context);
+        decltype(Traits::{{name}}(input))
+            in_{{name}} = Traits::{{name}}(input);
 {%-   if kind|is_object_kind %}
         typename decltype(result->data.f_{{name}})::BaseType::BufferWriter
             value_writer;
@@ -88,8 +85,6 @@
       }
 {%- endfor %}
     }
-
-    CustomContextHelper<Traits>::TearDown(input, custom_context);
   }
 
   static bool Deserialize({{data_type}}* input,
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc
index f021464..1fca778 100644
--- a/net/base/network_change_notifier.cc
+++ b/net/base/network_change_notifier.cc
@@ -207,7 +207,7 @@
   CHECK(false);
   return NULL;
 #elif defined(OS_CHROMEOS)
-  return new NetworkChangeNotifierPosix(CONNECTION_UNKNOWN, SUBTYPE_UNKNOWN);
+  return new NetworkChangeNotifierPosix(CONNECTION_NONE, SUBTYPE_NONE);
 #elif defined(OS_LINUX)
   return new NetworkChangeNotifierLinux(std::unordered_set<std::string>());
 #elif defined(OS_MACOSX)
diff --git a/net/base/network_change_notifier_posix.cc b/net/base/network_change_notifier_posix.cc
index e8dc5b1..68d1f090b 100644
--- a/net/base/network_change_notifier_posix.cc
+++ b/net/base/network_change_notifier_posix.cc
@@ -24,9 +24,8 @@
     : public net::internal::DnsConfigServicePosix {
  public:
   DnsConfigService() {
-    // After construction it lives on
-    // NetworkChangeNotifierPosix::dns_config_service_runner_.
-    DETACH_FROM_SEQUENCE(sequence_checker_);
+    WatchConfig(base::BindRepeating(&NetworkChangeNotifier::SetDnsConfig));
+    OnNetworkChange();
   }
   ~DnsConfigService() override = default;
 
@@ -51,7 +50,7 @@
       dns_config_service_runner_(
           base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})),
       dns_config_service_(
-          new DnsConfigService(),
+          nullptr,
           // Ensure DnsConfigService lives on |dns_config_service_runner_|
           // to prevent races where NetworkChangeNotifierPosix outlives
           // ScopedTaskEnvironment. https://crbug.com/938126
@@ -62,15 +61,19 @@
               initial_connection_subtype)) {
   dns_config_service_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(
-          &NetworkChangeNotifierPosix::DnsConfigService::WatchConfig,
-          base::Unretained(dns_config_service_.get()),
-          base::BindRepeating(&NetworkChangeNotifier::SetDnsConfig)));
-  OnDNSChanged();
+      base::BindOnce(&NetworkChangeNotifierPosix::CreateDnsConfigService,
+                     // The Unretained pointer is safe here because |this| owns
+                     // |dns_config_service_runner_|.
+                     base::Unretained(this)));
 }
 
 NetworkChangeNotifierPosix::~NetworkChangeNotifierPosix() = default;
 
+void NetworkChangeNotifierPosix::CreateDnsConfigService() {
+  DCHECK(dns_config_service_runner_->RunsTasksInCurrentSequence());
+  dns_config_service_.reset(new DnsConfigService());
+}
+
 void NetworkChangeNotifierPosix::OnDNSChanged() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   dns_config_service_runner_->PostTask(
diff --git a/net/base/network_change_notifier_posix.h b/net/base/network_change_notifier_posix.h
index eefd15f..1fdd88c2 100644
--- a/net/base/network_change_notifier_posix.h
+++ b/net/base/network_change_notifier_posix.h
@@ -59,11 +59,7 @@
 
   class DnsConfigService;
 
-  // |dns_config_service_| will live on this runner.
-  scoped_refptr<base::SequencedTaskRunner> dns_config_service_runner_;
-  // DnsConfigService that lives on |dns_config_service_runner_|.
-  std::unique_ptr<DnsConfigService, base::OnTaskRunnerDeleter>
-      dns_config_service_;
+  void CreateDnsConfigService();
 
   // Calculates parameters used for network change notifier online/offline
   // signals.
@@ -72,6 +68,12 @@
 
   THREAD_CHECKER(thread_checker_);
 
+  // |dns_config_service_| will live on this runner.
+  scoped_refptr<base::SequencedTaskRunner> dns_config_service_runner_;
+  // DnsConfigService that lives on |dns_config_service_runner_|.
+  std::unique_ptr<DnsConfigService, base::OnTaskRunnerDeleter>
+      dns_config_service_;
+
   mutable base::Lock lock_;
   NetworkChangeNotifier::ConnectionType
       connection_type_;        // Guarded by |lock_|.
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 11af7a65..d93a76a 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -286,18 +286,24 @@
     // Now we force HTTPS for subtrees of google.com.
     { "name": "accounts.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "admin.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "apis.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "appengine.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "calendar.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "checkout.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "chrome.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "classroom.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "cloud.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "code.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "contributor.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "dl.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "docs.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "domains.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "drive.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "encrypted.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "fi.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "glass.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "goto.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
+    { "name": "groups.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "hangouts.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "history.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "hostedtalkgadget.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
@@ -324,21 +330,17 @@
     { "name": "dns.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
 
     // Other Google-related domains that must use HTTPS.
-    { "name": "apis.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "build.chromium.org", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "bugs.chromium.org", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "cdn.ampproject.org", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "chrome.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "chrome-devtools-frontend.appspot.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "chromiumcodereview.appspot.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
-    { "name": "code.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "codereview.appspot.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "codereview.chromium.org", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "crbug.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "crosbug.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "crrev.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
-    { "name": "dl.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
-    { "name": "drive.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "firebaseio.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "g.co", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "g4w.co", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
@@ -348,7 +350,6 @@
     { "name": "googlemail.com", "policy": "google", "mode": "force-https", "pins": "google" },
     { "name": "googleplex.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "googlesource.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
-    { "name": "groups.google.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "gvt2.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "gvt3.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "developer.android.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
@@ -379,11 +380,6 @@
     { "name": "w-spotlight.appspot.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
     { "name": "xbrlsuccess.appspot.com", "policy": "google", "mode": "force-https", "include_subdomains": true, "pins": "google" },
 
-    // chart.apis.google.com is *not* HSTS because the certificate doesn't match
-    // and there are lots of links out there that still use the name. The correct
-    // hostname for this is chart.googleapis.com.
-    { "name": "chart.apis.google.com", "policy": "google", "include_subdomains": true, "pins": "google" },
-
     // Other Google-related domains that must use an acceptable certificate
     // iff using SSL.
     { "name": "2mdn.net", "policy": "google", "include_subdomains": true, "pins": "google" },
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index 0ce0c96..3c5ca3f1e 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -2619,7 +2619,6 @@
   EXPECT_TRUE(StaticShouldRedirect("plus.google.com"));
   EXPECT_TRUE(StaticShouldRedirect("groups.google.com"));
   EXPECT_TRUE(StaticShouldRedirect("apis.google.com"));
-  EXPECT_FALSE(StaticShouldRedirect("chart.apis.google.com"));
   EXPECT_TRUE(StaticShouldRedirect("ssl.google-analytics.com"));
   EXPECT_TRUE(StaticShouldRedirect("google"));
   EXPECT_TRUE(StaticShouldRedirect("foo.google"));
diff --git a/ppapi/PRESUBMIT.py b/ppapi/PRESUBMIT.py
index ff33c21..b48b01b 100644
--- a/ppapi/PRESUBMIT.py
+++ b/ppapi/PRESUBMIT.py
@@ -167,7 +167,8 @@
                      'ppapi/thunk/interfaces_ppb_private_no_permissions.h',
                      'ppapi/thunk/interfaces_ppb_public_dev_channel.h',
                      'ppapi/thunk/interfaces_ppb_public_dev.h',
-                     'ppapi/thunk/interfaces_ppb_public_stable.h')
+                     'ppapi/thunk/interfaces_ppb_public_stable.h',
+                     'ppapi/thunk/interfaces_ppb_public_socket.h')
   HISTOGRAM_XML_FILE = 'tools/metrics/histograms/enums.xml'
   interface_changes = []
   has_histogram_xml_change = False
diff --git a/ppapi/proxy/interface_list.cc b/ppapi/proxy/interface_list.cc
index e8702cc..cd51d9f 100644
--- a/ppapi/proxy/interface_list.cc
+++ b/ppapi/proxy/interface_list.cc
@@ -180,6 +180,7 @@
              INTERFACE_THUNK_NAME(iface_struct)(), \
              current_required_permission);
 
+  // clang-format off
   {
     Permission current_required_permission = PERMISSION_NONE;
     #include "ppapi/thunk/interfaces_ppb_private_no_permissions.h"
@@ -207,9 +208,14 @@
     Permission current_required_permission = PERMISSION_DEV_CHANNEL;
     #include "ppapi/thunk/interfaces_ppb_public_dev_channel.h"
   }
+  {
+    Permission current_required_permission = PERMISSION_SOCKET;
+    #include "ppapi/thunk/interfaces_ppb_public_socket.h"
+  }
+  // clang-format on
 
-  #undef PROXIED_API
-  #undef PROXIED_IFACE
+#undef PROXIED_API
+#undef PROXIED_IFACE
 
   // Manually add some special proxies. Some of these don't have interfaces
   // that they support, so aren't covered by the macros above, but have proxies
diff --git a/ppapi/shared_impl/ppapi_permissions.h b/ppapi/shared_impl/ppapi_permissions.h
index 5d3ef58..da55c81 100644
--- a/ppapi/shared_impl/ppapi_permissions.h
+++ b/ppapi/shared_impl/ppapi_permissions.h
@@ -40,19 +40,22 @@
   // PDF-related interfaces.
   PERMISSION_PDF = 1 << 6,
 
+  // Socket APIs. Formerly part of public APIs.
+  PERMISSION_SOCKET = 1 << 7,
+
   // NOTE: If you add stuff be sure to update PERMISSION_ALL_BITS.
 
   // Meta permission for for initializing plugins with permissions that have
-  // historically been part of public APIs but will be covered by finer-grained
-  // permissions in the future.
-  PERMISSION_DEFAULT = PERMISSION_NONE,
+  // historically been part of public APIs but are now covered by finer-grained
+  // permissions.
+  PERMISSION_DEFAULT = PERMISSION_SOCKET,
 
   // Meta permission for initializing plugins registered on the command line
   // that get all permissions.
   PERMISSION_ALL_BITS = PERMISSION_DEV | PERMISSION_PRIVATE |
                         PERMISSION_BYPASS_USER_GESTURE | PERMISSION_TESTING |
                         PERMISSION_FLASH | PERMISSION_DEV_CHANNEL |
-                        PERMISSION_PDF
+                        PERMISSION_PDF | PERMISSION_SOCKET,
 };
 
 class PPAPI_SHARED_EXPORT PpapiPermissions {
diff --git a/ppapi/thunk/BUILD.gn b/ppapi/thunk/BUILD.gn
index 8be4fa9d..79825273 100644
--- a/ppapi/thunk/BUILD.gn
+++ b/ppapi/thunk/BUILD.gn
@@ -19,6 +19,7 @@
     "interfaces_ppb_private_no_permissions.h",
     "interfaces_ppb_public_dev.h",
     "interfaces_ppb_public_dev_channel.h",
+    "interfaces_ppb_public_socket.h",
     "interfaces_ppb_public_stable.h",
     "interfaces_preamble.h",
     "ppapi_thunk_export.h",
diff --git a/ppapi/thunk/interfaces_ppb_public_socket.h b/ppapi/thunk/interfaces_ppb_public_socket.h
new file mode 100644
index 0000000..f79f7e7
--- /dev/null
+++ b/ppapi/thunk/interfaces_ppb_public_socket.h
@@ -0,0 +1,18 @@
+// Copyright (c) 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.
+
+// no-include-guard-because-multiply-included
+// NOLINT(build/header_guard)
+
+#include "ppapi/thunk/interfaces_preamble.h"
+
+// See interfaces_ppp_public_stable.h for documentation on these macros.
+PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_0, PPB_TCPSocket_1_0)
+PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_1, PPB_TCPSocket_1_1)
+PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_2, PPB_TCPSocket_1_2)
+PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_0, PPB_UDPSocket_1_0)
+PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_1, PPB_UDPSocket_1_1)
+PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_2, PPB_UDPSocket_1_2)
+
+#include "ppapi/thunk/interfaces_postamble.h"
diff --git a/ppapi/thunk/interfaces_ppb_public_stable.h b/ppapi/thunk/interfaces_ppb_public_stable.h
index 83b3cfa..4d66348 100644
--- a/ppapi/thunk/interfaces_ppb_public_stable.h
+++ b/ppapi/thunk/interfaces_ppb_public_stable.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.
 
+// no-include-guard-because-multiply-included
+// NOLINT(build/header_guard)
+
 #include "ppapi/thunk/interfaces_preamble.h"
 
 // This file contains lists of interfaces. It's intended to be included by
@@ -86,14 +89,8 @@
 PROXIED_IFACE(PPB_NETWORKLIST_INTERFACE_1_0, PPB_NetworkList_1_0)
 PROXIED_IFACE(PPB_NETWORKMONITOR_INTERFACE_1_0, PPB_NetworkMonitor_1_0)
 PROXIED_IFACE(PPB_NETWORKPROXY_INTERFACE_1_0, PPB_NetworkProxy_1_0)
-PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_0, PPB_TCPSocket_1_0)
-PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_1, PPB_TCPSocket_1_1)
-PROXIED_IFACE(PPB_TCPSOCKET_INTERFACE_1_2, PPB_TCPSocket_1_2)
 PROXIED_IFACE(PPB_TEXTINPUTCONTROLLER_INTERFACE_1_0,
               PPB_TextInputController_1_0)
-PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_0, PPB_UDPSocket_1_0)
-PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_1, PPB_UDPSocket_1_1)
-PROXIED_IFACE(PPB_UDPSOCKET_INTERFACE_1_2, PPB_UDPSocket_1_2)
 PROXIED_IFACE(PPB_URLLOADER_INTERFACE_1_0, PPB_URLLoader_1_0)
 PROXIED_IFACE(PPB_URLREQUESTINFO_INTERFACE_1_0, PPB_URLRequestInfo_1_0)
 PROXIED_IFACE(PPB_URLRESPONSEINFO_INTERFACE_1_0, PPB_URLResponseInfo_1_0)
diff --git a/ppapi/thunk/thunk.h b/ppapi/thunk/thunk.h
index 54b3ded..99a24c6 100644
--- a/ppapi/thunk/thunk.h
+++ b/ppapi/thunk/thunk.h
@@ -24,8 +24,8 @@
 #include "ppapi/thunk/interfaces_ppb_private_pdf.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev.h"
 #include "ppapi/thunk/interfaces_ppb_public_dev_channel.h"
+#include "ppapi/thunk/interfaces_ppb_public_socket.h"
 #include "ppapi/thunk/interfaces_ppb_public_stable.h"
-
 #undef PROXIED_IFACE
 
 namespace ppapi {
diff --git a/services/service_manager/sandbox/features.cc b/services/service_manager/sandbox/features.cc
index 91f9952b..0674345 100644
--- a/services/service_manager/sandbox/features.cc
+++ b/services/service_manager/sandbox/features.cc
@@ -26,7 +26,7 @@
     "WinSboxDisableExtensionPoint", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Controls whether the isolated XR service is sandboxed.
-const base::Feature kXRSandbox{"XRSandbox", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kXRSandbox{"XRSandbox", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif  // defined(OS_WIN)
 
 }  // namespace features
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 66c1014..9f152855 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -91,6 +91,7 @@
     "origin_trials/trial_token.h",
     "origin_trials/trial_token_validator.h",
     "page/launching_process_state.h",
+    "prerender/prerender_rel_type.h",
     "privacy_preferences.h",
     "screen_orientation/web_screen_orientation_lock_type.h",
     "screen_orientation/web_screen_orientation_type.h",
diff --git a/third_party/blink/public/common/prerender/OWNERS b/third_party/blink/public/common/prerender/OWNERS
new file mode 100644
index 0000000..c71af0e
--- /dev/null
+++ b/third_party/blink/public/common/prerender/OWNERS
@@ -0,0 +1,3 @@
+file://chrome/browser/prerender/OWNERS
+
+# COMPONENT: Internals>Preload
diff --git a/third_party/blink/public/common/prerender/prerender_rel_type.h b/third_party/blink/public/common/prerender/prerender_rel_type.h
new file mode 100644
index 0000000..e0450ac
--- /dev/null
+++ b/third_party/blink/public/common/prerender/prerender_rel_type.h
@@ -0,0 +1,19 @@
+// 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 THIRD_PARTY_BLINK_PUBLIC_COMMON_PRERENDER_PRERENDER_REL_TYPE_H_
+#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRERENDER_PRERENDER_REL_TYPE_H_
+
+namespace blink {
+
+// WebPrerenderRelType is a bitfield since multiple rel attributes can be set on
+// the same prerender.
+enum WebPrerenderRelType {
+  kPrerenderRelTypePrerender = 0x1,
+  kPrerenderRelTypeNext = 0x2,
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_PRERENDER_PRERENDER_REL_TYPE_H_
diff --git a/third_party/blink/public/platform/DEPS b/third_party/blink/public/platform/DEPS
index 78346615..cafa3e7 100644
--- a/third_party/blink/public/platform/DEPS
+++ b/third_party/blink/public/platform/DEPS
@@ -34,6 +34,7 @@
 
     # Enforce to use mojom-shared.h in blink/public so that it can compile
     # inside and outside Blink.
+    "+media/mojo/interfaces/display_media_information.mojom-shared.h",
     "+services/network/public/mojom/cors.mojom-shared.h",
     "+services/network/public/mojom/fetch_api.mojom-shared.h",
     "+services/network/public/mojom/request_context_frame_type.mojom-shared.h",
diff --git a/third_party/blink/public/platform/web_media_stream_track.h b/third_party/blink/public/platform/web_media_stream_track.h
index 5fa8699..9234b84 100644
--- a/third_party/blink/public/platform/web_media_stream_track.h
+++ b/third_party/blink/public/platform/web_media_stream_track.h
@@ -28,6 +28,7 @@
 #include <memory>
 
 #include "base/optional.h"
+#include "media/mojo/interfaces/display_media_information.mojom-shared.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -45,13 +46,6 @@
 class WebMediaStreamTrack {
  public:
   enum class FacingMode { kNone, kUser, kEnvironment, kLeft, kRight };
-  enum class DisplayCaptureSurfaceType {
-    kMonitor,
-    kWindow,
-    kApplication,
-    kBrowser
-  };
-  enum class CursorCaptureType { kNever, kAlways, kMotion };
 
   BLINK_PLATFORM_EXPORT static const char kResizeModeNone[];
   BLINK_PLATFORM_EXPORT static const char kResizeModeRescale[];
@@ -92,9 +86,9 @@
     WebString video_kind;
 
     // Screen Capture extensions
-    base::Optional<DisplayCaptureSurfaceType> display_surface;
+    base::Optional<media::mojom::DisplayCaptureSurfaceType> display_surface;
     base::Optional<bool> logical_surface;
-    base::Optional<CursorCaptureType> cursor;
+    base::Optional<media::mojom::CursorCaptureType> cursor;
   };
 
   enum class ContentHintType {
diff --git a/third_party/blink/public/platform/web_prerender.h b/third_party/blink/public/platform/web_prerender.h
index 8f7ca35..4a3317ce 100644
--- a/third_party/blink/public/platform/web_prerender.h
+++ b/third_party/blink/public/platform/web_prerender.h
@@ -41,13 +41,6 @@
 
 class Prerender;
 
-// WebPrerenderRelType is a bitfield since multiple rel attributes can be set on
-// the same prerender.
-enum WebPrerenderRelType {
-  kPrerenderRelTypePrerender = 0x1,
-  kPrerenderRelTypeNext = 0x2,
-};
-
 class WebPrerender {
  public:
   class ExtraData {
diff --git a/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util_video_device.h b/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util_video_device.h
index e4b6005c..803fa9f 100644
--- a/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util_video_device.h
+++ b/third_party/blink/public/web/modules/mediastream/media_stream_constraints_util_video_device.h
@@ -28,12 +28,6 @@
 BLINK_EXPORT WebMediaStreamTrack::FacingMode ToWebFacingMode(
     media::VideoFacingMode video_facing);
 
-BLINK_EXPORT WebMediaStreamTrack::DisplayCaptureSurfaceType ToWebDisplaySurface(
-    media::mojom::DisplayCaptureSurfaceType display_surface);
-
-BLINK_EXPORT WebMediaStreamTrack::CursorCaptureType ToWebCursorCaptureType(
-    media::mojom::CursorCaptureType cursor);
-
 // This is a temporary struct to bridge blink and content mojo types.
 // TODO(crbug.com/704136): Replace references to this type with the blink mojo
 // type once all dependent types are migrated to Blink.
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
index c890fb0..04fc3a09 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
@@ -195,8 +195,8 @@
   // TODO(jbroman): Ideally this method would take a WTF::StringView, but the
   // StringUTF8Adaptor trick doesn't yet work with StringView.
   StringUTF8Adaptor utf8(string);
-  WriteUint32(utf8.length());
-  WriteRawBytes(utf8.Data(), utf8.length());
+  WriteUint32(utf8.size());
+  WriteRawBytes(utf8.data(), utf8.size());
 }
 
 bool V8ScriptValueSerializer::WriteDOMObject(ScriptWrappable* wrappable,
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index fe2cd08..0ae9ccd3 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -192,8 +192,7 @@
     acquire_resolver_ =
         MakeGarbageCollected<ScriptPromiseResolver>(script_state);
     if (auto* layout_object = element_->GetLayoutObject()) {
-      layout_object->SetNeedsLayout(
-          layout_invalidation_reason::kDisplayLockCommitting);
+      layout_object->SetNeedsLayout(layout_invalidation_reason::kDisplayLock);
     }
     MarkPaintLayerNeedsRepaint();
     ScheduleAnimation();
@@ -540,8 +539,7 @@
   // for just the box itself. Note that we use the non-display locked version to
   // ensure all the hooks are property invoked.
   ToLayoutBox(layout_object)->SetFrameRect(pending_frame_rect_);
-  layout_object->SetNeedsLayout(
-      layout_invalidation_reason::kDisplayLockCommitting);
+  layout_object->SetNeedsLayout(layout_invalidation_reason::kDisplayLock);
 }
 
 void DisplayLockContext::StartUpdateIfNeeded() {
@@ -781,7 +779,7 @@
   // the parent so that it's up to date. This property is updated during
   // layout.
   if (auto* parent = element_->GetLayoutObject()->Parent()) {
-    parent->SetNeedsLayout(layout_invalidation_reason::kDisplayLockCommitting);
+    parent->SetNeedsLayout(layout_invalidation_reason::kDisplayLock);
   }
 }
 
diff --git a/third_party/blink/renderer/core/dom/OWNERS b/third_party/blink/renderer/core/dom/OWNERS
index 9ba5f94..1828548 100644
--- a/third_party/blink/renderer/core/dom/OWNERS
+++ b/third_party/blink/renderer/core/dom/OWNERS
@@ -1,5 +1,4 @@
 hayato@chromium.org
-tkent@chromium.org
 
 per-file *_struct_traits*.*=set noparent
 per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/renderer/core/exported/prerendering_test.cc b/third_party/blink/renderer/core/exported/prerendering_test.cc
index 59aada9..1c970df 100644
--- a/third_party/blink/renderer/core/exported/prerendering_test.cc
+++ b/third_party/blink/renderer/core/exported/prerendering_test.cc
@@ -34,6 +34,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/prerender/prerender_rel_type.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_cache.h"
 #include "third_party/blink/public/platform/web_prerender.h"
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index f54ff82..ca2ee4f 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -11595,16 +11595,17 @@
   frame->EnsureTextFinder().SelectNearestFindMatch(result_rect.Center(),
                                                    nullptr);
 
-  VisualViewport& visual_viewport = local_frame->GetPage()->GetVisualViewport();
-  EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(box1_rect));
-
+  EXPECT_TRUE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      box1_rect));
   result_rect = static_cast<FloatRect>(web_match_rects[1]);
   frame->EnsureTextFinder().SelectNearestFindMatch(result_rect.Center(),
                                                    nullptr);
 
-  EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(box2_rect))
+  EXPECT_TRUE(
+      frame_view->GetScrollableArea()->VisibleContentRect().Contains(box2_rect))
       << "Box [" << box2_rect.ToString() << "] is not visible in viewport ["
-      << visual_viewport.VisibleRectInDocument().ToString() << "]";
+      << frame_view->GetScrollableArea()->VisibleContentRect().ToString()
+      << "]";
 }
 
 // Test bubbling a document (End key) scroll from an inner iframe. This test
@@ -11710,13 +11711,13 @@
 
   auto* frame = To<LocalFrame>(WebView().GetPage()->MainFrame());
   LocalFrameView* frame_view = frame->View();
-  VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
-  FloatRect inputRect(200, 600, 100, 20);
+  IntRect inputRect(200, 600, 100, 20);
 
   frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(0, 0),
                                                    kProgrammaticScroll);
 
-  ASSERT_EQ(FloatPoint(), visual_viewport.VisibleRectInDocument().Location());
+  ASSERT_EQ(FloatPoint(),
+            frame_view->GetScrollableArea()->VisibleContentRect().Location());
 
   WebView()
       .MainFrameImpl()
@@ -11730,7 +11731,8 @@
           WebView().FakePageScaleAnimationTargetPositionForTesting())),
       kProgrammaticScroll);
 
-  EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
+  EXPECT_TRUE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      inputRect));
 
   // Reset the testing getters.
   WebView().EnableFakePageScaleAnimationForTesting(true);
@@ -11748,7 +11750,8 @@
   // Now resize the visual viewport so that the input box is no longer in view
   // (e.g. a keyboard is overlayed).
   WebView().MainFrameWidget()->ResizeVisualViewport(IntSize(200, 100));
-  ASSERT_FALSE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
+  ASSERT_FALSE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      inputRect));
 
   WebView()
       .MainFrameImpl()
@@ -11759,7 +11762,8 @@
           WebView().FakePageScaleAnimationTargetPositionForTesting())),
       kProgrammaticScroll);
 
-  EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
+  EXPECT_TRUE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      inputRect));
   EXPECT_EQ(1, WebView().FakePageScaleAnimationPageScaleForTesting());
 }
 
@@ -11823,11 +11827,13 @@
   rs_controller.RootScrollerArea()->SetScrollOffset(ScrollOffset(0, 300),
                                                     kProgrammaticScroll);
 
-  FloatRect inputRect(200, 700, 100, 20);
+  LocalFrameView* frame_view = frame->View();
+  IntRect inputRect(200, 700, 100, 20);
   ASSERT_EQ(1, visual_viewport.Scale());
   ASSERT_EQ(FloatPoint(0, 300),
-            visual_viewport.VisibleRectInDocument().Location());
-  ASSERT_FALSE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
+            frame_view->GetScrollableArea()->VisibleContentRect().Location());
+  ASSERT_FALSE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      inputRect));
 
   WebView()
       .MainFrameImpl()
@@ -11842,7 +11848,8 @@
   rs_controller.RootScrollerArea()->SetScrollOffset(target_offset,
                                                     kProgrammaticScroll);
 
-  EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
+  EXPECT_TRUE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
+      inputRect));
 }
 
 TEST_F(WebFrameSimTest, ScrollFocusedIntoViewClipped) {
@@ -11905,7 +11912,8 @@
   LocalFrameView* frame_view = frame->View();
   VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
 
-  ASSERT_EQ(FloatPoint(), visual_viewport.VisibleRectInDocument().Location());
+  ASSERT_EQ(FloatPoint(),
+            frame_view->GetScrollableArea()->VisibleContentRect().Location());
 
   // Simulate the keyboard being shown and resizing the widget. Cause a scroll
   // into view after.
@@ -11982,7 +11990,7 @@
   auto* frame = To<LocalFrame>(WebView().GetPage()->MainFrame());
   LocalFrameView* frame_view = frame->View();
   VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
-  FloatRect target_rect_in_document(2000, 3000, 100, 100);
+  IntRect target_rect_in_document(2000, 3000, 100, 100);
 
   ASSERT_EQ(0.5f, visual_viewport.Scale());
 
@@ -12009,7 +12017,7 @@
                                                      kProgrammaticScroll);
 
     EXPECT_FLOAT_EQ(1, visual_viewport.Scale());
-    EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(
+    EXPECT_TRUE(frame_view->GetScrollableArea()->VisibleContentRect().Contains(
         target_rect_in_document));
   }
 
@@ -12023,7 +12031,7 @@
     WebRect block_bounds = ComputeBlockBoundHelper(&WebView(), point, false);
     WebView().AnimateDoubleTapZoom(IntPoint(point), block_bounds);
     EXPECT_TRUE(WebView().FakeDoubleTapAnimationPendingForTesting());
-    FloatPoint target_offset(
+    IntPoint target_offset(
         WebView().FakePageScaleAnimationTargetPositionForTesting());
     float new_scale = WebView().FakePageScaleAnimationPageScaleForTesting();
 
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 20d774a..6baa3d4 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -2250,9 +2250,11 @@
   else
     new_scale = PageScaleFactor();
 
+  ScrollableArea* root_viewport =
+      MainFrameImpl()->GetFrame()->View()->GetScrollableArea();
+
   // If the caret is offscreen, then animate.
-  if (!visual_viewport.VisibleRectInDocument().Contains(
-          caret_bounds_in_content))
+  if (!root_viewport->VisibleContentRect().Contains(caret_bounds_in_content))
     need_animation = true;
 
   // If the box is partially offscreen and it's possible to bring it fully
@@ -2261,8 +2263,7 @@
           element_bounds_in_content.Width() &&
       visual_viewport.VisibleRect().Height() >=
           element_bounds_in_content.Height() &&
-      !visual_viewport.VisibleRectInDocument().Contains(
-          element_bounds_in_content))
+      !root_viewport->VisibleContentRect().Contains(element_bounds_in_content))
     need_animation = true;
 
   if (!need_animation)
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index ada062c..27ec352c 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -452,8 +452,8 @@
     DigestValue digest;
     if (algorithm_map.csp_hash_algorithm & hash_algorithms_used) {
       bool digest_success =
-          ComputeDigest(algorithm_map.algorithm, utf8_source.Data(),
-                        utf8_source.length(), digest);
+          ComputeDigest(algorithm_map.algorithm, utf8_source.data(),
+                        utf8_source.size(), digest);
       if (digest_success) {
         csp_hash_values->push_back(
             CSPHashValue(algorithm_map.csp_hash_algorithm, digest));
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index d8da1c0..d4ebebc 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -34,8 +34,8 @@
 String GetSha256String(const String& content) {
   DigestValue digest;
   StringUTF8Adaptor utf8_content(content);
-  bool digest_success = ComputeDigest(kHashAlgorithmSha256, utf8_content.Data(),
-                                      utf8_content.length(), digest);
+  bool digest_success = ComputeDigest(kHashAlgorithmSha256, utf8_content.data(),
+                                      utf8_content.size(), digest);
   if (!digest_success) {
     return "sha256-...";
   }
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 3076565..6e4bc65 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -370,16 +370,6 @@
   return FloatRect(FloatPoint(GetScrollOffset()), visible_size);
 }
 
-FloatRect VisualViewport::VisibleRectInDocument(
-    IncludeScrollbarsInRect scrollbar_inclusion) const {
-  if (!MainFrame() || !MainFrame()->View())
-    return FloatRect();
-
-  FloatPoint view_location =
-      FloatPoint(MainFrame()->View()->GetScrollableArea()->GetScrollOffset());
-  return FloatRect(view_location, VisibleRect(scrollbar_inclusion).Size());
-}
-
 FloatPoint VisualViewport::ViewportCSSPixelsToRootFrame(
     const FloatPoint& point) const {
   // Note, this is in CSS Pixels so we don't apply scale.
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.h b/third_party/blink/renderer/core/frame/visual_viewport.h
index b109fd3..4f93bdc5 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.h
+++ b/third_party/blink/renderer/core/frame/visual_viewport.h
@@ -132,11 +132,6 @@
   // +/- zooming).
   FloatRect VisibleRect(IncludeScrollbarsInRect = kExcludeScrollbars) const;
 
-  // Similar to VisibleRect but this returns the rect relative to the main
-  // document's top-left corner.
-  FloatRect VisibleRectInDocument(
-      IncludeScrollbarsInRect = kExcludeScrollbars) const;
-
   // Resets the viewport to initial state.
   void Reset();
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 4208920..33ae138 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -582,32 +582,6 @@
   EXPECT_FLOAT_RECT_EQ(expected_rect, visual_viewport.VisibleRect());
 }
 
-// Make sure that the visibleRectInDocument method acurately reflects the scale
-// and scroll location of the viewport relative to the document.
-TEST_P(VisualViewportTest, TestVisibleRectInDocument) {
-  InitializeWithDesktopSettings();
-  WebView()->MainFrameWidget()->Resize(IntSize(100, 400));
-
-  RegisterMockedHttpURLLoad("200-by-800-viewport.html");
-  NavigateTo(base_url_ + "200-by-800-viewport.html");
-
-  VisualViewport& visual_viewport = GetFrame()->GetPage()->GetVisualViewport();
-
-  // Scale the viewport to 2X and move it.
-  visual_viewport.SetScale(2);
-  visual_viewport.SetLocation(FloatPoint(10, 15));
-  EXPECT_FLOAT_RECT_EQ(FloatRect(10, 15, 50, 200),
-                       visual_viewport.VisibleRectInDocument());
-
-  // Scroll the layout viewport. Ensure its offset is reflected in
-  // visibleRectInDocument().
-  LocalFrameView& frame_view = *WebView()->MainFrameImpl()->GetFrameView();
-  frame_view.LayoutViewport()->SetScrollOffset(ScrollOffset(40, 100),
-                                               kProgrammaticScroll);
-  EXPECT_FLOAT_RECT_EQ(FloatRect(50, 115, 50, 200),
-                       visual_viewport.VisibleRectInDocument());
-}
-
 TEST_P(VisualViewportTest, TestFractionalScrollOffsetIsNotOverwritten) {
   ScopedFractionalScrollOffsetsForTest fractional_scroll_offsets(true);
   InitializeWithAndroidSettings();
@@ -2420,7 +2394,9 @@
 
 // Ensure we create effect node for scrollbar properly.
 TEST_P(VisualViewportTest, EnsureEffectNodeForScrollbars) {
-  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
+  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+      // TODO(wangxianzhu): Should this work for CompositeAfterPaint?
+      RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
     return;
 
   InitializeWithAndroidSettings();
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index 0a8009a..fa4877ef 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -309,11 +309,6 @@
     if (event->keyCode() == kVKeyProcessKey)
       return;
 
-    if (event->keyCode() == kVKeySpatNavBack &&
-        DefaultSpatNavBackEventHandler(event)) {
-      return;
-    }
-
     if (event->key() == "Tab") {
       DefaultTabEventHandler(event);
     } else if (event->key() == "Escape") {
@@ -332,8 +327,13 @@
     if (event->charCode() == ' ')
       DefaultSpaceEventHandler(event, possible_focused_node);
   } else if (event->type() == event_type_names::kKeyup) {
-    if (event->key() == "Enter")
+    if (event->key() == "Enter") {
       DefaultEnterEventHandler(event);
+      return;
+    }
+
+    if (event->keyCode() == kVKeySpatNavBack)
+      DefaultSpatNavBackEventHandler(event);
   }
 }
 
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.cc b/third_party/blink/renderer/core/inspector/devtools_session.cc
index 13d2868..6435384 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_session.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/inspector/devtools_session.h"
 
+#include "base/containers/span.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/use_counter.h"
@@ -50,8 +51,8 @@
     result->data = std::move(message.binary);
   } else {
     WTF::StringUTF8Adaptor adaptor(message.json);
-    result->data = mojo_base::BigBuffer(base::make_span(
-        reinterpret_cast<const uint8_t*>(adaptor.Data()), adaptor.length()));
+    result->data =
+        mojo_base::BigBuffer(base::as_bytes(base::make_span(adaptor)));
   }
   return result;
 }
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 3055e56..162e924 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -700,7 +700,7 @@
 const char kTextControlChanged[] = "Text control changed";
 const char kSvgChanged[] = "SVG changed";
 const char kScrollbarChanged[] = "Scrollbar changed";
-const char kDisplayLockCommitting[] = "Display lock committing";
+const char kDisplayLock[] = "Display lock";
 }  // namespace layout_invalidation_reason
 
 std::unique_ptr<TracedValue> inspector_layout_invalidation_tracking_event::Data(
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.h b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
index fca953b..566e160 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.h
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
@@ -248,7 +248,7 @@
 // size related invalidations.
 extern const char kSvgChanged[];
 extern const char kScrollbarChanged[];
-extern const char kDisplayLockCommitting[];
+extern const char kDisplayLock[];
 }  // namespace layout_invalidation_reason
 
 // LayoutInvalidationReasonForTracing is strictly for tracing. Blink logic must
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
index ef8636497..5f64e2c3 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
@@ -76,7 +76,7 @@
   // OverrideContainingBlockContentLogicalWidth/Height are used by e.g. grid
   // layout. Override sizes are padding box size, not border box, so we must add
   // borders and scrollbars to compensate.
-  NGBoxStrut borders_and_scrollbars =
+  NGBoxStrut border_scrollbar =
       ComputeBorders(constraint_space, container_node) +
       NGBlockNode(container).GetScrollbarSizes();
 
@@ -92,14 +92,14 @@
   if (HasOverrideContainingBlockContentLogicalWidth()) {
     container_border_box_logical_width =
         OverrideContainingBlockContentLogicalWidth() +
-        borders_and_scrollbars.InlineSum();
+        border_scrollbar.InlineSum();
   } else {
     container_border_box_logical_width = container->LogicalWidth();
   }
   if (HasOverrideContainingBlockContentLogicalHeight()) {
     container_border_box_logical_height =
         OverrideContainingBlockContentLogicalHeight() +
-        borders_and_scrollbars.BlockSum();
+        border_scrollbar.BlockSum();
   } else {
     container_border_box_logical_height = container->LogicalHeight();
   }
@@ -133,10 +133,10 @@
   // Run(). Otherwise, NGOutOfFlowLayoutPart may also lay out other objects
   // it discovers that are part of the same containing block, but those
   // should get laid out by the actual containing block.
-  NGOutOfFlowLayoutPart(
-      &container_builder, css_container->CanContainAbsolutePositionObjects(),
-      css_container->CanContainFixedPositionObjects(), borders_and_scrollbars,
-      constraint_space, *container_style, initial_containing_block_fixed_size)
+  NGOutOfFlowLayoutPart(css_container->CanContainAbsolutePositionObjects(),
+                        css_container->CanContainFixedPositionObjects(),
+                        *container_style, constraint_space, border_scrollbar,
+                        &container_builder, initial_containing_block_fixed_size)
       .Run(/* only_layout */ this);
   scoped_refptr<const NGLayoutResult> result =
       container_builder.ToBoxFragment();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index c67bec5..9b00e26 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -700,9 +700,8 @@
   // Only layout absolute and fixed children if we aren't going to revisit this
   // layout.
   if (unpositioned_floats_.IsEmpty()) {
-    NGOutOfFlowLayoutPart(&container_builder_, Node().IsAbsoluteContainer(),
-                          Node().IsFixedContainer(), borders + scrollbars,
-                          ConstraintSpace(), Style())
+    NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders + scrollbars,
+                          &container_builder_)
         .Run();
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index 0fcbece..243fdbf 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -20,6 +20,8 @@
 
 namespace blink {
 
+namespace {
+
 using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*,
                               const NGPhysicalLineBoxFragment*>;
 void GatherInlineContainerFragmentsFromLinebox(
@@ -31,7 +33,7 @@
     if (!descendant.fragment->IsBox())
       continue;
     LayoutObject* key = descendant.fragment->GetLayoutObject();
-    // Key for inline is continuation root if it exists.
+    // Key for inline is the continuation root if it exists.
     if (key->IsLayoutInline() && key->GetNode())
       key = key->GetNode()->GetLayoutObject();
     auto it = inline_containing_block_map->find(key);
@@ -70,6 +72,8 @@
   }
 }
 
+}  // namespace
+
 void NGBoxFragmentBuilder::RemoveChildren() {
   child_break_tokens_.resize(0);
   inline_break_tokens_.resize(0);
@@ -258,12 +262,12 @@
   return base::AdoptRef(new NGLayoutResult(status, this));
 }
 
-// Finds InlineContainingBlockGeometry that define inline containing blocks.
-// |inline_containing_block_map| is a map whose keys specify which
-// inline containing blocks are required.
+// Computes the geometry required for any inline containing blocks.
+// |inline_containing_block_map| is a map whose keys specify which inline
+// containing block geometry is required.
 void NGBoxFragmentBuilder::ComputeInlineContainerFragments(
     InlineContainingBlockMap* inline_containing_block_map) {
-  if (!inline_containing_block_map->size())
+  if (inline_containing_block_map->IsEmpty())
     return;
 
   // This function has detailed knowledge of inline fragment tree structure,
@@ -271,8 +275,8 @@
   DCHECK_GE(InlineSize(), LayoutUnit());
   DCHECK_GE(BlockSize(), LayoutUnit());
 
-  // std::pair.first points to start linebox fragment.
-  // std::pair.second points to ending linebox fragment.
+  // std::pair.first points to the start linebox fragment.
+  // std::pair.second points to the end linebox fragment.
   using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*,
                                 const NGPhysicalLineBoxFragment*>;
   HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map;
@@ -297,9 +301,8 @@
       // split inlines. The inline container fragments might be inside
       // anonymous boxes. To find inline container fragments, traverse
       // lineboxes inside anonymous box.
-      // For more on this special case, see "css container is an inline,
-      // with inline splitting" comment in
-      // NGOutOfFlowLayoutPart::LayoutDescendant.
+      // For more on this special case, see "css container is an inline, with
+      // inline splitting" comment in NGOutOfFlowLayoutPart::LayoutDescendant.
       const NGPhysicalOffset box_offset = offsets_[i].ConvertToPhysical(
           GetWritingMode(), Direction(),
           ToNGPhysicalSize(Size(), GetWritingMode()), box_fragment->Size());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
index aad1f91..911faa1 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
@@ -180,9 +180,8 @@
     break;
   } while (true);
 
-  NGOutOfFlowLayoutPart(&container_builder_, Node().IsAbsoluteContainer(),
-                        Node().IsFixedContainer(), borders + scrollbars,
-                        ConstraintSpace(), Style())
+  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders + scrollbars,
+                        &container_builder_)
       .Run();
 
   // TODO(mstensho): Propagate baselines.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
index 5c2613bd..b1d6eefc 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
@@ -131,9 +131,8 @@
   container_builder_.SetBorders(borders);
   container_builder_.SetPadding(padding);
 
-  NGOutOfFlowLayoutPart(&container_builder_, Node().IsAbsoluteContainer(),
-                        Node().IsFixedContainer(), borders_with_legend,
-                        ConstraintSpace(), Style())
+  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_with_legend,
+                        &container_builder_)
       .Run();
 
   return container_builder_.ToBoxFragment();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
index 98f0768..e1c45a8a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_flex_layout_algorithm.cc
@@ -274,10 +274,9 @@
 
   GiveLinesAndItemsFinalPositionAndSize();
 
-  NGOutOfFlowLayoutPart(&container_builder_, Node().IsAbsoluteContainer(),
-                        Node().IsFixedContainer(),
+  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(),
                         borders_ + Node().GetScrollbarSizes(),
-                        ConstraintSpace(), Style())
+                        &container_builder_)
       .Run();
 
   return container_builder_.ToBoxFragment();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 345cd3a..9ae716b7 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -23,17 +23,19 @@
 
 namespace blink {
 
+namespace {
+
 bool IsAnonymousContainer(const LayoutObject* layout_object) {
   return layout_object->IsAnonymousBlock() &&
          layout_object->CanContainAbsolutePositionObjects();
 }
 
-// When containing block is a split inline, Legacy and NG use different
-// containers to place OOF descendant.
-// Legacy uses the anonymous block generated by inline.
-// NG uses the anonymous' parent block, that contains all the anonymous
-// continuations.
-// This function finds anonymous parent block.
+// When the containing block is a split inline, Legacy and NG use different
+// containers to place the OOF descendant:
+//  - Legacy uses the anonymous block generated by inline.
+//  - NG uses the anonymous' parent block, that contains all the anonymous
+//    continuations.
+// This function finds the correct anonymous parent block.
 LayoutBoxModelObject* GetOOFContainingBlockFromAnonymous(
     const LayoutObject* anonymous_block,
     EPosition child_position) {
@@ -61,13 +63,27 @@
       absolute_containing_block->GetNode()->GetLayoutObject());
 }
 
+}  // namespace
+
 NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart(
-    NGBoxFragmentBuilder* container_builder,
+    const NGBlockNode& container_node,
+    const NGConstraintSpace& container_space,
+    const NGBoxStrut& border_scrollbar,
+    NGBoxFragmentBuilder* container_builder)
+    : NGOutOfFlowLayoutPart(container_node.IsAbsoluteContainer(),
+                            container_node.IsFixedContainer(),
+                            container_node.Style(),
+                            container_space,
+                            border_scrollbar,
+                            container_builder) {}
+
+NGOutOfFlowLayoutPart::NGOutOfFlowLayoutPart(
     bool contains_absolute,
     bool contains_fixed,
-    const NGBoxStrut& border_scrollbar,
-    const NGConstraintSpace& container_space,
     const ComputedStyle& container_style,
+    const NGConstraintSpace& container_space,
+    const NGBoxStrut& border_scrollbar,
+    NGBoxFragmentBuilder* container_builder,
     base::Optional<NGLogicalSize> initial_containing_block_fixed_size)
     : container_builder_(container_builder),
       contains_absolute_(contains_absolute),
@@ -77,21 +93,9 @@
            ->HasPositionedObjects())
     return;
 
-  NGPhysicalBoxStrut physical_border_scrollbar =
-      border_scrollbar.ConvertToPhysical(container_style.GetWritingMode(),
-                                         container_style.Direction());
-
   default_containing_block_.style = &container_style;
   default_containing_block_.content_size_for_absolute =
-      container_builder_->Size();
-  default_containing_block_.content_size_for_absolute.inline_size =
-      std::max(default_containing_block_.content_size_for_absolute.inline_size -
-                   border_scrollbar.InlineSum(),
-               LayoutUnit());
-  default_containing_block_.content_size_for_absolute.block_size =
-      std::max(default_containing_block_.content_size_for_absolute.block_size -
-                   border_scrollbar.BlockSum(),
-               LayoutUnit());
+      ShrinkAvailableSize(container_builder_->Size(), border_scrollbar);
   default_containing_block_.content_size_for_fixed =
       initial_containing_block_fixed_size
           ? *initial_containing_block_fixed_size
@@ -99,6 +103,10 @@
 
   default_containing_block_.container_offset = NGLogicalOffset(
       border_scrollbar.inline_start, border_scrollbar.block_start);
+
+  NGPhysicalBoxStrut physical_border_scrollbar =
+      border_scrollbar.ConvertToPhysical(container_style.GetWritingMode(),
+                                         container_style.Direction());
   default_containing_block_.physical_container_offset = NGPhysicalOffset(
       physical_border_scrollbar.left, physical_border_scrollbar.top);
 }
@@ -116,10 +124,10 @@
     return;
 
   // Special case: containing block is a split inline.
-  // If current container was generated by a split inline,
-  // do not position descendants inside this container. Let its
-  // non-anonymous parent handle it. Only parent has geometry information
-  // needed to compute containing block geometry.
+  // If current container was generated by a split inline, do not position
+  // descendants inside this container. Let its non-anonymous parent handle it.
+  // Only parent has geometry information needed to compute containing block
+  // geometry.
   // See "Special case: oof css container" comment for detailed description.
   if (descendant_candidates.size() > 0 && current_container && !only_layout &&
       IsAnonymousContainer(current_container)) {
@@ -225,14 +233,14 @@
   return true;
 }
 
-NGOutOfFlowLayoutPart::ContainingBlockInfo
+const NGOutOfFlowLayoutPart::ContainingBlockInfo&
 NGOutOfFlowLayoutPart::GetContainingBlockInfo(
     const NGOutOfFlowPositionedDescendant& descendant) const {
-  // TODO remove containing_blocks_map_.Contains
-  if (descendant.inline_container &&
-      containing_blocks_map_.Contains(descendant.inline_container)) {
-    DCHECK(containing_blocks_map_.Contains(descendant.inline_container));
-    return containing_blocks_map_.at(descendant.inline_container);
+  if (descendant.inline_container) {
+    const auto it = containing_blocks_map_.find(descendant.inline_container);
+    // TODO(atotic): Make this if-condition a DCHECK.
+    if (it != containing_blocks_map_.end())
+      return it->value;
   }
   return default_containing_block_;
 }
@@ -432,7 +440,8 @@
           descendant.node.GetLayoutBox()->ContainingBlock()) ||
          descendant.node.GetLayoutBox()->ContainingBlock()->IsTable());
 
-  ContainingBlockInfo container_info = GetContainingBlockInfo(descendant);
+  const ContainingBlockInfo& container_info =
+      GetContainingBlockInfo(descendant);
   const ComputedStyle& descendant_style = descendant.node.Style();
 
   WritingMode container_writing_mode(container_info.style->GetWritingMode());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index 1e9648b..09982131 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -33,26 +33,31 @@
   STACK_ALLOCATED();
 
  public:
-  // The container_builder, borders_and_scrollers, container_space and
-  // container_style parameters are all with respect to the containing block of
-  // the relevant out-of-flow positioned descendants. If the CSS "containing
-  // block" of such an out-of-flow positioned descendant isn't a true block (but
-  // e.g. a relatively positioned inline instead), the containing block here is
+  NGOutOfFlowLayoutPart(const NGBlockNode& container_node,
+                        const NGConstraintSpace& container_space,
+                        const NGBoxStrut& border_scrollbar,
+                        NGBoxFragmentBuilder* container_builder);
+
+  // The |container_builder|, |border_scrollbar|, |container_space|, and
+  // |container_style| parameters are all with respect to the containing block
+  // of the relevant out-of-flow positioned descendants. If the CSS "containing
+  // block" of such an out-of-flow positioned descendant isn't a true block
+  // (e.g. a relatively positioned inline instead), the containing block here is
   // the containing block of said non-block.
   NGOutOfFlowLayoutPart(
-      NGBoxFragmentBuilder* container_builder,
       bool contains_absolute,
       bool contains_fixed,
-      const NGBoxStrut& borders_and_scrollers,
-      const NGConstraintSpace& container_space,
       const ComputedStyle& container_style,
+      const NGConstraintSpace& container_space,
+      const NGBoxStrut& border_scrollbar,
+      NGBoxFragmentBuilder* container_builder,
       base::Optional<NGLogicalSize> initial_containing_block_fixed_size =
           base::nullopt);
 
-  // Normally this function lays out and positions all out-of-flow objects
-  // from the container_builder and additional ones it discovers through laying
-  // out those objects. However, if only_layout is specified, only that object
-  // will get laid out; any additional ones will be stored as out-of-flow
+  // Normally this function lays out and positions all out-of-flow objects from
+  // the container_builder and additional ones it discovers through laying out
+  // those objects. However, if only_layout is specified, only that object will
+  // get laid out; any additional ones will be stored as out-of-flow
   // descendants in the builder for use via
   // LayoutResult::OutOfFlowPositionedDescendants.
   void Run(const LayoutBox* only_layout = nullptr);
@@ -92,7 +97,7 @@
 
   bool SweepLegacyDescendants(HashSet<const LayoutObject*>* placed_objects);
 
-  ContainingBlockInfo GetContainingBlockInfo(
+  const ContainingBlockInfo& GetContainingBlockInfo(
       const NGOutOfFlowPositionedDescendant&) const;
 
   void ComputeInlineContainingBlocks(
@@ -118,10 +123,10 @@
       const NGAbsolutePhysicalPosition& node_position);
 
   NGBoxFragmentBuilder* container_builder_;
-  bool contains_absolute_;
-  bool contains_fixed_;
   ContainingBlockInfo default_containing_block_;
   HashMap<const LayoutObject*, ContainingBlockInfo> containing_blocks_map_;
+  bool contains_absolute_;
+  bool contains_fixed_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc
index 8d88234..10750034 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_page_layout_algorithm.cc
@@ -71,9 +71,8 @@
   container_builder_.SetBorders(ComputeBorders(ConstraintSpace(), Node()));
   container_builder_.SetPadding(ComputePadding(ConstraintSpace(), Style()));
 
-  NGOutOfFlowLayoutPart(&container_builder_, Node().IsAbsoluteContainer(),
-                        Node().IsFixedContainer(), borders + scrollbars,
-                        ConstraintSpace(), Style())
+  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders + scrollbars,
+                        &container_builder_)
       .Run();
 
   // TODO(mstensho): Propagate baselines.
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
index 0e97a54..19c8fcfb 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_tree_as_text.cc
@@ -72,9 +72,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_types.h"
-
-#include <math.h>
-#include <memory>
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -567,8 +565,8 @@
     ts << "\n";
     // Creating a placeholder filter which is passed to the builder.
     FloatRect dummy_rect;
-    Filter* dummy_filter =
-        Filter::Create(dummy_rect, dummy_rect, 1, Filter::kBoundingBox);
+    auto* dummy_filter = MakeGarbageCollected<Filter>(dummy_rect, dummy_rect, 1,
+                                                      Filter::kBoundingBox);
     SVGFilterBuilder builder(dummy_filter->GetSourceGraphic());
     builder.BuildGraph(dummy_filter, ToSVGFilterElement(*filter->GetElement()),
                        dummy_rect);
diff --git a/third_party/blink/renderer/core/loader/link_loader.cc b/third_party/blink/renderer/core/loader/link_loader.cc
index 0ae76cb7..60a0f4bf3 100644
--- a/third_party/blink/renderer/core/loader/link_loader.cc
+++ b/third_party/blink/renderer/core/loader/link_loader.cc
@@ -31,7 +31,7 @@
 
 #include "third_party/blink/renderer/core/loader/link_loader.h"
 
-#include "third_party/blink/public/platform/web_prerender.h"
+#include "third_party/blink/public/common/prerender/prerender_rel_type.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/use_counter.h"
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
index 3b3bccc..3058d24 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc
@@ -61,17 +61,14 @@
 
   const ComputedStyle& style = layout_object.StyleRef();
 
-  if (RequiresCompositingForTransform(layout_object))
+  if (RequiresCompositingFor3DTransform(layout_object))
     reasons |= CompositingReason::k3DTransform;
 
   if (style.BackfaceVisibility() == EBackfaceVisibility::kHidden)
     reasons |= CompositingReason::kBackfaceVisibilityHidden;
 
   reasons |= CompositingReasonsForAnimation(style);
-
-  if (style.HasWillChangeCompositingHint() &&
-      !style.SubtreeWillChangeContents())
-    reasons |= CompositingReason::kWillChangeCompositingHint;
+  reasons |= CompositingReasonsForWillChange(style);
 
   if (style.UsedTransformStyle3D() == ETransformStyle3D::kPreserve3d)
     reasons |= CompositingReason::kPreserve3DWith3DDescendants;
@@ -122,15 +119,12 @@
     return CompositingReason::kNone;
 
   const ComputedStyle& style = object.StyleRef();
-  auto reasons = CompositingReasonsForAnimation(style);
+  auto reasons = CompositingReasonsForAnimation(style) |
+                 CompositingReasonsForWillChange(style);
 
-  if (RequiresCompositingForTransform(object))
+  if (RequiresCompositingFor3DTransform(object))
     reasons |= CompositingReason::k3DTransform;
 
-  if (style.HasWillChangeCompositingHint() &&
-      !style.SubtreeWillChangeContents())
-    reasons |= CompositingReason::kWillChangeCompositingHint;
-
   auto* layer = ToLayoutBoxModelObject(object).Layer();
   if (layer->Has3DTransformedDescendant()) {
     if (style.HasPerspective())
@@ -148,7 +142,7 @@
   return reasons;
 }
 
-bool CompositingReasonFinder::RequiresCompositingForTransform(
+bool CompositingReasonFinder::RequiresCompositingFor3DTransform(
     const LayoutObject& layout_object) {
   // Note that we ask the layoutObject if it has a transform, because the style
   // may have transforms, but the layoutObject may be an inline that doesn't
@@ -213,39 +207,38 @@
 CompositingReasons CompositingReasonFinder::CompositingReasonsForAnimation(
     const ComputedStyle& style) {
   CompositingReasons reasons = CompositingReason::kNone;
-  if (RequiresCompositingForTransformAnimation(style))
+  if (style.SubtreeWillChangeContents())
+    return reasons;
+
+  if (style.HasCurrentTransformAnimation())
     reasons |= CompositingReason::kActiveTransformAnimation;
-  if (RequiresCompositingForOpacityAnimation(style))
+  if (style.HasCurrentOpacityAnimation())
     reasons |= CompositingReason::kActiveOpacityAnimation;
-  if (RequiresCompositingForFilterAnimation(style))
+  if (style.HasCurrentFilterAnimation())
     reasons |= CompositingReason::kActiveFilterAnimation;
-  if (RequiresCompositingForBackdropFilterAnimation(style))
+  if (style.HasCurrentBackdropFilterAnimation())
     reasons |= CompositingReason::kActiveBackdropFilterAnimation;
   return reasons;
 }
 
-bool CompositingReasonFinder::RequiresCompositingForOpacityAnimation(
+CompositingReasons CompositingReasonFinder::CompositingReasonsForWillChange(
     const ComputedStyle& style) {
-  return style.HasCurrentOpacityAnimation() &&
-         !style.SubtreeWillChangeContents();
-}
+  CompositingReasons reasons = CompositingReason::kNone;
+  if (style.SubtreeWillChangeContents())
+    return reasons;
 
-bool CompositingReasonFinder::RequiresCompositingForFilterAnimation(
-    const ComputedStyle& style) {
-  return style.HasCurrentFilterAnimation() &&
-         !style.SubtreeWillChangeContents();
-}
+  if (style.HasWillChangeTransformHint())
+    reasons |= CompositingReason::kWillChangeTransform;
+  if (style.HasWillChangeOpacityHint())
+    reasons |= CompositingReason::kWillChangeOpacity;
 
-bool CompositingReasonFinder::RequiresCompositingForBackdropFilterAnimation(
-    const ComputedStyle& style) {
-  return style.HasCurrentBackdropFilterAnimation() &&
-         !style.SubtreeWillChangeContents();
-}
+  // kWillChangeOther is needed only when neither kWillChangeTransform nor
+  // kWillChangeOpacity is set.
+  if (reasons == CompositingReason::kNone &&
+      style.HasWillChangeCompositingHint())
+    reasons |= CompositingReason::kWillChangeOther;
 
-bool CompositingReasonFinder::RequiresCompositingForTransformAnimation(
-    const ComputedStyle& style) {
-  return style.HasCurrentTransformAnimation() &&
-         !style.SubtreeWillChangeContents();
+  return reasons;
 }
 
 bool CompositingReasonFinder::RequiresCompositingForRootScroller(
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h
index fd0fa32..397600f 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h
@@ -37,12 +37,9 @@
   static bool RequiresCompositingForScrollableFrame(const LayoutView&);
   static CompositingReasons CompositingReasonsForAnimation(
       const ComputedStyle&);
-  static bool RequiresCompositingForOpacityAnimation(const ComputedStyle&);
-  static bool RequiresCompositingForFilterAnimation(const ComputedStyle&);
-  static bool RequiresCompositingForBackdropFilterAnimation(
+  static CompositingReasons CompositingReasonsForWillChange(
       const ComputedStyle&);
-  static bool RequiresCompositingForTransformAnimation(const ComputedStyle&);
-  static bool RequiresCompositingForTransform(const LayoutObject&);
+  static bool RequiresCompositingFor3DTransform(const LayoutObject&);
   static bool RequiresCompositingForRootScroller(const PaintLayer&);
   static bool RequiresCompositingForScrollTimeline(const PaintLayer&);
 
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
index 1e9800b0..039fbb03 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
@@ -144,32 +144,6 @@
           ->GetCompositingState());
 }
 
-TEST_F(CompositingReasonFinderTest, RequiresCompositingForTransformAnimation) {
-  scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
-  style->SetSubtreeWillChangeContents(false);
-
-  style->SetHasCurrentTransformAnimation(false);
-  EXPECT_FALSE(
-      CompositingReasonFinder::RequiresCompositingForTransformAnimation(
-          *style));
-
-  style->SetHasCurrentTransformAnimation(true);
-  EXPECT_TRUE(CompositingReasonFinder::RequiresCompositingForTransformAnimation(
-      *style));
-
-  style->SetSubtreeWillChangeContents(true);
-
-  style->SetHasCurrentTransformAnimation(false);
-  EXPECT_FALSE(
-      CompositingReasonFinder::RequiresCompositingForTransformAnimation(
-          *style));
-
-  style->SetHasCurrentTransformAnimation(true);
-  EXPECT_FALSE(
-      CompositingReasonFinder::RequiresCompositingForTransformAnimation(
-          *style));
-}
-
 TEST_F(CompositingReasonFinderTest, CompositingReasonsForAnimation) {
   scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
 
diff --git a/third_party/blink/renderer/core/paint/filter_effect_builder.cc b/third_party/blink/renderer/core/paint/filter_effect_builder.cc
index 5ab79f8..63acec2 100644
--- a/third_party/blink/renderer/core/paint/filter_effect_builder.cc
+++ b/third_party/blink/renderer/core/paint/filter_effect_builder.cc
@@ -45,6 +45,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
 #include "third_party/blink/renderer/platform/graphics/interpolation_space.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
 namespace blink {
@@ -134,7 +135,7 @@
     bool input_tainted) const {
   // Create a parent filter for shorthand filters. These have already been
   // scaled by the CSS code for page zoom, so scale is 1.0 here.
-  Filter* parent_filter = Filter::Create(1.0f);
+  auto* parent_filter = MakeGarbageCollected<Filter>(1.0f);
   FilterEffect* previous_effect = parent_filter->GetSourceGraphic();
   if (input_tainted)
     previous_effect->SetOriginTainted();
@@ -160,22 +161,22 @@
       case FilterOperation::GRAYSCALE: {
         Vector<float> input_parameters = GrayscaleMatrix(
             To<BasicColorMatrixFilterOperation>(filter_operation)->Amount());
-        effect = FEColorMatrix::Create(parent_filter, FECOLORMATRIX_TYPE_MATRIX,
-                                       input_parameters);
+        effect = MakeGarbageCollected<FEColorMatrix>(
+            parent_filter, FECOLORMATRIX_TYPE_MATRIX, input_parameters);
         break;
       }
       case FilterOperation::SEPIA: {
         Vector<float> input_parameters = SepiaMatrix(
             To<BasicColorMatrixFilterOperation>(filter_operation)->Amount());
-        effect = FEColorMatrix::Create(parent_filter, FECOLORMATRIX_TYPE_MATRIX,
-                                       input_parameters);
+        effect = MakeGarbageCollected<FEColorMatrix>(
+            parent_filter, FECOLORMATRIX_TYPE_MATRIX, input_parameters);
         break;
       }
       case FilterOperation::SATURATE: {
         Vector<float> input_parameters;
         input_parameters.push_back(clampTo<float>(
             To<BasicColorMatrixFilterOperation>(filter_operation)->Amount()));
-        effect = FEColorMatrix::Create(
+        effect = MakeGarbageCollected<FEColorMatrix>(
             parent_filter, FECOLORMATRIX_TYPE_SATURATE, input_parameters);
         break;
       }
@@ -183,7 +184,7 @@
         Vector<float> input_parameters;
         input_parameters.push_back(clampTo<float>(
             To<BasicColorMatrixFilterOperation>(filter_operation)->Amount()));
-        effect = FEColorMatrix::Create(
+        effect = MakeGarbageCollected<FEColorMatrix>(
             parent_filter, FECOLORMATRIX_TYPE_HUEROTATE, input_parameters);
         break;
       }
@@ -200,9 +201,9 @@
         transfer_function.table_values = transfer_parameters;
 
         ComponentTransferFunction null_function;
-        effect = FEComponentTransfer::Create(parent_filter, transfer_function,
-                                             transfer_function,
-                                             transfer_function, null_function);
+        effect = MakeGarbageCollected<FEComponentTransfer>(
+            parent_filter, transfer_function, transfer_function,
+            transfer_function, null_function);
         break;
       }
       case FilterOperation::OPACITY: {
@@ -216,9 +217,9 @@
         transfer_function.table_values = transfer_parameters;
 
         ComponentTransferFunction null_function;
-        effect = FEComponentTransfer::Create(parent_filter, null_function,
-                                             null_function, null_function,
-                                             transfer_function);
+        effect = MakeGarbageCollected<FEComponentTransfer>(
+            parent_filter, null_function, null_function, null_function,
+            transfer_function);
         break;
       }
       case FilterOperation::BRIGHTNESS: {
@@ -230,9 +231,9 @@
         transfer_function.intercept = 0;
 
         ComponentTransferFunction null_function;
-        effect = FEComponentTransfer::Create(parent_filter, transfer_function,
-                                             transfer_function,
-                                             transfer_function, null_function);
+        effect = MakeGarbageCollected<FEComponentTransfer>(
+            parent_filter, transfer_function, transfer_function,
+            transfer_function, null_function);
         break;
       }
       case FilterOperation::CONTRAST: {
@@ -245,31 +246,31 @@
         transfer_function.intercept = -0.5 * amount + 0.5;
 
         ComponentTransferFunction null_function;
-        effect = FEComponentTransfer::Create(parent_filter, transfer_function,
-                                             transfer_function,
-                                             transfer_function, null_function);
+        effect = MakeGarbageCollected<FEComponentTransfer>(
+            parent_filter, transfer_function, transfer_function,
+            transfer_function, null_function);
         break;
       }
       case FilterOperation::BLUR: {
         float std_deviation = FloatValueForLength(
             To<BlurFilterOperation>(filter_operation)->StdDeviation(), 0);
-        effect =
-            FEGaussianBlur::Create(parent_filter, std_deviation, std_deviation);
+        effect = MakeGarbageCollected<FEGaussianBlur>(
+            parent_filter, std_deviation, std_deviation);
         break;
       }
       case FilterOperation::DROP_SHADOW: {
         const ShadowData& shadow =
             To<DropShadowFilterOperation>(*filter_operation).Shadow();
-        effect = FEDropShadow::Create(parent_filter, shadow.Blur(),
-                                      shadow.Blur(), shadow.X(), shadow.Y(),
-                                      shadow.GetColor().GetColor(), 1);
+        effect = MakeGarbageCollected<FEDropShadow>(
+            parent_filter, shadow.Blur(), shadow.Blur(), shadow.X(), shadow.Y(),
+            shadow.GetColor().GetColor(), 1);
         break;
       }
       case FilterOperation::BOX_REFLECT: {
         BoxReflectFilterOperation* box_reflect_operation =
             To<BoxReflectFilterOperation>(filter_operation);
-        effect = FEBoxReflect::Create(parent_filter,
-                                      box_reflect_operation->Reflection());
+        effect = MakeGarbageCollected<FEBoxReflect>(
+            parent_filter, box_reflect_operation->Reflection());
         break;
       }
       default:
@@ -436,8 +437,8 @@
       SVGUnitTypes::kSvgUnitTypeObjectboundingbox;
   Filter::UnitScaling unit_scaling =
       primitive_bounding_box_mode ? Filter::kBoundingBox : Filter::kUserSpace;
-  Filter* result =
-      Filter::Create(reference_box_, filter_region, zoom_, unit_scaling);
+  auto* result = MakeGarbageCollected<Filter>(reference_box_, filter_region,
+                                              zoom_, unit_scaling);
   if (!previous_effect)
     previous_effect = result->GetSourceGraphic();
   SVGFilterBuilder builder(previous_effect, node_map, fill_flags_,
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index f5fb431..4be210f 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -616,6 +616,24 @@
       style.TransformOriginZ());
 }
 
+// TODO(crbug.com/900241): Remove this function and let the caller use
+// CompositingReason::kDirectReasonForTransformProperty directly.
+static CompositingReasons CompositingReasonsForTransformProperty() {
+  CompositingReasons reasons =
+      CompositingReason::kDirectReasonsForTransformProperty;
+  // TODO(crbug.com/900241): Check for nodes for each KeyframeModel target
+  // property instead of creating all nodes and only create a transform/
+  // effect/filter node if needed.
+  reasons |= CompositingReason::kComboActiveAnimation;
+  // We also need to create transform node if the opacity node is created for
+  // will-change:opacity to avoid raster invalidation (caused by otherwise a
+  // created/deleted effect node) when we start/stop an opacity animation.
+  // https://crbug.com/942681
+  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    reasons |= CompositingReason::kWillChangeOpacity;
+  return reasons;
+}
+
 static bool NeedsTransform(const LayoutObject& object,
                            CompositingReasons direct_compositing_reasons) {
   if (object.IsText())
@@ -626,12 +644,7 @@
       object.StyleRef().BackfaceVisibility() == EBackfaceVisibility::kHidden)
     return true;
 
-  // TODO(crbug.com/900241): Currently kDirectReasonsForTransformProperty
-  // includes all will-change compositing hints including opacity. This is
-  // needed to avoid creating/deleting transform nodes on start/end of an
-  // opacity animation. https://crbug.com/942681
-  if (direct_compositing_reasons &
-      CompositingReason::kDirectReasonsForTransformProperty)
+  if (direct_compositing_reasons & CompositingReasonsForTransformProperty())
     return true;
 
   if (!object.IsBox())
@@ -694,7 +707,7 @@
           }
           state.direct_compositing_reasons =
               full_context_.direct_compositing_reasons &
-              CompositingReason::kDirectReasonsForTransformProperty;
+              CompositingReasonsForTransformProperty();
         }
       }
 
@@ -748,21 +761,24 @@
   return false;
 }
 
+// TODO(crbug.com/900241): Remove this function and let the caller use
+// CompositingReason::kDirectReasonForEffectProperty directly.
 static CompositingReasons CompositingReasonsForEffectProperty() {
-  // TODO(crbug.com/900241): See the comment in compositing_reasons.h about
-  // the bug for the reason of this.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    return CompositingReason::kDirectReasonsForEffectProperty;
-  }
-  // We also need to create effect node if the transform node is created for
-  // will-change to avoid raster invalidation (caused by otherwise a created/
-  // deleted effect node) when we start/stop a transform animation.
-  // https://crbug.com/942681
+  CompositingReasons reasons =
+      CompositingReason::kDirectReasonsForEffectProperty;
   if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
-    return CompositingReason::kDirectReasonsForEffectProperty |
-           CompositingReason::kWillChangeCompositingHint;
+    // TODO(crbug.com/900241): Check for nodes for each KeyframeModel target
+    // property instead of creating all nodes and only create a transform/
+    // effect/filter node if needed.
+    reasons |= CompositingReason::kComboActiveAnimation;
+    // We also need to create effect node if the transform node is created for
+    // will-change:transform to avoid raster invalidation (caused by otherwise a
+    // created/deleted effect node) when we start/stop a transform animation.
+    // https://crbug.com/942681
+    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+      reasons |= CompositingReason::kWillChangeTransform;
   }
-  return CompositingReason::kActiveOpacityAnimation;
+  return reasons;
 }
 
 static bool NeedsEffect(const LayoutObject& object,
@@ -827,7 +843,7 @@
   if (!style.BackdropFilter().IsEmpty())
     return true;
 
-  if (style.Opacity() != 1.0f || style.HasWillChangeOpacityHint())
+  if (style.Opacity() != 1.0f)
     return true;
 
   if (direct_compositing_reasons & CompositingReasonsForEffectProperty())
@@ -969,12 +985,9 @@
         //
         // Currently, we use the existence of this id to check if effect nodes
         // have been created for animations on this element.
-        // TODO(flackr): Check for nodes for each KeyframeModel target
-        // property instead of creating all nodes and create each type of
-        // node as needed, https://crbug.com/900241
         state.direct_compositing_reasons =
             full_context_.direct_compositing_reasons &
-            CompositingReason::kDirectReasonsForEffectProperty;
+            CompositingReasonsForEffectProperty();
         if (state.direct_compositing_reasons) {
           state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
               object_.UniqueId(), CompositorElementIdNamespace::kPrimaryEffect);
@@ -1081,21 +1094,26 @@
   return page->GetLinkHighlights().NeedsHighlightEffect(object);
 }
 
+// TODO(crbug.com/900241): Remove this function and let the caller use
+// CompositingReason::kDirectReasonForFilterProperty directly.
 static CompositingReasons CompositingReasonsForFilterProperty() {
-  // TODO(crbug.com/900241): See the comment in compositing_reasons.h about
-  // the bug for the reason of this.
-  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    return CompositingReason::kDirectReasonsForFilterProperty;
-  }
-  // We also need to create filter node if the transform node is created for
-  // will-change to avoid raster invalidation (caused by otherwise a created/
-  // deleted filter node) when we start/stop a transform/opacity animation.
-  // https://crbug.com/942681
+  CompositingReasons reasons =
+      CompositingReason::kDirectReasonsForFilterProperty;
   if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
-    return CompositingReason::kDirectReasonsForFilterProperty |
-           CompositingReason::kWillChangeCompositingHint;
+    // TODO(crbug.com/900241): Check for nodes for each KeyframeModel target
+    // property instead of creating all nodes and only create a transform/
+    // effect/filter node if needed.
+    reasons |= CompositingReason::kComboActiveAnimation;
+    // We also need to create filter node if the transform/effect node is
+    // created for will-change:transform/opacity to avoid raster invalidation
+    // (caused by otherwise a created/deleted filter node) when we start/stop a
+    // transform/opacity animation. https://crbug.com/942681
+    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+      reasons |= CompositingReason::kWillChangeTransform |
+                 CompositingReason::kWillChangeOpacity;
+    }
   }
-  return CompositingReason::kActiveFilterAnimation;
+  return reasons;
 }
 
 static bool NeedsFilter(const LayoutObject& object,
@@ -1170,7 +1188,7 @@
         // current.
         state.direct_compositing_reasons =
             full_context_.direct_compositing_reasons &
-            CompositingReason::kDirectReasonsForFilterProperty;
+            CompositingReasonsForFilterProperty();
         state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
             object_.UniqueId(), CompositorElementIdNamespace::kEffectFilter);
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 4cbfce4..2715a9be 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -810,6 +810,16 @@
                           GetDocument().View()->GetLayoutView());
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, NoEffectAndFilterForNonStackingContext) {
+  SetBodyInnerHTML(R"HTML(
+    <div id="target" style="will-change: right; backface-visibility: hidden">
+    </div>
+  )HTML");
+  EXPECT_NE(nullptr, PaintPropertiesForElement("target")->Transform());
+  EXPECT_EQ(nullptr, PaintPropertiesForElement("target")->Effect());
+  EXPECT_EQ(nullptr, PaintPropertiesForElement("target")->Filter());
+}
+
 TEST_P(PaintPropertyTreeBuilderTest, RelativePositionInline) {
   LoadTestData("relative-position-inline.html");
 
@@ -4711,7 +4721,7 @@
         transform-origin: 75% 75% 0;
       }
       #willChange {
-        will-change: opacity;
+        will-change: transform;
         transform-origin: 75% 75% 0;
       }
     </style>
@@ -4750,7 +4760,7 @@
         transform-origin: 50% 50% 0;
       }
       #willChange {
-        will-change: opacity;
+        will-change: transform;
         transform-origin: 50% 50% 0;
       }
     </style>
@@ -5526,13 +5536,11 @@
   opacity_element->setAttribute(html_names::kStyleAttr, "opacity: 0.5");
   GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
 
-  if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
-    // TODO(crbug.com/900241): In BlinkGenPropertyTrees/CompositeAfterPaint we
-    // create effect and filter nodes when the transform node needs compositing,
-    // for crbug.com/942681.
-    // TODO(crbug.com/943671): CompositeAfterPaint also does this but the layer
-    // is marked needing repaint because of paint invalidation. Figure out if
-    // this is an over invalidation.
+  if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
+      !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    // TODO(crbug.com/900241): In BlinkGenPropertyTrees (but not
+    // CompoisteAfterPaint) we create effect and filter nodes when the transform
+    // node needs compositing for will-change:transform, for crbug.com/942681.
     EXPECT_FALSE(ToLayoutBoxModelObject(target)->Layer()->NeedsRepaint());
   } else {
     // All paint chunks contained by the new opacity effect node need to be
@@ -6426,9 +6434,10 @@
 
 TEST_P(PaintPropertyTreeBuilderTest, SimpleOpacityChangeDoesNotCausePacUpdate) {
   // This is a BGPT test only.
-  // TODO(vmpstr): For CAP, we don't seem to get a cc_effect, which we need to
-  // investigate.
-  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
+  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+      // TODO(vmpstr): For CompositeAfterPaint, we don't seem to get a
+      // cc_effect, which we need to investigate.
+      RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
     return;
 
   SetHtmlInnerHTML(R"HTML(
@@ -6488,7 +6497,9 @@
 
 TEST_P(PaintPropertyTreeBuilderTest, SimpleScrollChangeDoesNotCausePacUpdate) {
   // This is a BGPT test only.
-  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
+  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+      // TODO(vmpstr): Make this test pass for CompositeAfterPaint.
+      RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
     return;
 
   SetHtmlInnerHTML(R"HTML(
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index a289b3c..6cc767df 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -281,6 +281,7 @@
          context.paint_invalidator_context.NeedsVisualRectUpdate(object);
 }
 
+#if DCHECK_IS_ON()
 void PrePaintTreeWalk::CheckTreeBuilderContextState(
     const LayoutObject& object,
     const PrePaintTreeWalkContext& parent_context) {
@@ -290,20 +291,21 @@
     return;
   }
 
-  CHECK(!object.NeedsPaintPropertyUpdate());
-  CHECK(!object.DescendantNeedsPaintPropertyUpdate());
-  CHECK(!object.DescendantNeedsPaintOffsetAndVisualRectUpdate());
+  DCHECK(!object.NeedsPaintPropertyUpdate());
+  DCHECK(!object.DescendantNeedsPaintPropertyUpdate());
+  DCHECK(!object.DescendantNeedsPaintOffsetAndVisualRectUpdate());
   if (parent_context.paint_invalidator_context.NeedsVisualRectUpdate(object)) {
     // Note that if paint_invalidator_context's NeedsVisualRectUpdate(object) is
-    // true, we definitely want to CHECK. However, we would also like to know
+    // true, we definitely want to DCHECK. However, we would also like to know
     // the value of object.NeedsPaintOffsetAndVisualRectUpdate(), hence one of
-    // the two CHECKs below will definitely trigger, and depending on which one
+    // the two DCHECKs below will definitely trigger, and depending on which one
     // does we will know the value.
-    CHECK(object.NeedsPaintOffsetAndVisualRectUpdate());
-    CHECK(!object.NeedsPaintOffsetAndVisualRectUpdate());
+    DCHECK(object.NeedsPaintOffsetAndVisualRectUpdate());
+    DCHECK(!object.NeedsPaintOffsetAndVisualRectUpdate());
   }
-  CHECK(false) << "Unknown reason.";
+  NOTREACHED();
 }
+#endif
 
 void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
                                     PrePaintTreeWalkContext& context) {
@@ -424,8 +426,9 @@
     return;
   }
 
-  // The following is for debugging crbug.com/816810.
+#if DCHECK_IS_ON()
   CheckTreeBuilderContextState(object, parent_context());
+#endif
 
   // Early out from the tree walk if possible.
   if (!needs_tree_builder_context_update && !ObjectRequiresPrePaint(object) &&
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h
index f41d965..74a0a35 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.h
@@ -86,8 +86,10 @@
   static bool ContextRequiresTreeBuilderContext(const PrePaintTreeWalkContext&,
                                                 const LayoutObject&);
 
+#if DCHECK_IS_ON()
   void CheckTreeBuilderContextState(const LayoutObject&,
                                     const PrePaintTreeWalkContext&);
+#endif
 
   const PrePaintTreeWalkContext& ContextAt(wtf_size_t index) {
     DCHECK_LT(index, context_storage_.size());
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
index 82ecdfc3..c21a934 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
@@ -97,8 +97,7 @@
     int argc = 0,
     v8::Local<v8::Value> argv[] = nullptr) {
   String js = String("({start: ") + function_definition + "})" + '\0';
-  ScriptValue underlying_value =
-      EvalWithPrintingError(scope, WTF::StringUTF8Adaptor(js).Data());
+  ScriptValue underlying_value = EvalWithPrintingError(scope, js.Utf8().data());
   auto underlying_object = underlying_value.V8Value().As<v8::Object>();
   auto* algo = CreateAlgorithmFromUnderlyingMethod(
       scope->GetScriptState(), underlying_object, "start",
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index c10b31c..e5f0efa 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -885,10 +885,10 @@
           other.HasCurrentBackdropFilterAnimation() ||
       SubtreeWillChangeContents() != other.SubtreeWillChangeContents() ||
       BackfaceVisibility() != other.BackfaceVisibility() ||
-      HasWillChangeCompositingHint() != other.HasWillChangeCompositingHint() ||
       UsedTransformStyle3D() != other.UsedTransformStyle3D() ||
       ContainsPaint() != other.ContainsPaint() ||
-      IsOverflowVisible() != other.IsOverflowVisible()) {
+      IsOverflowVisible() != other.IsOverflowVisible() ||
+      WillChangeProperties() != other.WillChangeProperties()) {
     diff.SetCompositingReasonsChanged();
   }
 }
@@ -999,46 +999,51 @@
   SetContentInternal(content_data);
 }
 
-bool ComputedStyle::HasWillChangeCompositingHint() const {
-  for (const auto& property : WillChangeProperties()) {
-    switch (property) {
-      case CSSPropertyID::kOpacity:
-      case CSSPropertyID::kTransform:
-      case CSSPropertyID::kAliasWebkitTransform:
-      case CSSPropertyID::kTranslate:
-      case CSSPropertyID::kScale:
-      case CSSPropertyID::kRotate:
-      case CSSPropertyID::kTop:
-      case CSSPropertyID::kLeft:
-      case CSSPropertyID::kBottom:
-      case CSSPropertyID::kRight:
-        return true;
-      default:
-        break;
-    }
+static bool IsWillChangeTransformHintProperty(CSSPropertyID property) {
+  switch (property) {
+    case CSSPropertyID::kTransform:
+    case CSSPropertyID::kAliasWebkitTransform:
+    case CSSPropertyID::kPerspective:
+    case CSSPropertyID::kTranslate:
+    case CSSPropertyID::kScale:
+    case CSSPropertyID::kRotate:
+    case CSSPropertyID::kOffsetPath:
+    case CSSPropertyID::kOffsetPosition:
+      return true;
+    default:
+      break;
   }
   return false;
 }
 
-bool ComputedStyle::HasWillChangeTransformHint() const {
-  for (const auto& property : WillChangeProperties()) {
-    switch (property) {
-      case CSSPropertyID::kTransform:
-      case CSSPropertyID::kAliasWebkitTransform:
-      case CSSPropertyID::kPerspective:
-      case CSSPropertyID::kTranslate:
-      case CSSPropertyID::kScale:
-      case CSSPropertyID::kRotate:
-      case CSSPropertyID::kOffsetPath:
-      case CSSPropertyID::kOffsetPosition:
-        return true;
-      default:
-        break;
-    }
+static bool IsWillChangeCompositingHintProperty(CSSPropertyID property) {
+  if (IsWillChangeTransformHintProperty(property))
+    return true;
+  switch (property) {
+    case CSSPropertyID::kOpacity:
+    case CSSPropertyID::kTop:
+    case CSSPropertyID::kLeft:
+    case CSSPropertyID::kBottom:
+    case CSSPropertyID::kRight:
+      return true;
+    default:
+      break;
   }
   return false;
 }
 
+bool ComputedStyle::HasWillChangeCompositingHint() const {
+  const auto& properties = WillChangeProperties();
+  return std::any_of(properties.begin(), properties.end(),
+                     IsWillChangeCompositingHintProperty);
+}
+
+bool ComputedStyle::HasWillChangeTransformHint() const {
+  const auto& properties = WillChangeProperties();
+  return std::any_of(properties.begin(), properties.end(),
+                     IsWillChangeTransformHintProperty);
+}
+
 bool ComputedStyle::RequireTransformOrigin(
     ApplyTransformOrigin apply_origin,
     ApplyMotionPath apply_motion_path) const {
diff --git a/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc b/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
index f514d9efe..f704944 100644
--- a/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
+++ b/third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.cc
@@ -30,6 +30,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_alpha.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -113,18 +114,19 @@
   FilterEffect* source_graphic_ref = source_graphic;
   builtin_effects_.insert(FilterInputKeywords::GetSourceGraphic(),
                           source_graphic_ref);
-  builtin_effects_.insert(FilterInputKeywords::SourceAlpha(),
-                          SourceAlpha::Create(source_graphic_ref));
+  builtin_effects_.insert(
+      FilterInputKeywords::SourceAlpha(),
+      MakeGarbageCollected<SourceAlpha>(source_graphic_ref));
   if (fill_flags) {
     builtin_effects_.insert(FilterInputKeywords::FillPaint(),
-                            PaintFilterEffect::Create(
+                            MakeGarbageCollected<PaintFilterEffect>(
                                 source_graphic_ref->GetFilter(), *fill_flags));
   }
   if (stroke_flags) {
     builtin_effects_.insert(
         FilterInputKeywords::StrokePaint(),
-        PaintFilterEffect::Create(source_graphic_ref->GetFilter(),
-                                  *stroke_flags));
+        MakeGarbageCollected<PaintFilterEffect>(source_graphic_ref->GetFilter(),
+                                                *stroke_flags));
   }
   AddBuiltinEffects();
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_blend_element.cc b/third_party/blink/renderer/core/svg/svg_fe_blend_element.cc
index fba813e9..6560309 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_blend_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_blend_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_blend.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -138,8 +139,8 @@
   DCHECK(input1);
   DCHECK(input2);
 
-  FilterEffect* effect =
-      FEBlend::Create(filter, ToBlendMode(mode_->CurrentValue()->EnumValue()));
+  auto* effect = MakeGarbageCollected<FEBlend>(
+      filter, ToBlendMode(mode_->CurrentValue()->EnumValue()));
   FilterEffectVector& input_effects = effect->InputEffects();
   input_effects.ReserveCapacity(2);
   input_effects.push_back(input1);
diff --git a/third_party/blink/renderer/core/svg/svg_fe_color_matrix_element.cc b/third_party/blink/renderer/core/svg/svg_fe_color_matrix_element.cc
index 07d47ab0..2e715d6 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_color_matrix_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_color_matrix_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -100,8 +101,8 @@
 
   ColorMatrixType filter_type = type_->CurrentValue()->EnumValue();
   Vector<float> filter_values = values_->CurrentValue()->ToFloatVector();
-  FilterEffect* effect =
-      FEColorMatrix::Create(filter, filter_type, filter_values);
+  auto* effect =
+      MakeGarbageCollected<FEColorMatrix>(filter, filter_type, filter_values);
   effect->InputEffects().push_back(input1);
   return effect;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_component_transfer_element.cc b/third_party/blink/renderer/core/svg/svg_fe_component_transfer_element.cc
index 6255f1e..ab0bec7 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_component_transfer_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_component_transfer_element.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/renderer/core/svg/svg_fe_func_r_element.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -81,8 +82,8 @@
       alpha = func_a->TransferFunction();
   }
 
-  FilterEffect* effect =
-      FEComponentTransfer::Create(filter, red, green, blue, alpha);
+  auto* effect = MakeGarbageCollected<FEComponentTransfer>(filter, red, green,
+                                                           blue, alpha);
   effect->InputEffects().push_back(input1);
   return effect;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_composite_element.cc b/third_party/blink/renderer/core/svg/svg_fe_composite_element.cc
index 68c63e647..ac7e5e6 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_composite_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_composite_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -124,7 +125,7 @@
   DCHECK(input1);
   DCHECK(input2);
 
-  FilterEffect* effect = FEComposite::Create(
+  auto* effect = MakeGarbageCollected<FEComposite>(
       filter, svg_operator_->CurrentValue()->EnumValue(),
       k1_->CurrentValue()->Value(), k2_->CurrentValue()->Value(),
       k3_->CurrentValue()->Value(), k4_->CurrentValue()->Value());
diff --git a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
index e699329..3b4cc6c 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/geometry/int_point.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -206,7 +207,7 @@
       AtomicString(in1_->CurrentValue()->Value()));
   DCHECK(input1);
 
-  FilterEffect* effect = FEConvolveMatrix::Create(
+  auto* effect = MakeGarbageCollected<FEConvolveMatrix>(
       filter, MatrixOrder(), ComputeDivisor(), bias_->CurrentValue()->Value(),
       TargetPoint(), edge_mode_->CurrentValue()->EnumValue(),
       preserve_alpha_->CurrentValue()->Value(),
diff --git a/third_party/blink/renderer/core/svg/svg_fe_diffuse_lighting_element.cc b/third_party/blink/renderer/core/svg/svg_fe_diffuse_lighting_element.cc
index 85e8226..427b5f5af 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_diffuse_lighting_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_diffuse_lighting_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -154,7 +155,7 @@
   scoped_refptr<LightSource> light_source =
       light_node ? light_node->GetLightSource(filter) : nullptr;
 
-  FilterEffect* effect = FEDiffuseLighting::Create(
+  auto* effect = MakeGarbageCollected<FEDiffuseLighting>(
       filter, color, surface_scale_->CurrentValue()->Value(),
       diffuse_constant_->CurrentValue()->Value(), std::move(light_source));
   effect->InputEffects().push_back(input1);
diff --git a/third_party/blink/renderer/core/svg/svg_fe_displacement_map_element.cc b/third_party/blink/renderer/core/svg/svg_fe_displacement_map_element.cc
index 9ae2ddd..50fcd40 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_displacement_map_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_displacement_map_element.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -113,7 +114,7 @@
   DCHECK(input1);
   DCHECK(input2);
 
-  FilterEffect* effect = FEDisplacementMap::Create(
+  auto* effect = MakeGarbageCollected<FEDisplacementMap>(
       filter, x_channel_selector_->CurrentValue()->EnumValue(),
       y_channel_selector_->CurrentValue()->EnumValue(),
       scale_->CurrentValue()->Value());
diff --git a/third_party/blink/renderer/core/svg/svg_fe_drop_shadow_element.cc b/third_party/blink/renderer/core/svg/svg_fe_drop_shadow_element.cc
index bc187c9..11481e0 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_drop_shadow_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_drop_shadow_element.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -108,7 +109,7 @@
   // Clamp std.dev. to non-negative. (See SVGFEGaussianBlurElement::build)
   float std_dev_x = std::max(0.0f, stdDeviationX()->CurrentValue()->Value());
   float std_dev_y = std::max(0.0f, stdDeviationY()->CurrentValue()->Value());
-  FilterEffect* effect = FEDropShadow::Create(
+  auto* effect = MakeGarbageCollected<FEDropShadow>(
       filter, std_dev_x, std_dev_y, dx_->CurrentValue()->Value(),
       dy_->CurrentValue()->Value(), color, opacity);
   effect->InputEffects().push_back(input1);
diff --git a/third_party/blink/renderer/core/svg/svg_fe_flood_element.cc b/third_party/blink/renderer/core/svg/svg_fe_flood_element.cc
index 3ea9634..d0ae516 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_flood_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_flood_element.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/style/svg_computed_style.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_flood.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -58,7 +59,7 @@
   Color color = style->VisitedDependentColor(GetCSSPropertyFloodColor());
   float opacity = style->SvgStyle().FloodOpacity();
 
-  return FEFlood::Create(filter, color, opacity);
+  return MakeGarbageCollected<FEFlood>(filter, color, opacity);
 }
 
 bool SVGFEFloodElement::TaintsOrigin() const {
diff --git a/third_party/blink/renderer/core/svg/svg_fe_gaussian_blur_element.cc b/third_party/blink/renderer/core/svg/svg_fe_gaussian_blur_element.cc
index 6bee7ec..051c741 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_gaussian_blur_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_gaussian_blur_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -77,7 +78,8 @@
   // => Clamp to non-negative.
   float std_dev_x = std::max(0.0f, stdDeviationX()->CurrentValue()->Value());
   float std_dev_y = std::max(0.0f, stdDeviationY()->CurrentValue()->Value());
-  FilterEffect* effect = FEGaussianBlur::Create(filter, std_dev_x, std_dev_y);
+  auto* effect =
+      MakeGarbageCollected<FEGaussianBlur>(filter, std_dev_x, std_dev_y);
   effect->InputEffects().push_back(input1);
   return effect;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_merge_element.cc b/third_party/blink/renderer/core/svg/svg_fe_merge_element.cc
index 8c1565d..cebb72f 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_merge_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_merge_element.cc
@@ -35,7 +35,7 @@
 
 FilterEffect* SVGFEMergeElement::Build(SVGFilterBuilder* filter_builder,
                                        Filter* filter) {
-  FilterEffect* effect = FEMerge::Create(filter);
+  FilterEffect* effect = MakeGarbageCollected<FEMerge>(filter);
   FilterEffectVector& merge_inputs = effect->InputEffects();
   for (SVGFEMergeNodeElement& merge_node :
        Traversal<SVGFEMergeNodeElement>::ChildrenOf(*this)) {
diff --git a/third_party/blink/renderer/core/svg/svg_fe_morphology_element.cc b/third_party/blink/renderer/core/svg/svg_fe_morphology_element.cc
index 15e75d0..f1989a3 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_morphology_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_morphology_element.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -112,7 +113,7 @@
   // (This is handled by FEMorphology)
   float x_radius = radiusX()->CurrentValue()->Value();
   float y_radius = radiusY()->CurrentValue()->Value();
-  FilterEffect* effect = FEMorphology::Create(
+  auto* effect = MakeGarbageCollected<FEMorphology>(
       filter, svg_operator_->CurrentValue()->EnumValue(), x_radius, y_radius);
   effect->InputEffects().push_back(input1);
   return effect;
diff --git a/third_party/blink/renderer/core/svg/svg_fe_offset_element.cc b/third_party/blink/renderer/core/svg/svg_fe_offset_element.cc
index 349fc64..bca8ea0 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_offset_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_offset_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_offset.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -62,8 +63,8 @@
       AtomicString(in1_->CurrentValue()->Value()));
   DCHECK(input1);
 
-  FilterEffect* effect = FEOffset::Create(filter, dx_->CurrentValue()->Value(),
-                                          dy_->CurrentValue()->Value());
+  auto* effect = MakeGarbageCollected<FEOffset>(
+      filter, dx_->CurrentValue()->Value(), dy_->CurrentValue()->Value());
   effect->InputEffects().push_back(input1);
   return effect;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_specular_lighting_element.cc b/third_party/blink/renderer/core/svg/svg_fe_specular_lighting_element.cc
index e077742..ebc2b3b 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_specular_lighting_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_specular_lighting_element.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -164,7 +165,7 @@
   scoped_refptr<LightSource> light_source =
       light_node ? light_node->GetLightSource(filter) : nullptr;
 
-  FilterEffect* effect = FESpecularLighting::Create(
+  auto* effect = MakeGarbageCollected<FESpecularLighting>(
       filter, color, surface_scale_->CurrentValue()->Value(),
       specular_constant_->CurrentValue()->Value(),
       specular_exponent_->CurrentValue()->Value(), std::move(light_source));
diff --git a/third_party/blink/renderer/core/svg/svg_fe_tile_element.cc b/third_party/blink/renderer/core/svg/svg_fe_tile_element.cc
index aa39c46..e708a27 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_tile_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_tile_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 #include "third_party/blink/renderer/platform/graphics/filters/fe_tile.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -55,7 +56,7 @@
       AtomicString(in1_->CurrentValue()->Value()));
   DCHECK(input1);
 
-  FilterEffect* effect = FETile::Create(filter);
+  auto* effect = MakeGarbageCollected<FETile>(filter);
   effect->InputEffects().push_back(input1);
   return effect;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc b/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
index 091ce66..7f328bfc 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
@@ -22,6 +22,7 @@
 
 #include "third_party/blink/renderer/core/svg/svg_enumeration_map.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -121,7 +122,7 @@
 }
 
 FilterEffect* SVGFETurbulenceElement::Build(SVGFilterBuilder*, Filter* filter) {
-  return FETurbulence::Create(
+  return MakeGarbageCollected<FETurbulence>(
       filter, type_->CurrentValue()->EnumValue(),
       baseFrequencyX()->CurrentValue()->Value(),
       baseFrequencyY()->CurrentValue()->Value(),
diff --git a/third_party/blink/renderer/devtools/front_end/Runtime.js b/third_party/blink/renderer/devtools/front_end/Runtime.js
index 46698e7..28cb0dc 100644
--- a/third_party/blink/renderer/devtools/front_end/Runtime.js
+++ b/third_party/blink/renderer/devtools/front_end/Runtime.js
@@ -549,8 +549,9 @@
   }
 
   /**
-   * @param {!Function} constructorFunction
-   * @return {!Object}
+   * @param {function(new:T)} constructorFunction
+   * @return {!T}
+   * @template T
    */
   sharedInstance(constructorFunction) {
     if (Runtime._instanceSymbol in constructorFunction &&
diff --git a/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js b/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
index 8e88d14..5611239 100644
--- a/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
+++ b/third_party/blink/renderer/devtools/front_end/perf_ui/LineLevelProfile.js
@@ -60,15 +60,6 @@
     }
     this._helper.scheduleUpdate();
   }
-
-  /**
-   * @return {!PerfUI.LineLevelProfile.Performance}
-   */
-  static instance() {
-    if (!PerfUI.LineLevelProfile.Performance._instance)
-      PerfUI.LineLevelProfile.Performance._instance = new PerfUI.LineLevelProfile.Performance();
-    return PerfUI.LineLevelProfile.Performance._instance;
-  }
 };
 
 PerfUI.LineLevelProfile.Memory = class {
@@ -103,15 +94,6 @@
       helper.addLineData(target, script, line, node.selfSize);
     }
   }
-
-  /**
-   * @return {!PerfUI.LineLevelProfile.Memory}
-   */
-  static instance() {
-    if (!PerfUI.LineLevelProfile.Memory._instance)
-      PerfUI.LineLevelProfile.Memory._instance = new PerfUI.LineLevelProfile.Memory();
-    return PerfUI.LineLevelProfile.Memory._instance;
-  }
 };
 
 PerfUI.LineLevelProfile._Helper = class {
diff --git a/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js b/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js
index e8b74a7..ed94316d 100644
--- a/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js
+++ b/third_party/blink/renderer/devtools/front_end/perf_ui/LiveHeapProfile.js
@@ -50,7 +50,7 @@
   async onUpdateProfiles() {
     const models = SDK.targetManager.models(SDK.HeapProfilerModel);
     const profiles = await Promise.all(models.map(model => model.getSamplingProfile()));
-    const lineLevelProfile = PerfUI.LineLevelProfile.Memory.instance();
+    const lineLevelProfile = self.runtime.sharedInstance(PerfUI.LineLevelProfile.Memory);
     lineLevelProfile.reset();
     for (let i = 0; i < profiles.length; ++i) {
       if (profiles[i])
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js b/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
index 8157ccd..5ed69064 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
@@ -46,7 +46,7 @@
    */
   wasShown() {
     super.wasShown();
-    const lineLevelProfile = PerfUI.LineLevelProfile.Performance.instance();
+    const lineLevelProfile = self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance);
     lineLevelProfile.reset();
     lineLevelProfile.appendCPUProfile(this._profileHeader.profileModel());
   }
diff --git a/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js b/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
index cfaf9de..c9b1471 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
+++ b/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
@@ -40,9 +40,27 @@
     this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement);
     this._setupToolbar();
 
+    /**
+     * This will contain the DataGrid for displaying events, and a panel at the bottom for showing
+     * extra metadata related to the selected event.
+     * @const {!UI.SplitWidget}
+     */
+    this._splitWidget = new UI.SplitWidget(/* isVertical= */ false, /* secondIsSidebar= */ true);
+    this._splitWidget.show(this.contentElement);
+
     /** @const {!DataGrid.DataGrid} */
     this._dataGrid = this._createDataGrid();
-    this._dataGrid.asWidget().show(this.contentElement);
+
+    /** @const {!UI.VBox} */
+    this._previewPanel = new UI.VBox();
+
+    /** @type {?UI.Widget} */
+    this._preview = null;
+
+    this._splitWidget.setMainWidget(this._dataGrid.asWidget());
+    this._splitWidget.setSidebarWidget(this._previewPanel);
+
+    this._showPreview(null);
   }
 
   /**
@@ -55,28 +73,36 @@
     this._recordButton.setToggleWithRedColor(true);
     this._toolbar.appendToolbarItem(this._recordButton);
 
-    const refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh');
-    refreshButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._refreshView());
-    this._toolbar.appendToolbarItem(refreshButton);
-
     const clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear');
-    clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clearView());
+    clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clearEvents());
     this._toolbar.appendToolbarItem(clearButton);
 
     this._toolbar.appendSeparator();
 
-    const deleteButton = new UI.ToolbarButton(Common.UIString('Delete'), 'largeicon-trash-bin');
-    deleteButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._deleteEvents());
-    this._toolbar.appendToolbarItem(deleteButton);
-
-    this._toolbar.appendSeparator();
-
     this._originCheckbox =
         new UI.ToolbarCheckbox(Common.UIString('Show events from other domains'), undefined, () => this._refreshView());
     this._toolbar.appendToolbarItem(this._originCheckbox);
   }
 
   /**
+   * Displays all available events in the grid.
+   */
+  _refreshView() {
+    this._clearView();
+    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
+    for (const event of events)
+      this._addEvent(event);
+  }
+
+  /**
+   * Clears the grid and panel.
+   */
+  _clearView() {
+    this._dataGrid.rootNode().removeChildren();
+    this._showPreview(null);
+  }
+
+  /**
    * Called when the `Toggle Record` button is clicked.
    */
   _toggleRecording() {
@@ -84,26 +110,9 @@
   }
 
   /**
-   * Called when the `Refresh` button is clicked.
-   */
-  _refreshView() {
-    this._clearView();
-    const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
-    for (const event of events)
-      this._addEvent(event);
-  }
-
-  /**
    * Called when the `Clear` button is clicked.
    */
-  _clearView() {
-    this._dataGrid.rootNode().removeChildren();
-  }
-
-  /**
-   * Called when the `Delete` button is clicked.
-   */
-  _deleteEvents() {
+  _clearEvents() {
     this._model.clearEvents(this._serviceName);
     this._clearView();
   }
@@ -158,6 +167,11 @@
     ]);
     const dataGrid = new DataGrid.DataGrid(columns);
     dataGrid.setStriped(true);
+
+    dataGrid.addEventListener(
+        DataGrid.DataGrid.Events.SelectedNode,
+        event => this._showPreview(/** @type {!Resources.BackgroundServiceView.EventDataNode} */ (event.data)));
+
     return dataGrid;
   }
 
@@ -205,6 +219,21 @@
 
     return this._securityOriginManager.securityOrigins().includes(origin);
   }
+
+  /**
+   * @param {?Resources.BackgroundServiceView.EventDataNode} dataNode
+   */
+  _showPreview(dataNode) {
+    if (this._preview)
+      this._preview.detach();
+
+    if (dataNode)
+      this._preview = dataNode.createPreview();
+    else
+      this._preview = new UI.EmptyWidget(ls`Select a value to preview`);
+
+    this._preview.show(this._previewPanel.contentElement);
+  }
 };
 
 /**
@@ -229,60 +258,15 @@
 
     /** @const {!Array<!Protocol.BackgroundService.EventMetadata>} */
     this._eventMetadata = eventMetadata;
-
-    /** @type {?UI.PopoverHelper} */
-    this._popoverHelper = null;
   }
 
   /**
-   * @override
-   * @return {!Element}
+   * @return {!UI.SearchableView}
    */
-  createElement() {
-    const element = super.createElement();
-
-    this._popoverHelper = new UI.PopoverHelper(element, event => this._createPopover(event));
-    this._popoverHelper.setHasPadding(true);
-    this._popoverHelper.setTimeout(300, 300);
-
-    return element;
-  }
-
-  /**
-   * @param {!Event} event
-   * @return {?UI.PopoverRequest}
-   */
-  _createPopover(event) {
-    if (event.type !== 'mousedown')
-      return null;
-
-    // Create popover container.
-    const container = createElementWithClass('div', 'background-service-popover-container');
-    UI.appendStyle(container, 'resources/backgroundServiceView.css');
-
-    if (!this._eventMetadata.length) {
-      const entryDiv = createElementWithClass('div', 'background-service-metadata-entry');
-      entryDiv.textContent = 'There is no metadata for this event';
-      container.appendChild(entryDiv);
-    }
-
-    for (const entry of this._eventMetadata) {
-      const entryDiv = createElementWithClass('div', 'background-service-metadata-entry');
-      const key = createElementWithClass('label', 'background-service-metadata-key');
-      key.textContent = `${entry.key}: `;
-      const value = createElementWithClass('label', 'background-service-metadata-value');
-      value.textContent = entry.value;
-      entryDiv.appendChild(key);
-      entryDiv.appendChild(value);
-      container.appendChild(entryDiv);
-    }
-
-    return {
-      box: event.target.boxInWindow(),
-      show: popover => {
-        popover.contentElement.appendChild(container);
-        return Promise.resolve(true);
-      },
-    };
+  createPreview() {
+    const metadata = {};
+    for (const entry of this._eventMetadata)
+      metadata[entry.key] = entry.value;
+    return SourceFrame.JSONView.createViewSync(metadata);
   }
 };
diff --git a/third_party/blink/renderer/devtools/front_end/resources/backgroundServiceView.css b/third_party/blink/renderer/devtools/front_end/resources/backgroundServiceView.css
index 356e4fc..c276c7a6 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/backgroundServiceView.css
+++ b/third_party/blink/renderer/devtools/front_end/resources/backgroundServiceView.css
@@ -5,20 +5,5 @@
 
 .data-grid {
     flex: auto;
-}
-
-.background-service-popover-container {
-    padding: 12px 16px 0px 16px;
-}
-
-.background-service-metadata-entry {
-    margin-bottom: 12px;
-}
-
-.background-service-metadata-key {
-    font-weight: bold;
-}
-
-.background-service-metadata-value {
-    font-family: monospace;
+    border: none;
 }
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/HeapProfilerModel.js b/third_party/blink/renderer/devtools/front_end/sdk/HeapProfilerModel.js
index bd70c6a..6919fa7 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/HeapProfilerModel.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/HeapProfilerModel.js
@@ -12,6 +12,7 @@
     this._heapProfilerAgent = target.heapProfilerAgent();
     this._memoryAgent = target.memoryAgent();
     this._runtimeModel = /** @type {!SDK.RuntimeModel} */ (target.model(SDK.RuntimeModel));
+    this._samplingProfilerDepth = 0;
   }
 
   /**
@@ -40,6 +41,8 @@
    * @param {number=} samplingRateInBytes
    */
   startSampling(samplingRateInBytes) {
+    if (this._samplingProfilerDepth++)
+      return;
     const defaultSamplingIntervalInBytes = 16384;
     this._heapProfilerAgent.startSampling(samplingRateInBytes || defaultSamplingIntervalInBytes);
   }
@@ -48,6 +51,10 @@
    * @return {!Promise<?Protocol.HeapProfiler.SamplingHeapProfile>}
    */
   stopSampling() {
+    if (!this._samplingProfilerDepth)
+      throw new Error('Sampling profiler is not running.');
+    if (--this._samplingProfilerDepth)
+      return this.getSamplingProfile();
     return this._heapProfilerAgent.stopSampling();
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelinePanel.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelinePanel.js
index 46e4019..a944356 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelinePanel.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelinePanel.js
@@ -549,7 +549,7 @@
   }
 
   _reset() {
-    PerfUI.LineLevelProfile.Performance.instance().reset();
+    self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance).reset();
     this._setModel(null);
   }
 
@@ -582,7 +582,7 @@
           Timeline.PerformanceModel.Events.WindowChanged, this._onModelWindowChanged, this);
       this._overviewPane.setBounds(
           model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime());
-      const lineLevelProfile = PerfUI.LineLevelProfile.Performance.instance();
+      const lineLevelProfile = self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance);
       lineLevelProfile.reset();
       for (const profile of model.timelineModel().cpuProfiles())
         lineLevelProfile.appendCPUProfile(profile);
diff --git a/third_party/blink/renderer/modules/media_controls/BUILD.gn b/third_party/blink/renderer/modules/media_controls/BUILD.gn
index d981bac..600a477 100644
--- a/third_party/blink/renderer/modules/media_controls/BUILD.gn
+++ b/third_party/blink/renderer/modules/media_controls/BUILD.gn
@@ -92,6 +92,8 @@
     "media_controls_resource_loader.h",
     "media_controls_rotate_to_fullscreen_delegate.cc",
     "media_controls_rotate_to_fullscreen_delegate.h",
+    "media_controls_shared_helper.cc",
+    "media_controls_shared_helper.h",
     "media_controls_text_track_manager.cc",
     "media_controls_text_track_manager.h",
   ]
@@ -111,6 +113,8 @@
       "touchless/elements/media_controls_touchless_play_button_element.h",
       "touchless/elements/media_controls_touchless_seek_button_element.cc",
       "touchless/elements/media_controls_touchless_seek_button_element.h",
+      "touchless/elements/media_controls_touchless_timeline_element.cc",
+      "touchless/elements/media_controls_touchless_timeline_element.h",
       "touchless/elements/media_controls_touchless_volume_button_element.cc",
       "touchless/elements/media_controls_touchless_volume_button_element.h",
       "touchless/media_controls_touchless_impl.cc",
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
index ce16173..e2c07153 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
@@ -29,11 +29,11 @@
 #include "third_party/blink/renderer/modules/media_controls/elements/media_control_elements_helper.h"
 #include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
 #include "third_party/blink/renderer/modules/media_controls/media_controls_resource_loader.h"
+#include "third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace {
 
-const double kCurrentTimeBufferedDelta = 1.0;
 const int kThumbRadius = 6;
 
 // Only respond to main button of primary pointer(s).
@@ -219,22 +219,14 @@
   if (MediaControlsImpl::IsModern())
     before_segment.width = current_position;
 
-  // Calculate the size of the after segment (i.e. what has been buffered).
-  for (unsigned i = 0; i < buffered_time_ranges->length(); ++i) {
-    float start = buffered_time_ranges->start(i, ASSERT_NO_EXCEPTION);
-    float end = buffered_time_ranges->end(i, ASSERT_NO_EXCEPTION);
-    // The delta is there to avoid corner cases when buffered
-    // ranges is out of sync with current time because of
-    // asynchronous media pipeline and current time caching in
-    // HTMLMediaElement.
-    // This is related to https://www.w3.org/Bugs/Public/show_bug.cgi?id=28125
-    // FIXME: Remove this workaround when WebMediaPlayer
-    // has an asynchronous pause interface.
-    if (std::isnan(start) || std::isnan(end) ||
-        start > current_time + kCurrentTimeBufferedDelta ||
-        end < current_time) {
-      continue;
-    }
+  base::Optional<unsigned> current_buffered_time_range =
+      MediaControlsSharedHelpers::GetCurrentBufferedTimeRange(MediaElement());
+
+  if (current_buffered_time_range) {
+    float start = buffered_time_ranges->start(
+        current_buffered_time_range.value(), ASSERT_NO_EXCEPTION);
+    float end = buffered_time_ranges->end(current_buffered_time_range.value(),
+                                          ASSERT_NO_EXCEPTION);
 
     double start_position = start / duration;
     double end_position = end / duration;
@@ -256,10 +248,6 @@
         before_segment.width = end_position - current_position;
       }
     }
-
-    // Break out of the loop since we've drawn the only buffered range
-    // we're going to draw.
-    break;
   }
 
   // Update the positions of the segments.
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.cc b/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.cc
new file mode 100644
index 0000000..9071fdf
--- /dev/null
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.cc
@@ -0,0 +1,55 @@
+// 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 "third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h"
+
+#include <cmath>
+#include "third_party/blink/renderer/core/html/media/html_media_element.h"
+#include "third_party/blink/renderer/core/html/time_ranges.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+
+namespace {
+
+const double kCurrentTimeBufferedDelta = 1.0;
+
+}
+
+namespace blink {
+
+base::Optional<unsigned>
+MediaControlsSharedHelpers::GetCurrentBufferedTimeRange(
+    HTMLMediaElement& media_element) {
+  double current_time = media_element.currentTime();
+  double duration = media_element.duration();
+  TimeRanges* buffered_time_ranges = media_element.buffered();
+
+  DCHECK(buffered_time_ranges);
+
+  if (std::isnan(duration) || std::isinf(duration) || !duration ||
+      std::isnan(current_time)) {
+    return base::nullopt;
+  }
+
+  // Calculate the size of the after segment (i.e. what has been buffered).
+  for (unsigned i = 0; i < buffered_time_ranges->length(); ++i) {
+    float start = buffered_time_ranges->start(i, ASSERT_NO_EXCEPTION);
+    float end = buffered_time_ranges->end(i, ASSERT_NO_EXCEPTION);
+    // The delta is there to avoid corner cases when buffered
+    // ranges is out of sync with current time because of
+    // asynchronous media pipeline and current time caching in
+    // HTMLMediaElement.
+    // This is related to https://www.w3.org/Bugs/Public/show_bug.cgi?id=28125
+    // FIXME: Remove this workaround when WebMediaPlayer
+    // has an asynchronous pause interface.
+    if (!std::isnan(start) && !std::isnan(end) &&
+        start <= current_time + kCurrentTimeBufferedDelta &&
+        end > current_time) {
+      return i;
+    }
+  }
+
+  return base::nullopt;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h b/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h
new file mode 100644
index 0000000..d8a3e97c
--- /dev/null
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h
@@ -0,0 +1,25 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_MEDIA_CONTROLS_SHARED_HELPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_MEDIA_CONTROLS_SHARED_HELPER_H_
+
+#include "base/optional.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+class HTMLMediaElement;
+
+class MediaControlsSharedHelpers final {
+  STATIC_ONLY(MediaControlsSharedHelpers);
+
+ public:
+  static base::Optional<unsigned> GetCurrentBufferedTimeRange(
+      HTMLMediaElement& media_element);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_MEDIA_CONTROLS_SHARED_HELPER_H_
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.cc b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.cc
index 22c5801d..e6142888 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.cc
@@ -15,6 +15,10 @@
   media_controls_->MediaEventListener().AddObserver(this);
 }
 
+HTMLMediaElement& MediaControlsTouchlessElement::MediaElement() const {
+  return media_controls_->MediaElement();
+}
+
 void MediaControlsTouchlessElement::Trace(blink::Visitor* visitor) {
   visitor->Trace(media_controls_);
 }
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.h b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.h
index e4509ac..0785f485 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.h
+++ b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.h
@@ -11,17 +11,21 @@
 
 namespace blink {
 
+class HTMLMediaElement;
 class MediaControlsTouchlessImpl;
 
 class MediaControlsTouchlessElement
     : public MediaControlsTouchlessMediaEventListenerObserver {
  public:
+  HTMLMediaElement& MediaElement() const;
+
   void Trace(blink::Visitor* visitor) override;
 
   // Non-touch media event listener observer implementation.
   void OnFocusIn() override {}
   void OnTimeUpdate() override {}
   void OnDurationChange() override {}
+  void OnLoadingProgress() override {}
   void OnPlay() override {}
   void OnPause() override {}
   void OnError() override {}
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.cc b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.cc
new file mode 100644
index 0000000..e06998a
--- /dev/null
+++ b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.cc
@@ -0,0 +1,95 @@
+// 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 "third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.h"
+
+#include "third_party/blink/renderer/core/html/media/html_media_element.h"
+#include "third_party/blink/renderer/core/html/time_ranges.h"
+#include "third_party/blink/renderer/modules/media_controls/elements/media_control_elements_helper.h"
+#include "third_party/blink/renderer/modules/media_controls/media_controls_shared_helper.h"
+#include "third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+MediaControlsTouchlessTimelineElement::MediaControlsTouchlessTimelineElement(
+    MediaControlsTouchlessImpl& media_controls)
+    : HTMLDivElement(media_controls.GetDocument()),
+      MediaControlsTouchlessElement(media_controls) {
+  SetShadowPseudoId(
+      AtomicString("-internal-media-controls-touchless-timeline"));
+
+  loaded_bar_ = MediaControlElementsHelper::CreateDiv(
+      "-internal-media-controls-touchless-timeline-loaded", this);
+  progress_bar_ = MediaControlElementsHelper::CreateDiv(
+      "-internal-media-controls-touchless-timeline-progress", loaded_bar_);
+}
+
+void MediaControlsTouchlessTimelineElement::OnLoadingProgress() {
+  UpdateBarsCSS();
+}
+
+void MediaControlsTouchlessTimelineElement::OnTimeUpdate() {
+  current_time_ = MediaElement().currentTime();
+  UpdateBars();
+}
+
+void MediaControlsTouchlessTimelineElement::OnDurationChange() {
+  duration_ = MediaElement().duration();
+  UpdateBars();
+}
+
+void MediaControlsTouchlessTimelineElement::UpdateBars() {
+  if (std::isnan(duration_) || std::isinf(duration_) || !duration_ ||
+      std::isnan(current_time_)) {
+    progress_percent_ = 0;
+    loaded_percent_ = 0;
+    UpdateBarsCSS();
+    return;
+  }
+
+  progress_percent_ = current_time_ / duration_;
+  loaded_percent_ = progress_percent_;
+
+  base::Optional<unsigned> current_buffered_time_range =
+      MediaControlsSharedHelpers::GetCurrentBufferedTimeRange(MediaElement());
+  if (current_buffered_time_range) {
+    TimeRanges* buffered_time_ranges = MediaElement().buffered();
+    float end = buffered_time_ranges->end(current_buffered_time_range.value(),
+                                          ASSERT_NO_EXCEPTION);
+    loaded_percent_ = end / duration_;
+  }
+
+  UpdateBarsCSS();
+}
+
+void MediaControlsTouchlessTimelineElement::UpdateBarsCSS() {
+  SetBarWidth(loaded_bar_, loaded_percent_);
+
+  // Since progress bar is a child of loaded bar, we need to calculate
+  // the percentage accordingly.
+  double adjusted_width_percent = 0;
+  if (loaded_percent_ != 0)
+    adjusted_width_percent = progress_percent_ / loaded_percent_;
+
+  SetBarWidth(progress_bar_, adjusted_width_percent);
+}
+
+void MediaControlsTouchlessTimelineElement::SetBarWidth(HTMLDivElement* bar,
+                                                        double percent) {
+  StringBuilder builder;
+  builder.Append("width:");
+  builder.AppendNumber(percent * 100);
+  builder.Append("%");
+  bar->setAttribute("style", builder.ToAtomicString());
+}
+
+void MediaControlsTouchlessTimelineElement::Trace(blink::Visitor* visitor) {
+  visitor->Trace(loaded_bar_);
+  visitor->Trace(progress_bar_);
+  HTMLDivElement::Trace(visitor);
+  MediaControlsTouchlessElement::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.h b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.h
new file mode 100644
index 0000000..0050fcf
--- /dev/null
+++ b/third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.h
@@ -0,0 +1,47 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_TOUCHLESS_ELEMENTS_MEDIA_CONTROLS_TOUCHLESS_TIMELINE_ELEMENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_TOUCHLESS_ELEMENTS_MEDIA_CONTROLS_TOUCHLESS_TIMELINE_ELEMENT_H_
+
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_element.h"
+
+namespace blink {
+
+class MediaControlsTouchlessTimelineElement
+    : public HTMLDivElement,
+      public MediaControlsTouchlessElement {
+  USING_GARBAGE_COLLECTED_MIXIN(MediaControlsTouchlessTimelineElement);
+
+ public:
+  explicit MediaControlsTouchlessTimelineElement(MediaControlsTouchlessImpl&);
+
+  // MediaControlsTouchlessMediaEventListenerObserver overrides
+  void OnTimeUpdate() override;
+  void OnDurationChange() override;
+  void OnLoadingProgress() override;
+
+  void Trace(blink::Visitor*) override;
+
+ private:
+  void SetBarWidth(HTMLDivElement* bar, double percent);
+
+  void UpdateBars();
+  void UpdateBarsCSS();
+
+  Member<HTMLDivElement> loaded_bar_;
+  Member<HTMLDivElement> progress_bar_;
+  double current_time_;
+  double duration_;
+
+  // Used for setting the width of the progress and loaded bars. This is
+  // a percentage of the duration of the media.
+  double progress_percent_;
+  double loaded_percent_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_TOUCHLESS_ELEMENTS_MEDIA_CONTROLS_TOUCHLESS_TIMELINE_ELEMENT_H_
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.cc b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.cc
index 94d034c..4b97be0 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.cc
+++ b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.h"
 #include "third_party/blink/renderer/modules/media_controls/media_controls_text_track_manager.h"
 #include "third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_overlay_element.h"
+#include "third_party/blink/renderer/modules/media_controls/touchless/elements/media_controls_touchless_timeline_element.h"
 #include "third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener.h"
 #include "third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_resource_loader.h"
 #include "third_party/blink/renderer/platform/keyboard_codes.h"
@@ -80,8 +81,11 @@
       MakeGarbageCollected<MediaControlsTouchlessImpl>(media_element);
   MediaControlsTouchlessOverlayElement* overlay_element =
       MakeGarbageCollected<MediaControlsTouchlessOverlayElement>(*controls);
+  MediaControlsTouchlessTimelineElement* timeline_element =
+      MakeGarbageCollected<MediaControlsTouchlessTimelineElement>(*controls);
 
   controls->ParserAppendChild(overlay_element);
+  controls->ParserAppendChild(timeline_element);
 
   // Controls start hidden.
   controls->MakeTransparent();
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.h b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.h
index a893d0f3..bcb6d391 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.h
+++ b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl.h
@@ -53,6 +53,7 @@
   void OnFocusIn() override;
   void OnTimeUpdate() override {}
   void OnDurationChange() override {}
+  void OnLoadingProgress() override {}
   void OnPlay() override {}
   void OnPause() override {}
   void OnError() override {}
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl_test.cc b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl_test.cc
index 7007591..8d5c1f1b 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_impl_test.cc
@@ -8,14 +8,17 @@
 
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/css_property_value_set.h"
 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
 #include "third_party/blink/renderer/core/html/media/html_media_test_helper.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
+#include "third_party/blink/renderer/core/html/time_ranges.h"
 #include "third_party/blink/renderer/core/loader/empty_clients.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/keyboard_codes.h"
@@ -30,7 +33,9 @@
  public:
   WebTimeRanges Seekable() const override { return seekable_; }
   bool HasVideo() const override { return true; }
+  WebTimeRanges Buffered() const override { return buffered_; }
 
+  WebTimeRanges buffered_;
   WebTimeRanges seekable_;
 };
 
@@ -66,11 +71,11 @@
         &clients, MakeGarbageCollected<test::MediaStubLocalFrameClient>(
                       std::make_unique<MockWebMediaPlayerForTouchlessImpl>()));
 
-    GetDocument().write("<video>");
+    GetDocument().write("<video controls>");
     HTMLMediaElement& video =
         ToHTMLVideoElement(*GetDocument().QuerySelector("video"));
-    media_controls_ = MediaControlsTouchlessImpl::Create(
-        video, video.EnsureUserAgentShadowRoot());
+    media_controls_ =
+        static_cast<MediaControlsTouchlessImpl*>(video.GetMediaControls());
   }
 
   MediaControlsTouchlessImpl& MediaControls() { return *media_controls_; }
@@ -98,6 +103,11 @@
     MediaElement().DurationChanged(duration, false /* requestSeek */);
   }
 
+  void SetBufferedRange(double end) {
+    WebTimeRange time_range(0.0, end);
+    WebMediaPlayer()->buffered_.Assign(&time_range, 1);
+  }
+
   bool IsControlsVisible() {
     return !MediaControls().classList().contains("transparent");
   }
@@ -232,6 +242,39 @@
   ASSERT_FALSE(play_button->classList().contains("playing"));
 }
 
+TEST_F(MediaControlsTouchlessImplTest, ProgressBar) {
+  const double duration = 100.0;
+  const double buffered = 60.0;
+  const double current_time = 15.0;
+
+  LoadMediaWithDuration(duration);
+  SetBufferedRange(buffered);
+  MediaElement().setCurrentTime(current_time);
+  test::RunPendingTasks();
+
+  MediaElement().DispatchEvent(*Event::Create(event_type_names::kTimeupdate));
+
+  Element* timeline =
+      GetControlByShadowPseudoId("-internal-media-controls-touchless-timeline");
+  Element* progress_bar = GetControlByShadowPseudoId(
+      "-internal-media-controls-touchless-timeline-progress");
+  Element* loaded_bar = GetControlByShadowPseudoId(
+      "-internal-media-controls-touchless-timeline-loaded");
+
+  ASSERT_NE(nullptr, timeline);
+  ASSERT_NE(nullptr, progress_bar);
+  ASSERT_NE(nullptr, loaded_bar);
+
+  double timeline_width = timeline->getBoundingClientRect()->width();
+  double progress_bar_width = progress_bar->getBoundingClientRect()->width();
+  double loaded_bar_width = loaded_bar->getBoundingClientRect()->width();
+  ASSERT_GT(timeline_width, 0);
+
+  EXPECT_DOUBLE_EQ(buffered / duration, loaded_bar_width / timeline_width);
+  EXPECT_DOUBLE_EQ(current_time / buffered,
+                   progress_bar_width / loaded_bar_width);
+}
+
 TEST_F(MediaControlsTouchlessImplTestWithMockScheduler, ControlsShowAndHide) {
   // Controls should starts hidden.
   ASSERT_FALSE(IsControlsVisible());
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener.cc b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener.cc
index d29a590..7c3d8c3 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener.cc
+++ b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener.cc
@@ -35,6 +35,7 @@
   media_element_->addEventListener(event_type_names::kTimeupdate, this, false);
   media_element_->addEventListener(event_type_names::kDurationchange, this,
                                    false);
+  media_element_->addEventListener(event_type_names::kProgress, this, false);
 
   media_element_->addEventListener(event_type_names::kPlay, this, false);
   media_element_->addEventListener(event_type_names::kPause, this, false);
@@ -70,6 +71,11 @@
       observer->OnDurationChange();
     return;
   }
+  if (event->type() == event_type_names::kProgress) {
+    for (auto& observer : observers_)
+      observer->OnLoadingProgress();
+    return;
+  }
   if (event->type() == event_type_names::kPlay) {
     for (auto& observer : observers_)
       observer->OnPlay();
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener_observer.h b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener_observer.h
index bb3cf4b..947702d 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener_observer.h
+++ b/third_party/blink/renderer/modules/media_controls/touchless/media_controls_touchless_media_event_listener_observer.h
@@ -17,6 +17,7 @@
   virtual void OnFocusIn() = 0;
   virtual void OnTimeUpdate() = 0;
   virtual void OnDurationChange() = 0;
+  virtual void OnLoadingProgress() = 0;
   virtual void OnPlay() = 0;
   virtual void OnPause() = 0;
   virtual void OnError() = 0;
diff --git a/third_party/blink/renderer/modules/media_controls/touchless/resources/mediaControlsTouchless.css b/third_party/blink/renderer/modules/media_controls/touchless/resources/mediaControlsTouchless.css
index 73488b4..a2e86f64 100644
--- a/third_party/blink/renderer/modules/media_controls/touchless/resources/mediaControlsTouchless.css
+++ b/third_party/blink/renderer/modules/media_controls/touchless/resources/mediaControlsTouchless.css
@@ -97,3 +97,23 @@
   background-position: center;
   background-repeat: no-repeat;
 }
+
+video::-internal-media-controls-touchless-timeline {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  height: 4px;
+  background-color: rgba(0, 0, 0, 0.2);
+}
+
+video::-internal-media-controls-touchless-timeline-loaded {
+  height: 100%;
+  border-radius: 0 2px 2px 0;
+  background-color: rgba(255, 255, 255, 0.54);
+}
+
+video::-internal-media-controls-touchless-timeline-progress {
+  height: 100%;
+  border-radius: 0 2px 2px 0;
+  background-color: rgba(255, 255, 255, 1.0);
+}
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
index f1ad9c9..bac7a0bf 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.cc
@@ -555,32 +555,6 @@
   }
 }
 
-WebMediaStreamTrack::DisplayCaptureSurfaceType ToWebDisplaySurface(
-    media::mojom::DisplayCaptureSurfaceType display_surface) {
-  switch (display_surface) {
-    case media::mojom::DisplayCaptureSurfaceType::MONITOR:
-      return WebMediaStreamTrack::DisplayCaptureSurfaceType::kMonitor;
-    case media::mojom::DisplayCaptureSurfaceType::WINDOW:
-      return WebMediaStreamTrack::DisplayCaptureSurfaceType::kWindow;
-    case media::mojom::DisplayCaptureSurfaceType::APPLICATION:
-      return WebMediaStreamTrack::DisplayCaptureSurfaceType::kApplication;
-    case media::mojom::DisplayCaptureSurfaceType::BROWSER:
-      return WebMediaStreamTrack::DisplayCaptureSurfaceType::kBrowser;
-  }
-}
-
-WebMediaStreamTrack::CursorCaptureType ToWebCursorCaptureType(
-    media::mojom::CursorCaptureType cursor) {
-  switch (cursor) {
-    case media::mojom::CursorCaptureType::NEVER:
-      return WebMediaStreamTrack::CursorCaptureType::kNever;
-    case media::mojom::CursorCaptureType::ALWAYS:
-      return WebMediaStreamTrack::CursorCaptureType::kAlways;
-    case media::mojom::CursorCaptureType::MOTION:
-      return WebMediaStreamTrack::CursorCaptureType::kMotion;
-  }
-}
-
 VideoDeviceCaptureCapabilities::VideoDeviceCaptureCapabilities() = default;
 VideoDeviceCaptureCapabilities::VideoDeviceCaptureCapabilities(
     VideoDeviceCaptureCapabilities&& other) = default;
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index 313e3eb..6a2ba35 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -532,16 +532,16 @@
   if (platform_settings.display_surface) {
     WTF::String value;
     switch (platform_settings.display_surface.value()) {
-      case WebMediaStreamTrack::DisplayCaptureSurfaceType::kMonitor:
+      case media::mojom::DisplayCaptureSurfaceType::MONITOR:
         value = "monitor";
         break;
-      case WebMediaStreamTrack::DisplayCaptureSurfaceType::kWindow:
+      case media::mojom::DisplayCaptureSurfaceType::WINDOW:
         value = "window";
         break;
-      case WebMediaStreamTrack::DisplayCaptureSurfaceType::kApplication:
+      case media::mojom::DisplayCaptureSurfaceType::APPLICATION:
         value = "application";
         break;
-      case WebMediaStreamTrack::DisplayCaptureSurfaceType::kBrowser:
+      case media::mojom::DisplayCaptureSurfaceType::BROWSER:
         value = "browser";
         break;
     }
@@ -552,13 +552,13 @@
   if (platform_settings.cursor) {
     WTF::String value;
     switch (platform_settings.cursor.value()) {
-      case WebMediaStreamTrack::CursorCaptureType::kNever:
+      case media::mojom::CursorCaptureType::NEVER:
         value = "never";
         break;
-      case WebMediaStreamTrack::CursorCaptureType::kAlways:
+      case media::mojom::CursorCaptureType::ALWAYS:
         value = "always";
         break;
-      case WebMediaStreamTrack::CursorCaptureType::kMotion:
+      case media::mojom::CursorCaptureType::MOTION:
         value = "motion";
         break;
     }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
index a5860f8..b6b0f29 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_track.cc
@@ -450,9 +450,9 @@
                                        : WebMediaStreamTrack::kResizeModeNone));
   if (source_->device().display_media_info.has_value()) {
     const auto& info = source_->device().display_media_info.value();
-    settings.display_surface = ToWebDisplaySurface(info->display_surface);
+    settings.display_surface = info->display_surface;
     settings.logical_surface = info->logical_surface;
-    settings.cursor = ToWebCursorCaptureType(info->cursor);
+    settings.cursor = info->cursor;
   }
 }
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 093d1b3..cf44193 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -4529,9 +4529,9 @@
     return;
   shader->SetSource(string);
   WTF::StringUTF8Adaptor adaptor(string_without_comments);
-  const GLchar* shader_data = adaptor.Data();
+  const GLchar* shader_data = adaptor.data();
   // TODO(danakj): Use base::saturated_cast<GLint>.
-  const GLint shader_length = adaptor.length();
+  const GLint shader_length = adaptor.size();
   ContextGL()->ShaderSource(ObjectOrZero(shader), 1, &shader_data,
                             &shader_length);
 }
diff --git a/third_party/blink/renderer/platform/exported/web_string.cc b/third_party/blink/renderer/platform/exported/web_string.cc
index 52b5189..c8406475 100644
--- a/third_party/blink/renderer/platform/exported/web_string.cc
+++ b/third_party/blink/renderer/platform/exported/web_string.cc
@@ -82,7 +82,7 @@
 std::string WebString::Utf8(UTF8ConversionMode mode) const {
   StringUTF8Adaptor utf8(impl_.get(),
                          static_cast<WTF::UTF8ConversionMode>(mode));
-  return std::string(utf8.Data(), utf8.length());
+  return utf8.AsStdString();
 }
 
 WebString WebString::FromUTF8(const char* data, size_t length) {
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index 5de70cc..7872f3b 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -1197,7 +1197,7 @@
   auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                              kNotScrollingOnMain, scroll_element_id);
   auto scroll_translation = CreateScrollTranslation(
-      t0(), 7, 9, *scroll, CompositingReason::kWillChangeCompositingHint);
+      t0(), 7, 9, *scroll, CompositingReason::kWillChangeTransform);
 
   auto transform = CreateTransform(
       *scroll_translation, TransformationMatrix().Translate(5, 5),
@@ -1236,7 +1236,7 @@
   auto scroll_1 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                                kNotScrollingOnMain, scroll_1_element_id);
   auto scroll_translation_1 = CreateScrollTranslation(
-      t0(), 7, 9, *scroll_1, CompositingReason::kWillChangeCompositingHint);
+      t0(), 7, 9, *scroll_1, CompositingReason::kWillChangeTransform);
 
   auto clip_2 = CreateClip(*clip_1, *scroll_translation_1,
                            FloatRoundedRect(0, 0, 50, 50));
@@ -1244,7 +1244,7 @@
   auto scroll_2 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState2(),
                                kNotScrollingOnMain, scroll_2_element_id);
   auto scroll_translation_2 = CreateScrollTranslation(
-      t0(), 0, 0, *scroll_2, CompositingReason::kWillChangeCompositingHint);
+      t0(), 0, 0, *scroll_2, CompositingReason::kWillChangeTransform);
 
   TestPaintArtifact artifact;
   CreateScrollableChunk(artifact, *scroll_translation_1, *clip_1->Parent(),
@@ -2577,8 +2577,7 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, e0())
@@ -2628,7 +2627,7 @@
   // applying clip path to a composited effect.
   auto c1 = CreateClipPathClip(c0(), t0(), FloatRoundedRect(50, 50, 300, 200));
   auto e1 = CreateOpacityEffect(e0(), t0(), c1.get(), 1,
-                                CompositingReason::kWillChangeCompositingHint);
+                                CompositingReason::kWillChangeOpacity);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, *e1)
@@ -2681,13 +2680,12 @@
   // This tests the case that a two back-to-back composited layers having
   // the same composited rounded clip can share the synthesized mask.
   auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
-                            CompositingReason::kWillChangeCompositingHint);
+                            CompositingReason::kWillChangeTransform);
 
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, e0())
@@ -2750,13 +2748,12 @@
   // composited rounded clip cannot share the synthesized mask if there is
   // another layer in the middle.
   auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
-                            CompositingReason::kWillChangeCompositingHint);
+                            CompositingReason::kWillChangeTransform);
 
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, e0())
@@ -2844,10 +2841,9 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
   auto e1 = CreateOpacityEffect(e0(), t0(), c1.get(), 1,
-                                CompositingReason::kWillChangeCompositingHint);
+                                CompositingReason::kWillChangeOpacity);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, e0())
@@ -2914,13 +2910,12 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
 
   CompositorFilterOperations non_trivial_filter;
   non_trivial_filter.AppendBlurFilter(5);
   auto e1 = CreateFilterEffect(e0(), non_trivial_filter, FloatPoint(),
-                               CompositingReason::kWillChangeCompositingHint);
+                               CompositingReason::kActiveFilterAnimation);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), *c1, e0())
@@ -3019,15 +3014,13 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
-                       CompositingReason::kWillChangeCompositingHint);
+  auto c1 = CreateClip(c0(), t0(), rrect);
 
   EffectPaintPropertyNode::State e1_state;
   e1_state.local_transform_space = &t0();
   e1_state.output_clip = c1.get();
   e1_state.blend_mode = SkBlendMode::kMultiply;
-  e1_state.direct_compositing_reasons =
-      CompositingReason::kWillChangeCompositingHint;
+  e1_state.direct_compositing_reasons = CompositingReason::kWillChangeOpacity;
   auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(e1_state));
 
   TestPaintArtifact artifact;
@@ -3370,16 +3363,14 @@
   //   L0  L1         L5
   auto e = CreateOpacityEffect(e0(), 0.1f);
   auto a = CreateOpacityEffect(*e, 0.2f);
-  auto b = CreateOpacityEffect(*e, 0.3f,
-                               CompositingReason::kWillChangeCompositingHint);
-  auto c = CreateOpacityEffect(*e, 0.4f,
-                               CompositingReason::kWillChangeCompositingHint);
-  auto aa = CreateOpacityEffect(*a, 0.5f,
-                                CompositingReason::kWillChangeCompositingHint);
-  auto ab = CreateOpacityEffect(*a, 0.6f,
-                                CompositingReason::kWillChangeCompositingHint);
-  auto ca = CreateOpacityEffect(*c, 0.7f,
-                                CompositingReason::kWillChangeCompositingHint);
+  auto b = CreateOpacityEffect(*e, 0.3f, CompositingReason::kWillChangeOpacity);
+  auto c = CreateOpacityEffect(*e, 0.4f, CompositingReason::kWillChangeOpacity);
+  auto aa =
+      CreateOpacityEffect(*a, 0.5f, CompositingReason::kWillChangeOpacity);
+  auto ab =
+      CreateOpacityEffect(*a, 0.6f, CompositingReason::kWillChangeOpacity);
+  auto ca =
+      CreateOpacityEffect(*c, 0.7f, CompositingReason::kWillChangeOpacity);
   auto t = CreateTransform(t0(), TransformationMatrix().Rotate(90),
                            FloatPoint3D(), CompositingReason::k3DTransform);
 
@@ -3537,11 +3528,10 @@
 
 TEST_P(PaintArtifactCompositorTest, OpacityIndirectlyAffectingTwoLayers) {
   auto opacity = CreateOpacityEffect(e0(), 0.5f);
-  auto child_composited_effect = CreateOpacityEffect(
-      *opacity, 1.f, CompositingReason::kWillChangeCompositingHint);
-  auto grandchild_composited_effect =
-      CreateOpacityEffect(*child_composited_effect, 1.f,
-                          CompositingReason::kWillChangeCompositingHint);
+  auto child_composited_effect =
+      CreateOpacityEffect(*opacity, 1.f, CompositingReason::kWillChangeOpacity);
+  auto grandchild_composited_effect = CreateOpacityEffect(
+      *child_composited_effect, 1.f, CompositingReason::kWillChangeOpacity);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), c0(), *child_composited_effect)
@@ -3616,7 +3606,7 @@
   CompositorFilterOperations filter;
   filter.AppendBlurFilter(5);
   auto e1 = CreateFilterEffect(e0(), filter, FloatPoint(),
-                               CompositingReason::kWillChangeCompositingHint);
+                               CompositingReason::kActiveFilterAnimation);
   Update(TestPaintArtifact()
              .Chunk(t0(), c0(), *e1)
              .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
@@ -3640,9 +3630,8 @@
 TEST_P(PaintArtifactCompositorTest, BackdropFilterCreatesRenderSurface) {
   CompositorFilterOperations filter;
   filter.AppendBlurFilter(5);
-  auto e1 =
-      CreateBackdropFilterEffect(e0(), filter, FloatPoint(),
-                                 CompositingReason::kWillChangeCompositingHint);
+  auto e1 = CreateBackdropFilterEffect(e0(), filter, FloatPoint(),
+                                       CompositingReason::kBackdropFilter);
   Update(TestPaintArtifact()
              .Chunk(t0(), c0(), *e1)
              .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.cc b/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
index b5824d8..ee60a09 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
@@ -36,8 +36,6 @@
     {CompositingReason::kActiveBackdropFilterAnimation,
      "activeBackdropFilterAnimation",
      "Has an active accelerated backdrop filter animation or transition"},
-    {CompositingReason::kTransitionProperty, "transitionProperty",
-     "Has an acceleratable transition property (active or inactive)"},
     {CompositingReason::kScrollDependentPosition, "scrollDependentPosition",
      "Is fixed or sticky position"},
     {CompositingReason::kOverflowScrollingTouch, "overflowScrollingTouch",
@@ -48,8 +46,12 @@
      "Has clipping ancestor"},
     {CompositingReason::kVideoOverlay, "videoOverlay",
      "Is overlay controls for video"},
-    {CompositingReason::kWillChangeCompositingHint, "willChange",
-     "Has a will-change compositing hint"},
+    {CompositingReason::kWillChangeTransform, "willChangeTransform",
+     "Has a will-change: transform compositing hint"},
+    {CompositingReason::kWillChangeOpacity, "willChangeOpacity",
+     "Has a will-change: opacity compositing hint"},
+    {CompositingReason::kWillChangeOther, "willChangeOther",
+     "Has a will-change compositing hint other than transform and opacity"},
     {CompositingReason::kBackdropFilter, "backdropFilter",
      "Has a backdrop filter"},
     {CompositingReason::kRootScroller, "rootScroller",
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.h b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
index cda21292..d6d89e28 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.h
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
@@ -27,13 +27,16 @@
   V(ActiveOpacityAnimation)                                                   \
   V(ActiveFilterAnimation)                                                    \
   V(ActiveBackdropFilterAnimation)                                            \
-  V(TransitionProperty)                                                       \
   V(ScrollDependentPosition)                                                  \
   V(OverflowScrollingTouch)                                                   \
   V(OverflowScrollingParent)                                                  \
   V(OutOfFlowClipping)                                                        \
   V(VideoOverlay)                                                             \
-  V(WillChangeCompositingHint)                                                \
+  V(WillChangeTransform)                                                      \
+  V(WillChangeOpacity)                                                        \
+  /* This flag is needed only when neither kWillChangeTransform nor           \
+     kWillChangeOpacity is set */                                             \
+  V(WillChangeOther)                                                          \
   V(BackdropFilter)                                                           \
   V(RootScroller)                                                             \
   V(ScrollTimelineTarget)                                                     \
@@ -121,7 +124,8 @@
 
     kComboAllDirectStyleDeterminedReasons =
         k3DTransform | kBackfaceVisibilityHidden | kComboActiveAnimation |
-        kTransitionProperty | kWillChangeCompositingHint | kBackdropFilter,
+        kWillChangeTransform | kWillChangeOpacity | kWillChangeOther |
+        kBackdropFilter,
 
     kComboAllDirectNonStyleDeterminedReasons =
         kVideo | kCanvas | kPlugin | kIFrame | kOverflowScrollingParent |
@@ -151,17 +155,13 @@
         kOverlap | kAssumedOverlap | kOverflowScrollingParent,
 
     kDirectReasonsForTransformProperty =
-        k3DTransform | kWillChangeCompositingHint |
+        k3DTransform | kWillChangeTransform | kWillChangeOther |
         kPerspectiveWith3DDescendants | kPreserve3DWith3DDescendants |
-        // Currently, we create transform/effect/filter nodes for an element
-        // whenever any property is being animated so that the existence of the
-        // effect node implies the existence of all nodes.
-        // TODO(flackr): Check for nodes for each KeyframeModel target property
-        // instead of creating all nodes and only create a transform/effect/
-        // filter node if needed, https://crbug.com/900241
-        kComboActiveAnimation,
-    kDirectReasonsForEffectProperty = kComboActiveAnimation,
-    kDirectReasonsForFilterProperty = kComboActiveAnimation,
+        kActiveTransformAnimation,
+    kDirectReasonsForEffectProperty = kActiveOpacityAnimation |
+                                      kWillChangeOpacity |
+                                      kActiveBackdropFilterAnimation,
+    kDirectReasonsForFilterProperty = kActiveFilterAnimation,
   };
 };
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc b/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
index 80f3535..062977d 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_blend.cc
@@ -34,10 +34,6 @@
 FEBlend::FEBlend(Filter* filter, BlendMode mode)
     : FilterEffect(filter), mode_(mode) {}
 
-FEBlend* FEBlend::Create(Filter* filter, BlendMode mode) {
-  return MakeGarbageCollected<FEBlend>(filter, mode);
-}
-
 bool FEBlend::SetBlendMode(BlendMode mode) {
   if (mode_ == mode)
     return false;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_blend.h b/third_party/blink/renderer/platform/graphics/filters/fe_blend.h
index 0b23ad9..dfb3937 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_blend.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_blend.h
@@ -30,8 +30,6 @@
 
 class PLATFORM_EXPORT FEBlend final : public FilterEffect {
  public:
-  static FEBlend* Create(Filter*, BlendMode);
-
   FEBlend(Filter*, BlendMode);
 
   BlendMode GetBlendMode() const { return mode_; }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_box_reflect.h b/third_party/blink/renderer/platform/graphics/filters/fe_box_reflect.h
index a28d295..3c137e1 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_box_reflect.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_box_reflect.h
@@ -14,10 +14,6 @@
 // Used to implement the -webkit-box-reflect property as a filter.
 class PLATFORM_EXPORT FEBoxReflect final : public FilterEffect {
  public:
-  static FEBoxReflect* Create(Filter* filter, const BoxReflection& reflection) {
-    return MakeGarbageCollected<FEBoxReflect>(filter, reflection);
-  }
-
   FEBoxReflect(Filter*, const BoxReflection&);
 
   // FilterEffect implementation
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
index 616cb43..0558c30 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.cc
@@ -37,12 +37,6 @@
                              const Vector<float>& values)
     : FilterEffect(filter), type_(type), values_(values) {}
 
-FEColorMatrix* FEColorMatrix::Create(Filter* filter,
-                                     ColorMatrixType type,
-                                     const Vector<float>& values) {
-  return MakeGarbageCollected<FEColorMatrix>(filter, type, values);
-}
-
 ColorMatrixType FEColorMatrix::GetType() const {
   return type_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.h b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.h
index 212eb96..1621b60e 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.h
@@ -38,8 +38,6 @@
 
 class PLATFORM_EXPORT FEColorMatrix final : public FilterEffect {
  public:
-  static FEColorMatrix* Create(Filter*, ColorMatrixType, const Vector<float>&);
-
   FEColorMatrix(Filter*, ColorMatrixType, const Vector<float>&);
 
   ColorMatrixType GetType() const;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
index 6a33e1f..431d16d 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.cc
@@ -49,16 +49,6 @@
       blue_func_(blue_func),
       alpha_func_(alpha_func) {}
 
-FEComponentTransfer* FEComponentTransfer::Create(
-    Filter* filter,
-    const ComponentTransferFunction& red_func,
-    const ComponentTransferFunction& green_func,
-    const ComponentTransferFunction& blue_func,
-    const ComponentTransferFunction& alpha_func) {
-  return MakeGarbageCollected<FEComponentTransfer>(filter, red_func, green_func,
-                                                   blue_func, alpha_func);
-}
-
 static void Identity(unsigned char*, const ComponentTransferFunction&) {}
 
 static void Table(unsigned char* values,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h
index 1509883..d1d8e477 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h
@@ -60,13 +60,6 @@
 
 class PLATFORM_EXPORT FEComponentTransfer final : public FilterEffect {
  public:
-  static FEComponentTransfer* Create(
-      Filter*,
-      const ComponentTransferFunction& red_func,
-      const ComponentTransferFunction& green_func,
-      const ComponentTransferFunction& blue_func,
-      const ComponentTransferFunction& alpha_func);
-
   FEComponentTransfer(Filter*,
                       const ComponentTransferFunction& red_func,
                       const ComponentTransferFunction& green_func,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc b/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
index 2b0834f..f77cba2 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_composite.cc
@@ -41,15 +41,6 @@
                          float k4)
     : FilterEffect(filter), type_(type), k1_(k1), k2_(k2), k3_(k3), k4_(k4) {}
 
-FEComposite* FEComposite::Create(Filter* filter,
-                                 const CompositeOperationType& type,
-                                 float k1,
-                                 float k2,
-                                 float k3,
-                                 float k4) {
-  return MakeGarbageCollected<FEComposite>(filter, type, k1, k2, k3, k4);
-}
-
 CompositeOperationType FEComposite::Operation() const {
   return type_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_composite.h b/third_party/blink/renderer/platform/graphics/filters/fe_composite.h
index d416525a..90b2320 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_composite.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_composite.h
@@ -41,13 +41,6 @@
 
 class PLATFORM_EXPORT FEComposite final : public FilterEffect {
  public:
-  static FEComposite* Create(Filter*,
-                             const CompositeOperationType&,
-                             float,
-                             float,
-                             float,
-                             float);
-
   FEComposite(Filter*,
               const CompositeOperationType&,
               float,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_composite_test.cc b/third_party/blink/renderer/platform/graphics/filters/fe_composite_test.cc
index 74972a9..ac95f36e 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_composite_test.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_composite_test.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/fe_offset.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
@@ -21,21 +22,22 @@
     // Use big filter region to avoid it from affecting FEComposite's MapRect
     // results.
     FloatRect filter_region(-10000, -10000, 20000, 20000);
-    auto* filter =
-        Filter::Create(FloatRect(), filter_region, 1, Filter::kUserSpace);
+    auto* filter = MakeGarbageCollected<Filter>(FloatRect(), filter_region, 1,
+                                                Filter::kUserSpace);
 
     // Input 1 of composite has a fixed output rect.
-    auto* source_graphic1 = SourceGraphic::Create(filter);
+    auto* source_graphic1 = MakeGarbageCollected<SourceGraphic>(filter);
     source_graphic1->SetClipsToBounds(false);
     source_graphic1->SetSourceRect(kInput1Rect);
 
     // Input 2 of composite will pass composite->MapRect()'s parameter as its
     // output.
-    auto* source_graphic2 = SourceGraphic::Create(filter);
+    auto* source_graphic2 = MakeGarbageCollected<SourceGraphic>(filter);
     source_graphic2->SetClipsToBounds(false);
 
     // Composite input 1 and input 2.
-    auto* composite = FEComposite::Create(filter, type, k1, k2, k3, k4);
+    auto* composite =
+        MakeGarbageCollected<FEComposite>(filter, type, k1, k2, k3, k4);
     composite->SetClipsToBounds(false);
     composite->InputEffects().push_back(source_graphic1);
     composite->InputEffects().push_back(source_graphic2);
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
index 2598ea2..261dcf6 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.cc
@@ -49,19 +49,6 @@
       preserve_alpha_(preserve_alpha),
       kernel_matrix_(kernel_matrix) {}
 
-FEConvolveMatrix* FEConvolveMatrix::Create(Filter* filter,
-                                           const IntSize& kernel_size,
-                                           float divisor,
-                                           float bias,
-                                           const IntPoint& target_offset,
-                                           EdgeModeType edge_mode,
-                                           bool preserve_alpha,
-                                           const Vector<float>& kernel_matrix) {
-  return MakeGarbageCollected<FEConvolveMatrix>(filter, kernel_size, divisor,
-                                                bias, target_offset, edge_mode,
-                                                preserve_alpha, kernel_matrix);
-}
-
 FloatRect FEConvolveMatrix::MapEffect(const FloatRect& rect) const {
   if (!ParametersValid())
     return rect;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.h b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.h
index 9212f5a..2dd262b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_convolve_matrix.h
@@ -41,15 +41,6 @@
 
 class PLATFORM_EXPORT FEConvolveMatrix final : public FilterEffect {
  public:
-  static FEConvolveMatrix* Create(Filter*,
-                                  const IntSize&,
-                                  float,
-                                  float,
-                                  const IntPoint&,
-                                  EdgeModeType,
-                                  bool,
-                                  const Vector<float>&);
-
   FEConvolveMatrix(Filter*,
                    const IntSize&,
                    float,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.cc b/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.cc
index 8d0c99d..a041530 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.cc
@@ -41,17 +41,6 @@
                  0,
                  std::move(light_source)) {}
 
-FEDiffuseLighting* FEDiffuseLighting::Create(
-    Filter* filter,
-    const Color& lighting_color,
-    float surface_scale,
-    float diffuse_constant,
-    scoped_refptr<LightSource> light_source) {
-  return MakeGarbageCollected<FEDiffuseLighting>(
-      filter, lighting_color, surface_scale, diffuse_constant,
-      std::move(light_source));
-}
-
 FEDiffuseLighting::~FEDiffuseLighting() = default;
 
 Color FEDiffuseLighting::LightingColor() const {
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.h b/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.h
index 5db5f8af..1d5353de 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_diffuse_lighting.h
@@ -31,12 +31,6 @@
 
 class PLATFORM_EXPORT FEDiffuseLighting final : public FELighting {
  public:
-  static FEDiffuseLighting* Create(Filter*,
-                                   const Color&,
-                                   float,
-                                   float,
-                                   scoped_refptr<LightSource>);
-
   FEDiffuseLighting(Filter*,
                     const Color&,
                     float,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
index 02ecb59e..a82b346 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.cc
@@ -40,15 +40,6 @@
       y_channel_selector_(y_channel_selector),
       scale_(scale) {}
 
-FEDisplacementMap* FEDisplacementMap::Create(
-    Filter* filter,
-    ChannelSelectorType x_channel_selector,
-    ChannelSelectorType y_channel_selector,
-    float scale) {
-  return MakeGarbageCollected<FEDisplacementMap>(filter, x_channel_selector,
-                                                 y_channel_selector, scale);
-}
-
 FloatRect FEDisplacementMap::MapEffect(const FloatRect& rect) const {
   FloatRect result = rect;
   result.InflateX(GetFilter()->ApplyHorizontalScale(std::abs(scale_) / 2));
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.h b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.h
index fac11a8..952732b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_displacement_map.h
@@ -37,11 +37,6 @@
 
 class PLATFORM_EXPORT FEDisplacementMap final : public FilterEffect {
  public:
-  static FEDisplacementMap* Create(Filter*,
-                                   ChannelSelectorType x_channel_selector,
-                                   ChannelSelectorType y_channel_selector,
-                                   float);
-
   FEDisplacementMap(Filter*,
                     ChannelSelectorType x_channel_selector,
                     ChannelSelectorType y_channel_selector,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
index e9d121f6..1a5fb28e 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
@@ -43,17 +43,6 @@
       shadow_color_(shadow_color),
       shadow_opacity_(shadow_opacity) {}
 
-FEDropShadow* FEDropShadow::Create(Filter* filter,
-                                   float std_x,
-                                   float std_y,
-                                   float dx,
-                                   float dy,
-                                   const Color& shadow_color,
-                                   float shadow_opacity) {
-  return MakeGarbageCollected<FEDropShadow>(filter, std_x, std_y, dx, dy,
-                                            shadow_color, shadow_opacity);
-}
-
 FloatRect FEDropShadow::MapEffect(const FloatSize& std_deviation,
                                   const FloatPoint& offset,
                                   const FloatRect& rect) {
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h
index e3fa045..16ef4d2 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h
@@ -28,9 +28,6 @@
 
 class PLATFORM_EXPORT FEDropShadow final : public FilterEffect {
  public:
-  static FEDropShadow*
-  Create(Filter*, float, float, float, float, const Color&, float);
-
   FEDropShadow(Filter*, float, float, float, float, const Color&, float);
 
   // Compute which destination area will be affected when applying a drop
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc b/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
index 08715d1..795630b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_flood.cc
@@ -36,12 +36,6 @@
   FilterEffect::SetOperatingInterpolationSpace(kInterpolationSpaceSRGB);
 }
 
-FEFlood* FEFlood::Create(Filter* filter,
-                         const Color& flood_color,
-                         float flood_opacity) {
-  return MakeGarbageCollected<FEFlood>(filter, flood_color, flood_opacity);
-}
-
 Color FEFlood::FloodColor() const {
   return flood_color_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_flood.h b/third_party/blink/renderer/platform/graphics/filters/fe_flood.h
index 43147f60..1803563 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_flood.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_flood.h
@@ -30,8 +30,6 @@
 
 class PLATFORM_EXPORT FEFlood final : public FilterEffect {
  public:
-  static FEFlood* Create(Filter*, const Color&, float);
-
   FEFlood(Filter*, const Color&, float);
 
   Color FloodColor() const;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
index 7be8b94..373144a 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.cc
@@ -59,10 +59,6 @@
 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y)
     : FilterEffect(filter), std_x_(x), std_y_(y) {}
 
-FEGaussianBlur* FEGaussianBlur::Create(Filter* filter, float x, float y) {
-  return MakeGarbageCollected<FEGaussianBlur>(filter, x, y);
-}
-
 FloatRect FEGaussianBlur::MapEffect(const FloatSize& std_deviation,
                                     const FloatRect& rect) {
   IntSize kernel_size = CalculateKernelSize(std_deviation);
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h
index ec92a6eb..7f97f40 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h
@@ -29,8 +29,6 @@
 
 class PLATFORM_EXPORT FEGaussianBlur final : public FilterEffect {
  public:
-  static FEGaussianBlur* Create(Filter*, float, float);
-
   FEGaussianBlur(Filter*, float, float);
 
   // Compute which destination area will be affected when applying a gaussian
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc b/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
index f3c40fe..6a1291c 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_merge.cc
@@ -31,10 +31,6 @@
 
 FEMerge::FEMerge(Filter* filter) : FilterEffect(filter) {}
 
-FEMerge* FEMerge::Create(Filter* filter) {
-  return MakeGarbageCollected<FEMerge>(filter);
-}
-
 sk_sp<PaintFilter> FEMerge::CreateImageFilter() {
   unsigned size = NumberOfEffectInputs();
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_merge.h b/third_party/blink/renderer/platform/graphics/filters/fe_merge.h
index 02dab02a..2e23910f 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_merge.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_merge.h
@@ -29,8 +29,6 @@
 
 class PLATFORM_EXPORT FEMerge final : public FilterEffect {
  public:
-  static FEMerge* Create(Filter*);
-
   explicit FEMerge(Filter*);
 
   WTF::TextStream& ExternalRepresentation(WTF::TextStream&,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
index d8ee86f..52ec2fe 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.cc
@@ -40,13 +40,6 @@
       radius_x_(std::max(0.0f, radius_x)),
       radius_y_(std::max(0.0f, radius_y)) {}
 
-FEMorphology* FEMorphology::Create(Filter* filter,
-                                   MorphologyOperatorType type,
-                                   float radius_x,
-                                   float radius_y) {
-  return MakeGarbageCollected<FEMorphology>(filter, type, radius_x, radius_y);
-}
-
 MorphologyOperatorType FEMorphology::MorphologyOperator() const {
   return type_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.h b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.h
index baa3ca6f4..d3a846d6 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_morphology.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_morphology.h
@@ -35,11 +35,6 @@
 
 class PLATFORM_EXPORT FEMorphology final : public FilterEffect {
  public:
-  static FEMorphology* Create(Filter*,
-                              MorphologyOperatorType,
-                              float radius_x,
-                              float radius_y);
-
   FEMorphology(Filter*, MorphologyOperatorType, float radius_x, float radius_y);
 
   MorphologyOperatorType MorphologyOperator() const;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc b/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
index f5844d6..bbe9f3c 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_offset.cc
@@ -34,10 +34,6 @@
 FEOffset::FEOffset(Filter* filter, float dx, float dy)
     : FilterEffect(filter), dx_(dx), dy_(dy) {}
 
-FEOffset* FEOffset::Create(Filter* filter, float dx, float dy) {
-  return MakeGarbageCollected<FEOffset>(filter, dx, dy);
-}
-
 float FEOffset::Dx() const {
   return dx_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_offset.h b/third_party/blink/renderer/platform/graphics/filters/fe_offset.h
index b7c1f1b..bf65e55 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_offset.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_offset.h
@@ -29,8 +29,6 @@
 
 class PLATFORM_EXPORT FEOffset final : public FilterEffect {
  public:
-  static FEOffset* Create(Filter*, float dx, float dy);
-
   FEOffset(Filter*, float dx, float dy);
 
   float Dx() const;
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.cc b/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.cc
index d2679a7..5bf5281 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.cc
@@ -44,18 +44,6 @@
                  specular_exponent,
                  std::move(light_source)) {}
 
-FESpecularLighting* FESpecularLighting::Create(
-    Filter* filter,
-    const Color& lighting_color,
-    float surface_scale,
-    float specular_constant,
-    float specular_exponent,
-    scoped_refptr<LightSource> light_source) {
-  return MakeGarbageCollected<FESpecularLighting>(
-      filter, lighting_color, surface_scale, specular_constant,
-      specular_exponent, std::move(light_source));
-}
-
 FESpecularLighting::~FESpecularLighting() = default;
 
 Color FESpecularLighting::LightingColor() const {
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.h b/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.h
index e35c0e4..96c9c2ed 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_specular_lighting.h
@@ -29,13 +29,6 @@
 
 class PLATFORM_EXPORT FESpecularLighting final : public FELighting {
  public:
-  static FESpecularLighting* Create(Filter*,
-                                    const Color&,
-                                    float,
-                                    float,
-                                    float,
-                                    scoped_refptr<LightSource>);
-
   FESpecularLighting(Filter*,
                      const Color&,
                      float,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc b/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
index 70cb722..f29c9f4 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_tile.cc
@@ -30,10 +30,6 @@
 
 FETile::FETile(Filter* filter) : FilterEffect(filter) {}
 
-FETile* FETile::Create(Filter* filter) {
-  return MakeGarbageCollected<FETile>(filter);
-}
-
 FloatRect FETile::MapInputs(const FloatRect& rect) const {
   return AbsoluteBounds();
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_tile.h b/third_party/blink/renderer/platform/graphics/filters/fe_tile.h
index 1a7b7c5..b20b4bf 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_tile.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_tile.h
@@ -29,8 +29,6 @@
 
 class PLATFORM_EXPORT FETile final : public FilterEffect {
  public:
-  static FETile* Create(Filter*);
-
   FETile(Filter*);
 
   WTF::TextStream& ExternalRepresentation(WTF::TextStream&,
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
index 05e4acf..1186e8b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.cc
@@ -47,18 +47,6 @@
       seed_(seed),
       stitch_tiles_(stitch_tiles) {}
 
-FETurbulence* FETurbulence::Create(Filter* filter,
-                                   TurbulenceType type,
-                                   float base_frequency_x,
-                                   float base_frequency_y,
-                                   int num_octaves,
-                                   float seed,
-                                   bool stitch_tiles) {
-  return MakeGarbageCollected<FETurbulence>(filter, type, base_frequency_x,
-                                            base_frequency_y, num_octaves, seed,
-                                            stitch_tiles);
-}
-
 TurbulenceType FETurbulence::GetType() const {
   return type_;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.h b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.h
index 21ef942..3ab4828 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.h
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_turbulence.h
@@ -37,9 +37,6 @@
 
 class PLATFORM_EXPORT FETurbulence final : public FilterEffect {
  public:
-  static FETurbulence*
-  Create(Filter*, TurbulenceType, float, float, int, float, bool);
-
   FETurbulence(Filter*, TurbulenceType, float, float, int, float, bool);
 
   TurbulenceType GetType() const;
diff --git a/third_party/blink/renderer/platform/graphics/filters/filter.cc b/third_party/blink/renderer/platform/graphics/filters/filter.cc
index 57ab7b7..515d6d7 100644
--- a/third_party/blink/renderer/platform/graphics/filters/filter.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/filter.cc
@@ -32,9 +32,13 @@
 
 #include "third_party/blink/renderer/platform/graphics/filters/filter_effect.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 namespace blink {
 
+Filter::Filter(float scale)
+    : Filter(FloatRect(), FloatRect(), scale, kUserSpace) {}
+
 Filter::Filter(const FloatRect& reference_box,
                const FloatRect& filter_region,
                float scale,
@@ -43,20 +47,7 @@
       filter_region_(filter_region),
       scale_(scale),
       unit_scaling_(unit_scaling),
-      source_graphic_(SourceGraphic::Create(this)) {}
-
-Filter* Filter::Create(const FloatRect& reference_box,
-                       const FloatRect& filter_region,
-                       float scale,
-                       UnitScaling unit_scaling) {
-  return MakeGarbageCollected<Filter>(reference_box, filter_region, scale,
-                                      unit_scaling);
-}
-
-Filter* Filter::Create(float scale) {
-  return MakeGarbageCollected<Filter>(FloatRect(), FloatRect(), scale,
-                                      kUserSpace);
-}
+      source_graphic_(MakeGarbageCollected<SourceGraphic>(this)) {}
 
 void Filter::Trace(blink::Visitor* visitor) {
   visitor->Trace(source_graphic_);
diff --git a/third_party/blink/renderer/platform/graphics/filters/filter.h b/third_party/blink/renderer/platform/graphics/filters/filter.h
index ecdfd6c2..d5073d5 100644
--- a/third_party/blink/renderer/platform/graphics/filters/filter.h
+++ b/third_party/blink/renderer/platform/graphics/filters/filter.h
@@ -38,12 +38,7 @@
  public:
   enum UnitScaling { kUserSpace, kBoundingBox };
 
-  static Filter* Create(const FloatRect& reference_box,
-                        const FloatRect& filter_region,
-                        float scale,
-                        UnitScaling);
-  static Filter* Create(float scale);
-
+  Filter(float scale);
   Filter(const FloatRect& reference_box,
          const FloatRect& filter_region,
          float scale,
diff --git a/third_party/blink/renderer/platform/graphics/filters/image_filter_builder_test.cc b/third_party/blink/renderer/platform/graphics/filters/image_filter_builder_test.cc
index ad4f8c7..0c0eb2d 100644
--- a/third_party/blink/renderer/platform/graphics/filters/image_filter_builder_test.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/image_filter_builder_test.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 
 using testing::Test;
 
@@ -38,21 +39,21 @@
  protected:
   void InterpolationSpaceTest() {
     // Build filter tree
-    Filter* reference_filter = Filter::Create(1.0f);
+    auto* reference_filter = MakeGarbageCollected<Filter>(1.0f);
 
     // Add a dummy source graphic input
     FilterEffect* source_effect = reference_filter->GetSourceGraphic();
     source_effect->SetOperatingInterpolationSpace(kInterpolationSpaceSRGB);
 
     // Add a blur effect (with input : source)
-    FilterEffect* blur_effect =
-        FEGaussianBlur::Create(reference_filter, 3.0f, 3.0f);
+    auto* blur_effect =
+        MakeGarbageCollected<FEGaussianBlur>(reference_filter, 3.0f, 3.0f);
     blur_effect->SetOperatingInterpolationSpace(kInterpolationSpaceLinear);
     blur_effect->InputEffects().push_back(source_effect);
 
     // Add a blend effect (with inputs : blur, source)
-    FilterEffect* blend_effect =
-        FEBlend::Create(reference_filter, BlendMode::kNormal);
+    auto* blend_effect =
+        MakeGarbageCollected<FEBlend>(reference_filter, BlendMode::kNormal);
     blend_effect->SetOperatingInterpolationSpace(kInterpolationSpaceSRGB);
     FilterEffectVector& blend_inputs = blend_effect->InputEffects();
     blend_inputs.ReserveCapacity(2);
@@ -60,7 +61,7 @@
     blend_inputs.push_back(blur_effect);
 
     // Add a merge effect (with inputs : blur, blend)
-    FilterEffect* merge_effect = FEMerge::Create(reference_filter);
+    auto* merge_effect = MakeGarbageCollected<FEMerge>(reference_filter);
     merge_effect->SetOperatingInterpolationSpace(kInterpolationSpaceLinear);
     FilterEffectVector& merge_inputs = merge_effect->InputEffects();
     merge_inputs.ReserveCapacity(2);
diff --git a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
index b7172f9..042813a 100644
--- a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.cc
@@ -17,11 +17,6 @@
 
 PaintFilterEffect::~PaintFilterEffect() = default;
 
-PaintFilterEffect* PaintFilterEffect::Create(Filter* filter,
-                                             const PaintFlags& flags) {
-  return MakeGarbageCollected<PaintFilterEffect>(filter, flags);
-}
-
 sk_sp<PaintFilter> PaintFilterEffect::CreateImageFilter() {
   return sk_make_sp<PaintFlagsPaintFilter>(flags_);
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.h b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.h
index 5150316..cd24b79 100644
--- a/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.h
+++ b/third_party/blink/renderer/platform/graphics/filters/paint_filter_effect.h
@@ -12,8 +12,6 @@
 
 class PLATFORM_EXPORT PaintFilterEffect : public FilterEffect {
  public:
-  static PaintFilterEffect* Create(Filter*, const PaintFlags&);
-
   PaintFilterEffect(Filter*, const PaintFlags&);
   ~PaintFilterEffect() override;
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc b/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
index 4559afff..99f9f1ce 100644
--- a/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/source_alpha.cc
@@ -29,10 +29,6 @@
 
 namespace blink {
 
-SourceAlpha* SourceAlpha::Create(FilterEffect* source_effect) {
-  return MakeGarbageCollected<SourceAlpha>(source_effect);
-}
-
 SourceAlpha::SourceAlpha(FilterEffect* source_effect)
     : FilterEffect(source_effect->GetFilter()) {
   SetOperatingInterpolationSpace(source_effect->OperatingInterpolationSpace());
diff --git a/third_party/blink/renderer/platform/graphics/filters/source_alpha.h b/third_party/blink/renderer/platform/graphics/filters/source_alpha.h
index 4997830..9c6da57 100644
--- a/third_party/blink/renderer/platform/graphics/filters/source_alpha.h
+++ b/third_party/blink/renderer/platform/graphics/filters/source_alpha.h
@@ -27,8 +27,6 @@
 
 class PLATFORM_EXPORT SourceAlpha final : public FilterEffect {
  public:
-  static SourceAlpha* Create(FilterEffect*);
-
   explicit SourceAlpha(FilterEffect*);
 
   WTF::TextStream& ExternalRepresentation(WTF::TextStream&,
diff --git a/third_party/blink/renderer/platform/graphics/filters/source_graphic.cc b/third_party/blink/renderer/platform/graphics/filters/source_graphic.cc
index a4871a8..8e91d13 100644
--- a/third_party/blink/renderer/platform/graphics/filters/source_graphic.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/source_graphic.cc
@@ -31,10 +31,6 @@
 
 SourceGraphic::~SourceGraphic() = default;
 
-SourceGraphic* SourceGraphic::Create(Filter* filter) {
-  return MakeGarbageCollected<SourceGraphic>(filter);
-}
-
 FloatRect SourceGraphic::MapInputs(const FloatRect& rect) const {
   return !source_rect_.IsEmpty() ? FloatRect(source_rect_) : rect;
 }
diff --git a/third_party/blink/renderer/platform/graphics/filters/source_graphic.h b/third_party/blink/renderer/platform/graphics/filters/source_graphic.h
index 76cc9af..335f44b 100644
--- a/third_party/blink/renderer/platform/graphics/filters/source_graphic.h
+++ b/third_party/blink/renderer/platform/graphics/filters/source_graphic.h
@@ -29,8 +29,6 @@
 
 class PLATFORM_EXPORT SourceGraphic final : public FilterEffect {
  public:
-  static SourceGraphic* Create(Filter*);
-
   explicit SourceGraphic(Filter*);
   ~SourceGraphic() override;
 
diff --git a/third_party/blink/renderer/platform/graphics/logging_canvas.cc b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
index 74f9c32..a648a6a 100644
--- a/third_party/blink/renderer/platform/graphics/logging_canvas.cc
+++ b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
@@ -319,11 +319,7 @@
 }
 
 std::unique_ptr<JSONObject> ObjectForSkShader(const SkShader& shader) {
-  auto shader_item = std::make_unique<JSONObject>();
-  const SkMatrix local_matrix = shader.getLocalMatrix();
-  if (!local_matrix.isIdentity())
-    shader_item->SetArray("localMatrix", ArrayForSkMatrix(local_matrix));
-  return shader_item;
+  return std::make_unique<JSONObject>();
 }
 
 String StringForSkColor(const SkColor& color) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
index d3c4e58..8a41fa8 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
@@ -49,11 +49,9 @@
   // If this DCHECKs are hit we are missing a call to update the properties.
   // See: ScopedPaintChunkProperties.
   DCHECK(!IsInInitialState());
+  // At this point we should have all of the properties given to us.
+  DCHECK(current_properties_.IsInitialized());
 #endif
-  // TODO(crbug.com/923729): This CHECK is a temporary to determine the cause of
-  // the referenced bug. At this point we should have all of the properties
-  // given to us.
-  CHECK(current_properties_.IsInitialized());
 
   bool item_forces_new_chunk = item.IsForeignLayer() || item.IsScrollHitTest();
   if (item_forces_new_chunk)
diff --git a/third_party/blink/renderer/platform/graphics/paint/property_tree_state.h b/third_party/blink/renderer/platform/graphics/paint/property_tree_state.h
index d407f039..5fa74731 100644
--- a/third_party/blink/renderer/platform/graphics/paint/property_tree_state.h
+++ b/third_party/blink/renderer/platform/graphics/paint/property_tree_state.h
@@ -21,11 +21,9 @@
                     const ClipPaintPropertyNode& clip,
                     const EffectPaintPropertyNode& effect)
       : transform_(&transform), clip_(&clip), effect_(&effect) {
-    // TODO(crbug.com/923729): These CHECKs are temporary to determine the cause
-    // of the referenced bug.
-    CHECK(transform_);
-    CHECK(clip_);
-    CHECK(effect_);
+    DCHECK(transform_);
+    DCHECK(clip_);
+    DCHECK(effect_);
   }
 
   static const PropertyTreeState& Root();
@@ -49,9 +47,7 @@
   }
   void SetTransform(const TransformPaintPropertyNode& node) {
     transform_ = &node;
-    // TODO(crbug.com/923729): This CHECK is temporary to determine the cause
-    // of the referenced bug.
-    CHECK(transform_);
+    DCHECK(transform_);
   }
 
   const ClipPaintPropertyNode& Clip() const {
@@ -60,9 +56,7 @@
   }
   void SetClip(const ClipPaintPropertyNode& node) {
     clip_ = &node;
-    // TODO(crbug.com/923729): This CHECK is temporary to determine the cause
-    // of the referenced bug.
-    CHECK(clip_);
+    DCHECK(clip_);
   }
 
   const EffectPaintPropertyNode& Effect() const {
@@ -71,9 +65,7 @@
   }
   void SetEffect(const EffectPaintPropertyNode& node) {
     effect_ = &node;
-    // TODO(crbug.com/923729): This CHECK is temporary to determine the cause
-    // of the referenced bug.
-    CHECK(effect_);
+    DCHECK(effect_);
   }
 
   void ClearChangedToRoot() const {
diff --git a/third_party/blink/renderer/platform/heap/incremental_marking_test.cc b/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
index a39cd7e..7abd302 100644
--- a/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
+++ b/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
@@ -219,11 +219,6 @@
 
 class Object : public GarbageCollected<Object> {
  public:
-  static Object* Create() { return MakeGarbageCollected<Object>(); }
-  static Object* Create(Object* next) {
-    return MakeGarbageCollected<Object>(next);
-  }
-
   Object() : next_(nullptr) {}
   explicit Object(Object* next) : next_(next) {}
 
@@ -255,7 +250,7 @@
 }
 
 TEST(IncrementalMarkingTest, ManualWriteBarrierTriggersWhenMarkingIsOn) {
-  Object* object = Object::Create();
+  auto* object = MakeGarbageCollected<Object>();
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {object});
     EXPECT_FALSE(object->IsMarked());
@@ -265,7 +260,7 @@
 }
 
 TEST(IncrementalMarkingTest, ManualWriteBarrierBailoutWhenMarkingIsOff) {
-  Object* object = Object::Create();
+  auto* object = MakeGarbageCollected<Object>();
   EXPECT_FALSE(object->IsMarked());
   MarkingVisitor::WriteBarrier(object);
   EXPECT_FALSE(object->IsMarked());
@@ -276,8 +271,8 @@
 // =============================================================================
 
 TEST(IncrementalMarkingTest, MemberSetUnmarkedObject) {
-  Object* parent = Object::Create();
-  Object* child = Object::Create();
+  auto* parent = MakeGarbageCollected<Object>();
+  auto* child = MakeGarbageCollected<Object>();
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {child});
     EXPECT_FALSE(child->IsMarked());
@@ -287,8 +282,8 @@
 }
 
 TEST(IncrementalMarkingTest, MemberSetMarkedObjectNoBarrier) {
-  Object* parent = Object::Create();
-  Object* child = Object::Create();
+  auto* parent = MakeGarbageCollected<Object>();
+  auto* child = MakeGarbageCollected<Object>();
   HeapObjectHeader::FromPayload(child)->Mark();
   {
     ExpectNoWriteBarrierFires scope(ThreadState::Current(), {child});
@@ -297,12 +292,12 @@
 }
 
 TEST(IncrementalMarkingTest, MemberInitializingStoreNoBarrier) {
-  Object* object1 = Object::Create();
+  auto* object1 = MakeGarbageCollected<Object>();
   HeapObjectHeader* object1_header = HeapObjectHeader::FromPayload(object1);
   {
     IncrementalMarkingScope scope(ThreadState::Current());
     EXPECT_FALSE(object1_header->IsMarked());
-    Object* object2 = Object::Create(object1);
+    auto* object2 = MakeGarbageCollected<Object>(object1);
     HeapObjectHeader* object2_header = HeapObjectHeader::FromPayload(object2);
     EXPECT_FALSE(object1_header->IsMarked());
     EXPECT_FALSE(object2_header->IsMarked());
@@ -310,7 +305,7 @@
 }
 
 TEST(IncrementalMarkingTest, MemberReferenceAssignMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Member<Object> m1;
   Member<Object>& m2 = m1;
   Member<Object> m3(obj);
@@ -345,7 +340,7 @@
 }
 
 TEST(IncrementalMarkingTest, MemberHashTraitIsDeletedValueNoBarrier) {
-  Member<Object> m1(Object::Create());
+  Member<Object> m1(MakeGarbageCollected<Object>());
   {
     ExpectNoWriteBarrierFires scope(ThreadState::Current(), {});
     EXPECT_FALSE(HashTraits<Member<Object>>::IsDeletedValue(m1));
@@ -382,8 +377,6 @@
   USING_GARBAGE_COLLECTED_MIXIN(Child);
 
  public:
-  static Child* Create() { return MakeGarbageCollected<Child>(); }
-
   Child() : ClassWithVirtual(), Mixin() {}
   ~Child() override {}
 
@@ -395,10 +388,6 @@
 
 class ParentWithMixinPointer : public GarbageCollected<ParentWithMixinPointer> {
  public:
-  static ParentWithMixinPointer* Create() {
-    return MakeGarbageCollected<ParentWithMixinPointer>();
-  }
-
   ParentWithMixinPointer() : mixin_(nullptr) {}
 
   void set_mixin(Mixin* mixin) { mixin_ = mixin; }
@@ -412,8 +401,9 @@
 }  // namespace
 
 TEST(IncrementalMarkingTest, WriteBarrierOnUnmarkedMixinApplication) {
-  ParentWithMixinPointer* parent = ParentWithMixinPointer::Create();
-  Child* child = Child::Create();
+  ParentWithMixinPointer* parent =
+      MakeGarbageCollected<ParentWithMixinPointer>();
+  auto* child = MakeGarbageCollected<Child>();
   Mixin* mixin = static_cast<Mixin*>(child);
   EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
   {
@@ -423,8 +413,9 @@
 }
 
 TEST(IncrementalMarkingTest, NoWriteBarrierOnMarkedMixinApplication) {
-  ParentWithMixinPointer* parent = ParentWithMixinPointer::Create();
-  Child* child = Child::Create();
+  ParentWithMixinPointer* parent =
+      MakeGarbageCollected<ParentWithMixinPointer>();
+  auto* child = MakeGarbageCollected<Child>();
   HeapObjectHeader::FromPayload(child)->Mark();
   Mixin* mixin = static_cast<Mixin*>(child);
   EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
@@ -477,7 +468,7 @@
 }  // namespace
 
 TEST(IncrementalMarkingTest, HeapVectorPushBackMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -486,7 +477,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorPushBackNonGCedContainer) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<NonGarbageCollectedContainer> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -495,8 +486,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorPushBackStdPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj1, obj2});
@@ -505,7 +496,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorEmplaceBackMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -514,7 +505,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorEmplaceBackNonGCedContainer) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<NonGarbageCollectedContainer> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -523,8 +514,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorEmplaceBackStdPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj1, obj2});
@@ -533,7 +524,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorCopyMember) {
-  Object* object = Object::Create();
+  auto* object = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec1;
   vec1.push_back(object);
   {
@@ -543,7 +534,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorCopyNonGCedContainer) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<NonGarbageCollectedContainer> vec1;
   vec1.emplace_back(obj, 1);
   {
@@ -553,8 +544,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorCopyStdPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec1;
   vec1.emplace_back(obj1, obj2);
   {
@@ -564,7 +555,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorMoveMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec1;
   vec1.push_back(obj);
   {
@@ -574,7 +565,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorMoveNonGCedContainer) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapVector<NonGarbageCollectedContainer> vec1;
   vec1.emplace_back(obj, 1);
   {
@@ -584,8 +575,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorMoveStdPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec1;
   vec1.emplace_back(obj1, obj2);
   {
@@ -595,8 +586,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorSwapMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec1;
   vec1.push_back(obj1);
   HeapVector<Member<Object>> vec2;
@@ -608,8 +599,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorSwapNonGCedContainer) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<NonGarbageCollectedContainer> vec1;
   vec1.emplace_back(obj1, 1);
   HeapVector<NonGarbageCollectedContainer> vec2;
@@ -621,8 +612,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorSwapStdPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec1;
   vec1.emplace_back(obj1, nullptr);
   HeapVector<std::pair<Member<Object>, Member<Object>>> vec2;
@@ -634,8 +625,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorSubscriptOperator) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapVector<Member<Object>> vec;
   vec.push_back(obj1);
   {
@@ -649,9 +640,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapVectorEagerTracingStopsAtMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   obj1->set_next(obj3);
   HeapVector<NonGarbageCollectedContainerRoot> vec;
   {
@@ -691,7 +682,7 @@
 }  // namespace
 
 TEST(IncrementalMarkingTest, HeapDoublyLinkedListPush) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   ObjectNode* obj_node = MakeGarbageCollected<ObjectNode>(obj);
   HeapDoublyLinkedList<ObjectNode> list;
   {
@@ -703,7 +694,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDoublyLinkedListAppend) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   ObjectNode* obj_node = MakeGarbageCollected<ObjectNode>(obj);
   HeapDoublyLinkedList<ObjectNode> list;
   {
@@ -719,7 +710,7 @@
 // =============================================================================
 
 TEST(IncrementalMarkingTest, HeapDequePushBackMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -728,7 +719,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequePushFrontMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -737,7 +728,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequeEmplaceBackMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -746,7 +737,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequeEmplaceFrontMember) {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -755,7 +746,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequeCopyMember) {
-  Object* object = Object::Create();
+  auto* object = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq1;
   deq1.push_back(object);
   {
@@ -765,7 +756,7 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequeMoveMember) {
-  Object* object = Object::Create();
+  auto* object = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq1;
   deq1.push_back(object);
   {
@@ -775,8 +766,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapDequeSwapMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapDeque<Member<Object>> deq1;
   deq1.push_back(obj1);
   HeapDeque<Member<Object>> deq2;
@@ -795,7 +786,7 @@
 
 template <typename Container>
 void Insert() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -805,7 +796,7 @@
 
 template <typename Container>
 void InsertNoBarrier() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container;
   {
     ExpectNoWriteBarrierFires scope(ThreadState::Current(), {obj});
@@ -815,7 +806,7 @@
 
 template <typename Container>
 void Copy() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container1;
   container1.insert(obj);
   {
@@ -828,7 +819,7 @@
 
 template <typename Container>
 void CopyNoBarrier() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container1;
   container1.insert(obj);
   {
@@ -841,7 +832,7 @@
 
 template <typename Container>
 void Move() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container1;
   Container container2;
   container1.insert(obj);
@@ -853,7 +844,7 @@
 
 template <typename Container>
 void MoveNoBarrier() {
-  Object* obj = Object::Create();
+  auto* obj = MakeGarbageCollected<Object>();
   Container container1;
   container1.insert(obj);
   {
@@ -864,8 +855,8 @@
 
 template <typename Container>
 void Swap() {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   Container container1;
   container1.insert(obj1);
   Container container2;
@@ -878,8 +869,8 @@
 
 template <typename Container>
 void SwapNoBarrier() {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   Container container1;
   container1.insert(obj1);
   Container container2;
@@ -1009,8 +1000,8 @@
 namespace incremental_marking_test {
 
 TEST(IncrementalMarkingTest, HeapHashSetStrongWeakPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashSet<StrongWeakPair> set;
   {
     // Both, the weak and the strong field, are hit by the write barrier.
@@ -1020,8 +1011,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapLinkedHashSetStrongWeakPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapLinkedHashSet<StrongWeakPair> set;
   {
     // Both, the weak and the strong field, are hit by the write barrier.
@@ -1073,8 +1064,8 @@
 TEST(IncrementalMarkingTest, HeapHashCountedSetSwap) {
   // HeapHashCountedSet is not move constructible so we cannot use std::swap.
   {
-    Object* obj1 = Object::Create();
-    Object* obj2 = Object::Create();
+    auto* obj1 = MakeGarbageCollected<Object>();
+    auto* obj2 = MakeGarbageCollected<Object>();
     HeapHashCountedSet<Member<Object>> container1;
     container1.insert(obj1);
     HeapHashCountedSet<Member<Object>> container2;
@@ -1085,8 +1076,8 @@
     }
   }
   {
-    Object* obj1 = Object::Create();
-    Object* obj2 = Object::Create();
+    auto* obj1 = MakeGarbageCollected<Object>();
+    auto* obj2 = MakeGarbageCollected<Object>();
     HeapHashCountedSet<WeakMember<Object>> container1;
     container1.insert(obj1);
     HeapHashCountedSet<WeakMember<Object>> container2;
@@ -1104,8 +1095,8 @@
 // =============================================================================
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj1, obj2});
@@ -1114,8 +1105,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, WeakMember<Object>> map;
   {
     // Weak references are strongified for the current cycle.
@@ -1125,8 +1116,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, WeakMember<Object>> map;
   {
     // Weak references are strongified for the current cycle.
@@ -1136,8 +1127,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertWeakMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, Member<Object>> map;
   {
     // Weak references are strongified for the current cycle.
@@ -1147,8 +1138,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSetMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   {
     ExpectWriteBarrierFires scope(ThreadState::Current(), {obj1, obj2});
@@ -1157,9 +1148,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSetMemberUpdateValue) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   map.insert(obj1, obj2);
   {
@@ -1173,9 +1164,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapIteratorChangeKey) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   map.insert(obj1, obj2);
   {
@@ -1187,9 +1178,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapIteratorChangeValue) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   map.insert(obj1, obj2);
   {
@@ -1201,8 +1192,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1215,8 +1206,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyWeakMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1230,8 +1221,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1245,8 +1236,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyWeakMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1260,8 +1251,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapMoveMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1271,8 +1262,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapMoveWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1283,8 +1274,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapMoveMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1295,8 +1286,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapMoveWeakMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   {
@@ -1307,10 +1298,10 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSwapMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
-  Object* obj4 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
+  auto* obj4 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   HeapHashMap<Member<Object>, Member<Object>> map2;
@@ -1323,10 +1314,10 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSwapWeakMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
-  Object* obj4 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
+  auto* obj4 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   HeapHashMap<WeakMember<Object>, WeakMember<Object>> map2;
@@ -1340,10 +1331,10 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSwapMemberWeakMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
-  Object* obj4 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
+  auto* obj4 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, WeakMember<Object>> map1;
   map1.insert(obj1, obj2);
   HeapHashMap<Member<Object>, WeakMember<Object>> map2;
@@ -1357,10 +1348,10 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapSwapWeakMemberMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
-  Object* obj4 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
+  auto* obj4 = MakeGarbageCollected<Object>();
   HeapHashMap<WeakMember<Object>, Member<Object>> map1;
   map1.insert(obj1, obj2);
   HeapHashMap<WeakMember<Object>, Member<Object>> map2;
@@ -1374,9 +1365,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertStrongWeakPairMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   HeapHashMap<StrongWeakPair, Member<Object>> map;
   {
     // Tests that the write barrier also fires for entities such as
@@ -1387,9 +1378,9 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapInsertMemberStrongWeakPair) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
-  Object* obj3 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
+  auto* obj3 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, StrongWeakPair> map;
   {
     // Tests that the write barrier also fires for entities such as
@@ -1400,8 +1391,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyKeysToVectorMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   map.insert(obj1, obj2);
   HeapVector<Member<Object>> vec;
@@ -1414,8 +1405,8 @@
 }
 
 TEST(IncrementalMarkingTest, HeapHashMapCopyValuesToVectorMember) {
-  Object* obj1 = Object::Create();
-  Object* obj2 = Object::Create();
+  auto* obj1 = MakeGarbageCollected<Object>();
+  auto* obj2 = MakeGarbageCollected<Object>();
   HeapHashMap<Member<Object>, Member<Object>> map;
   map.insert(obj1, obj2);
   HeapVector<Member<Object>> vec;
@@ -1433,7 +1424,7 @@
 TEST(IncrementalMarkingTest, DISABLED_WeakHashMapPromptlyFreeDisabled) {
   ThreadState* state = ThreadState::Current();
   state->SetGCState(ThreadState::kIncrementalMarkingStepScheduled);
-  Persistent<Object> obj1 = Object::Create();
+  Persistent<Object> obj1 = MakeGarbageCollected<Object>();
   NormalPageArena* arena = static_cast<NormalPageArena*>(
       ThreadState::Current()->Heap().Arena(BlinkGC::kHashTableArenaIndex));
   CHECK(arena);
@@ -1606,7 +1597,7 @@
   using WeakStore = HeapHashCountedSet<WeakMember<Object>>;
 
   Persistent<WeakStore> persistent(new WeakStore);
-  persistent->insert(Object::Create());
+  persistent->insert(MakeGarbageCollected<Object>());
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   driver.Start();
   driver.FinishSteps();
@@ -1627,8 +1618,8 @@
   // structure. The values for .second are chosen to be non-null as they
   // would otherwise count as empty and be skipped during iteration after the
   // first part died.
-  persistent->insert({Object::Create(), 1});
-  persistent->insert({Object::Create(), 2});
+  persistent->insert({MakeGarbageCollected<Object>(), 1});
+  persistent->insert({MakeGarbageCollected<Object>(), 2});
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   driver.Start();
   // The backing is not treated as weak backing and thus eagerly processed,
@@ -1662,11 +1653,11 @@
   // would trigger the write barrier, mitigating the bug where a backing store
   // is promptly freed.
   for (size_t i = 0; i < 8; i++) {
-    persistent->insert({Object::Create(), i});
+    persistent->insert({MakeGarbageCollected<Object>(), i});
   }
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   driver.Start();
-  persistent->insert({Object::Create(), 8});
+  persistent->insert({MakeGarbageCollected<Object>(), 8});
   // Is not allowed to free the backing store as the previous insert may have
   // registered a slot.
   persistent->clear();
@@ -1678,7 +1669,7 @@
   using Store = HeapHashCountedSet<Member<Object>>;
 
   Persistent<Store> persistent(new Store());
-  persistent->insert(Object::Create());
+  persistent->insert(MakeGarbageCollected<Object>());
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   HeapCompact::ScheduleCompactionGCForTesting(true);
   driver.Start();
@@ -1697,7 +1688,7 @@
 
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   HeapCompact::ScheduleCompactionGCForTesting(true);
-  persistent->push_back(Object::Create());
+  persistent->push_back(MakeGarbageCollected<Object>());
   driver.Start();
   driver.FinishGC();
 
@@ -1720,7 +1711,7 @@
   HeapCompact::ScheduleCompactionGCForTesting(true);
   driver.Start();
   driver.FinishSteps();
-  persistent->insert(Object::Create());
+  persistent->insert(MakeGarbageCollected<Object>());
   driver.FinishGC();
 
   // Weak callback should register the slot.
@@ -1730,7 +1721,7 @@
 TEST(IncrementalMarkingTest, ConservativeGCWhileCompactionScheduled) {
   using Store = HeapVector<Member<Object>>;
   Persistent<Store> persistent(MakeGarbageCollected<Store>());
-  persistent->push_back(Object::Create());
+  persistent->push_back(MakeGarbageCollected<Object>());
 
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   HeapCompact::ScheduleCompactionGCForTesting(true);
@@ -1769,7 +1760,7 @@
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   driver.Start();
   driver.FinishSteps();
-  persistent->set_object(Object::Create());
+  persistent->set_object(MakeGarbageCollected<Object>());
   driver.FinishGC();
   ConservativelyCollectGarbage();
 }
diff --git a/third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.cc b/third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.cc
index ac5f287a..758c465 100644
--- a/third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.cc
+++ b/third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.cc
@@ -26,8 +26,7 @@
                                        const char* units,
                                        const String& value) {
   StringUTF8Adaptor adapter(value);
-  std::string utf8(adapter.Data(), adapter.length());
-  memory_allocator_dump_->AddString(name, units, utf8);
+  memory_allocator_dump_->AddString(name, units, adapter.AsStdString());
 }
 
 WebMemoryAllocatorDumpGuid WebMemoryAllocatorDump::Guid() const {
diff --git a/third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.cc b/third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.cc
index a8eb0b1..f5c46937 100644
--- a/third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.cc
+++ b/third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.cc
@@ -36,10 +36,9 @@
 blink::WebMemoryAllocatorDump* WebProcessMemoryDump::CreateMemoryAllocatorDump(
     const String& absolute_name) {
   StringUTF8Adaptor adapter(absolute_name);
-  std::string name(adapter.Data(), adapter.length());
   // Get a MemoryAllocatorDump from the base/ object.
   base::trace_event::MemoryAllocatorDump* memory_allocator_dump =
-      process_memory_dump_->CreateAllocatorDump(name);
+      process_memory_dump_->CreateAllocatorDump(adapter.AsStdString());
 
   return CreateWebMemoryAllocatorDump(memory_allocator_dump);
 }
@@ -48,11 +47,11 @@
     const String& absolute_name,
     blink::WebMemoryAllocatorDumpGuid guid) {
   StringUTF8Adaptor adapter(absolute_name);
-  std::string name(adapter.Data(), adapter.length());
   // Get a MemoryAllocatorDump from the base/ object with given guid.
   base::trace_event::MemoryAllocatorDump* memory_allocator_dump =
       process_memory_dump_->CreateAllocatorDump(
-          name, base::trace_event::MemoryAllocatorDumpGuid(guid));
+          adapter.AsStdString(),
+          base::trace_event::MemoryAllocatorDumpGuid(guid));
   return CreateWebMemoryAllocatorDump(memory_allocator_dump);
 }
 
@@ -76,11 +75,10 @@
 blink::WebMemoryAllocatorDump* WebProcessMemoryDump::GetMemoryAllocatorDump(
     const String& absolute_name) const {
   StringUTF8Adaptor adapter(absolute_name);
-  std::string name(adapter.Data(), adapter.length());
   // Retrieve the base MemoryAllocatorDump object and then reverse lookup
   // its wrapper.
   base::trace_event::MemoryAllocatorDump* memory_allocator_dump =
-      process_memory_dump_->GetAllocatorDump(name);
+      process_memory_dump_->GetAllocatorDump(adapter.AsStdString());
   if (!memory_allocator_dump)
     return nullptr;
 
@@ -148,17 +146,16 @@
     blink::WebMemoryAllocatorDumpGuid source,
     const String& target_node_name) {
   StringUTF8Adaptor adapter(target_node_name);
-  std::string target_node(adapter.Data(), adapter.length());
   process_memory_dump_->AddSuballocation(
-      base::trace_event::MemoryAllocatorDumpGuid(source), target_node);
+      base::trace_event::MemoryAllocatorDumpGuid(source),
+      adapter.AsStdString());
 }
 
 SkTraceMemoryDump* WebProcessMemoryDump::CreateDumpAdapterForSkia(
     const String& dump_name_prefix) {
   StringUTF8Adaptor adapter(dump_name_prefix);
-  std::string prefix(adapter.Data(), adapter.length());
   sk_trace_dump_list_.push_back(std::make_unique<skia::SkiaTraceMemoryDumpImpl>(
-      prefix, level_of_detail_, process_memory_dump_));
+      adapter.AsStdString(), level_of_detail_, process_memory_dump_));
   return sk_trace_dump_list_.back().get();
 }
 
diff --git a/third_party/blink/renderer/platform/link_hash.cc b/third_party/blink/renderer/platform/link_hash.cc
index 17328c1..4a1c915 100644
--- a/third_party/blink/renderer/platform/link_hash.cc
+++ b/third_party/blink/renderer/platform/link_hash.cc
@@ -46,11 +46,11 @@
   StringUTF8Adaptor base_utf8(base.GetString());
   if (relative.Is8Bit()) {
     StringUTF8Adaptor relative_utf8(relative);
-    return url::ResolveRelative(
-        base_utf8.Data(), base_utf8.length(), base.GetParsed(),
-        relative_utf8.Data(), relative_utf8.length(), nullptr, buffer, &parsed);
+    return url::ResolveRelative(base_utf8.data(), base_utf8.size(),
+                                base.GetParsed(), relative_utf8.data(),
+                                relative_utf8.size(), nullptr, buffer, &parsed);
   }
-  return url::ResolveRelative(base_utf8.Data(), base_utf8.length(),
+  return url::ResolveRelative(base_utf8.data(), base_utf8.size(),
                               base.GetParsed(), relative.Characters16(),
                               relative.length(), nullptr, buffer, &parsed);
 }
diff --git a/third_party/blink/renderer/platform/mojo/DEPS b/third_party/blink/renderer/platform/mojo/DEPS
index ea09dcb..cb52188 100644
--- a/third_party/blink/renderer/platform/mojo/DEPS
+++ b/third_party/blink/renderer/platform/mojo/DEPS
@@ -10,6 +10,7 @@
     "+base/containers/span.h",
     "+base/message_loop/message_loop_current.h",
     "+base/observer_list.h",
+    "+base/strings/latin1_string_conversions.h",
     "+base/strings/string16.h",
     "+mojo/public/cpp/base/time_mojom_traits.h",
     "+mojo/public/cpp/bindings/binding.h",
diff --git a/third_party/blink/renderer/platform/mojo/big_string_mojom_traits.cc b/third_party/blink/renderer/platform/mojo/big_string_mojom_traits.cc
index 1090633..a0c0200 100644
--- a/third_party/blink/renderer/platform/mojo/big_string_mojom_traits.cc
+++ b/third_party/blink/renderer/platform/mojo/big_string_mojom_traits.cc
@@ -17,9 +17,7 @@
 mojo_base::BigBuffer StructTraits<mojo_base::mojom::BigStringDataView,
                                   WTF::String>::data(const WTF::String& input) {
   WTF::StringUTF8Adaptor adaptor(input);
-  return mojo_base::BigBuffer(
-      base::make_span(reinterpret_cast<const uint8_t*>(adaptor.Data()),
-                      adaptor.length() * sizeof(char)));
+  return mojo_base::BigBuffer(base::as_bytes(base::make_span(adaptor)));
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/mojo/string16_mojom_traits.cc b/third_party/blink/renderer/platform/mojo/string16_mojom_traits.cc
index 985601f7..1c8d026 100644
--- a/third_party/blink/renderer/platform/mojo/string16_mojom_traits.cc
+++ b/third_party/blink/renderer/platform/mojo/string16_mojom_traits.cc
@@ -7,48 +7,32 @@
 #include <cstring>
 
 #include "base/containers/span.h"
+#include "base/strings/latin1_string_conversions.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
 
 namespace mojo {
 
-// static
-void* StructTraits<mojo_base::mojom::String16DataView,
-                   WTF::String>::SetUpContext(const WTF::String& input) {
-  // If it is null (i.e., StructTraits<>::IsNull() returns true), this method is
-  // guaranteed not to be called.
-  DCHECK(!input.IsNull());
+MaybeOwnedString16::MaybeOwnedString16(base::string16 owned_storage)
+    : owned_storage_(owned_storage),
+      unowned_(base::make_span(
+          reinterpret_cast<const uint16_t*>(owned_storage_.data()),
+          owned_storage_.size())) {}
 
-  if (!input.Is8Bit())
-    return nullptr;
+MaybeOwnedString16::MaybeOwnedString16(base::span<const uint16_t> unowned)
+    : unowned_(unowned) {}
 
-  return new base::string16(input.Characters8(),
-                            input.Characters8() + input.length());
-}
+MaybeOwnedString16::~MaybeOwnedString16() = default;
 
 // static
-void StructTraits<mojo_base::mojom::String16DataView,
-                  WTF::String>::TearDownContext(const WTF::String& input,
-                                                void* context) {
-  delete static_cast<base::string16*>(context);
-}
-
-// static
-base::span<const uint16_t>
-StructTraits<mojo_base::mojom::String16DataView, WTF::String>::data(
-    const WTF::String& input,
-    void* context) {
-  auto* contextObject = static_cast<base::string16*>(context);
-  DCHECK_EQ(input.Is8Bit(), !!contextObject);
-
-  if (contextObject) {
-    return base::make_span(
-        reinterpret_cast<const uint16_t*>(contextObject->data()),
-        contextObject->size());
+MaybeOwnedString16 StructTraits<mojo_base::mojom::String16DataView,
+                                WTF::String>::data(const WTF::String& input) {
+  if (input.Is8Bit()) {
+    return MaybeOwnedString16(base::Latin1OrUTF16ToUTF16(
+        input.length(), input.Characters8(), nullptr));
   }
-
-  return base::make_span(
-      reinterpret_cast<const uint16_t*>(input.Characters16()), input.length());
+  return MaybeOwnedString16(base::make_span(
+      reinterpret_cast<const uint16_t*>(input.Characters16()), input.length()));
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/mojo/string16_mojom_traits.h b/third_party/blink/renderer/platform/mojo/string16_mojom_traits.h
index 101e335..a73f44c 100644
--- a/third_party/blink/renderer/platform/mojo/string16_mojom_traits.h
+++ b/third_party/blink/renderer/platform/mojo/string16_mojom_traits.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_STRING16_MOJOM_TRAITS_H_
 
 #include "base/containers/span.h"
+#include "base/logging.h"
 #include "base/strings/string16.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "mojo/public/mojom/base/string16.mojom-blink.h"
@@ -17,17 +18,47 @@
 
 namespace mojo {
 
+// WTF::String stores string data as 8-bit strings if only Latin-1 characters
+// are present. During Mojo serialization, this helper provides a scratch buffer
+// that can be used for converting an 8-bit string to a 16-bit string.
+class PLATFORM_EXPORT MaybeOwnedString16 {
+ public:
+  explicit MaybeOwnedString16(base::string16 owned_storage);
+  explicit MaybeOwnedString16(base::span<const uint16_t> unowned);
+  ~MaybeOwnedString16();
+
+  const uint16_t* data() const { return unowned_.data(); }
+  size_t size() const { return unowned_.size(); }
+
+ private:
+  base::string16 owned_storage_;
+  base::span<const uint16_t> unowned_;
+};
+
+template <>
+struct PLATFORM_EXPORT ArrayTraits<MaybeOwnedString16> {
+  using Element = const uint16_t;
+
+  static bool IsNull(const MaybeOwnedString16&) { return false; }
+  static size_t GetSize(const MaybeOwnedString16& input) {
+    return input.size();
+  }
+  static const Element* GetData(const MaybeOwnedString16& input) {
+    return input.data();
+  }
+  static const Element& GetAt(const MaybeOwnedString16& input, size_t index) {
+    return input.data()[index];
+  }
+};
+
 template <>
 struct PLATFORM_EXPORT
     StructTraits<mojo_base::mojom::String16DataView, WTF::String> {
   static bool IsNull(const WTF::String& input) { return input.IsNull(); }
   static void SetToNull(WTF::String* output) { *output = WTF::String(); }
 
-  static void* SetUpContext(const WTF::String& input);
-  static void TearDownContext(const WTF::String& input, void* context);
+  static MaybeOwnedString16 data(const WTF::String& input);
 
-  static base::span<const uint16_t> data(const WTF::String& input,
-                                         void* context);
   static bool Read(mojo_base::mojom::String16DataView, WTF::String* out);
 };
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index ba6b3d8..70966b3 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -162,6 +162,7 @@
     {
       name: "BlinkGenPropertyTrees",
       status: "stable",
+      implied_by: ["CompositeAfterPaint"],
     },
     {
       name: "BlinkRuntimeCallStats",
diff --git a/third_party/blink/renderer/platform/testing/paint_test_configurations.h b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
index 614813a..f25b5f9 100644
--- a/third_party/blink/renderer/platform/testing/paint_test_configurations.h
+++ b/third_party/blink/renderer/platform/testing/paint_test_configurations.h
@@ -37,16 +37,19 @@
 #define INSTANTIATE_PAINT_TEST_SUITE_P(test_class) \
   INSTANTIATE_TEST_SUITE_P(                        \
       All, test_class,                             \
-      ::testing::Values(0, kBlinkGenPropertyTrees, kCompositeAfterPaint))
+      ::testing::Values(0, kBlinkGenPropertyTrees, \
+                        kBlinkGenPropertyTrees | kCompositeAfterPaint))
 
 #define INSTANTIATE_CAP_TEST_SUITE_P(test_class) \
-  INSTANTIATE_TEST_SUITE_P(All, test_class,      \
-                           ::testing::Values(kCompositeAfterPaint))
+  INSTANTIATE_TEST_SUITE_P(                      \
+      All, test_class,                           \
+      ::testing::Values(kBlinkGenPropertyTrees | kCompositeAfterPaint))
 
 #define INSTANTIATE_LAYER_LIST_TEST_SUITE_P(test_class) \
   INSTANTIATE_TEST_SUITE_P(                             \
       All, test_class,                                  \
-      ::testing::Values(kBlinkGenPropertyTrees, kCompositeAfterPaint))
+      ::testing::Values(kBlinkGenPropertyTrees,         \
+                        kBlinkGenPropertyTrees | kCompositeAfterPaint))
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/platform/weborigin/known_ports.cc b/third_party/blink/renderer/platform/weborigin/known_ports.cc
index bd0845ce..609b35a 100644
--- a/third_party/blink/renderer/platform/weborigin/known_ports.cc
+++ b/third_party/blink/renderer/platform/weborigin/known_ports.cc
@@ -79,8 +79,7 @@
   if (!effective_port)
     effective_port = DefaultPortForProtocol(protocol);
   StringUTF8Adaptor utf8(protocol);
-  return net::IsPortAllowedForScheme(effective_port,
-                                     std::string(utf8.Data(), utf8.length()));
+  return net::IsPortAllowedForScheme(effective_port, utf8.AsStdString());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.cc b/third_party/blink/renderer/platform/weborigin/kurl.cc
index b9a6a4c..8d66e1c 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.cc
+++ b/third_party/blink/renderer/platform/weborigin/kurl.cc
@@ -71,7 +71,7 @@
 // null character pointer since ReplaceComponents has special meaning for null.
 static const char* CharactersOrEmpty(const StringUTF8Adaptor& string) {
   static const char kZero = 0;
-  return string.Data() ? string.Data() : &kZero;
+  return string.data() ? string.data() : &kZero;
 }
 
 static bool IsSchemeFirstChar(char c) {
@@ -430,15 +430,15 @@
   // the URL and set "m_isValid."
   url::RawCanonOutputT<char> canon_protocol;
   url::Component protocol_component;
-  if (!url::CanonicalizeScheme(new_protocol_utf8.Data(),
-                               url::Component(0, new_protocol_utf8.length()),
+  if (!url::CanonicalizeScheme(new_protocol_utf8.data(),
+                               url::Component(0, new_protocol_utf8.size()),
                                &canon_protocol, &protocol_component) ||
       !protocol_component.is_nonempty())
     return false;
 
   url::Replacements<char> replacements;
   replacements.SetScheme(CharactersOrEmpty(new_protocol_utf8),
-                         url::Component(0, new_protocol_utf8.length()));
+                         url::Component(0, new_protocol_utf8.size()));
   ReplaceComponents(replacements);
 
   // isValid could be false but we still return true here. This is because
@@ -454,7 +454,7 @@
   StringUTF8Adaptor host_utf8(host);
   url::Replacements<char> replacements;
   replacements.SetHost(CharactersOrEmpty(host_utf8),
-                       url::Component(0, host_utf8.length()));
+                       url::Component(0, host_utf8.size()));
   ReplaceComponents(replacements);
 }
 
@@ -485,7 +485,7 @@
     url::Replacements<char> replacements;
     StringUTF8Adaptor host_utf8(host_and_port);
     replacements.SetHost(CharactersOrEmpty(host_utf8),
-                         url::Component(0, host_utf8.length()));
+                         url::Component(0, host_utf8.size()));
     ReplaceComponents(replacements);
     return;
   }
@@ -498,9 +498,9 @@
 
   url::Replacements<char> replacements;
   replacements.SetHost(CharactersOrEmpty(host_utf8),
-                       url::Component(0, host_utf8.length()));
+                       url::Component(0, host_utf8.size()));
   replacements.SetPort(CharactersOrEmpty(port_utf8),
-                       url::Component(0, port_utf8.length()));
+                       url::Component(0, port_utf8.size()));
   ReplaceComponents(replacements);
 }
 
@@ -543,7 +543,7 @@
   StringUTF8Adaptor user_utf8(user);
   url::Replacements<char> replacements;
   replacements.SetUsername(CharactersOrEmpty(user_utf8),
-                           url::Component(0, user_utf8.length()));
+                           url::Component(0, user_utf8.size()));
   ReplaceComponents(replacements);
 }
 
@@ -558,7 +558,7 @@
   StringUTF8Adaptor pass_utf8(pass);
   url::Replacements<char> replacements;
   replacements.SetPassword(CharactersOrEmpty(pass_utf8),
-                           url::Component(0, pass_utf8.length()));
+                           url::Component(0, pass_utf8.size()));
   ReplaceComponents(replacements);
 }
 
@@ -571,11 +571,12 @@
   StringUTF8Adaptor fragment_utf8(fragment);
 
   url::Replacements<char> replacements;
-  if (fragment.IsNull())
+  if (fragment.IsNull()) {
     replacements.ClearRef();
-  else
+  } else {
     replacements.SetRef(CharactersOrEmpty(fragment_utf8),
-                        url::Component(0, fragment_utf8.length()));
+                        url::Component(0, fragment_utf8.size()));
+  }
   ReplaceComponents(replacements);
 }
 
@@ -595,7 +596,7 @@
     // WebCore expects the query string to begin with a question mark, but
     // GoogleURL doesn't. So we trim off the question mark when setting.
     replacements.SetQuery(CharactersOrEmpty(query_utf8),
-                          url::Component(1, query_utf8.length() - 1));
+                          url::Component(1, query_utf8.size() - 1));
   } else {
     // When set with the empty string or something that doesn't begin with
     // a question mark, KURL.cpp will add a question mark for you. The only
@@ -604,7 +605,7 @@
     // URL, whereas we'll clear it.
     // FIXME We should eliminate this difference.
     replacements.SetQuery(CharactersOrEmpty(query_utf8),
-                          url::Component(0, query_utf8.length()));
+                          url::Component(0, query_utf8.size()));
   }
   ReplaceComponents(replacements);
 }
@@ -615,14 +616,14 @@
   StringUTF8Adaptor path_utf8(path);
   url::Replacements<char> replacements;
   replacements.SetPath(CharactersOrEmpty(path_utf8),
-                       url::Component(0, path_utf8.length()));
+                       url::Component(0, path_utf8.size()));
   ReplaceComponents(replacements);
 }
 
 String DecodeURLEscapeSequences(const String& string, DecodeURLMode mode) {
   StringUTF8Adaptor string_utf8(string);
   url::RawCanonOutputT<base::char16> unescaped;
-  url::DecodeURLEscapeSequences(string_utf8.Data(), string_utf8.length(), mode,
+  url::DecodeURLEscapeSequences(string_utf8.data(), string_utf8.size(), mode,
                                 &unescaped);
   return StringImpl::Create8BitIfPossible(
       reinterpret_cast<UChar*>(unescaped.data()), unescaped.length());
@@ -743,12 +744,12 @@
   url::RawCanonOutputT<char> output;
   if (!relative.IsNull() && relative.Is8Bit()) {
     StringUTF8Adaptor relative_utf8(relative);
-    is_valid_ = url::ResolveRelative(base_utf8.Data(), base_utf8.length(),
-                                     base.parsed_, relative_utf8.Data(),
-                                     clampTo<int>(relative_utf8.length()),
+    is_valid_ = url::ResolveRelative(base_utf8.data(), base_utf8.size(),
+                                     base.parsed_, relative_utf8.data(),
+                                     clampTo<int>(relative_utf8.size()),
                                      charset_converter, &output, &parsed_);
   } else {
-    is_valid_ = url::ResolveRelative(base_utf8.Data(), base_utf8.length(),
+    is_valid_ = url::ResolveRelative(base_utf8.data(), base_utf8.size(),
                                      base.parsed_, relative.Characters16(),
                                      clampTo<int>(relative.length()),
                                      charset_converter, &output, &parsed_);
@@ -857,7 +858,7 @@
 
   StringUTF8Adaptor utf8(string_);
   is_valid_ =
-      url::ReplaceComponents(utf8.Data(), utf8.length(), parsed_, replacements,
+      url::ReplaceComponents(utf8.data(), utf8.size(), parsed_, replacements,
                              nullptr, &output, &new_parsed);
 
   parsed_ = new_parsed;
@@ -872,7 +873,7 @@
 
 KURL::operator GURL() const {
   StringUTF8Adaptor utf8(string_);
-  return GURL(utf8.Data(), utf8.length(), parsed_, is_valid_);
+  return GURL(utf8.data(), utf8.size(), parsed_, is_valid_);
 }
 bool operator==(const KURL& a, const KURL& b) {
   return a.GetString() == b.GetString();
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin.cc b/third_party/blink/renderer/platform/weborigin/security_origin.cc
index 62f6af52..9d0f811 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin.cc
@@ -651,9 +651,8 @@
   url::RawCanonOutputT<char> canon_output;
   if (host.Is8Bit()) {
     StringUTF8Adaptor utf8(host);
-    *success =
-        url::CanonicalizeHost(utf8.Data(), url::Component(0, utf8.length()),
-                              &canon_output, &out_host);
+    *success = url::CanonicalizeHost(
+        utf8.data(), url::Component(0, utf8.size()), &canon_output, &out_host);
   } else {
     *success = url::CanonicalizeHost(host.Characters16(),
                                      url::Component(0, host.length()),
diff --git a/third_party/blink/renderer/platform/wtf/BUILD.gn b/third_party/blink/renderer/platform/wtf/BUILD.gn
index 9d861555..f4a73898 100644
--- a/third_party/blink/renderer/platform/wtf/BUILD.gn
+++ b/third_party/blink/renderer/platform/wtf/BUILD.gn
@@ -148,6 +148,7 @@
     "text/string_statics.h",
     "text/string_to_number.cc",
     "text/string_to_number.h",
+    "text/string_utf8_adaptor.cc",
     "text/string_utf8_adaptor.h",
     "text/string_view.cc",
     "text/string_view.h",
diff --git a/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.cc b/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.cc
new file mode 100644
index 0000000..9ead0694
--- /dev/null
+++ b/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.cc
@@ -0,0 +1,29 @@
+// 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 "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
+
+namespace WTF {
+
+StringUTF8Adaptor::StringUTF8Adaptor(const String& string,
+                                     UTF8ConversionMode mode) {
+  if (string.IsEmpty())
+    return;
+  // Unfortunately, 8 bit WTFStrings are encoded in Latin-1 and GURL uses
+  // UTF-8 when processing 8 bit strings. If |relative| is entirely ASCII, we
+  // luck out and can avoid mallocing a new buffer to hold the UTF-8 data
+  // because UTF-8 and Latin-1 use the same code units for ASCII code points.
+  if (string.Is8Bit() && string.ContainsOnlyASCIIOrEmpty()) {
+    data_ = reinterpret_cast<const char*>(string.Characters8());
+    size_ = string.length();
+  } else {
+    utf8_buffer_ = string.Utf8(mode);
+    data_ = utf8_buffer_.data();
+    size_ = utf8_buffer_.length();
+  }
+}
+
+StringUTF8Adaptor::~StringUTF8Adaptor() = default;
+
+}  // namespace WTF
diff --git a/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h b/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h
index 8babc91..de0c404 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h
+++ b/third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h
@@ -32,50 +32,43 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_TEXT_STRING_UTF8_ADAPTOR_H_
 
 #include "base/strings/string_piece.h"
-#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/cstring.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/wtf_export.h"
 
 namespace WTF {
 
 // This class lets you get UTF-8 data out of a String without mallocing a
 // separate buffer to hold the data if the String happens to be 8 bit and
 // contain only ASCII characters.
-class StringUTF8Adaptor final {
+class WTF_EXPORT StringUTF8Adaptor final {
   DISALLOW_NEW();
 
  public:
-  StringUTF8Adaptor(const String& string,
-                    UTF8ConversionMode mode = kLenientUTF8Conversion)
-      : data_(nullptr), length_(0) {
-    if (string.IsEmpty())
-      return;
-    // Unfortunately, 8 bit WTFStrings are encoded in Latin-1 and GURL uses
-    // UTF-8 when processing 8 bit strings. If |relative| is entirely ASCII, we
-    // luck out and can avoid mallocing a new buffer to hold the UTF-8 data
-    // because UTF-8 and Latin-1 use the same code units for ASCII code points.
-    if (string.Is8Bit() && string.ContainsOnlyASCIIOrEmpty()) {
-      data_ = reinterpret_cast<const char*>(string.Characters8());
-      length_ = string.length();
-    } else {
-      utf8_buffer_ = string.Utf8(mode);
-      data_ = utf8_buffer_.data();
-      length_ = utf8_buffer_.length();
-    }
-  }
+  using value_type = const char;
 
-  const char* Data() const { return data_; }
-  wtf_size_t length() const { return length_; }
+  StringUTF8Adaptor(const String& string,
+                    UTF8ConversionMode mode = kLenientUTF8Conversion);
+  ~StringUTF8Adaptor();
+
+  const char* data() const { return data_; }
+  wtf_size_t size() const { return size_; }
 
   base::StringPiece AsStringPiece() const {
-    return base::StringPiece(data_, length_);
+    return base::StringPiece(data_, size_);
+  }
+
+  std::string AsStdString() {
+    // TODO(dcheng): it might be nice to store a std::string and avoid the
+    // double conversion...
+    return std::string(data_, size_);
   }
 
  private:
   CString utf8_buffer_;
-  const char* data_;
-  wtf_size_t length_;
+  const char* data_ = nullptr;
+  wtf_size_t size_ = 0;
 };
 
 }  // namespace WTF
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index 9d1b7ccb..b9137fa 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -459,6 +459,11 @@
 # Other:
 crbug.com/935728 fast/frames/transparent-scrollbar.html [ Failure ]
 
+# Wrong contentsOpaque for will-change: opacity layers
+Bug(none) compositing/will-change/composited-layers.html [ Failure ]
+# Crash on non-contiguous effect on multiple columns
+Bug(none) fast/multicol/composited-layer-will-change.html [ Crash ]
+
 Bug(none) external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg [ Failure ]
 Bug(none) external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg [ Failure ]
 Bug(none) external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg [ Failure ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 3d3cf90..1b7fca1 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -294,9 +294,6 @@
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-preserve3d-010.html [ WontFix ]
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-preserve3d-013.html [ WontFix ]
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-rotatex-perspective-003.html [ WontFix ]
-crbug.com/753080 external/wpt/css/css-transforms/transform3d-scale-005.html [ WontFix ]
-crbug.com/753080 external/wpt/css/css-transforms/transform3d-scale-006.html [ WontFix ]
-crbug.com/753080 external/wpt/css/css-transforms/transform3d-scale-007.html [ WontFix ]
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-sorting-002.html [ WontFix ]
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-sorting-004.html [ WontFix ]
 crbug.com/753080 external/wpt/css/css-transforms/transform3d-sorting-006.html [ WontFix ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index dd06f72..1ff30eb56 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2269,6 +2269,8 @@
 crbug.com/492664 external/wpt/css/css-writing-modes/ortho-htb-alongside-vrl-floats-014.xht [ Failure ]
 crbug.com/492664 external/wpt/css/css-writing-modes/two-levels-of-orthogonal-flows-percentage.html [ Failure ]
 
+crbug.com/637055 fast/css/outline-offset-large.html [ Skip ]
+
 # Either "combo" or split should run: http://testthewebforward.org/docs/css-naming.html
 crbug.com/410320 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001.html [ Skip ]
 crbug.com/492664 external/wpt/css/css-writing-modes/text-orientation-script-001.html [ Skip ]
@@ -3025,6 +3027,32 @@
 crbug.com/939181 virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html [ Failure Timeout ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-090a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-046a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-091-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-048a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-092a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-096-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-048-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-003-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-091a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-002-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-090-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-092-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-095a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-049-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-041-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-040a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-097-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-001-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-045-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-082-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-096a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-044-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-004-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-085-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-097a-manual.html [ Skip ]
+crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-040-manual.html [ Skip ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/media-elements/src_object_blob.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/service-workers/service-worker/websocket.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/websockets/Secure-Close-4999-reason.any.html [ Timeout ]
@@ -5373,6 +5401,7 @@
 crbug.com/824539 [ Android ] fast/css/first-letter-hover.html [ Failure Timeout ]
 crbug.com/824539 [ Android ] fast/css/first-letter-visibility.html [ Failure Timeout ]
 crbug.com/824539 [ Android ] fast/css/focus-ring-multiline.html [ Failure Timeout ]
+crbug.com/824539 [ Android ] fast/css/font-face-default-font.html [ Timeout ]
 crbug.com/824539 [ Android ] fast/css/invalid-percentage-property.html [ Failure Timeout ]
 crbug.com/824539 [ Android ] fast/css/line-height-font-order.html [ Failure Timeout ]
 crbug.com/824539 [ Android ] fast/css/line-thickness-underline-strikethrough-overline.html [ Failure Timeout ]
diff --git a/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform-expected.txt b/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform-expected.txt
deleted file mode 100644
index 3e83a81..0000000
--- a/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-Running, 0 complete, 1 remain
diff --git a/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform.html b/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform.html
index 9373fcb..162cab2 100644
--- a/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform.html
+++ b/third_party/blink/web_tests/compositing/squashing/dont-squash-with-scale-transform.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
 <style>
 .scale-transform {
   transform: scale(0.75);
@@ -12,17 +13,17 @@
 <div class="scale-transform" style="position: absolute; width: 200px; height: 200px; top: 100px; left: 100px; background: lightblue"></div>
 <div class="translate-transform" style="position: absolute; width: 200px; height: 200px; top: 100px; left: 100px; background: lightblue"></div>
 <script>
-if (window.testRunner)
-    testRunner.dumpAsText();
 onload = function() {
-    if (window.internals) {
-        var layers = JSON.parse(internals.layerTreeAsText(document, internals.LAYER_TREE_INCLUDES_DEBUG_INFO))["layers"];
+  if (!window.internals)
+    return;
 
-        var willChangeDIVLayer = layers[4];
-        var scaleTransformDIVLayer = layers[5];
-        // Both transformed layers squash together after the will-change layer.
-        assert_true(willChangeDIVLayer.compositingReasons[0] == "Has a will-change compositing hint");
-        assert_true(scaleTransformDIVLayer.compositingReasons[0] == "Secondary layer, home for a group of squashable content");
-    }
+  test(function() {
+    var layers = JSON.parse(internals.layerTreeAsText(document, internals.LAYER_TREE_INCLUDES_DEBUG_INFO))["layers"];
+    var willChangeDIVLayer = layers[4];
+    var scaleTransformDIVLayer = layers[5];
+    // Both transformed layers squash together after the will-change layer.
+    assert_equals(willChangeDIVLayer.compositingReasons[0], "Has a will-change: transform compositing hint");
+    assert_equals(scaleTransformDIVLayer.compositingReasons[0], "Secondary layer, home for a group of squashable content");
+  });
 };
 </script>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index 7cf3e09..e25d2ab0 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -1681,6 +1681,162 @@
      {}
     ]
    ],
+   "css/css-text-decor/text-decoration-001-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-001-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-002-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-002-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-003-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-003-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-004-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-004-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-040-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-040-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-040a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-040a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-041-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-041-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-044-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-044-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-045-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-045-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-046a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-046a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-048-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-048-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-048a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-048a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-049-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-049-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-082-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-082-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-085-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-085-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-090-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-090-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-090a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-090a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-091-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-091-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-091a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-091a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-092-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-092-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-092a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-092a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-095a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-095a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-096-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-096-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-096a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-096a-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-097-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-097-manual.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/text-decoration-097a-manual.html": [
+    [
+     "/css/css-text-decor/text-decoration-097a-manual.html",
+     {}
+    ]
+   ],
    "css/css-text-decor/text-decoration-line-014.xht": [
     [
      "/css/css-text-decor/text-decoration-line-014.xht",
@@ -110165,6 +110321,18 @@
      {}
     ]
    ],
+   "svg/extensibility/foreignObject/compositing-backface-visibility.html": [
+    [
+     "/svg/extensibility/foreignObject/compositing-backface-visibility.html",
+     [
+      [
+       "/svg/extensibility/foreignObject/compositing-backface-visibility-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "svg/extensibility/foreignObject/foreign-object-margin-collapsing.html": [
     [
      "/svg/extensibility/foreignObject/foreign-object-margin-collapsing.html",
@@ -190052,6 +190220,11 @@
      {}
     ]
    ],
+   "svg/extensibility/foreignObject/compositing-backface-visibility-ref.html": [
+    [
+     {}
+    ]
+   ],
    "svg/extensibility/foreignObject/foreign-object-paints-before-rect-ref.html": [
     [
      {}
@@ -236324,6 +236497,18 @@
      }
     ]
    ],
+   "element-timing/background-image-multiple-elements.html": [
+    [
+     "/element-timing/background-image-multiple-elements.html",
+     {}
+    ]
+   ],
+   "element-timing/background-image-stretched.html": [
+    [
+     "/element-timing/background-image-stretched.html",
+     {}
+    ]
+   ],
    "element-timing/buffer-before-onload.html": [
     [
      "/element-timing/buffer-before-onload.html",
@@ -236390,6 +236575,18 @@
      {}
     ]
    ],
+   "element-timing/multiple-background-images.html": [
+    [
+     "/element-timing/multiple-background-images.html",
+     {}
+    ]
+   ],
+   "element-timing/observe-background-image.html": [
+    [
+     "/element-timing/observe-background-image.html",
+     {}
+    ]
+   ],
    "element-timing/observe-child-element.html": [
     [
      "/element-timing/observe-child-element.html",
@@ -277699,6 +277896,7 @@
     [
      "/portals/portals-post-message.sub.html",
      {
+      "testdriver": true,
       "timeout": "long"
      }
     ]
@@ -310963,162 +311161,6 @@
      {}
     ]
    ],
-   "css/css-text-decor/text-decoration-001.html": [
-    [
-     "/css/css-text-decor/text-decoration-001.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-002.html": [
-    [
-     "/css/css-text-decor/text-decoration-002.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-003.html": [
-    [
-     "/css/css-text-decor/text-decoration-003.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-004.html": [
-    [
-     "/css/css-text-decor/text-decoration-004.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-040.html": [
-    [
-     "/css/css-text-decor/text-decoration-040.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-040a.html": [
-    [
-     "/css/css-text-decor/text-decoration-040a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-041.html": [
-    [
-     "/css/css-text-decor/text-decoration-041.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-044.html": [
-    [
-     "/css/css-text-decor/text-decoration-044.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-045.html": [
-    [
-     "/css/css-text-decor/text-decoration-045.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-046a.html": [
-    [
-     "/css/css-text-decor/text-decoration-046a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-048.html": [
-    [
-     "/css/css-text-decor/text-decoration-048.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-048a.html": [
-    [
-     "/css/css-text-decor/text-decoration-048a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-049.html": [
-    [
-     "/css/css-text-decor/text-decoration-049.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-082.html": [
-    [
-     "/css/css-text-decor/text-decoration-082.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-085.html": [
-    [
-     "/css/css-text-decor/text-decoration-085.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-090.html": [
-    [
-     "/css/css-text-decor/text-decoration-090.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-090a.html": [
-    [
-     "/css/css-text-decor/text-decoration-090a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-091.html": [
-    [
-     "/css/css-text-decor/text-decoration-091.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-091a.html": [
-    [
-     "/css/css-text-decor/text-decoration-091a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-092.html": [
-    [
-     "/css/css-text-decor/text-decoration-092.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-092a.html": [
-    [
-     "/css/css-text-decor/text-decoration-092a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-095a.html": [
-    [
-     "/css/css-text-decor/text-decoration-095a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-096.html": [
-    [
-     "/css/css-text-decor/text-decoration-096.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-096a.html": [
-    [
-     "/css/css-text-decor/text-decoration-096a.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-097.html": [
-    [
-     "/css/css-text-decor/text-decoration-097.html",
-     {}
-    ]
-   ],
-   "css/css-text-decor/text-decoration-097a.html": [
-    [
-     "/css/css-text-decor/text-decoration-097a.html",
-     {}
-    ]
-   ],
    "css/css-text-decor/text-decoration-visibility-001.xht": [
     [
      "/css/css-text-decor/text-decoration-visibility-001.xht",
@@ -373634,109 +373676,109 @@
    "6bf79ba4527f99af740bdeca945449e8f9ed7a57",
    "support"
   ],
-  "css/css-text-decor/text-decoration-001.html": [
+  "css/css-text-decor/text-decoration-001-manual.html": [
    "9b620132697dfbf68f0d10575afaf7d902e649da",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-002.html": [
+  "css/css-text-decor/text-decoration-002-manual.html": [
    "71c815b34e0eb43d38456ac3ec67cf3a7f96308c",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-003.html": [
+  "css/css-text-decor/text-decoration-003-manual.html": [
    "ee987654bf1eab14c45393314adcc6425713c154",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-004.html": [
+  "css/css-text-decor/text-decoration-004-manual.html": [
    "0419d85f9625f07d439b57ebc72a422ced6a5099",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-040.html": [
+  "css/css-text-decor/text-decoration-040-manual.html": [
    "63b63759197870f9a0616b853a4dd58e282876b1",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-040a.html": [
+  "css/css-text-decor/text-decoration-040a-manual.html": [
    "90963c8db6ca41c9c962ee0f38d6153cc9e7fc2b",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-041.html": [
+  "css/css-text-decor/text-decoration-041-manual.html": [
    "32e0598ab4a9268fd4c2f4b48dec7bbc4632ab0b",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-044.html": [
+  "css/css-text-decor/text-decoration-044-manual.html": [
    "63732387f27a9bd32ee42fcf3400f4b8a22c642c",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-045.html": [
+  "css/css-text-decor/text-decoration-045-manual.html": [
    "e8a6904071419c0b049ed5b5aad83401e236957d",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-046a.html": [
+  "css/css-text-decor/text-decoration-046a-manual.html": [
    "a3d5e717e3f882ba2ce9a4bf148fd62856c73b76",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-048.html": [
+  "css/css-text-decor/text-decoration-048-manual.html": [
    "c9070bedb3b3450e7d2c0b2b0a76dfbdfdb8e4f2",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-048a.html": [
+  "css/css-text-decor/text-decoration-048a-manual.html": [
    "7d598af677e9f5be323886842560f9c04d79da8a",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-049.html": [
+  "css/css-text-decor/text-decoration-049-manual.html": [
    "b8fb70dcd79a50ae2a49bf705688695f66ee2a88",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-082.html": [
+  "css/css-text-decor/text-decoration-082-manual.html": [
    "a1d5497ad2ac01d8df295b8203a9769780317f27",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-085.html": [
+  "css/css-text-decor/text-decoration-085-manual.html": [
    "30522cf331fecc3eb17a43a53b1bc9aa5d63ae1d",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-090.html": [
+  "css/css-text-decor/text-decoration-090-manual.html": [
    "580fb98c87961670cd2c23f3d01be40477cd06db",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-090a.html": [
+  "css/css-text-decor/text-decoration-090a-manual.html": [
    "d9ac430079b621104da62a11fca6b0bd3d6cff57",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-091.html": [
+  "css/css-text-decor/text-decoration-091-manual.html": [
    "2097792f1c317daaad0f05281a3b547d3555cd3d",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-091a.html": [
+  "css/css-text-decor/text-decoration-091a-manual.html": [
    "ee2464c5263e1cd7eb1389d4b052bc08633a78e8",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-092.html": [
+  "css/css-text-decor/text-decoration-092-manual.html": [
    "5ba99f0de3b463eef56f477d5e2cfffd06f16786",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-092a.html": [
+  "css/css-text-decor/text-decoration-092a-manual.html": [
    "5f034b3e729955dbc4c0be3c1787745423f224ff",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-095a.html": [
+  "css/css-text-decor/text-decoration-095a-manual.html": [
    "dc4ae513d125b9cb065875ecd2b5155ff2169fe2",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-096.html": [
+  "css/css-text-decor/text-decoration-096-manual.html": [
    "b7c0dc4878b365e9009026c4f537a4312bab761c",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-096a.html": [
+  "css/css-text-decor/text-decoration-096a-manual.html": [
    "a1f13e57cafd437805633587bdcc1190c18fb26a",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-097.html": [
+  "css/css-text-decor/text-decoration-097-manual.html": [
    "4d352e4da17d515aa4426ac286ca663bd8a13834",
-   "visual"
+   "manual"
   ],
-  "css/css-text-decor/text-decoration-097a.html": [
+  "css/css-text-decor/text-decoration-097a-manual.html": [
    "64b3249dd6be2cf4364c3ebe62ff256a0373a29d",
-   "visual"
+   "manual"
   ],
   "css/css-text-decor/text-decoration-color-recalc.html": [
    "b7cde934fcfd3c73ac351f7b9566adadad542294",
@@ -409758,6 +409800,14 @@
    "7cd0be939f16e8aea7b00ff2b13a06102e26cc4d",
    "testharness"
   ],
+  "element-timing/background-image-multiple-elements.html": [
+   "669f94d6b0189ba387cf90c1b49c7d4120319673",
+   "testharness"
+  ],
+  "element-timing/background-image-stretched.html": [
+   "8f93b43524f7b3d051af90d9807a3f30ad299ae7",
+   "testharness"
+  ],
   "element-timing/buffer-before-onload.html": [
    "805777f29c297a22497d3e4f07b6ea462b0a0b7c",
    "testharness"
@@ -409802,6 +409852,14 @@
    "dbcad248e3aea0d148416275adeec93e20d8c267",
    "testharness"
   ],
+  "element-timing/multiple-background-images.html": [
+   "ca349fec45f6ae4bde53dbe03de673d19d295794",
+   "testharness"
+  ],
+  "element-timing/observe-background-image.html": [
+   "0669b4c4d83b6c2e81de94beb7db15c6ca775d1b",
+   "testharness"
+  ],
   "element-timing/observe-child-element.html": [
    "9166a4b0e6d129c356d74da2b81a6e02c08105b6",
    "testharness"
@@ -451039,7 +451097,7 @@
    "testharness"
   ],
   "portals/portals-post-message.sub.html": [
-   "9fc6f6192cfa114053ae3ae6976e1c4491f299ad",
+   "fe58e25180ecec148e8f5bee4d79eaa366268476",
    "testharness"
   ],
   "portals/portals-rendering.html": [
@@ -451115,7 +451173,7 @@
    "support"
   ],
   "portals/resources/portal-post-message-cross-origin-portal.sub.html": [
-   "4aca1bde6983027fc363f318f22cfaaa156dee59",
+   "577c10ac666536eed4ed4a62fdc4924f06a47b6f",
    "support"
   ],
   "portals/resources/portal-post-message-during-activate-window.html": [
@@ -451123,7 +451181,7 @@
    "support"
   ],
   "portals/resources/portal-post-message-portal.html": [
-   "c657894f8ac4ec83e6a3ed3fd66dbc3be3b0c985",
+   "539048c13f5e8c21f7bda5e7d7e1b4772efb5ded",
    "support"
   ],
   "portals/resources/portals-adopt-predecessor-portal.html": [
@@ -467650,6 +467708,14 @@
    "120941444a4898197d6b6001f9908a6cd48b62ba",
    "support"
   ],
+  "svg/extensibility/foreignObject/compositing-backface-visibility-ref.html": [
+   "b0f504974a552545053a1a5d7c1e55eefa527fdb",
+   "support"
+  ],
+  "svg/extensibility/foreignObject/compositing-backface-visibility.html": [
+   "1551e25f8f9675dd5cde0dc656635656f5d33023",
+   "reftest"
+  ],
   "svg/extensibility/foreignObject/containing-block.html": [
    "da0728c96b5d0eb81435efad329bdcc3ee4dfb26",
    "testharness"
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
index 678d2a0..8b9bd0d 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
@@ -263,25 +263,43 @@
 }
 
 function decodeImageData(rgba) {
-  var rgb = new Uint8ClampedArray(rgba.length);
+  let decodedBytes = new Uint8ClampedArray(rgba.length);
+  let decodedLength = 0;
 
-  // RGBA -> RGB.
-  var rgb_length = 0;
-  for (var i = 0; i < rgba.length; ++i) {
-    // Skip alpha component.
-    if (i % 4 == 3)
-      continue;
+  for (var i = 0; i + 12 <= rgba.length; i += 12) {
+    // A single byte is encoded in three pixels. 8 pixel octets (among
+    // 9 octets = 3 pixels * 3 channels) are used to encode 8 bits,
+    // the most significant bit first, where `0` and `255` in pixel values
+    // represent `0` and `1` in bits, respectively.
+    // This encoding is used to avoid errors due to different color spaces.
+    const bits = [];
+    for (let j = 0; j < 3; ++j) {
+      bits.push(rgba[i + j * 4 + 0]);
+      bits.push(rgba[i + j * 4 + 1]);
+      bits.push(rgba[i + j * 4 + 2]);
+      // rgba[i + j * 4 + 3]: Skip alpha channel.
+    }
+    // The last one element is not used.
+    bits.pop();
+
+    // Decode a single byte.
+    let byte = 0;
+    for (let j = 0; j < 8; ++j) {
+      byte <<= 1;
+      if (bits[j] >= 128)
+        byte |= 1;
+    }
 
     // Zero is the string terminator.
-    if (rgba[i] == 0)
+    if (byte == 0)
       break;
 
-    rgb[rgb_length++] = rgba[i];
+    decodedBytes[decodedLength++] = byte;
   }
 
   // Remove trailing nulls from data.
-  rgb = rgb.subarray(0, rgb_length);
-  var string_data = (new TextDecoder("ascii")).decode(rgb);
+  decodedBytes = decodedBytes.subarray(0, decodedLength);
+  var string_data = (new TextDecoder("ascii")).decode(decodedBytes);
 
   return JSON.parse(string_data);
 }
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/subresource/image.py b/third_party/blink/web_tests/external/wpt/common/security-features/subresource/image.py
index d132889..42ebc07 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/subresource/image.py
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/subresource/image.py
@@ -60,20 +60,17 @@
 
 def encode_string_as_bmp_image(string_data):
     data_bytes = array.array("B", string_data)
+
     num_bytes = len(data_bytes)
 
-    # Convert data bytes to color data (RGB).
+    # Encode data bytes to color data (RGB), one bit per channel.
+    # This is to avoid errors due to different color spaces used in decoding.
     color_data = []
-    num_components = 3
-    rgb = [0] * num_components
-    i = 0
     for byte in data_bytes:
-        component_index = i % num_components
-        rgb[component_index] = byte
-        if component_index == (num_components - 1) or i == (num_bytes - 1):
-            color_data.append(tuple(rgb))
-            rgb = [0] * num_components
-        i += 1
+        p = [int(x) * 255 for x in '{0:08b}'.format(byte)]
+        color_data.append((p[0], p[1], p[2]))
+        color_data.append((p[3], p[4], p[5]))
+        color_data.append((p[6], p[7], 0))
 
     # Render image.
     num_pixels = len(color_data)
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-001.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-001-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-001.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-001-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-002.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-002-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-002.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-002-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-003.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-003-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-003.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-003-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-004.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-004-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-004.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-004-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-040a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-041.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-041-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-041.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-041-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-044.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-044-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-044.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-044-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-045.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-045-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-045.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-045-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-046a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-046a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-046a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-046a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-048a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-049.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-049-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-049.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-049-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-082.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-082-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-082.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-082-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-085.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-085-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-085.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-085-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-090a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-091a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-092a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-095a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-095a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-095a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-095a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-096a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097a.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097a-manual.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097a.html
rename to third_party/blink/web_tests/external/wpt/css/css-text-decor/text-decoration-097a-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-notref.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-notref.html
index c2bde5a..ab5ae4cb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-notref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-notref.html
@@ -6,7 +6,8 @@
   </head>
   <body>
     <div style="width: 100px; height: 100px">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-ref.html
index 213d81bed..05e7b31 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001-ref.html
@@ -7,7 +7,8 @@
   </head>
   <body>
     <div style="transform: scaleX(2) scaleY(2); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001.html
index 36fe800..d6287818 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-001.html
@@ -14,7 +14,8 @@
   </head>
   <body>
     <div style="transform: scale3D(2,2,2); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-002.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-002.html
index 723dfc8a..a677aae 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-002.html
@@ -15,7 +15,8 @@
   </head>
   <body>
     <div style="transform: rotatex(90deg) scale3D(2,1,2) rotatex(-90deg); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-003.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-003.html
index 0bb647b..e613ba5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-003.html
@@ -14,7 +14,8 @@
   </head>
   <body>
     <div style="transform: scaleX(2) scaleY(2) scaleZ(2); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-004.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-004.html
index f5a161c0..ed8247c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-004.html
@@ -13,7 +13,8 @@
   <body>
     <p>Nothing should appear except this sentence.</p>
     <div style="transform: scale3d(2, 2, 0); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005-ref.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005-ref.html
index 201300c..270e413d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005-ref.html
@@ -7,7 +7,8 @@
   </head>
   <body>
     <div style="transform: scaleY(2); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005.html
index fda6f7f5..524323c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-005.html
@@ -14,7 +14,8 @@
   </head>
   <body>
     <div style="transform: rotatex(90deg) scale3D(1,1,2) rotatex(-90deg); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-006.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-006.html
index b821776..b3dd537 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-006.html
@@ -14,7 +14,8 @@
   </head>
   <body>
     <div style="transform: rotatex(90deg) scaleZ(2) rotatex(-90deg); transform-origin: 0 0; width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007-ref.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007-ref.html
index f476aa9..b719044 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007-ref.html
@@ -7,7 +7,8 @@
   </head>
   <body>
     <div style="transform: scaleY(-1); width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007.html
index e9c1997..88261b7a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform3d-scale-007.html
@@ -12,7 +12,8 @@
   </head>
   <body>
     <div style="transform: rotatex(180deg) scaleZ(-1); width: 100px; height: 100px;">
-      Test Text
+      <div style="background: blue; width: 50px; height: 50px;"></div>
+      <div style="background: lime; width: 50px; height: 50px; margin-left: 50px;"></div>
     </div>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_coordinates_when_locked.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_coordinates_when_locked.html
index 2556e83..9dfd5e1 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_coordinates_when_locked.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_coordinates_when_locked.html
@@ -73,15 +73,15 @@
                 });
 
                 var actions = new test_driver.Actions();
-                actions.pointerMove(/* x = */ 0, /* y = */ 0, {origin: target}).pointerDown();
+                actions.pointerMove(/* x = */ 0, /* y = */ 0, {origin: div1}).pointerDown();
 
-                pos_x = target.getBoundingClientRect().x + target.offsetWidth / 2;
-                pos_y = target.getBoundingClientRect().y + target.offsetHeight / 2;
+                pos_x = div1.offsetWidth / 2;
+                pos_y = div1.offsetHeight / 2;
                 for (var i = 0; i < 10; i++) {
                     // Alternatively move left/right and up/down.
                     pos_x += ((-1)**i) * i * 10;
                     pos_y -= ((-1)**i) * i * 10;
-                    actions.pointerMove(pos_x, pos_y);
+                    actions.pointerMove(pos_x, pos_y, {origin: div1});
                 }
                 actions.pointerUp().send();
             }
diff --git a/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic-expected.txt b/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic-expected.txt
new file mode 100644
index 0000000..9cbe4dd
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic-expected.txt
@@ -0,0 +1,6 @@
+Tests that the sampling heap profiler works and supports nesting.
+
+profile1 size is ok: true
+profile2 size is ok: true
+Expected error: Error: Sampling profiler is not running.
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic.js b/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic.js
new file mode 100644
index 0000000..62ccd65
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/profiler/sampling-profiler-basic.js
@@ -0,0 +1,29 @@
+// 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.
+
+(async function() {
+  TestRunner.addResult(`Tests that the sampling heap profiler works and supports nesting.\n`);
+  await TestRunner.loadModule('profiler');
+
+  const profiler = SDK.targetManager.mainTarget().model(SDK.HeapProfilerModel);
+  await profiler.startSampling();
+  await profiler.startSampling();
+  await TestRunner.evaluateInPagePromise(`let dump = new Array(5e4).fill(42.42)`);
+  const profile1 = await profiler.stopSampling();
+  await TestRunner.evaluateInPagePromise(`let dump2 = new Array(5e4).fill(42.42)`);
+  const profile2 = await profiler.stopSampling();
+
+  const totalSize = node => node.children.reduce((acc, c) => acc + totalSize(c), node.selfSize);
+  const checkValue = (expected, value) => expected * 0.8 < value && value < expected * 1.2;
+  TestRunner.addResult(`profile1 size is ok: ${checkValue(400e3, totalSize(profile1.head))}`);
+  TestRunner.addResult(`profile2 size is ok: ${checkValue(800e3, totalSize(profile2.head))}`);
+
+  try {
+    await profiler.stopSampling();
+  } catch (e) {
+    TestRunner.addResult(`Expected error: ${e}`);
+  }
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile.js
index 21d6d5e6..6f6d190 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile.js
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile.js
@@ -45,10 +45,10 @@
   }
 
   function frameRevealed(frame) {
-    var url = frame.uiSourceCode().url();
+    const url = frame.uiSourceCode().url();
     TestRunner.addResult(TestRunner.formatters.formatAsURL(url));
     cpuProfile.nodes.forEach(n => n.callFrame.url = url);
-    var lineProfile = PerfUI.LineLevelProfile.Performance.instance();
+    const lineProfile = self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance);
     lineProfile.appendCPUProfile(new SDK.CPUProfileDataModel(cpuProfile));
     setTimeout(() => TestRunner.completeTest(), 0);
   }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-expected.txt
index 42a6d0d5..a5325e5 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-expected.txt
@@ -31,6 +31,11 @@
       "backgroundColor": "#0000FF",
       "paintInvalidations": [
         {
+          "object": "LayoutBlockFlow (positioned) DIV id='container'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        },
+        {
           "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
           "rect": [50, 50, 75, 75],
           "reason": "chunk appeared"
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-individual-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-individual-expected.txt
index 42a6d0d5..a5325e5 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-individual-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-added-individual-expected.txt
@@ -31,6 +31,11 @@
       "backgroundColor": "#0000FF",
       "paintInvalidations": [
         {
+          "object": "LayoutBlockFlow (positioned) DIV id='container'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        },
+        {
           "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
           "rect": [50, 50, 75, 75],
           "reason": "chunk appeared"
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-expected.txt
index 781ddf2..5c6af203 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-expected.txt
@@ -32,6 +32,11 @@
       "backgroundColor": "#0000FF",
       "paintInvalidations": [
         {
+          "object": "LayoutBlockFlow (positioned) DIV id='container'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        },
+        {
           "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
           "rect": [50, 50, 75, 75],
           "reason": "chunk disappeared"
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-individual-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
index e92ac21..4daecdb 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
@@ -33,6 +33,11 @@
       "backgroundColor": "#0000FF",
       "paintInvalidations": [
         {
+          "object": "LayoutBlockFlow (positioned) DIV id='container'",
+          "rect": [0, 0, 100, 100],
+          "reason": "paint property change"
+        },
+        {
           "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
           "rect": [50, 50, 75, 75],
           "reason": "chunk disappeared"
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-expected.txt
new file mode 100644
index 0000000..42a6d0d5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-expected.txt
@@ -0,0 +1,42 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk disappeared"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [200, 100],
+      "bounds": [125, 125],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk appeared"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-individual-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-individual-expected.txt
new file mode 100644
index 0000000..42a6d0d5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-added-individual-expected.txt
@@ -0,0 +1,42 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk disappeared"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [200, 100],
+      "bounds": [125, 125],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk appeared"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-expected.txt
new file mode 100644
index 0000000..781ddf2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-expected.txt
@@ -0,0 +1,43 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk appeared"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [200, 100],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk disappeared"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-individual-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
new file mode 100644
index 0000000..e92ac21
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
@@ -0,0 +1,44 @@
+CONSOLE MESSAGE: line 30: debug
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk appeared"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "position": [200, 100],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV class='fixed'",
+          "rect": [50, 50, 75, 75],
+          "reason": "chunk disappeared"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/webcomponentsv0-runtimeflags-disabled/http/tests/origin_trials/webexposed/README.txt b/third_party/blink/web_tests/virtual/webcomponentsv0-runtimeflags-disabled/http/tests/origin_trials/webexposed/README.txt
deleted file mode 100644
index dcc64a6..0000000
--- a/third_party/blink/web_tests/virtual/webcomponentsv0-runtimeflags-disabled/http/tests/origin_trials/webexposed/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-This suite runs the tests in origin_trials/webexposed with the WebComponents V0
-features explicitly disabled:
---disable-blink-features=ShadowDOMV0,CustomElementsV0,HTMLImports
diff --git a/third_party/closure_compiler/externs/pending.js b/third_party/closure_compiler/externs/pending.js
index d567fa8..0fcefd4 100644
--- a/third_party/closure_compiler/externs/pending.js
+++ b/third_party/closure_compiler/externs/pending.js
@@ -10,7 +10,7 @@
 
 /**
  * @see https://drafts.fxtf.org/geometry-1/#domrectreadonly
- * TODO(scottchen): Remove this once it is added to Closure Compiler itself.
+ * TODO(dpapad): Remove this once it is added to Closure Compiler itself.
  */
 class DOMRectReadOnly {
   /**
@@ -77,13 +77,13 @@
  * @see https://wicg.github.io/ResizeObserver/#resizeobserverentry
  * @typedef {{contentRect: DOMRectReadOnly,
  *            target: Element}}
- * TODO(scottchen): Remove this once it is added to Closure Compiler itself.
+ * TODO(dpapad): Remove this once it is added to Closure Compiler itself.
  */
 let ResizeObserverEntry;
 
 /**
  * @see https://wicg.github.io/ResizeObserver/#api
- * TODO(scottchen): Remove this once it is added to Closure Compiler itself.
+ * TODO(dpapad): Remove this once it is added to Closure Compiler itself.
  */
 class ResizeObserver {
   /**
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 8b32bdc..787f99e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -41246,6 +41246,9 @@
     The item was terminated because it remained in the pipeline for more than 7
     days.
   </int>
+  <int value="1175" label="INVALID_ITEM">
+    The item had some invalid data, probably due to database corruption.
+  </int>
   <int value="1200" label="GET_OPERATION_MAX_ATTEMPTS_REACHED">
     Exceeding maximum retries for get operation request.
   </int>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 28a9147..80f6971 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -83551,8 +83551,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.BrowserWaitForRendererTime.FrameTree"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Total time the browser process waited for all render processes to save their
     respective frames while saving a page into MHTML.
@@ -83561,8 +83562,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.BrowserWaitForRendererTime.SingleFrame"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Time the browser process waited on a single frame to be saved by a render
     processes while saving a page into MHTML.
@@ -83570,8 +83572,9 @@
 </histogram>
 
 <histogram name="PageSerialization.MhtmlGeneration.EncodingTime.SingleFrame"
-    units="microseconds">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Time taken to encode into MTHML the fully serialized contents of a frame.
 
@@ -83586,18 +83589,21 @@
 <histogram name="PageSerialization.MhtmlGeneration.FinalSaveStatus"
     enum="MhtmlGenerationFinalSaveStatus" expires_after="M77">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>Final status of the MHTML save operation for a page.</summary>
 </histogram>
 
 <histogram name="PageSerialization.MhtmlGeneration.FullPageSavingTime"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>Time taken to save a page into an MHTML file.</summary>
 </histogram>
 
 <histogram name="PageSerialization.MhtmlGeneration.PopupOverlaySkipped"
     enum="BooleanSkipped" expires_after="2018-08-30">
   <owner>jianli@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Recorded when the popup overlays are removed from MHTML serialization.
   </summary>
@@ -83605,8 +83611,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.RendererMainThreadTime.FrameTree"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Time spent by the main threads of all involved render processes while saving
     the frame tree of a page to MHTML.
@@ -83615,8 +83622,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.RendererMainThreadTime.SingleFrame"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Time spent by the renderer main thread while saving one frame of a page to
     MHTML.
@@ -83625,8 +83633,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.RendererMainThreadTime.SlowestFrame"
-    units="ms">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     The longest time taken by the main thread of a render processes to save one
     frame of a page being saved to MHTML. In other words this is the maximum
@@ -83638,8 +83647,9 @@
 
 <histogram
     name="PageSerialization.MhtmlGeneration.SerializationTime.SingleFrame"
-    units="microseconds">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
     Time taken to fully serialize the contents of a frame, including HTML and
     CSS and image resources.
@@ -83647,10 +83657,14 @@
 </histogram>
 
 <histogram name="PageSerialization.MhtmlGeneration.WriteToDiskTime.SingleFrame"
-    units="ms" expires_after="M77">
+    units="ms" expires_after="2020-04-01">
   <owner>carlosk@chromium.org</owner>
+  <owner>offline-dev@chromium.org</owner>
   <summary>
-    Time spent writing a frame's encoded MHTML data to the file on disk.
+    Time spent writing a frame's encoded MHTML data to the file or data pipe
+    handle passed to the Renderer, recorded when the writing is done and the
+    handle is closed. Note that when writing to a data pipe, this metric does
+    not precisely reflect the time taken to write the data to disk.
   </summary>
 </histogram>
 
@@ -134086,7 +134100,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.EchoCanceller.MaxCaptureJitter"
-    units="frames (10 ms)" expires_after="2019-05-01">
+    units="frames (10 ms)" expires_after="2020-05-01">
   <owner>peah@chromium.org</owner>
   <owner>gustaf@chromium.org</owner>
   <summary>
@@ -134096,7 +134110,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.EchoCanceller.MaxRenderJitter"
-    units="frames (10 ms)" expires_after="2019-05-01">
+    units="frames (10 ms)" expires_after="2020-05-01">
   <owner>peah@chromium.org</owner>
   <owner>gustaf@chromium.org</owner>
   <summary>
@@ -134122,7 +134136,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.EchoCanceller.MinCaptureJitter"
-    units="frames (10 ms)" expires_after="2019-05-01">
+    units="frames (10 ms)" expires_after="2020-05-01">
   <owner>peah@chromium.org</owner>
   <owner>gustaf@chromium.org</owner>
   <summary>
@@ -134132,7 +134146,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.EchoCanceller.MinRenderJitter"
-    units="frames (10 ms)" expires_after="2019-05-01">
+    units="frames (10 ms)" expires_after="2020-05-01">
   <owner>peah@chromium.org</owner>
   <owner>gustaf@chromium.org</owner>
   <summary>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index a400afc7..408baea 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -7,7 +7,7 @@
 blink_perf.bindings,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",Blink>Bindings,https://bit.ly/blink-perf-benchmarks,
 blink_perf.canvas,"aaronhk@chromium.org, fserb@chromium.org",Blink>Canvas,https://bit.ly/blink-perf-benchmarks,
 blink_perf.css,"futhark@chromium.org, andruud@chromium.org",Blink>CSS,https://bit.ly/blink-perf-benchmarks,
-blink_perf.dom,"hayato@chromium.org, tkent@chromium.org",Blink>DOM,https://bit.ly/blink-perf-benchmarks,
+blink_perf.dom,hayato@chromium.org,Blink>DOM,https://bit.ly/blink-perf-benchmarks,
 blink_perf.events,hayato@chromium.org,Blink>DOM,https://bit.ly/blink-perf-benchmarks,
 blink_perf.image_decoder,cblume@chromium.org,Internals>Images>Codecs,https://bit.ly/blink-perf-benchmarks,
 blink_perf.layout,eae@chromium.org,,https://bit.ly/blink-perf-benchmarks,
diff --git a/tools/perf/benchmarks/blink_perf.py b/tools/perf/benchmarks/blink_perf.py
index 789cb63..679c39c 100644
--- a/tools/perf/benchmarks/blink_perf.py
+++ b/tools/perf/benchmarks/blink_perf.py
@@ -434,8 +434,7 @@
       page.skipped_gpus = []
     return story_set
 
-@benchmark.Info(emails=['hayato@chromium.org',
-                        'tkent@chromium.org'],
+@benchmark.Info(emails=['hayato@chromium.org'],
                 component='Blink>DOM',
                 documentation_url='https://bit.ly/blink-perf-benchmarks')
 class BlinkPerfDOM(_BlinkPerfBenchmark):
diff --git a/ui/aura/mus/input_method_mus.cc b/ui/aura/mus/input_method_mus.cc
index ad8fca7..ae0bec6d 100644
--- a/ui/aura/mus/input_method_mus.cc
+++ b/ui/aura/mus/input_method_mus.cc
@@ -37,6 +37,11 @@
       .Run(handled ? EventResult::HANDLED : EventResult::UNHANDLED);
 }
 
+void CallKeyAckCallback(InputMethodMus::KeyAckCallback ack_callback,
+                        EventResult result) {
+  std::move(ack_callback).Run(result == EventResult::HANDLED);
+}
+
 void OnDispatchKeyEventPostIME(InputMethodMus::EventResultCallback callback,
                                bool handled,
                                bool stopped_propagation) {
@@ -106,21 +111,12 @@
     connector->BindInterface(ws::mojom::kServiceName, &ime_driver_);
 }
 
-ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(
-    ui::KeyEvent* event,
-    EventResultCallback ack_callback) {
-  DCHECK(event->type() == ui::ET_KEY_PRESSED ||
-         event->type() == ui::ET_KEY_RELEASED);
-
-  // If no text input client or the event is synthesized, dispatch the devent
-  // directly without forwarding it to the real input method.
-  if (!GetTextInputClient() || (event->flags() & ui::EF_IS_SYNTHESIZED)) {
-    return DispatchKeyEventPostIME(
-        event,
-        base::BindOnce(&OnDispatchKeyEventPostIME, std::move(ack_callback)));
-  }
-
-  return SendKeyEventToInputMethod(*event, std::move(ack_callback));
+////////////////////////////////////////////////////////////////////////////////
+// InputMethodMus, ui::AsyncKeyDispatcher implementation:
+void InputMethodMus::DispatchKeyEventAsync(ui::KeyEvent* event,
+                                           KeyAckCallback cb) {
+  ignore_result(DispatchKeyEventInternal(
+      event, base::BindOnce(&CallKeyAckCallback, std::move(cb))));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -138,13 +134,17 @@
 
 ui::EventDispatchDetails InputMethodMus::DispatchKeyEvent(ui::KeyEvent* event) {
   ui::EventDispatchDetails dispatch_details =
-      DispatchKeyEvent(event, EventResultCallback());
+      DispatchKeyEventInternal(event, EventResultCallback());
   // Mark the event as handled so that EventGenerator doesn't attempt to
   // deliver event as well.
   event->SetHandled();
   return dispatch_details;
 }
 
+ui::AsyncKeyDispatcher* InputMethodMus::GetAsyncKeyDispatcher() {
+  return this;
+}
+
 void InputMethodMus::OnTextInputTypeChanged(const ui::TextInputClient* client) {
   InputMethodBase::OnTextInputTypeChanged(client);
   if (!IsTextInputClientFocused(client))
@@ -204,6 +204,23 @@
     input_method_->ShowVirtualKeyboardIfEnabled();
 }
 
+ui::EventDispatchDetails InputMethodMus::DispatchKeyEventInternal(
+    ui::KeyEvent* event,
+    EventResultCallback ack_callback) {
+  DCHECK(event->type() == ui::ET_KEY_PRESSED ||
+         event->type() == ui::ET_KEY_RELEASED);
+
+  // If no text input client or the event is synthesized, dispatch the devent
+  // directly without forwarding it to the real input method.
+  if (!GetTextInputClient() || (event->flags() & ui::EF_IS_SYNTHESIZED)) {
+    return DispatchKeyEventPostIME(
+        event,
+        base::BindOnce(&OnDispatchKeyEventPostIME, std::move(ack_callback)));
+  }
+
+  return SendKeyEventToInputMethod(*event, std::move(ack_callback));
+}
+
 ui::EventDispatchDetails InputMethodMus::SendKeyEventToInputMethod(
     const ui::KeyEvent& event,
     EventResultCallback ack_callback) {
diff --git a/ui/aura/mus/input_method_mus.h b/ui/aura/mus/input_method_mus.h
index e32c3fd..c8d5ed15 100644
--- a/ui/aura/mus/input_method_mus.h
+++ b/ui/aura/mus/input_method_mus.h
@@ -27,23 +27,27 @@
 class InputMethodMusTestApi;
 class TextInputClientImpl;
 
-class AURA_EXPORT InputMethodMus : public ui::InputMethodBase {
+class AURA_EXPORT InputMethodMus : public ui::InputMethodBase,
+                                   public ui::AsyncKeyDispatcher {
  public:
   using EventResultCallback = base::OnceCallback<void(ws::mojom::EventResult)>;
+  using KeyAckCallback = base::OnceCallback<void(bool)>;
 
   InputMethodMus(ui::internal::InputMethodDelegate* delegate,
                  InputMethodMusDelegate* input_method_mus_delegate);
   ~InputMethodMus() override;
 
   void Init(service_manager::Connector* connector);
-  ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event,
-                                            EventResultCallback ack_callback)
-      WARN_UNUSED_RESULT;
+
+  // Overridden from ui::AsyncKeyDispatcher:
+  void DispatchKeyEventAsync(ui::KeyEvent* event,
+                             KeyAckCallback ack_callback) override;
 
   // Overridden from ui::InputMethod:
   void OnFocus() override;
   void OnBlur() override;
   ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
+  ui::AsyncKeyDispatcher* GetAsyncKeyDispatcher() override;
   void OnTextInputTypeChanged(const ui::TextInputClient* client) override;
   void OnCaretBoundsChanged(const ui::TextInputClient* client) override;
   void CancelComposition(const ui::TextInputClient* client) override;
@@ -55,6 +59,10 @@
   friend class InputMethodMusTestApi;
   friend TextInputClientImpl;
 
+  ui::EventDispatchDetails DispatchKeyEventInternal(
+      ui::KeyEvent* event,
+      EventResultCallback ack_callback) WARN_UNUSED_RESULT;
+
   // Called from DispatchKeyEvent() to call to the InputMethod.
   ui::EventDispatchDetails SendKeyEventToInputMethod(
       const ui::KeyEvent& event,
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc
index c6a5c47..fdd132b 100644
--- a/ui/aura/mus/window_tree_client.cc
+++ b/ui/aura/mus/window_tree_client.cc
@@ -130,6 +130,11 @@
   return key == client::kModalKey;
 }
 
+void DispatchKeyEventCallback(EventResultCallback cb, bool handled) {
+  std::move(cb).Run(handled ? ws::mojom::EventResult::HANDLED
+                            : ws::mojom::EventResult::UNHANDLED);
+}
+
 }  // namespace
 
 // static
@@ -1446,8 +1451,13 @@
   if (event->IsKeyEvent()) {
     InputMethodMus* input_method = GetWindowTreeHostMus(window)->input_method();
     if (input_method) {
-      ignore_result(input_method->DispatchKeyEvent(
-          event->AsKeyEvent(), CreateEventResultCallback(event_id)));
+      ui::AsyncKeyDispatcher* dispatcher =
+          input_method->GetAsyncKeyDispatcher();
+      DCHECK(dispatcher);
+      dispatcher->DispatchKeyEventAsync(
+          event->AsKeyEvent(),
+          base::BindOnce(DispatchKeyEventCallback,
+                         CreateEventResultCallback(event_id)));
       return;
     }
   }
diff --git a/ui/base/clipboard/clipboard.cc b/ui/base/clipboard/clipboard.cc
index 2634410..a34a877 100644
--- a/ui/base/clipboard/clipboard.cc
+++ b/ui/base/clipboard/clipboard.cc
@@ -45,7 +45,7 @@
     // This shouldn't happen. The clipboard should not already exist.
     NOTREACHED();
   }
-  clipboard_map->insert(std::make_pair(id, std::move(platform_clipboard)));
+  clipboard_map->insert({id, std::move(platform_clipboard)});
 }
 
 // static
@@ -59,7 +59,7 @@
     return it->second.get();
 
   Clipboard* clipboard = Clipboard::Create();
-  clipboard_map->insert(std::make_pair(id, base::WrapUnique(clipboard)));
+  clipboard_map->insert({id, base::WrapUnique(clipboard)});
   return clipboard;
 }
 
diff --git a/ui/base/clipboard/custom_data_helper.cc b/ui/base/clipboard/custom_data_helper.cc
index a20b6ed..2cb28b39 100644
--- a/ui/base/clipboard/custom_data_helper.cc
+++ b/ui/base/clipboard/custom_data_helper.cc
@@ -103,7 +103,7 @@
       result->clear();
       return;
     }
-    auto insert_result = result->insert(std::make_pair(type, base::string16()));
+    auto insert_result = result->insert({type, base::string16()});
     if (!iter.ReadString16(&insert_result.first->second)) {
       // Data is corrupt, return an empty map.
       result->clear();
diff --git a/ui/base/clipboard/custom_data_helper_unittest.cc b/ui/base/clipboard/custom_data_helper_unittest.cc
index b5360ba..338447e 100644
--- a/ui/base/clipboard/custom_data_helper_unittest.cc
+++ b/ui/base/clipboard/custom_data_helper_unittest.cc
@@ -23,9 +23,9 @@
 
 void PrepareTestData(base::Pickle* pickle) {
   std::unordered_map<base::string16, base::string16> data;
-  data.insert(std::make_pair(ASCIIToUTF16("abc"), base::string16()));
-  data.insert(std::make_pair(ASCIIToUTF16("de"), ASCIIToUTF16("1")));
-  data.insert(std::make_pair(ASCIIToUTF16("f"), ASCIIToUTF16("23")));
+  data.insert({ASCIIToUTF16("abc"), base::string16()});
+  data.insert({ASCIIToUTF16("de"), ASCIIToUTF16("1")});
+  data.insert({ASCIIToUTF16("f"), ASCIIToUTF16("23")});
   WriteCustomDataToPickle(data, pickle);
 }
 
@@ -109,9 +109,9 @@
   ReadCustomDataIntoMap(pickle.data(), pickle.size(), &result);
 
   std::unordered_map<base::string16, base::string16> expected;
-  expected.insert(std::make_pair(ASCIIToUTF16("abc"), base::string16()));
-  expected.insert(std::make_pair(ASCIIToUTF16("de"), ASCIIToUTF16("1")));
-  expected.insert(std::make_pair(ASCIIToUTF16("f"), ASCIIToUTF16("23")));
+  expected.insert({ASCIIToUTF16("abc"), base::string16()});
+  expected.insert({ASCIIToUTF16("de"), ASCIIToUTF16("1")});
+  expected.insert({ASCIIToUTF16("f"), ASCIIToUTF16("23")});
   EXPECT_EQ(expected, result);
 }
 
diff --git a/ui/base/ime/chromeos/input_method_chromeos.cc b/ui/base/ime/chromeos/input_method_chromeos.cc
index 3394ba7..bc94b4eb 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos.cc
@@ -81,7 +81,12 @@
   }
 }
 
-ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent(
+void InputMethodChromeOS::DispatchKeyEventAsync(ui::KeyEvent* event,
+                                                AckCallback ack_callback) {
+  ignore_result(DispatchKeyEventInternal(event, std::move(ack_callback)));
+}
+
+ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEventInternal(
     ui::KeyEvent* event,
     AckCallback ack_callback) {
   ResultCallback result_callback =
@@ -208,7 +213,11 @@
 
 ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent(
     ui::KeyEvent* event) {
-  return DispatchKeyEvent(event, AckCallback());
+  return DispatchKeyEventInternal(event, AckCallback());
+}
+
+AsyncKeyDispatcher* InputMethodChromeOS::GetAsyncKeyDispatcher() {
+  return this;
 }
 
 void InputMethodChromeOS::OnTextInputTypeChanged(
diff --git a/ui/base/ime/chromeos/input_method_chromeos.h b/ui/base/ime/chromeos/input_method_chromeos.h
index 219f5cf..e7d968ca 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.h
+++ b/ui/base/ime/chromeos/input_method_chromeos.h
@@ -24,19 +24,23 @@
 
 namespace ui {
 
-// A ui::InputMethod implementation based on IBus.
+// A ui::InputMethod implementation for ChromeOS.
 class COMPONENT_EXPORT(UI_BASE_IME_CHROMEOS) InputMethodChromeOS
-    : public InputMethodBase {
+    : public InputMethodBase,
+      public AsyncKeyDispatcher {
  public:
   explicit InputMethodChromeOS(internal::InputMethodDelegate* delegate);
   ~InputMethodChromeOS() override;
 
   using AckCallback = base::OnceCallback<void(bool)>;
-  ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event,
-                                            AckCallback ack_callback);
+
+  // Overridden from AsyncKeyDispatcher:
+  void DispatchKeyEventAsync(ui::KeyEvent* event,
+                             AckCallback ack_callback) override;
 
   // Overridden from InputMethod:
   ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
+  AsyncKeyDispatcher* GetAsyncKeyDispatcher() override;
   void OnTextInputTypeChanged(const TextInputClient* client) override;
   void OnCaretBoundsChanged(const TextInputClient* client) override;
   void CancelComposition(const TextInputClient* client) override;
@@ -69,6 +73,9 @@
  private:
   class PendingKeyEvent;
 
+  ui::EventDispatchDetails DispatchKeyEventInternal(ui::KeyEvent* event,
+                                                    AckCallback ack_callback);
+
   // Asks the client to confirm current composition text.
   void ConfirmCompositionText();
 
diff --git a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
index 59863cf..07ad8b0 100644
--- a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
@@ -1114,10 +1114,11 @@
   // should not be run immediately.
   bool async_callback_run = false;
   bool async_callback_handled_result = false;
-  ime_->DispatchKeyEvent(&event, base::BindLambdaForTesting([&](bool handled) {
-    async_callback_handled_result = handled;
-    async_callback_run = true;
-  }));
+  ime_->DispatchKeyEventAsync(&event,
+                              base::BindLambdaForTesting([&](bool handled) {
+                                async_callback_handled_result = handled;
+                                async_callback_run = true;
+                              }));
   EXPECT_FALSE(async_callback_run);
   ASSERT_EQ(1u, caching_input_method_delegate_->callbacks().size());
 
@@ -1141,10 +1142,11 @@
   // should not be run immediately.
   bool async_callback_run = false;
   bool async_callback_handled_result = false;
-  ime_->DispatchKeyEvent(&event, base::BindLambdaForTesting([&](bool handled) {
-    async_callback_handled_result = handled;
-    async_callback_run = true;
-  }));
+  ime_->DispatchKeyEventAsync(&event,
+                              base::BindLambdaForTesting([&](bool handled) {
+                                async_callback_handled_result = handled;
+                                async_callback_run = true;
+                              }));
   EXPECT_FALSE(async_callback_run);
   ASSERT_EQ(1u, caching_input_method_delegate_->callbacks().size());
 
@@ -1168,10 +1170,11 @@
   // should not be run immediately.
   bool async_callback_run = false;
   bool async_callback_handled_result = false;
-  ime_->DispatchKeyEvent(&event, base::BindLambdaForTesting([&](bool handled) {
-    async_callback_handled_result = handled;
-    async_callback_run = true;
-  }));
+  ime_->DispatchKeyEventAsync(&event,
+                              base::BindLambdaForTesting([&](bool handled) {
+                                async_callback_handled_result = handled;
+                                async_callback_run = true;
+                              }));
   EXPECT_FALSE(async_callback_run);
   ASSERT_EQ(1u, caching_input_method_delegate_->callbacks().size());
 
diff --git a/ui/base/ime/dummy_input_method.cc b/ui/base/ime/dummy_input_method.cc
index 4e3dff4..da1fdfa 100644
--- a/ui/base/ime/dummy_input_method.cc
+++ b/ui/base/ime/dummy_input_method.cc
@@ -45,6 +45,10 @@
   return ui::EventDispatchDetails();
 }
 
+AsyncKeyDispatcher* DummyInputMethod::GetAsyncKeyDispatcher() {
+  return nullptr;
+}
+
 void DummyInputMethod::OnTextInputTypeChanged(const TextInputClient* client) {
 }
 
diff --git a/ui/base/ime/dummy_input_method.h b/ui/base/ime/dummy_input_method.h
index 9865876..2702187 100644
--- a/ui/base/ime/dummy_input_method.h
+++ b/ui/base/ime/dummy_input_method.h
@@ -32,6 +32,7 @@
   void DetachTextInputClient(TextInputClient* client) override;
   TextInputClient* GetTextInputClient() const override;
   ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
+  AsyncKeyDispatcher* GetAsyncKeyDispatcher() override;
   void OnTextInputTypeChanged(const TextInputClient* client) override;
   void OnCaretBoundsChanged(const TextInputClient* client) override;
   void CancelComposition(const TextInputClient* client) override;
diff --git a/ui/base/ime/input_method.h b/ui/base/ime/input_method.h
index b400ece..85eeef83 100644
--- a/ui/base/ime/input_method.h
+++ b/ui/base/ime/input_method.h
@@ -28,6 +28,7 @@
 class InputMethodDelegate;
 }  // namespace internal
 
+class AsyncKeyDispatcher;
 class InputMethodKeyboardController;
 class InputMethodObserver;
 class KeyEvent;
@@ -107,6 +108,12 @@
   virtual ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event)
       WARN_UNUSED_RESULT = 0;
 
+  // Gets the async key dispatcher interface which is used to dispatch key
+  // events with a callback. Some platforms (e.g. ChromeOS) require a callback
+  // to dispatch the key event, so DispatchKeyEvent() doesn't match the need.
+  // Returns null on platforms that aren't async.
+  virtual AsyncKeyDispatcher* GetAsyncKeyDispatcher() = 0;
+
   // Called by the focused client whenever its text input type is changed.
   // Before calling this method, the focused client must confirm or clear
   // existing composition text and call InputMethod::CancelComposition() when
@@ -186,6 +193,16 @@
   bool track_key_events_for_testing_;
 };
 
+// An interface to support dispatch key event with a callback.
+// This is required on certain platforms, e.g. ChromeOS.
+class AsyncKeyDispatcher {
+ public:
+  // Dispatches a key event with a callback to receive the result of whether
+  // the event is handled by the input method.
+  virtual void DispatchKeyEventAsync(ui::KeyEvent* event,
+                                     base::OnceCallback<void(bool)> cb) = 0;
+};
+
 }  // namespace ui
 
 #endif  // UI_BASE_IME_INPUT_METHOD_H_
diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc
index 58502e92..b2203feb 100644
--- a/ui/base/ime/input_method_base.cc
+++ b/ui/base/ime/input_method_base.cc
@@ -86,6 +86,10 @@
     text_input_client_->EnsureCaretNotInRect(keyboard_bounds_);
 }
 
+AsyncKeyDispatcher* InputMethodBase::GetAsyncKeyDispatcher() {
+  return nullptr;
+}
+
 void InputMethodBase::OnTextInputTypeChanged(const TextInputClient* client) {
   if (!IsTextInputClientFocused(client))
     return;
diff --git a/ui/base/ime/input_method_base.h b/ui/base/ime/input_method_base.h
index 368fa8f..be649f4 100644
--- a/ui/base/ime/input_method_base.h
+++ b/ui/base/ime/input_method_base.h
@@ -52,6 +52,7 @@
   void DetachTextInputClient(TextInputClient* client) override;
   TextInputClient* GetTextInputClient() const override;
   void SetOnScreenKeyboardBounds(const gfx::Rect& new_bounds) override;
+  AsyncKeyDispatcher* GetAsyncKeyDispatcher() override;
 
   // If a derived class overrides this method, it should call parent's
   // implementation.
diff --git a/ui/base/ime/mock_input_method.cc b/ui/base/ime/mock_input_method.cc
index 04ff47b..733cd97 100644
--- a/ui/base/ime/mock_input_method.cc
+++ b/ui/base/ime/mock_input_method.cc
@@ -48,6 +48,10 @@
   return delegate_->DispatchKeyEventPostIME(event, base::NullCallback());
 }
 
+AsyncKeyDispatcher* MockInputMethod::GetAsyncKeyDispatcher() {
+  return nullptr;
+}
+
 void MockInputMethod::OnFocus() {
   for (InputMethodObserver& observer : observer_list_)
     observer.OnFocus();
diff --git a/ui/base/ime/mock_input_method.h b/ui/base/ime/mock_input_method.h
index 2d72e99a..d343865f 100644
--- a/ui/base/ime/mock_input_method.h
+++ b/ui/base/ime/mock_input_method.h
@@ -44,6 +44,7 @@
   void DetachTextInputClient(TextInputClient* client) override;
   TextInputClient* GetTextInputClient() const override;
   ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override;
+  AsyncKeyDispatcher* GetAsyncKeyDispatcher() override;
   void OnTextInputTypeChanged(const TextInputClient* client) override;
   void OnCaretBoundsChanged(const TextInputClient* client) override;
   void CancelComposition(const TextInputClient* client) override;
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index b94206b..696adc7 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -264,13 +264,6 @@
     const base::TimeTicks now) {
   ui::LatencyInfo monitored_latency_info = event_with_callback->latency_info();
 
-  if (event_with_callback->event().GetType() ==
-      WebInputEvent::kGestureScrollUpdate) {
-    monitored_latency_info.set_scroll_update_delta(
-        static_cast<const WebGestureEvent&>(event_with_callback->event())
-            .data.scroll_update.delta_y);
-  }
-
   std::unique_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
       input_handler_->CreateLatencyInfoSwapPromiseMonitor(
           &monitored_latency_info);
diff --git a/ui/events/blink/input_handler_proxy_unittest.cc b/ui/events/blink/input_handler_proxy_unittest.cc
index 8fc2f90..61404a4 100644
--- a/ui/events/blink/input_handler_proxy_unittest.cc
+++ b/ui/events/blink/input_handler_proxy_unittest.cc
@@ -1899,67 +1899,6 @@
   testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
 }
 
-TEST_F(InputHandlerProxyEventQueueTest, LatencyInfoScrollUpdateDelta) {
-  // Scroll on compositor.
-  cc::InputHandlerScrollResult scroll_result_did_scroll_;
-  scroll_result_did_scroll_.did_scroll = true;
-
-  EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _))
-      .WillOnce(testing::Return(kImplThreadScrollState));
-  EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1);
-  EXPECT_CALL(
-      mock_input_handler_,
-      ScrollBy(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0))))
-      .WillOnce(testing::Return(scroll_result_did_scroll_));
-  EXPECT_CALL(mock_input_handler_, ScrollEnd(_, true));
-
-  HandleGestureEvent(WebInputEvent::kGestureScrollBegin);
-  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -20);
-  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -40);
-  HandleGestureEvent(WebInputEvent::kGestureScrollEnd);
-  input_handler_proxy_.DeliverInputForBeginFrame();
-
-  EXPECT_EQ(0ul, event_queue().size());
-  // Should run callbacks for every original events.
-  EXPECT_EQ(4ul, event_disposition_recorder_.size());
-  EXPECT_EQ(4ul, latency_info_recorder_.size());
-  EXPECT_EQ(false, latency_info_recorder_[1].coalesced());
-  EXPECT_EQ(-60, latency_info_recorder_[1].scroll_update_delta());
-
-  EXPECT_EQ(true, latency_info_recorder_[2].coalesced());
-  EXPECT_EQ(-60, latency_info_recorder_[2].scroll_update_delta());
-
-  testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
-
-  latency_info_recorder_.clear();
-
-  // Scroll on main thread.
-  cc::InputHandlerScrollResult scroll_result_did_not_scroll_;
-  scroll_result_did_not_scroll_.did_scroll = false;
-  EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _))
-      .WillOnce(testing::Return(kMainThreadScrollState));
-  EXPECT_CALL(mock_input_handler_, ScrollEnd(_, true));
-
-  HandleGestureEvent(WebInputEvent::kGestureScrollBegin);
-  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -20);
-  HandleGestureEvent(WebInputEvent::kGestureScrollUpdate, -40);
-  HandleGestureEvent(WebInputEvent::kGestureScrollEnd);
-  input_handler_proxy_.DeliverInputForBeginFrame();
-
-  EXPECT_EQ(0ul, event_queue().size());
-  // Should run callbacks for every original events.
-  EXPECT_EQ(8ul, event_disposition_recorder_.size());
-  EXPECT_EQ(4ul, latency_info_recorder_.size());
-
-  EXPECT_EQ(false, latency_info_recorder_[1].coalesced());
-  EXPECT_EQ(-20, latency_info_recorder_[1].scroll_update_delta());
-
-  EXPECT_EQ(false, latency_info_recorder_[2].coalesced());
-  EXPECT_EQ(-40, latency_info_recorder_[2].scroll_update_delta());
-
-  testing::Mock::VerifyAndClearExpectations(&mock_input_handler_);
-}
-
 class InputHandlerProxyMainThreadScrollingReasonTest
     : public InputHandlerProxyTest {
  public:
diff --git a/ui/gl/android/android_surface_control_compat.cc b/ui/gl/android/android_surface_control_compat.cc
index 6cdf316..e6e462f 100644
--- a/ui/gl/android/android_surface_control_compat.cc
+++ b/ui/gl/android/android_surface_control_compat.cc
@@ -272,6 +272,8 @@
                                        ASurfaceTransactionStats* stats) {
   auto* ack_ctx = static_cast<TransactionAckCtx*>(context);
   auto transaction_stats = ToTransactionStats(stats);
+  TRACE_EVENT_ASYNC_END0("gpu,benchmark", "SurfaceControlTransaction",
+                         ack_ctx->id);
 
   if (ack_ctx->task_runner) {
     ack_ctx->task_runner->PostTask(
@@ -281,7 +283,6 @@
     std::move(ack_ctx->callback).Run(std::move(transaction_stats));
   }
 
-  TRACE_EVENT_ASYNC_END0("gpu", "SurfaceControlTransaction", ack_ctx->id);
   delete ack_ctx;
 }
 }  // namespace
@@ -430,7 +431,7 @@
 }
 
 void SurfaceControl::Transaction::Apply() {
-  TRACE_EVENT_ASYNC_BEGIN0("gpu", "SurfaceControlTransaction", id_);
+  TRACE_EVENT_ASYNC_BEGIN0("gpu,benchmark", "SurfaceControlTransaction", id_);
   SurfaceControlMethods::Get().ASurfaceTransaction_applyFn(transaction_);
 }
 
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index 0b132a5..9692d59 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -489,10 +489,7 @@
   for (KeyboardControllerObserver& observer : observer_list_)
     observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
 
-  if (IsKeyboardEnableRequested() && !IsEnabled())
-    EnableKeyboard();
-  else if (!IsKeyboardEnableRequested() && IsEnabled())
-    DisableKeyboard();
+  UpdateKeyboardAsRequestedBy(flag);
 }
 
 void KeyboardController::ClearEnableFlag(mojom::KeyboardEnableFlag flag) {
@@ -503,10 +500,7 @@
   for (KeyboardControllerObserver& observer : observer_list_)
     observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
 
-  if (IsKeyboardEnableRequested() && !IsEnabled())
-    EnableKeyboard();
-  else if (!IsKeyboardEnableRequested() && IsEnabled())
-    DisableKeyboard();
+  UpdateKeyboardAsRequestedBy(flag);
 }
 
 bool KeyboardController::IsEnableFlagSet(mojom::KeyboardEnableFlag flag) const {
@@ -541,6 +535,25 @@
          IsEnableFlagSet(KeyboardEnableFlag::kTouchEnabled);
 }
 
+void KeyboardController::UpdateKeyboardAsRequestedBy(
+    mojom::KeyboardEnableFlag flag) {
+  if (IsKeyboardEnableRequested()) {
+    // Note that there are two versions of the on-screen keyboard. A full layout
+    // is provided for accessibility, which includes sticky modifier keys to
+    // enable typing of hotkeys. A compact version is used in tablet mode to
+    // provide a layout with larger keys to facilitate touch typing. In the
+    // event that the a11y keyboard is being disabled, an on-screen keyboard
+    // might still be enabled and a forced reset is required to pick up the
+    // layout change.
+    if (IsEnabled() && flag == mojom::KeyboardEnableFlag::kAccessibilityEnabled)
+      RebuildKeyboardIfEnabled();
+    else
+      EnableKeyboard();
+  } else {
+    DisableKeyboard();
+  }
+}
+
 bool KeyboardController::IsKeyboardOverscrollEnabled() const {
   if (!IsEnabled())
     return false;
diff --git a/ui/keyboard/keyboard_controller.h b/ui/keyboard/keyboard_controller.h
index 38cf908..baf8b6a 100644
--- a/ui/keyboard/keyboard_controller.h
+++ b/ui/keyboard/keyboard_controller.h
@@ -321,6 +321,10 @@
   // of Set/ClearEnableFlag should cause the keyboard to be enabled.
   bool IsKeyboardEnableRequested() const;
 
+  // Enables or disables the keyboard based on |IsKeyboardEnableRequested|,
+  // as requested by |flag|.
+  void UpdateKeyboardAsRequestedBy(mojom::KeyboardEnableFlag flag);
+
   // Attach the keyboard window as a child of the given parent window.
   // Can only be called when the keyboard is not activated. |parent| must not
   // have any children.
diff --git a/ui/keyboard/keyboard_util_unittest.cc b/ui/keyboard/keyboard_util_unittest.cc
index df47523..00d5220 100644
--- a/ui/keyboard/keyboard_util_unittest.cc
+++ b/ui/keyboard/keyboard_util_unittest.cc
@@ -12,6 +12,7 @@
 #include "ui/keyboard/keyboard_ui.h"
 #include "ui/keyboard/keyboard_util.h"
 #include "ui/keyboard/test/keyboard_test_util.h"
+#include "ui/keyboard/test/test_keyboard_controller_observer.h"
 #include "ui/keyboard/test/test_keyboard_layout_delegate.h"
 #include "ui/keyboard/test/test_keyboard_ui_factory.h"
 
@@ -199,4 +200,25 @@
   EXPECT_FALSE(keyboard_controller_.IsKeyboardOverscrollEnabled());
 }
 
+// See https://crbug.com/946358.
+TEST_F(KeyboardUtilTest, RebuildsWhenChangingAccessibilityFlag) {
+  // Virtual keyboard enabled with compact layout.
+  keyboard::SetTouchKeyboardEnabled(true);
+
+  keyboard::TestKeyboardControllerObserver observer;
+  keyboard_controller_.AddObserver(&observer);
+
+  // Virtual keyboard should rebuild to switch to a11y layout.
+  keyboard::SetAccessibilityKeyboardEnabled(true);
+  EXPECT_EQ(1, observer.disabled_count);
+  EXPECT_EQ(1, observer.enabled_count);
+
+  // Virtual keyboard should rebuild to switch back to compact layout.
+  keyboard::SetAccessibilityKeyboardEnabled(false);
+  EXPECT_EQ(2, observer.disabled_count);
+  EXPECT_EQ(2, observer.enabled_count);
+
+  keyboard_controller_.RemoveObserver(&observer);
+}
+
 }  // namespace keyboard
diff --git a/ui/latency/BUILD.gn b/ui/latency/BUILD.gn
index 6afee94..b40ad83 100644
--- a/ui/latency/BUILD.gn
+++ b/ui/latency/BUILD.gn
@@ -7,6 +7,8 @@
 
 jumbo_source_set("latency") {
   sources = [
+    "average_lag_tracker.cc",
+    "average_lag_tracker.h",
     "fixed_point.cc",
     "fixed_point.h",
     "frame_metrics.cc",
@@ -49,6 +51,7 @@
 
 test("latency_unittests") {
   sources = [
+    "average_lag_tracker_unittest.cc",
     "fixed_point_unittest.cc",
     "frame_metrics_test_common.cc",
     "frame_metrics_test_common.h",
diff --git a/ui/latency/average_lag_tracker.cc b/ui/latency/average_lag_tracker.cc
new file mode 100644
index 0000000..e1419ba
--- /dev/null
+++ b/ui/latency/average_lag_tracker.cc
@@ -0,0 +1,159 @@
+// 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/latency/average_lag_tracker.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace ui {
+
+AverageLagTracker::AverageLagTracker() = default;
+
+AverageLagTracker::~AverageLagTracker() = default;
+
+void AverageLagTracker::AddLatencyInFrame(
+    const ui::LatencyInfo& latency,
+    base::TimeTicks gpu_swap_begin_timestamp,
+    const std::string& scroll_name) {
+  base::TimeTicks event_timestamp;
+  bool found_component = latency.FindLatency(
+      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT,
+      &event_timestamp);
+  DCHECK(found_component);
+  // Skip if no event timestamp.
+  if (!found_component)
+    return;
+
+  if (scroll_name == "ScrollBegin") {
+    // Flush all unfinished frames.
+    while (!frame_lag_infos_.empty()) {
+      base::TimeTicks last_time =
+          std::max(last_event_timestamp_, last_finished_frame_time_);
+      frame_lag_infos_.front().lag_area +=
+          std::abs(last_event_accumulated_delta_ -
+                   frame_lag_infos_.front().rendered_accumulated_delta) *
+          (frame_lag_infos_.front().frame_time - last_time).InMillisecondsF();
+      // Record UMA when it's the last item in queue.
+      CalculateAndReportAverageLagUma(frame_lag_infos_.size() == 1);
+    }
+    // |accumulated_lag_| should be cleared/reset.
+    DCHECK(accumulated_lag_ == 0);
+
+    // Create ScrollBegin report, with report time equals to gpu swap time.
+    LagAreaInFrame first_frame(gpu_swap_begin_timestamp);
+    frame_lag_infos_.push_back(first_frame);
+
+    // Reset fields.
+    last_reported_time_ = event_timestamp;
+    last_finished_frame_time_ = event_timestamp;
+    last_event_accumulated_delta_ = 0;
+    last_rendered_accumulated_delta_ = 0;
+    is_begin_ = true;
+  } else if (scroll_name == "ScrollUpdate" &&
+             !last_event_timestamp_.is_null()) {
+    DCHECK((event_timestamp - last_event_timestamp_).InMilliseconds() >= 0);
+    // Pop all frames where frame_time <= event_timestamp.
+    while (!frame_lag_infos_.empty() &&
+           frame_lag_infos_.front().frame_time <= event_timestamp) {
+      base::TimeTicks front_time =
+          std::max(last_event_timestamp_, last_finished_frame_time_);
+      base::TimeTicks back_time = frame_lag_infos_.front().frame_time;
+      frame_lag_infos_.front().lag_area +=
+          LagBetween(front_time, back_time, latency, event_timestamp);
+
+      CalculateAndReportAverageLagUma();
+    }
+
+    // Initialize a new LagAreaInFrame when current_frame_time > frame_time.
+    if (frame_lag_infos_.empty() ||
+        gpu_swap_begin_timestamp > frame_lag_infos_.back().frame_time) {
+      LagAreaInFrame new_frame(gpu_swap_begin_timestamp,
+                               last_rendered_accumulated_delta_);
+      frame_lag_infos_.push_back(new_frame);
+    }
+
+    // last_frame_time <= event_timestamp < frame_time
+    if (!frame_lag_infos_.empty()) {
+      // The front element in queue (if any) must satisfy frame_time >
+      // event_timestamp, otherwise it would be popped in the while loop.
+      DCHECK(last_finished_frame_time_ <= event_timestamp &&
+             event_timestamp <= frame_lag_infos_.front().frame_time);
+      base::TimeTicks front_time =
+          std::max(last_finished_frame_time_, last_event_timestamp_);
+      base::TimeTicks back_time = event_timestamp;
+
+      frame_lag_infos_.front().lag_area +=
+          LagBetween(front_time, back_time, latency, event_timestamp);
+    }
+  }
+
+  last_event_timestamp_ = event_timestamp;
+  last_event_accumulated_delta_ += latency.scroll_update_delta();
+  last_rendered_accumulated_delta_ += latency.scroll_update_delta();
+}
+
+float AverageLagTracker::LagBetween(base::TimeTicks front_time,
+                                    base::TimeTicks back_time,
+                                    const LatencyInfo& latency,
+                                    base::TimeTicks event_timestamp) {
+  // In some tests, we use const event time. return 0 to avoid divided by 0.
+  if (event_timestamp == last_event_timestamp_)
+    return 0;
+
+  float front_delta =
+      (last_event_accumulated_delta_ +
+       (latency.scroll_update_delta() *
+        ((front_time - last_event_timestamp_).InMillisecondsF() /
+         (event_timestamp - last_event_timestamp_).InMillisecondsF()))) -
+      frame_lag_infos_.front().rendered_accumulated_delta;
+
+  float back_delta =
+      (last_event_accumulated_delta_ +
+       latency.scroll_update_delta() *
+
+           ((back_time - last_event_timestamp_).InMillisecondsF() /
+            (event_timestamp - last_event_timestamp_).InMillisecondsF())) -
+      frame_lag_infos_.front().rendered_accumulated_delta;
+
+  // Calculate the trapezoid area.
+  if (front_delta * back_delta >= 0) {
+    return 0.5f * std::abs(front_delta + back_delta) *
+           (back_time - front_time).InMillisecondsF();
+  }
+
+  // Corner case that rendered_accumulated_delta is in between of front_pos
+  // and back_pos.
+  return 0.5f *
+         std::abs((front_delta * front_delta + back_delta * back_delta) /
+                  (back_delta - front_delta)) *
+         (back_time - front_time).InMillisecondsF();
+}
+
+void AverageLagTracker::CalculateAndReportAverageLagUma(bool send_anyway) {
+  DCHECK(!frame_lag_infos_.empty());
+  const LagAreaInFrame& frame_lag = frame_lag_infos_.front();
+
+  DCHECK(frame_lag.lag_area >= 0.f);
+  accumulated_lag_ += frame_lag.lag_area;
+
+  // |send_anyway| is true when we are flush all remaining frames on next
+  // |ScrollBegin|. Otherwise record UMA when it's ScrollBegin, or when
+  // reaching the 1 second gap.
+  if (send_anyway || is_begin_ ||
+      (frame_lag.frame_time - last_reported_time_).InSecondsF() >= 1.0f) {
+    std::string scroll_name = is_begin_ ? "ScrollBegin" : "ScrollUpdate";
+    base::UmaHistogramCounts1000(
+        "Event.Latency." + scroll_name + ".Touch.AverageLag",
+        accumulated_lag_ /
+            (frame_lag.frame_time - last_reported_time_).InMillisecondsF());
+    accumulated_lag_ = 0;
+    last_reported_time_ = frame_lag.frame_time;
+    is_begin_ = false;
+  }
+
+  last_finished_frame_time_ = frame_lag.frame_time;
+  frame_lag_infos_.pop_front();
+}
+
+}  // namespace ui
diff --git a/ui/latency/average_lag_tracker.h b/ui/latency/average_lag_tracker.h
new file mode 100644
index 0000000..898e9e9
--- /dev/null
+++ b/ui/latency/average_lag_tracker.h
@@ -0,0 +1,78 @@
+// 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_LATENCY_AVERAGE_LAG_TRACKER_H_
+#define UI_LATENCY_AVERAGE_LAG_TRACKER_H_
+
+#include <deque>
+
+#include "base/macros.h"
+#include "ui/latency/latency_info.h"
+
+namespace ui {
+
+// A class for reporting AverageLag metrics. See
+// https://docs.google.com/document/d/1e8NuzPblIv2B9bz01oSj40rmlse7_PHq5oFS3lqz6N4/
+class AverageLagTracker {
+ public:
+  AverageLagTracker();
+  ~AverageLagTracker();
+  void AddLatencyInFrame(const LatencyInfo& latency,
+                         base::TimeTicks gpu_swap_begin_timestamp,
+                         const std::string& scroll_name);
+
+ private:
+  typedef struct LagAreaInFrame {
+    LagAreaInFrame(base::TimeTicks time, float rendered_pos = 0)
+        : frame_time(time),
+          rendered_accumulated_delta(rendered_pos),
+          lag_area(0) {}
+    base::TimeTicks frame_time;
+    float rendered_accumulated_delta;
+    float lag_area;
+  } LagAreaInFrame;
+
+  // Calculate lag in 1 seconds intervals and report UMA.
+  void CalculateAndReportAverageLagUma(bool send_anyway = false);
+
+  // Helper function to calculate lag area between |front_time| to
+  // |back_time|.
+  float LagBetween(base::TimeTicks front_time,
+                   base::TimeTicks back_time,
+                   const LatencyInfo& latency,
+                   base::TimeTicks event_time);
+
+  std::deque<LagAreaInFrame> frame_lag_infos_;
+
+  // Last scroll event's timestamp in the sequence, reset on ScrollBegin.
+  base::TimeTicks last_event_timestamp_;
+  // Timestamp of the last frame popped from |frame_lag_infos_| queue.
+  base::TimeTicks last_finished_frame_time_;
+
+  // Accumulated scroll delta for actual scroll update events. Cumulated from
+  // latency.scroll_update_delta(). Reset on ScrollBegin.
+  float last_event_accumulated_delta_;
+  // Accumulated scroll delta got rendered on gpu swap. Cumulated from
+  // latency.predicted_scroll_update_delta(). It always has same value as
+  // |last_event_accumulated_delta_| when scroll prediction is disabled.
+  float last_rendered_accumulated_delta_;
+
+  // This keeps track of the last report_time when we report to UMA, so we can
+  // calculate the report's duration by current - last. Reset on ScrollBegin.
+  base::TimeTicks last_reported_time_;
+
+  // True if the first element of |frame_lag_infos_| is for ScrollBegin.
+  // For ScrollBegin, we don't wait for the 1 second interval but record the
+  // UMA once the frame is finished.
+  bool is_begin_ = false;
+
+  // Accumulated lag area in the 1 second intervals.
+  float accumulated_lag_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(AverageLagTracker);
+};
+
+}  // namespace ui
+
+#endif  // UI_LATENCY_AVERAGE_LAG_TRACKER_H_
diff --git a/ui/latency/average_lag_tracker_unittest.cc b/ui/latency/average_lag_tracker_unittest.cc
new file mode 100644
index 0000000..c4c34f1
--- /dev/null
+++ b/ui/latency/average_lag_tracker_unittest.cc
@@ -0,0 +1,248 @@
+// 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/latency/average_lag_tracker.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Bucket;
+using testing::ElementsAre;
+
+namespace ui {
+namespace {
+
+class AverageLagTrackerTest : public testing::Test {
+ public:
+  AverageLagTrackerTest() { ResetHistograms(); }
+
+  void ResetHistograms() {
+    histogram_tester_.reset(new base::HistogramTester());
+  }
+
+  const base::HistogramTester& histogram_tester() { return *histogram_tester_; }
+
+  void SetUp() override {
+    average_lag_tracker_ = std::make_unique<AverageLagTracker>();
+  }
+
+  void SyntheticTouchScrollBeginLatencyInfo(base::TimeTicks event_time,
+                                            base::TimeTicks frame_time,
+                                            float delta) {
+    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
+    touch_latency.set_scroll_update_delta(delta);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+        event_time, 1);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
+        1);
+    average_lag_tracker_->AddLatencyInFrame(touch_latency, frame_time,
+                                            "ScrollBegin");
+  }
+
+  void SyntheticTouchScrollUpdateLatencyInfo(base::TimeTicks event_time,
+                                             base::TimeTicks frame_time,
+                                             float delta) {
+    ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
+    touch_latency.set_scroll_update_delta(delta);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, event_time,
+        1);
+    touch_latency.AddLatencyNumberWithTimestamp(
+        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, event_time,
+        1);
+    average_lag_tracker_->AddLatencyInFrame(touch_latency, frame_time,
+                                            "ScrollUpdate");
+  }
+
+ protected:
+  std::unique_ptr<AverageLagTracker> average_lag_tracker_;
+
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
+};
+
+base::TimeTicks MillisecondsToTimeTicks(float t_ms) {
+  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(t_ms);
+}
+
+// Simulate a simple situation that events at every 10ms and start at t=15ms,
+// frame swaps at every 10ms too and start at t=20ms and test we record one
+// UMA for ScrollUpdate in one second.
+TEST_F(AverageLagTrackerTest, OneSecondInterval) {
+  base::TimeTicks event_time =
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(5);
+  base::TimeTicks frame_time =
+      base::TimeTicks() + base::TimeDelta::FromMilliseconds(10);
+  float scroll_delta = 10;
+
+  // ScrollBegin
+  event_time += base::TimeDelta::FromMilliseconds(10);  // 15ms
+  frame_time += base::TimeDelta::FromMilliseconds(10);  // 20ms
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // Send 101 ScrollUpdate events to verify that there is 1 AverageLag record
+  // per 1 second.
+  const int kUpdates = 101;
+  for (int i = 0; i < kUpdates; i++) {
+    event_time += base::TimeDelta::FromMilliseconds(10);
+    frame_time += base::TimeDelta::FromMilliseconds(10);
+    // First 50 has positive delta, others negetive delta.
+    const int sign = (i < kUpdates / 2) ? 1 : -1;
+    SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                          sign * scroll_delta);
+  }
+
+  // ScrollBegin report_time is at 20ms, so the next ScrollUpdate report_time is
+  // at 1020ms. The last event_time that finish this report should be later than
+  // 1020ms.
+  EXPECT_EQ(event_time,
+            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1025));
+  EXPECT_EQ(frame_time,
+            base::TimeTicks() + base::TimeDelta::FromMilliseconds(1030));
+
+  // ScrollBegin AverageLag are the area between the event original component
+  // (time=15ms, delta=10px) to the frame swap time (time=20ms, expect finger
+  // position at delta=15px). The AverageLag scaled to 1 second is
+  // (0.5*(10px+15px)*5ms)/5ms = 12.5px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(12, 1)));
+  // This ScrollUpdate AverageLag are calculated as the finger uniformly scroll
+  // 10px each frame. For scroll up/down frame, the Lag at the last frame swap
+  // is 5px, and Lag at this frame swap is 15px. For the one changing direction,
+  // the Lag is from 5 to 10 and down to 5 again. So total LagArea is 99 * 100,
+  // plus 75. the AverageLag in 1 second is 9.975px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(9, 1)));
+  ResetHistograms();
+
+  // Send another ScrollBegin to end the unfinished ScrollUpdate report.
+  event_time += base::TimeDelta::FromMilliseconds(10);
+  frame_time += base::TimeDelta::FromMilliseconds(10);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // The last ScrollUpdate's lag is 8.75px and truncated to 8.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(8, 1)));
+}
+
+// Test the case that event's frame swap time is later than next event's
+// creation time. (i.e, event at t=10ms will be dispatch at t=30ms, while next
+// event is at t=20ms).
+TEST_F(AverageLagTrackerTest, LargerLatency) {
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time =
+      event_time + base::TimeDelta::FromMilliseconds(20);
+  float scroll_delta = 10;
+
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+
+  // Send 2 ScrollUpdate. The second one will record AverageLag.ScrollBegin as
+  // it's event_time is larger or equal to ScrollBegin's frame_time.
+  for (int i = 0; i < 2; i++) {
+    event_time += base::TimeDelta::FromMilliseconds(10);
+    frame_time = event_time + base::TimeDelta::FromMilliseconds(20);
+    SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time, scroll_delta);
+  }
+
+  // ScrollBegin AveragLag are from t=10ms to t=30ms, with absolute scroll
+  // position from 10 to 30. The AverageLag should be:
+  // (0.5*(10px + 30px)*20ms/20ms) = 20px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(20, 1)));
+
+  // Another ScrollBegin to flush unfinished frames.
+  // event_time doesn't matter here because the previous frames' lag are
+  // compute from their frame_time.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, scroll_delta);
+  // The to unfinished frames' lag are (finger_positon-rendered_position)*time,
+  // AverageLag is ((30px-10px)*10ms+(30px-20px)*10ms)/20ms = 15px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(14, 1)));
+}
+
+// Test that multiple latency being flush in the same frame swap.
+TEST_F(AverageLagTrackerTest, TwoLatencyInfoInSameFrame) {
+  // ScrollBegin
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time = MillisecondsToTimeTicks(20);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time,
+                                       -10 /* scroll_delta */);
+
+  // ScrollUpdate with event_time >= ScrollBegin frame_time will generate
+  // a histogram for AverageLag.ScrollBegin.
+  event_time = MillisecondsToTimeTicks(20);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        -10 /* scroll_delta */);
+
+  // Absolute position from -10 to -20. The AverageLag should be:
+  // (0.5*(10px + 20px)*10ms/10ms) = 15px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollBegin.Touch.AverageLag"),
+              ElementsAre(Bucket(14, 1)));
+
+  event_time = MillisecondsToTimeTicks(25);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        5 /* scroll_delta */);
+
+  // Another ScrollBegin to flush unfinished frames.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, 0);
+
+  // The ScrollUpdates are at t=20ms, finger_pos=-20px, rendered_pos=-10px,
+  // at t=25ms, finger_pos=-15px, rendered_pos=-10px;
+  // To t=30ms both events get flush.
+  // AverageLag is (0.5*(10px+5px)*5ms + 5px*5ms)/10ms = 6.25px
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(6, 1)));
+}
+
+// Test the case that switching direction causes lag at current frame
+// time and previous frame time are in different direction.
+TEST_F(AverageLagTrackerTest, ChangeDirectionInFrame) {
+  // ScrollBegin
+  base::TimeTicks event_time = MillisecondsToTimeTicks(10);
+  base::TimeTicks frame_time = MillisecondsToTimeTicks(20);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time,
+                                       10 /* scroll_delta */);
+
+  // At t=20, lag = 10px.
+  event_time = MillisecondsToTimeTicks(20);
+  frame_time = MillisecondsToTimeTicks(30);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        10 /* scroll_delta */);
+
+  // At t=30, lag = -10px.
+  event_time = MillisecondsToTimeTicks(30);
+  frame_time = MillisecondsToTimeTicks(40);
+  SyntheticTouchScrollUpdateLatencyInfo(event_time, frame_time,
+                                        -20 /* scroll_delta */);
+
+  // Another ScrollBegin to flush unfinished frames.
+  event_time = MillisecondsToTimeTicks(1000);
+  frame_time = MillisecondsToTimeTicks(1000);
+  SyntheticTouchScrollBeginLatencyInfo(event_time, frame_time, 0);
+
+  // From t=20 to t=30, lag_area=2*(0.5*10px*5ms)=50px*ms.
+  // From t=30 to t=40, lag_area=20px*10ms=200px*ms
+  // AverageLag = (50+200)/20 = 12.5px.
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  "Event.Latency.ScrollUpdate.Touch.AverageLag"),
+              ElementsAre(Bucket(12, 1)));
+}
+
+}  // namespace
+}  // namespace ui
diff --git a/ui/latency/latency_tracker.cc b/ui/latency/latency_tracker.cc
index d98a8e0..4c851c1 100644
--- a/ui/latency/latency_tracker.cc
+++ b/ui/latency/latency_tracker.cc
@@ -260,7 +260,8 @@
          (IsInertialScroll(latency) && scroll_name == "ScrollInertial"));
 
   if (!IsInertialScroll(latency) && input_modality == "Touch")
-    CalculateAverageLag(latency, gpu_swap_begin_timestamp, scroll_name);
+    average_lag_tracker_.AddLatencyInFrame(latency, gpu_swap_begin_timestamp,
+                                           scroll_name);
 
   base::TimeTicks rendering_scheduled_timestamp;
   bool rendering_scheduled_on_main = latency.FindLatency(
@@ -343,133 +344,6 @@
       gpu_swap_begin_timestamp, gpu_swap_end_timestamp);
 }
 
-void LatencyTracker::CalculateAverageLag(
-    const ui::LatencyInfo& latency,
-    base::TimeTicks gpu_swap_begin_timestamp,
-    const std::string& scroll_name) {
-  base::TimeTicks event_timestamp;
-  bool found_component = latency.FindLatency(
-      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT,
-      &event_timestamp);
-  DCHECK_AND_RETURN_ON_FAIL(found_component);
-
-  if (scroll_name == "ScrollBegin") {
-    // Clear both lag_reports.
-    ReportAverageLagUma(std::move(pending_finished_lag_report_));
-    if (current_lag_report_)
-      current_lag_report_->report_time = last_frame_time_;
-    ReportAverageLagUma(std::move(current_lag_report_));
-
-    // Create ScrollBegin report, with report time equals to gpu swap time.
-    LagData new_report(scroll_name);
-    pending_finished_lag_report_ = std::make_unique<LagData>(scroll_name);
-    pending_finished_lag_report_->report_time = gpu_swap_begin_timestamp;
-    // For ScrollBegin, we don't have the previous time to calculate the
-    // interpolated area, so the lag is the area between the current event
-    // creation time and gpu swap begin time.
-    pending_finished_lag_report_->lag =
-        (gpu_swap_begin_timestamp - event_timestamp).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta());
-    // The next report time should be a least 1 second away from current report
-    // time.
-    next_report_time_ = pending_finished_lag_report_->report_time +
-                        base::TimeDelta::FromSeconds(1);
-    // Reset last_reported_time to event time.
-    last_reported_time_ = event_timestamp;
-  } else if (scroll_name == "ScrollUpdate" &&
-             !last_event_timestamp_.is_null()) {
-    DCHECK((event_timestamp - last_event_timestamp_).InMilliseconds() >= 0);
-
-    // |pending_finger_move_lag| is the interpolated area between last event to
-    // current event. We assume the finger moved at a constant velocity between
-    // the past two events, so the lag in this duration is calculated by the
-    // average delta(current delta/2).
-    float pending_finger_move_lag =
-        (event_timestamp - last_event_timestamp_).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta() / 2);
-
-    // |event_dispatch_lag| is the area between the current event creation time
-    // (i.e. last coalesced event of current event creation time) and gpu swap
-    // begin time of this event.
-    float event_dispatch_lag =
-        (gpu_swap_begin_timestamp - event_timestamp).InMillisecondsF() *
-        std::abs(latency.scroll_update_delta());
-
-    if (pending_finished_lag_report_) {
-      if (event_timestamp >= pending_finished_lag_report_->report_time) {
-        DCHECK_GE(pending_finished_lag_report_->report_time,
-                  last_event_timestamp_);
-        // This event is created after this report's report time, so part of
-        // the |pending_finger_move_lag| should be counted in this report, the
-        // rest should be count in the following report. The area of first part
-        // is calculated by similar triangle area.
-        float ratio =
-            (pending_finished_lag_report_->report_time - last_event_timestamp_)
-                .InMillisecondsF() /
-            (event_timestamp - last_event_timestamp_).InMillisecondsF();
-        pending_finished_lag_report_->lag +=
-            pending_finger_move_lag * ratio * ratio;
-        pending_finger_move_lag *= 1 - ratio * ratio;
-        ReportAverageLagUma(std::move(pending_finished_lag_report_));
-      } else {  // event_timestamp < pending_finished_lag_report_->report_time
-        DCHECK_LE(pending_finished_lag_report_->report_time,
-                  gpu_swap_begin_timestamp);
-        // This event is created before this report's report_time, so
-        // |pending_finger_move_lag|, and also part of |event_dispatch_lag| that
-        // before |report_time| should be counted in this report.
-        float lag_after_report_time =
-            (gpu_swap_begin_timestamp -
-             pending_finished_lag_report_->report_time)
-                .InMillisecondsF() *
-            std::abs(latency.scroll_update_delta());
-        pending_finished_lag_report_->lag += pending_finger_move_lag +
-                                             event_dispatch_lag -
-                                             lag_after_report_time;
-        pending_finger_move_lag = 0;
-        event_dispatch_lag = lag_after_report_time;
-      }
-    }
-
-    // Remaining pending lag should be counted in the |current_lag_report_|.
-    if (pending_finger_move_lag + event_dispatch_lag != 0) {
-      if (!current_lag_report_)
-        current_lag_report_ = std::make_unique<LagData>(scroll_name);
-
-      current_lag_report_->lag += pending_finger_move_lag + event_dispatch_lag;
-
-      // When the |pending_finished_lag_report_| is finished, and the current
-      // gpu_swap_time is larger than the |next_report_time_|, it means the we
-      // reach the 1 second gap, and we can filled in the timestamp and move it
-      // to |pending_finished_lag_report_|. We use the
-      // current|gpu_swap_begin_timestamp| as the report_time, so it can be
-      // align with gpu swaps.
-      if (!pending_finished_lag_report_ &&
-          gpu_swap_begin_timestamp >= next_report_time_) {
-        current_lag_report_->report_time = gpu_swap_begin_timestamp;
-        // The next report time is 1 second away from this report time.
-        next_report_time_ =
-            gpu_swap_begin_timestamp + base::TimeDelta::FromSeconds(1);
-        pending_finished_lag_report_ = std::move(current_lag_report_);
-      }
-    }
-  }
-  last_event_timestamp_ = event_timestamp;
-  last_frame_time_ = gpu_swap_begin_timestamp;
-}
-
-void LatencyTracker::ReportAverageLagUma(std::unique_ptr<LagData> report) {
-  if (report) {
-    DCHECK(!report->report_time.is_null());
-    DCHECK(report->lag >= 0.f);
-    base::UmaHistogramCounts1000(
-        "Event.Latency." + report->scroll_name + ".Touch.AverageLag",
-        report->lag /
-            (report->report_time - last_reported_time_).InMillisecondsF());
-
-    last_reported_time_ = report->report_time;
-  }
-}
-
 // static
 void LatencyTracker::SetLatencyInfoProcessorForTesting(
     const LatencyInfoProcessor& processor) {
diff --git a/ui/latency/latency_tracker.h b/ui/latency/latency_tracker.h
index d7ac971..02ecf72 100644
--- a/ui/latency/latency_tracker.h
+++ b/ui/latency/latency_tracker.h
@@ -5,8 +5,8 @@
 #ifndef UI_LATENCY_LATENCY_TRACKER_H_
 #define UI_LATENCY_LATENCY_TRACKER_H_
 
-#include <deque>
 #include "base/macros.h"
+#include "ui/latency/average_lag_tracker.h"
 #include "ui/latency/latency_info.h"
 
 namespace ui {
@@ -52,41 +52,7 @@
       base::TimeTicks gpu_swap_end_timestamp,
       const LatencyInfo& latency);
 
-  void CalculateAverageLag(const ui::LatencyInfo& latency,
-                           base::TimeTicks gpu_swap_begin_timestamp,
-                           const std::string& scroll_name);
-
-  // Used for reporting AverageLag metrics.
-  typedef struct LagData {
-    LagData(const std::string& name)
-        : report_time(base::TimeTicks()), lag(0), scroll_name(name) {}
-    // Lag report's report_time, align with |gpu_swap_begin_time|. It should has
-    // one second gap between previous report. We do not set the report_time
-    // before the 1 second gap is reached.
-    base::TimeTicks report_time;
-    float lag;
-    const std::string scroll_name;
-  } LagData;
-
-  void ReportAverageLagUma(std::unique_ptr<LagData> report);
-
-  // Last scroll event's timestamp in the sequence, reset on ScrollBegin.
-  base::TimeTicks last_event_timestamp_;
-  // next_report_time is always 1 second after the newest report's report_time.
-  base::TimeTicks next_report_time_;
-  // This keeps track the last report_time when we report to UMA, so we can
-  // calculate the report's duration by current - last. Reset on ScrollBegin.
-  base::TimeTicks last_reported_time_;
-  // Keeps track of last gpu_swap time, so we can end the previous unfinished
-  // report on the new ScrollBegin.
-  base::TimeTicks last_frame_time_;
-  // Lag report that already filled in the report_time, and it will be finished
-  // and report once we have an event whose timestamp is later then the
-  // report_time.
-  std::unique_ptr<LagData> pending_finished_lag_report_;
-  // The current unfinished lag report, which doesn't reach the 1 second length
-  // yet. It's report_time is null and invalid now.
-  std::unique_ptr<LagData> current_lag_report_;
+  AverageLagTracker average_lag_tracker_;
 
   DISALLOW_COPY_AND_ASSIGN(LatencyTracker);
 };
diff --git a/ui/message_center/OWNERS b/ui/message_center/OWNERS
index 806bac8..0bd96e9 100644
--- a/ui/message_center/OWNERS
+++ b/ui/message_center/OWNERS
@@ -3,5 +3,6 @@
 peter@chromium.org
 stevenjb@chromium.org
 yoshiki@chromium.org
+tengs@chromium.org
 
 # COMPONENT: UI>Notifications
diff --git a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
index 761a10c..7385a97 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
@@ -286,7 +286,7 @@
     const int hit;
   } kTestCases[] = {
 #if defined(OS_WIN)
-    {0, is_aero_glass_enabled ? HTTRANSPARENT : HTNOWHERE},
+    {0, is_aero_glass_enabled ? HTTRANSPARENT : HTCAPTION},
 #else
     {0, HTTRANSPARENT},
 #endif
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
index 3828c02..a6a60190 100644
--- a/ui/views/bubble/bubble_frame_view.cc
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -181,19 +181,6 @@
   if (close_->visible() && close_->GetMirroredBounds().Contains(point))
     return HTCLOSE;
 
-  // Allow dialogs to show the system menu and be dragged.
-  if (GetWidget()->widget_delegate()->AsDialogDelegate() &&
-      !GetWidget()->widget_delegate()->AsBubbleDialogDelegate()) {
-    gfx::Rect bounds(GetContentsBounds());
-    bounds.Inset(title_margins_);
-    gfx::Rect sys_rect(0, 0, bounds.x(), bounds.y());
-    sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
-    if (sys_rect.Contains(point))
-      return HTSYSMENU;
-    if (point.y() < title()->bounds().bottom())
-      return HTCAPTION;
-  }
-
   // Convert to RRectF to accurately represent the rounded corners of the
   // dialog and allow events to pass through the shadows.
   gfx::RRectF round_contents_bounds(gfx::RectF(GetContentsBounds()),
@@ -204,6 +191,16 @@
   if (!round_contents_bounds.Contains(rectf_point))
     return HTTRANSPARENT;
 
+  if (HasTitle() && point.y() < title()->bounds().bottom()) {
+    auto* dialog_delegate = GetWidget()->widget_delegate()->AsDialogDelegate();
+    // Allow the dialog to be dragged if it is not modal. This can happen if the
+    // dialog has no parent browser window.
+    if (dialog_delegate &&
+        dialog_delegate->GetModalType() == ui::MODAL_TYPE_NONE) {
+      return HTCAPTION;
+    }
+  }
+
   return GetWidget()->client_view()->NonClientHitTest(point);
 }
 
diff --git a/ui/views/window/dialog_delegate_unittest.cc b/ui/views/window/dialog_delegate_unittest.cc
index b8e92a1..8aa6955 100644
--- a/ui/views/window/dialog_delegate_unittest.cc
+++ b/ui/views/window/dialog_delegate_unittest.cc
@@ -98,6 +98,13 @@
 
   views::Textfield* input() { return input_; }
 
+  ui::ModalType GetModalType() const override {
+    return modal_type_none_ ? ui::MODAL_TYPE_NONE : ui::MODAL_TYPE_WINDOW;
+  }
+  void set_modal_type_none(bool modal_type_none) {
+    modal_type_none_ = modal_type_none;
+  }
+
  private:
   views::Textfield* input_;
   bool canceled_ = false;
@@ -109,6 +116,7 @@
   bool show_close_button_ = true;
   bool should_handle_escape_ = false;
   int dialog_buttons_ = ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
+  bool modal_type_none_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TestDialog);
 };
@@ -216,22 +224,22 @@
   const NonClientView* view = dialog()->GetWidget()->non_client_view();
   BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view());
 
-  struct {
+  constexpr struct {
     const int point;
     const int hit;
-  } cases[] = {
-      {0, HTSYSMENU},
-      {10, HTSYSMENU},
+  } kCases[] = {
+      {0, HTTRANSPARENT},
+      {10, HTNOWHERE},
       {20, HTNOWHERE},
       {50, HTCLIENT /* Space is reserved for the close button. */},
       {60, HTCLIENT},
       {1000, HTNOWHERE},
   };
 
-  for (size_t i = 0; i < base::size(cases); ++i) {
-    gfx::Point point(cases[i].point, cases[i].point);
-    EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point))
-        << " case " << i << " at point " << cases[i].point;
+  for (const auto test_case : kCases) {
+    gfx::Point point(test_case.point, test_case.point);
+    EXPECT_EQ(test_case.hit, frame->NonClientHitTest(point))
+        << " at point " << test_case.point;
   }
 }
 
@@ -243,18 +251,18 @@
   const NonClientView* view = dialog()->GetWidget()->non_client_view();
   BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view());
 
-  struct {
+  constexpr struct {
     const int point;
     const int hit;
-  } cases[] = {
-      {0, HTSYSMENU}, {10, HTSYSMENU}, {20, HTCLIENT},
-      {50, HTCLIENT}, {60, HTCLIENT},  {1000, HTNOWHERE},
+  } kCases[] = {
+      {0, HTTRANSPARENT}, {10, HTCLIENT}, {20, HTCLIENT},
+      {50, HTCLIENT},     {60, HTCLIENT}, {1000, HTNOWHERE},
   };
 
-  for (size_t i = 0; i < base::size(cases); ++i) {
-    gfx::Point point(cases[i].point, cases[i].point);
-    EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point))
-        << " case " << i << " at point " << cases[i].point;
+  for (const auto test_case : kCases) {
+    gfx::Point point(test_case.point, test_case.point);
+    EXPECT_EQ(test_case.hit, frame->NonClientHitTest(point))
+        << " at point " << test_case.point;
   }
 }
 
@@ -266,18 +274,43 @@
   dialog()->GetWidget()->LayoutRootViewIfNecessary();
   BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view());
 
-  struct {
+  constexpr struct {
     const int point;
     const int hit;
-  } cases[] = {
-      {0, HTSYSMENU}, {10, HTSYSMENU}, {20, HTCAPTION},
-      {50, HTCLIENT}, {60, HTCLIENT},  {1000, HTNOWHERE},
+  } kCases[] = {
+      {0, HTTRANSPARENT}, {10, HTNOWHERE}, {20, HTNOWHERE},
+      {50, HTCLIENT},     {60, HTCLIENT},  {1000, HTNOWHERE},
   };
 
-  for (size_t i = 0; i < base::size(cases); ++i) {
-    gfx::Point point(cases[i].point, cases[i].point);
-    EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point))
-        << " at point " << cases[i].point;
+  for (const auto test_case : kCases) {
+    gfx::Point point(test_case.point, test_case.point);
+    EXPECT_EQ(test_case.hit, frame->NonClientHitTest(point))
+        << " at point " << test_case.point;
+  }
+}
+
+TEST_F(DialogTest, HitTest_ModalTypeNone_WithTitle) {
+  // Ensure that BubbleFrameView hit-tests as expected when the title is shown
+  // and the modal type is none.
+  const NonClientView* view = dialog()->GetWidget()->non_client_view();
+  dialog()->set_title(base::ASCIIToUTF16("Title"));
+  dialog()->GetWidget()->UpdateWindowTitle();
+  dialog()->set_modal_type_none(true);
+  dialog()->GetWidget()->LayoutRootViewIfNecessary();
+  BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view());
+
+  constexpr struct {
+    const int point;
+    const int hit;
+  } kCases[] = {
+      {0, HTTRANSPARENT}, {10, HTCAPTION}, {20, HTCAPTION},
+      {50, HTCLIENT},     {60, HTCLIENT},  {1000, HTNOWHERE},
+  };
+
+  for (const auto test_case : kCases) {
+    gfx::Point point(test_case.point, test_case.point);
+    EXPECT_EQ(test_case.hit, frame->NonClientHitTest(point))
+        << " at point " << test_case.point;
   }
 }
 
diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
index 8d400af..3f6a80f 100644
--- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
+++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
@@ -286,8 +286,6 @@
    * @private
    */
   onMouseover_: function(e) {
-    // TODO(scottchen): Using "focus" to determine selected item might mess
-    // with screen readers in some edge cases.
     let i = 0;
     let target;
     do {
diff --git a/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js b/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js
index 44f67b9..b3a5d49 100644
--- a/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js
+++ b/ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.js
@@ -4,7 +4,7 @@
 
 (function() {
 /**
- * TODO(scottchen): shim for not having Animation.finished implemented. Can
+ * TODO(dpapad): shim for not having Animation.finished implemented. Can
  * replace with Animation.finished if Chrome implements it (see:
  * crbug.com/257235).
  * @param {!Animation} animation
@@ -80,7 +80,7 @@
     const animationFunction = viewAnimations.get(animation);
     assert(animationFunction);
 
-    let effectiveView = view.matches('cr-lazy-render') ? view.get() : view;
+    const effectiveView = view.matches('cr-lazy-render') ? view.get() : view;
 
     effectiveView.classList.add('active');
     effectiveView.dispatchEvent(
diff --git a/ui/webui/resources/cr_elements/shared_vars_css.html b/ui/webui/resources/cr_elements/shared_vars_css.html
index e1a0627..301e373 100644
--- a/ui/webui/resources/cr_elements/shared_vars_css.html
+++ b/ui/webui/resources/cr_elements/shared_vars_css.html
@@ -163,7 +163,7 @@
       font-weight: 400;
     }
 
-    /* TODO (scottchen): re-implement with paddings instead; */
+    /* TODO (johntlee): re-implement with paddings instead; */
     /* These are used for row items such as radio buttons, check boxes, list
      * items etc. */
     --cr-section-min-height: 48px;