diff --git a/DEPS b/DEPS
index 5a1b8b2..05634da0 100644
--- a/DEPS
+++ b/DEPS
@@ -142,11 +142,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': '4e6a4416e5df9dd16d2d39172511a6d45cc86e35',
+  'skia_revision': 'f6da14697e15a91e68c49571385bcc5d3bea8db3',
   # 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': 'e545aee43105b9d8278377f25c4b23a62a1282c5',
+  'v8_revision': '2206e55ad023327a744c06084a594944486d0ef8',
   # 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.
@@ -154,15 +154,15 @@
   # 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': '8db211bc8d28156af958549c0d8a2669db87936a',
+  'angle_revision': '60e2f11eb584a80bcdd7e672e00cf7d1c594c5ee',
   # 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': '28ae0a4b2810b9f72547382333f32e0a46d87709',
+  'swiftshader_revision': '4cd9767e65651b636034aee688e502a8fbe6e559',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '9231ac5e1c6884555571fa2541fcbc10ff97a180',
+  'pdfium_revision': '3ec47b651aaa7d2d45640280e4625824a643e669',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -205,7 +205,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': '3efcccc6e77a54776a37f796183aa92c575060b5',
+  'catapult_revision': '14d669b045c865befc9b4e8bcf1295b78522a7aa',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9ec162df61d3ff15a3913c5f616d5de538b82369',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '992dcfc16c3ca68a57d0fda3ca3838199898f468',
       'condition': 'checkout_linux',
   },
 
@@ -900,7 +900,7 @@
   },
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'c538b5d796fb24dd418fdd650c7f76e56bcc3dd8',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '71892a5eda90fd7d3d6ccc12745f066d0ca5dc5f',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1057,7 +1057,7 @@
   },
 
   'src/third_party/libjpeg_turbo':
-    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '76aabbd351eea8a5988a5672526eda0677f2048d',
+    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '14eba7addfdcf0699970fcbac225499858a167f2',
 
   'src/third_party/liblouis/src': {
       'url': Var('chromium_git') + '/external/liblouis-github.git' + '@' + '97ce1c67fccbd3668291b7e63c06161c095d49f2',
@@ -1191,7 +1191,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '67a281286c52bd9eb4158d0d66c33acb162e67d9',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'da391939c5248fde66b3c2c76345a7d671e4fb10',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1359,7 +1359,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '91350f8ecf9ab2922ee062c114e4a759f24bd8d0',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ca160215c9d1f3fdb26cef072c01f12cd8b02fe6',
+    Var('webrtc_git') + '/src.git' + '@' + '0f0668e328995a62f2b7749150a67a6efa373d2e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1400,7 +1400,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3bf592f177c2a2dc1b7629b1853a7a04f7d06516',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@360b8d795ccb20b62f22004ac105932c73231f51',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/gfx/surfaces_instance.cc b/android_webview/browser/gfx/surfaces_instance.cc
index 8e61bba0..636d902 100644
--- a/android_webview/browser/gfx/surfaces_instance.cc
+++ b/android_webview/browser/gfx/surfaces_instance.cc
@@ -25,7 +25,6 @@
 #include "components/viz/service/display/display_scheduler.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
-#include "components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "gpu/command_buffer/service/sequence_id.h"
@@ -258,11 +257,7 @@
                                                  nullptr /* gr_shader_cache */);
     }
     if (settings.use_skia_renderer_non_ddl) {
-      output_surface = std::make_unique<viz::SkiaOutputSurfaceImplNonDDL>(
-          gl_surface_, shared_context_state_, task_executor->mailbox_manager(),
-          task_executor->shared_image_manager(),
-          task_executor->sync_point_manager(),
-          false /* need_swapbuffers_ack */);
+      NOTIMPLEMENTED();
     } else {
       output_surface = std::make_unique<viz::SkiaOutputSurfaceImpl>(
           std::make_unique<SkiaOutputSurfaceDependencyWebView>(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
index ef3dc7c..bd3904a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
@@ -392,11 +392,35 @@
 
     @Override
     public boolean onConsoleMessage(AwConsoleMessage consoleMessage) {
-        if (TRACE) Log.i(TAG, "onConsoleMessage " + consoleMessage);
+        // Log unconditionally, because JavaScript errors also generate ConsoleMessages (and
+        // developers generally expect logcat to show such errors).
+        logConsoleMessage(consoleMessage);
         mAddMessageToConsoleHelper.notifyCalled(consoleMessage);
         return false;
     }
 
+    private void logConsoleMessage(AwConsoleMessage consoleMessage) {
+        String formattedMessage = "[" + consoleMessage.sourceId() + ":"
+                + consoleMessage.lineNumber() + "] " + consoleMessage.message();
+        switch (consoleMessage.messageLevel()) {
+            case AwConsoleMessage.MESSAGE_LEVEL_TIP:
+            case AwConsoleMessage.MESSAGE_LEVEL_LOG:
+                Log.i(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            case AwConsoleMessage.MESSAGE_LEVEL_WARNING:
+                Log.w(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            case AwConsoleMessage.MESSAGE_LEVEL_ERROR:
+                Log.e(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            default:
+                // Should not be reached, but fall-through anyway.
+            case AwConsoleMessage.MESSAGE_LEVEL_DEBUG:
+                Log.d(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+        }
+    }
+
     /**
      * Callback helper for AddMessageToConsole.
      */
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 11baf96..9f5a8699 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -494,6 +494,8 @@
     "multi_user/user_switch_animator.h",
     "policy/policy_recommendation_restorer.cc",
     "policy/policy_recommendation_restorer.h",
+    "power/gatt_battery_percentage_fetcher.cc",
+    "power/gatt_battery_percentage_fetcher.h",
     "root_window_controller.cc",
     "root_window_settings.cc",
     "root_window_settings.h",
@@ -531,8 +533,6 @@
     "shelf/home_button.h",
     "shelf/home_button_controller.cc",
     "shelf/home_button_controller.h",
-    "shelf/kiosk_next_shelf_view.cc",
-    "shelf/kiosk_next_shelf_view.h",
     "shelf/login_shelf_view.cc",
     "shelf/login_shelf_view.h",
     "shelf/overflow_bubble.cc",
@@ -785,8 +785,6 @@
     "system/night_light/night_light_controller_impl.h",
     "system/night_light/night_light_feature_pod_controller.cc",
     "system/night_light/night_light_feature_pod_controller.h",
-    "system/night_light/night_light_toggle_button.cc",
-    "system/night_light/night_light_toggle_button.h",
     "system/night_light/time_of_day.cc",
     "system/night_light/time_of_day.h",
     "system/overview/overview_button_tray.cc",
@@ -847,8 +845,6 @@
     "system/power/power_prefs.h",
     "system/power/power_status.cc",
     "system/power/power_status.h",
-    "system/power/power_status_view.cc",
-    "system/power/power_status_view.h",
     "system/power/scoped_backlights_forced_off.cc",
     "system/power/scoped_backlights_forced_off.h",
     "system/power/tray_power.cc",
@@ -1697,6 +1693,7 @@
     "metrics/user_metrics_recorder_unittest.cc",
     "multi_device_setup/multi_device_notification_presenter_unittest.cc",
     "policy/policy_recommendation_restorer_unittest.cc",
+    "power/gatt_battery_percentage_fetcher_unittest.cc",
     "root_window_controller_unittest.cc",
     "rotator/screen_rotation_animation_unittest.cc",
     "rotator/screen_rotation_animator_unittest.cc",
@@ -1768,7 +1765,6 @@
     "system/power/power_notification_controller_unittest.cc",
     "system/power/power_prefs_unittest.cc",
     "system/power/power_status_unittest.cc",
-    "system/power/power_status_view_unittest.cc",
     "system/power/video_activity_notifier_unittest.cc",
     "system/rotation/rotation_lock_feature_pod_controller_unittest.cc",
     "system/screen_layout_observer_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 5d721beb..89ec2df 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -26,7 +26,6 @@
 #include "ash/ime/ime_controller.h"
 #include "ash/ime/ime_switch_type.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/magnifier/docked_magnifier_controller_impl.h"
 #include "ash/magnifier/magnification_controller.h"
 #include "ash/media/media_controller_impl.h"
@@ -1441,10 +1440,6 @@
     actions_allowed_in_pinned_mode_.insert(
         kActionsAllowedInAppModeOrPinnedMode[i]);
   }
-  for (size_t i = 0; i < kActionsAllowedForKioskNextShellLength; i++) {
-    actions_allowed_for_kiosk_next_shell_.insert(
-        kActionsAllowedForKioskNextShell[i]);
-  }
   for (size_t i = 0; i < kActionsAllowedInPinnedModeLength; ++i)
     actions_allowed_in_pinned_mode_.insert(kActionsAllowedInPinnedMode[i]);
   for (size_t i = 0; i < kActionsNeedingWindowLength; ++i)
@@ -2016,10 +2011,6 @@
 AcceleratorControllerImpl::AcceleratorProcessingRestriction
 AcceleratorControllerImpl::GetAcceleratorProcessingRestriction(
     int action) const {
-  if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled() &&
-      actions_allowed_for_kiosk_next_shell_.count(action) == 0) {
-    return RESTRICTION_PREVENT_PROCESSING_AND_PROPAGATION;
-  }
   if (Shell::Get()->screen_pinning_controller()->IsPinned() &&
       actions_allowed_in_pinned_mode_.find(action) ==
           actions_allowed_in_pinned_mode_.end()) {
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index 4807125..d2c36991 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -304,8 +304,6 @@
   std::set<int> actions_allowed_in_app_mode_;
   // Actions allowed in pinned mode.
   std::set<int> actions_allowed_in_pinned_mode_;
-  // Actions allowed when Kiosk Next Shell is enabled.
-  std::set<int> actions_allowed_for_kiosk_next_shell_;
   // Actions disallowed if there are no windows.
   std::set<int> actions_needing_window_;
   // Actions that can be performed without closing the menu (if one is present).
diff --git a/ash/accelerators/accelerator_table.cc b/ash/accelerators/accelerator_table.cc
index d47adc8..011e8260 100644
--- a/ash/accelerators/accelerator_table.cc
+++ b/ash/accelerators/accelerator_table.cc
@@ -375,34 +375,4 @@
 const size_t kActionsKeepingMenuOpenLength =
     base::size(kActionsKeepingMenuOpen);
 
-const AcceleratorAction kActionsAllowedForKioskNextShell[] = {
-    BRIGHTNESS_DOWN,
-    BRIGHTNESS_UP,
-    DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
-    DEBUG_TOGGLE_SHOW_FPS_COUNTER,
-    DEBUG_TOGGLE_SHOW_PAINT_RECTS,
-    KEYBOARD_BRIGHTNESS_DOWN,
-    KEYBOARD_BRIGHTNESS_UP,
-    MEDIA_NEXT_TRACK,
-    MEDIA_PLAY_PAUSE,
-    MEDIA_PREV_TRACK,
-    POWER_PRESSED,
-    POWER_RELEASED,
-    TAKE_PARTIAL_SCREENSHOT,
-    TAKE_SCREENSHOT,
-    TAKE_WINDOW_SCREENSHOT,
-    TOGGLE_CAPS_LOCK,
-    TOGGLE_DICTATION,
-    TOGGLE_DOCKED_MAGNIFIER,
-    TOGGLE_FULLSCREEN_MAGNIFIER,
-    TOGGLE_HIGH_CONTRAST,
-    TOGGLE_SPOKEN_FEEDBACK,
-    VOLUME_DOWN,
-    VOLUME_MUTE,
-    VOLUME_UP,
-};
-
-const size_t kActionsAllowedForKioskNextShellLength =
-    base::size(kActionsAllowedForKioskNextShell);
-
 }  // namespace ash
diff --git a/ash/accelerators/accelerator_table.h b/ash/accelerators/accelerator_table.h
index 6586c52..82278e1c 100644
--- a/ash/accelerators/accelerator_table.h
+++ b/ash/accelerators/accelerator_table.h
@@ -155,10 +155,6 @@
 ASH_EXPORT extern const AcceleratorAction kActionsKeepingMenuOpen[];
 ASH_EXPORT extern const size_t kActionsKeepingMenuOpenLength;
 
-// Actions that can be performed when the Kiosk Next Shell is enabled.
-ASH_EXPORT extern const AcceleratorAction kActionsAllowedForKioskNextShell[];
-ASH_EXPORT extern const size_t kActionsAllowedForKioskNextShellLength;
-
 }  // namespace ash
 
 #endif  // ASH_ACCELERATORS_ACCELERATOR_TABLE_H_
diff --git a/ash/accelerators/pre_target_accelerator_handler.cc b/ash/accelerators/pre_target_accelerator_handler.cc
index 4b35dd1..55f2ca8 100644
--- a/ash/accelerators/pre_target_accelerator_handler.cc
+++ b/ash/accelerators/pre_target_accelerator_handler.cc
@@ -8,7 +8,6 @@
 #include "ash/shell.h"
 #include "ash/wm/window_state.h"
 #include "base/feature_list.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "media/base/media_switches.h"
 #include "ui/aura/window.h"
@@ -56,7 +55,6 @@
   aura::Window* target = static_cast<aura::Window*>(key_event.target());
   // Callers should never supply null.
   DCHECK(target);
-  RecordSearchKeyStats(accelerator);
   // Special hardware keys like brightness and volume are handled in
   // special way. However, some windows can override this behavior
   // (e.g. Chrome v1 apps by default and Chrome v2 apps with
@@ -73,26 +71,6 @@
   return Shell::Get()->accelerator_controller()->Process(accelerator);
 }
 
-void PreTargetAcceleratorHandler::RecordSearchKeyStats(
-    const ui::Accelerator& accelerator) {
-  if (accelerator.IsCmdDown()) {
-    if (search_key_state_ == RELEASED) {
-      search_key_state_ = PRESSED;
-      search_key_pressed_timestamp_ = base::TimeTicks::Now();
-    }
-
-    if (accelerator.key_code() != ui::KeyboardCode::VKEY_COMMAND &&
-        search_key_state_ == PRESSED) {
-      search_key_state_ = RECORDED;
-      UMA_HISTOGRAM_TIMES(
-          "Keyboard.Shortcuts.CrosSearchKeyDelay",
-          base::TimeTicks::Now() - search_key_pressed_timestamp_);
-    }
-  } else {
-    search_key_state_ = RELEASED;
-  }
-}
-
 bool PreTargetAcceleratorHandler::CanConsumeSystemKeys(
     aura::Window* target,
     const ui::KeyEvent& event) {
diff --git a/ash/accelerators/pre_target_accelerator_handler.h b/ash/accelerators/pre_target_accelerator_handler.h
index 8640067..daebb82b 100644
--- a/ash/accelerators/pre_target_accelerator_handler.h
+++ b/ash/accelerators/pre_target_accelerator_handler.h
@@ -7,7 +7,6 @@
 
 #include "ash/ash_export.h"
 #include "base/macros.h"
-#include "base/time/time.h"
 #include "ui/wm/core/accelerator_delegate.h"
 
 namespace aura {
@@ -46,14 +45,6 @@
                                    const ui::KeyEvent& event,
                                    const ui::Accelerator& accelerator);
 
-  // Records a histogram on how long the "Search" key is held when a user
-  // presses an accelerator that involes the "Search" key.
-  void RecordSearchKeyStats(const ui::Accelerator& accelerator);
-
-  enum SearchKeyState { RELEASED = 0, PRESSED, RECORDED };
-  SearchKeyState search_key_state_ = RELEASED;
-  base::TimeTicks search_key_pressed_timestamp_;
-
   DISALLOW_COPY_AND_ASSIGN(PreTargetAcceleratorHandler);
 };
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index b8e4b19..aa54d781 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -228,9 +228,6 @@
       <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_SETTINGS_TOOLTIP" desc="The tooltip text used for the button in the status tray to show the Night Light feature (which controls the color temperature of the screen) settings.">
         Show Night Light settings
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT" desc="The label used for the button in the status tray to toggle the Night Light feature (which controls the color temperature of the screen) on or off.">
-        Night Light: <ph name="NIGHT_LIGHT_STATUS">$1<ex>On</ex></ph>
-      </message>
       <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_OFF_STATE" desc="The label for the Off state of the Night Light feature.">
         Off
       </message>
diff --git a/ash/power/DEPS b/ash/power/DEPS
new file mode 100644
index 0000000..b299852d
--- /dev/null
+++ b/ash/power/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/device_event_log/device_event_log.h",
+]
diff --git a/ash/power/gatt_battery_percentage_fetcher.cc b/ash/power/gatt_battery_percentage_fetcher.cc
new file mode 100644
index 0000000..a651ab8
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher.cc
@@ -0,0 +1,266 @@
+// 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 "ash/power/gatt_battery_percentage_fetcher.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "components/device_event_log/device_event_log.h"
+#include "device/bluetooth/bluetooth_gatt_connection.h"
+#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
+#include "device/bluetooth/bluetooth_remote_gatt_service.h"
+#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
+
+using device::BluetoothDevice;
+using device::BluetoothGattService;
+
+namespace ash {
+
+namespace {
+
+// UUIDs for the standardized Battery Service and Characteristic, defined by the
+// Bluetooth GATT Specification.
+// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.battery_service.xml
+constexpr char kBatteryServiceUUID[] = "180F";
+constexpr char kBatteryLevelUUID[] = "2A19";
+
+GattBatteryPercentageFetcher::Factory* g_test_factory_instance_ = nullptr;
+
+const device::BluetoothUUID& GetBatteryServiceUUID() {
+  static const device::BluetoothUUID battery_service_uuid(kBatteryServiceUUID);
+  return battery_service_uuid;
+}
+const device::BluetoothUUID& GetBatteryLevelUUID() {
+  static const device::BluetoothUUID battery_level_uuid(kBatteryLevelUUID);
+  return battery_level_uuid;
+}
+
+const char* BluetoothDeviceErrorCodeToString(
+    BluetoothDevice::ConnectErrorCode error_code) {
+  switch (error_code) {
+    case BluetoothDevice::ERROR_AUTH_CANCELED:
+      return "ERROR_AUTH_CANCELED";
+    case BluetoothDevice::ERROR_AUTH_FAILED:
+      return "ERROR_AUTH_FAILED";
+    case BluetoothDevice::ERROR_AUTH_REJECTED:
+      return "ERROR_AUTH_REJECTED";
+    case BluetoothDevice::ERROR_AUTH_TIMEOUT:
+      return "ERROR_AUTH_TIMEOUT";
+    case BluetoothDevice::ERROR_FAILED:
+      return "ERROR_FAILED";
+    case BluetoothDevice::ERROR_INPROGRESS:
+      return "ERROR_INPROGRESS";
+    case BluetoothDevice::ERROR_UNKNOWN:
+      return "ERROR_UNKNOWN";
+    case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
+      return "ERROR_UNSUPPORTED_DEVICE";
+    case BluetoothDevice::NUM_CONNECT_ERROR_CODES:
+      NOTREACHED();
+      return "";
+  }
+}
+
+const char* GattErrorCodeToString(
+    BluetoothGattService::GattErrorCode error_code) {
+  switch (error_code) {
+    case BluetoothGattService::GATT_ERROR_UNKNOWN:
+      return "GATT_ERROR_UNKNOWN";
+    case BluetoothGattService::GATT_ERROR_FAILED:
+      return "GATT_ERROR_FAILED";
+    case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
+      return "GATT_ERROR_IN_PROGRESS";
+    case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
+      return "GATT_ERROR_INVALID_LENGTH";
+    case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
+      return "GATT_ERROR_NOT_PERMITTED";
+    case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
+      return "GATT_ERROR_NOT_AUTHORIZED";
+    case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
+      return "GATT_ERROR_NOT_PAIRED";
+    case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
+      return "GATT_ERROR_NOT_SUPPORTED";
+  }
+}
+
+device::BluetoothRemoteGattService* GetGattBatteryService(
+    BluetoothDevice* device) {
+  for (device::BluetoothRemoteGattService* service :
+       device->GetGattServices()) {
+    if (service->GetUUID() == GetBatteryServiceUUID())
+      return service;
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<GattBatteryPercentageFetcher>
+GattBatteryPercentageFetcher::Factory::NewInstance(
+    scoped_refptr<device::BluetoothAdapter> adapter,
+    const std::string& device_address,
+    BatteryPercentageCallback callback) {
+  if (g_test_factory_instance_) {
+    return g_test_factory_instance_->BuildInstance(adapter, device_address,
+                                                   std::move(callback));
+  }
+  auto instance = base::WrapUnique(
+      new GattBatteryPercentageFetcher(device_address, std::move(callback)));
+  instance->SetAdapterAndStartFetching(adapter);
+  return instance;
+}
+
+// static
+void GattBatteryPercentageFetcher::Factory::SetFactoryForTesting(
+    Factory* factory) {
+  g_test_factory_instance_ = factory;
+}
+
+GattBatteryPercentageFetcher::GattBatteryPercentageFetcher(
+    const std::string& device_address,
+    BatteryPercentageCallback callback)
+    : device_address_(device_address), callback_(std::move(callback)) {}
+
+GattBatteryPercentageFetcher::~GattBatteryPercentageFetcher() {
+  if (adapter_)
+    adapter_->RemoveObserver(this);
+}
+
+void GattBatteryPercentageFetcher::SetAdapterAndStartFetching(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  DCHECK(adapter);
+  adapter_ = adapter;
+  adapter_->AddObserver(this);
+
+  // Create Gatt Connection.
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR)
+        << "GattBatteryPercentageFetcher error for device: " << device_address()
+        << ". Unable to get device from adapter on CreateGattConnection.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  DCHECK(!connection_);
+  device->CreateGattConnection(
+      base::Bind(&GattBatteryPercentageFetcher::OnGattConnected,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&GattBatteryPercentageFetcher::OnGattConnectError,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GattBatteryPercentageFetcher::OnGattConnected(
+    std::unique_ptr<device::BluetoothGattConnection> connection) {
+  DCHECK_EQ(connection->GetDeviceAddress(), this->device_address());
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR)
+        << "GattBatteryPercentageFetcher error for device: " << device_address()
+        << ". Unable to get device from adapter on OnGattConnected.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  connection_ = std::move(connection);
+
+  // If Gatt Services Discovery is not complete yet, wait until
+  // GattServicesDiscovered() is called to continue fetching the battery level.
+  if (device->IsGattServicesDiscoveryComplete())
+    AttemptToReadBatteryCharacteristic();
+}
+
+void GattBatteryPercentageFetcher::OnGattConnectError(
+    BluetoothDevice::ConnectErrorCode error_code) {
+  BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                       << device_address() << ". OnGattConnectError"
+                       << BluetoothDeviceErrorCodeToString(error_code);
+  InvokeCallbackWithFailedFetch();
+}
+
+void GattBatteryPercentageFetcher::GattServicesDiscovered(
+    device::BluetoothAdapter* adapter,
+    BluetoothDevice* device) {
+  if (device->GetAddress() == device_address())
+    AttemptToReadBatteryCharacteristic();
+}
+
+void GattBatteryPercentageFetcher::AttemptToReadBatteryCharacteristic() {
+  // This function should only be called once with an active GATT connection.
+  if (!connection_ || attempted_to_read_the_battery_characteristic_)
+    return;
+
+  attempted_to_read_the_battery_characteristic_ = true;
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Unable to get device from adapter on "
+                            "AttemptToReadBatteryCharacteristic.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  device::BluetoothRemoteGattService* service = GetGattBatteryService(device);
+  if (!service) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address() << ". No battery service.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  std::vector<device::BluetoothRemoteGattCharacteristic*> characteristics =
+      service->GetCharacteristicsByUUID(GetBatteryLevelUUID());
+
+  // If no battery characteristic exists, the value cannot be retrieved.
+  if (characteristics.empty()) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Bad format for battery level characteristic.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  // Only one characteristic is expected to exist according to the GATT Battery
+  // Service standard. If multiple characteristics are present, arbitrarily
+  // choose the first one.
+  characteristics[0]->ReadRemoteCharacteristic(
+      base::Bind(&GattBatteryPercentageFetcher::OnReadBatteryLevel,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&GattBatteryPercentageFetcher::OnReadBatteryLevelError,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GattBatteryPercentageFetcher::OnReadBatteryLevel(
+    const std::vector<uint8_t>& value) {
+  if (value.size() != 1 || value[0] > 100) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Wrong format for battery level.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  InvokeCallbackWithSuccessfulFetch(value[0]);
+}
+
+void GattBatteryPercentageFetcher::OnReadBatteryLevelError(
+    BluetoothGattService::GattErrorCode error_code) {
+  BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                       << device_address() << ". OnReadBatteryLevelError - "
+                       << GattErrorCodeToString(error_code);
+  InvokeCallbackWithFailedFetch();
+}
+
+void GattBatteryPercentageFetcher::InvokeCallbackWithSuccessfulFetch(
+    uint8_t battery_percentage) {
+  connection_.reset();
+  DCHECK(callback_);
+  std::move(callback_).Run(battery_percentage);
+}
+
+void GattBatteryPercentageFetcher::InvokeCallbackWithFailedFetch() {
+  connection_.reset();
+  DCHECK(callback_);
+  std::move(callback_).Run(base::nullopt);
+}
+
+}  // namespace ash
diff --git a/ash/power/gatt_battery_percentage_fetcher.h b/ash/power/gatt_battery_percentage_fetcher.h
new file mode 100644
index 0000000..18483693
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher.h
@@ -0,0 +1,110 @@
+// 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 ASH_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
+#define ASH_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_gatt_service.h"
+
+namespace device {
+class BluetoothGattConnection;
+}  // namespace device
+
+namespace ash {
+
+// Using the GATT Battery Service (BAS), returns the battery percentage (or
+// nullopt in case of error) via the |callback_| for a Bluetooth device with the
+// provided |device_address_|. An instance of this class should be created every
+// time such a value is needed and destroyed after the callback is called.
+class ASH_EXPORT GattBatteryPercentageFetcher
+    : public device::BluetoothAdapter::Observer {
+ public:
+  // Passes null if the battery percentage cannot be fetched.
+  using BatteryPercentageCallback =
+      base::OnceCallback<void(base::Optional<uint8_t>)>;
+
+  class Factory {
+   public:
+    virtual ~Factory() = default;
+
+    static void SetFactoryForTesting(Factory* factory);
+
+    static std::unique_ptr<GattBatteryPercentageFetcher> NewInstance(
+        scoped_refptr<device::BluetoothAdapter> adapter,
+        const std::string& device_address,
+        BatteryPercentageCallback callback);
+
+    virtual std::unique_ptr<GattBatteryPercentageFetcher> BuildInstance(
+        scoped_refptr<device::BluetoothAdapter> adapter,
+        const std::string& device_address,
+        BatteryPercentageCallback callback) = 0;
+  };
+
+  ~GattBatteryPercentageFetcher() override;
+
+  const std::string& device_address() const { return device_address_; }
+
+ protected:
+  GattBatteryPercentageFetcher(const std::string& device_address,
+                               BatteryPercentageCallback callback);
+
+  // Close |connection_| and return the fetched value through the callback.
+  void InvokeCallbackWithSuccessfulFetch(uint8_t battery_percentage);
+  void InvokeCallbackWithFailedFetch();
+
+ private:
+  friend class GattBatteryPercentageFetcherTest;
+
+  // device::BluetoothAdapter::Observer:
+  void GattServicesDiscovered(device::BluetoothAdapter* adapter,
+                              device::BluetoothDevice* device) override;
+
+  // Calling this function starts the fetching process. This allows tests to
+  // to create instances of this class without running the whole mechanism.
+  void SetAdapterAndStartFetching(
+      scoped_refptr<device::BluetoothAdapter> adapter);
+
+  // Checks if the GATT Services are discovered to gather the battery value,
+  // otherwise sets a flag to wait for them to complete.
+  void OnGattConnected(
+      std::unique_ptr<device::BluetoothGattConnection> connection);
+  void OnGattConnectError(device::BluetoothDevice::ConnectErrorCode error_code);
+
+  // Searches for the GATT Battery Service and Characteristic and requests to
+  // read its value.
+  void AttemptToReadBatteryCharacteristic();
+
+  // Callback when reading the battery percentage succeeds. Will return such
+  // value via |callback_|.
+  void OnReadBatteryLevel(const std::vector<uint8_t>& value);
+  void OnReadBatteryLevelError(
+      device::BluetoothGattService::GattErrorCode error_code);
+
+  const std::string device_address_;
+  BatteryPercentageCallback callback_;
+
+  // May be null in tests.
+  scoped_refptr<device::BluetoothAdapter> adapter_;
+  std::unique_ptr<device::BluetoothGattConnection> connection_;
+
+  // Flag to avoid fetching the battery level multiple times in case
+  // GattServicesDiscovered() is called more than once.
+  bool attempted_to_read_the_battery_characteristic_ = false;
+
+  base::WeakPtrFactory<GattBatteryPercentageFetcher> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(GattBatteryPercentageFetcher);
+};
+
+}  // namespace ash
+
+#endif  // ASH_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
diff --git a/ash/power/gatt_battery_percentage_fetcher_unittest.cc b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
new file mode 100644
index 0000000..ff64040
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
@@ -0,0 +1,258 @@
+// 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 "ash/power/gatt_battery_percentage_fetcher.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using device::BluetoothDevice;
+using device::BluetoothRemoteGattCharacteristic;
+using testing::_;
+using testing::DoAll;
+using testing::NiceMock;
+using testing::Return;
+using testing::SaveArg;
+
+namespace {
+
+constexpr char kServiceID[] = "service id";
+constexpr char kCharacteristicID[] = "characteristic id";
+constexpr char kDeviceAddress[] = "AA:BB:CC:DD:EE:FF";
+constexpr char kBatteryServiceUUID[] = "180F";
+constexpr char kBatteryLevelUUID[] = "2A19";
+const uint8_t kBatteryPercentage = 100;
+
+ACTION_TEMPLATE(MoveArg,
+                HAS_1_TEMPLATE_PARAMS(int, k),
+                AND_1_VALUE_PARAMS(pointer)) {
+  *pointer = std::move(::std::get<k>(args));
+}
+
+const device::BluetoothUUID& GetBatteryServiceUUID() {
+  static const device::BluetoothUUID battery_service_uuid(kBatteryServiceUUID);
+  return battery_service_uuid;
+}
+
+const device::BluetoothUUID& GetBatteryLevelUUID() {
+  static const device::BluetoothUUID battery_level_uuid(kBatteryLevelUUID);
+  return battery_level_uuid;
+}
+
+}  // namespace
+
+namespace ash {
+
+class GattBatteryPercentageFetcherTest : public testing::Test {
+ protected:
+  GattBatteryPercentageFetcherTest() = default;
+
+  ~GattBatteryPercentageFetcherTest() override = default;
+
+  void SetUp() override {
+    mock_adapter_ =
+        base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
+
+    // By default, |mock_device_| is paired, connected, with Gatt Services
+    // Discovery completed and returns |mock_service_| when requested for
+    // for available services. These behaviors can be overridden in tests.
+    mock_device_ = std::make_unique<NiceMock<device::MockBluetoothDevice>>(
+        mock_adapter_.get(), 0 /* bluetooth_class */, "device_name",
+        kDeviceAddress, true /* paired */, true /* connected */);
+    ON_CALL(*mock_adapter_, GetDevice(kDeviceAddress))
+        .WillByDefault(Return(mock_device_.get()));
+    ON_CALL(*mock_device_, IsGattServicesDiscoveryComplete())
+        .WillByDefault(Return(true));
+    ASSERT_FALSE(mock_device_->battery_percentage());
+
+    // By default, |mock_service_| returns a vector containing
+    // |mock_characteristic_| when requested for the battery level
+    // characteristic. This behavior can be overridden in tests.
+    mock_service_ =
+        std::make_unique<NiceMock<device::MockBluetoothGattService>>(
+            mock_device_.get(), kServiceID, GetBatteryServiceUUID(),
+            true /* is_primary */, false /* is_local */);
+    std::vector<device::BluetoothRemoteGattService*> services = {
+        mock_service_.get()};
+    ON_CALL(*mock_device_, GetGattServices()).WillByDefault(Return(services));
+
+    mock_characteristic_ =
+        std::make_unique<NiceMock<device::MockBluetoothGattCharacteristic>>(
+            mock_service_.get(), kCharacteristicID, GetBatteryLevelUUID(),
+            false /* is_local */,
+            BluetoothRemoteGattCharacteristic::PROPERTY_READ,
+            BluetoothRemoteGattCharacteristic::PERMISSION_READ);
+    std::vector<BluetoothRemoteGattCharacteristic*> characteristics = {
+        mock_characteristic_.get()};
+    ON_CALL(*mock_service_, GetCharacteristicsByUUID(GetBatteryLevelUUID()))
+        .WillByDefault(Return(characteristics));
+
+    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+
+    // Create a GattBatteryPercentageFetcher.
+    ExpectGattConnection();
+    fetcher_ = GattBatteryPercentageFetcher::Factory::NewInstance(
+        mock_adapter_, kDeviceAddress,
+        base::BindOnce(
+            &GattBatteryPercentageFetcherTest::OnBatteryPercentageFetched,
+            base::Unretained(this)));
+  }
+
+  void ExpectGattConnection() {
+    EXPECT_CALL(*mock_device_, CreateGattConnection(_, _))
+        .WillOnce(DoAll(SaveArg<0>(&create_gatt_connection_success_callback_),
+                        SaveArg<1>(&create_gatt_connection_error_callback_)));
+  }
+
+  void ExpectReadCharacteristic() {
+    EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _))
+        .WillOnce(
+            DoAll(MoveArg<0>(&read_remote_characteristic_callback_),
+                  MoveArg<1>(&read_remote_characteristic_error_callback_)));
+  }
+
+  void OnBatteryPercentageFetched(base::Optional<uint8_t> battery_percentage) {
+    battery_percentage_ = battery_percentage;
+    battery_percentage_callback_called_ = true;
+  }
+
+  void VerifyFetchResult(base::Optional<uint8_t> expected_result) {
+    EXPECT_TRUE(battery_percentage_callback_called_);
+    EXPECT_EQ(expected_result, battery_percentage_);
+  }
+
+  std::unique_ptr<GattBatteryPercentageFetcher> fetcher_;
+
+  base::Optional<uint8_t> battery_percentage_;
+  bool battery_percentage_callback_called_ = false;
+
+  scoped_refptr<NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
+  std::unique_ptr<device::MockBluetoothDevice> mock_device_;
+
+  std::unique_ptr<device::MockBluetoothGattService> mock_service_;
+  std::unique_ptr<device::MockBluetoothGattCharacteristic> mock_characteristic_;
+
+  BluetoothDevice::GattConnectionCallback
+      create_gatt_connection_success_callback_;
+  BluetoothDevice::ConnectErrorCallback create_gatt_connection_error_callback_;
+  BluetoothRemoteGattCharacteristic::ValueCallback
+      read_remote_characteristic_callback_;
+  BluetoothRemoteGattCharacteristic::ErrorCallback
+      read_remote_characteristic_error_callback_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GattBatteryPercentageFetcherTest);
+};
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       ReadBattery_GattServicesDiscoveredOnGattConnection) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  std::move(read_remote_characteristic_callback_).Run({kBatteryPercentage});
+  VerifyFetchResult(kBatteryPercentage);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       ReadBattery_GattServicesDiscoveredAfterGattConnection) {
+  ExpectReadCharacteristic();
+  ON_CALL(*mock_device_, IsGattServicesDiscoveryComplete())
+      .WillByDefault(Return(false));
+
+  // GattServicesDiscoveryComplete() should not have run yet.
+  EXPECT_TRUE(read_remote_characteristic_error_callback_.is_null());
+  EXPECT_TRUE(read_remote_characteristic_callback_.is_null());
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  mock_adapter_->NotifyGattServicesDiscovered(mock_device_.get());
+
+  // GattServicesDiscoveryComplete() should have run.
+  std::move(read_remote_characteristic_callback_).Run({kBatteryPercentage});
+  VerifyFetchResult(kBatteryPercentage);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       IgnoreGattServicesDiscoveredBeforeGattConnection) {
+  // Case where the GATT connection has not been established yet, but the
+  // services are done with discovery. Should not try reading the battery level.
+  EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _)).Times(0);
+  mock_adapter_->NotifyGattServicesDiscovered(mock_device_.get());
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, ErrorOpeningGattConnection) {
+  create_gatt_connection_error_callback_.Run(
+      BluetoothDevice::ERROR_AUTH_TIMEOUT);
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, BatteryServiceUnavailable) {
+  ON_CALL(*mock_device_, GetGattServices())
+      .WillByDefault(
+          Return(std::vector<device::BluetoothRemoteGattService*>()));
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, MissingBatteryLevelCharacteristic) {
+  ON_CALL(*mock_service_, GetCharacteristicsByUUID(GetBatteryLevelUUID()))
+      .WillByDefault(Return(std::vector<BluetoothRemoteGattCharacteristic*>()));
+  EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _)).Times(0);
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, ErrorReadingRemoteCharacteristic) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  std::move(read_remote_characteristic_error_callback_)
+      .Run(device::BluetoothGattService::GATT_ERROR_UNKNOWN);
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       BadFormatForBatteryLevelValue_MadeOfMultipleBytes) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  // Battery value made of a multibyte vector.
+  std::move(read_remote_characteristic_callback_)
+      .Run({kBatteryPercentage, kBatteryPercentage});
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       BadFormatForBatteryLevelValue_ValueAbove100Percent) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  uint8_t new_battery_percentage = 101;
+  std::move(read_remote_characteristic_callback_).Run({new_battery_percentage});
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+}  // namespace ash
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index d732c831..8150a751 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -189,8 +189,6 @@
     "system_menu_tracing.icon",
     "system_menu_timer.icon",
     "system_menu_new_user.icon",
-    "system_menu_night_light_off.icon",
-    "system_menu_night_light_on.icon",
     "system_menu_usb.icon",
     "system_menu_videocam.icon",
     "system_menu_volume_high.icon",
@@ -207,7 +205,6 @@
     "system_tray_do_not_disturb.icon",
     "system_tray_family_link.icon",
     "system_tray_managed.icon",
-    "system_tray_night_light.icon",
     "system_tray_notification_counter_plus.icon",
     "system_tray_recording.icon",
     "system_tray_rotation_lock_auto.icon",
diff --git a/ash/resources/vector_icons/system_menu_night_light_off.icon b/ash/resources/vector_icons/system_menu_night_light_off.icon
deleted file mode 100644
index cb618cec..0000000
--- a/ash/resources/vector_icons/system_menu_night_light_off.icon
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 40,
-MOVE_TO, 34.3f, 27.18f,
-LINE_TO, 18.38f, 11.26f,
-CUBIC_TO, 19.89f, 9.6f, 21.82f, 8.33f, 24, 7.63f,
-CUBIC_TO, 22.74f, 7.22f, 21.4f, 7, 20, 7,
-R_CUBIC_TO, -1.74f, 0, -3.4f, 0.34f, -4.92f, 0.96f,
-LINE_TO, 12.82f, 5.7f,
-CUBIC_TO, 14.98f, 4.61f, 17.42f, 4, 20, 4,
-R_CUBIC_TO, 8.83f, 0, 16, 7.17f, 16, 16,
-R_CUBIC_TO, 0, 2.58f, -0.61f, 5.02f, -1.7f, 7.18f,
-CLOSE,
-R_MOVE_TO, -3.56f, 4.68f,
-CUBIC_TO, 27.9f, 34.43f, 24.13f, 36, 20, 36,
-R_CUBIC_TO, -8.83f, 0, -16, -7.17f, -16, -16,
-R_CUBIC_TO, 0, -4.13f, 1.57f, -7.9f, 4.14f, -10.74f,
-R_LINE_TO, 2.12f, 2.12f,
-CUBIC_TO, 8.23f, 13.68f, 7, 16.7f, 7, 20,
-R_CUBIC_TO, 0, 7.18f, 5.82f, 13, 13, 13,
-R_CUBIC_TO, 1.4f, 0, 2.74f, -0.22f, 4, -0.63f,
-R_CUBIC_TO, -5.22f, -1.69f, -9, -6.59f, -9, -12.37f,
-R_CUBIC_TO, 0, -1.18f, 0.16f, -2.33f, 0.46f, -3.42f,
-LINE_TO, 30.74f, 31.86f,
-CLOSE,
-MOVE_TO, 5, 6.12f,
-LINE_TO, 7.12f, 4,
-R_LINE_TO, 28.63f, 28.63f,
-R_LINE_TO, -2.12f, 2.12f,
-LINE_TO, 5, 6.12f,
-CLOSE
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 17.15f, 13.59f,
-LINE_TO, 9.44f, 5.88f,
-R_CUBIC_TO, 0.7f, -0.72f, 1.57f, -1.27f, 2.56f, -1.59f,
-R_CUBIC_TO, -0.59f, -0.19f, -1.23f, -0.29f, -1.88f, -0.29f,
-R_CUBIC_TO, -0.76f, 0, -1.49f, 0.14f, -2.17f, 0.39f,
-LINE_TO, 6.41f, 2.85f,
-CUBIC_TO, 7.49f, 2.31f, 8.71f, 2, 10, 2,
-R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
-R_CUBIC_TO, 0, 1.29f, -0.31f, 2.51f, -0.85f, 3.59f,
-CLOSE,
-R_MOVE_TO, -1.78f, 2.34f,
-CUBIC_TO, 13.95f, 17.22f, 12.06f, 18, 10, 18,
-R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
-R_CUBIC_TO, 0, -2.06f, 0.78f, -3.95f, 2.07f, -5.37f,
-R_LINE_TO, 1.43f, 1.43f,
-CUBIC_TO, 4.57f, 7.12f, 4, 8.49f, 4, 10,
-R_CUBIC_TO, 0, 3.31f, 2.74f, 6, 6.12f, 6,
-R_CUBIC_TO, 0.66f, 0, 1.29f, -0.1f, 1.88f, -0.29f,
-R_CUBIC_TO, -2.46f, -0.78f, -4.24f, -3.04f, -4.24f, -5.71f,
-R_CUBIC_TO, 0, -0.51f, 0.07f, -1.01f, 0.19f, -1.49f,
-R_LINE_TO, 7.41f, 7.42f,
-CLOSE,
-MOVE_TO, 2.5f, 3.06f,
-LINE_TO, 3.56f, 2,
-R_LINE_TO, 14.31f, 14.31f,
-R_LINE_TO, -1.06f, 1.06f,
-LINE_TO, 2.5f, 3.06f,
-CLOSE
diff --git a/ash/resources/vector_icons/system_menu_night_light_on.icon b/ash/resources/vector_icons/system_menu_night_light_on.icon
deleted file mode 100644
index 442834d..0000000
--- a/ash/resources/vector_icons/system_menu_night_light_on.icon
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 40,
-MOVE_TO, 20, 4,
-R_CUBIC_TO, 8.83f, 0, 16, 7.17f, 16, 16,
-R_CUBIC_TO, 0, 8.83f, -7.17f, 16, -16, 16,
-CUBIC_TO_SHORTHAND, 4, 28.83f, 4, 20,
-CUBIC_TO_SHORTHAND, 11.17f, 4, 20, 4,
-CLOSE,
-R_MOVE_TO, 4, 3.63f,
-CUBIC_TO, 22.74f, 7.22f, 21.4f, 7, 20, 7,
-CUBIC_TO, 12.82f, 7, 7, 12.82f, 7, 20,
-R_CUBIC_TO, 0, 7.18f, 5.82f, 13, 13, 13,
-R_CUBIC_TO, 1.4f, 0, 2.74f, -0.22f, 4, -0.63f,
-R_CUBIC_TO, -5.22f, -1.69f, -9, -6.59f, -9, -12.37f,
-R_CUBIC_TO, 0, -5.78f, 3.78f, -10.69f, 9, -12.37f,
-CLOSE
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 10, 2,
-R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
-R_CUBIC_TO, 0, 4.42f, -3.58f, 8, -8, 8,
-R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
-R_CUBIC_TO, 0, -4.42f, 3.58f, -8, 8, -8,
-CLOSE,
-R_MOVE_TO, -0.65f, 2,
-CUBIC_TO, 6.4f, 4, 4, 6.69f, 4, 10,
-R_CUBIC_TO, 0, 3.31f, 2.4f, 6, 5.35f, 6,
-R_CUBIC_TO, 0.57f, 0, 1.13f, -0.1f, 1.65f, -0.29f,
-CUBIC_TO, 8.85f, 14.93f, 7.29f, 12.67f, 7.29f, 10,
-R_CUBIC_TO, 0, -2.67f, 1.56f, -4.93f, 3.71f, -5.71f,
-CUBIC_TO, 10.48f, 4.1f, 9.93f, 4, 9.35f, 4,
-CLOSE
diff --git a/ash/resources/vector_icons/system_tray_night_light.icon b/ash/resources/vector_icons/system_tray_night_light.icon
deleted file mode 100644
index 236d22a..0000000
--- a/ash/resources/vector_icons/system_tray_night_light.icon
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 32,
-MOVE_TO, 16, 3,
-R_CUBIC_TO, 7.18f, 0, 13, 5.82f, 13, 13,
-R_CUBIC_TO, 0, 7.18f, -5.82f, 13, -13, 13,
-CUBIC_TO_SHORTHAND, 3, 23.18f, 3, 16,
-CUBIC_TO_SHORTHAND, 8.82f, 3, 16, 3,
-CLOSE,
-R_MOVE_TO, 3, 2.53f,
-CUBIC_TO, 17.96f, 5.19f, 16.86f, 5, 15.71f, 5,
-CUBIC_TO, 9.79f, 5, 5, 9.93f, 5, 16,
-R_CUBIC_TO, 0, 6.07f, 4.79f, 11, 10.71f, 11,
-R_CUBIC_TO, 1.15f, 0, 2.26f, -0.19f, 3.29f, -0.53f,
-R_CUBIC_TO, -4.3f, -1.43f, -7.41f, -5.58f, -7.41f, -10.47f,
-R_CUBIC_TO, 0, -4.89f, 3.11f, -9.04f, 7.41f, -10.47f,
-CLOSE
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 7, 0,
-CUBIC_TO, 10.86f, 0, 14, 3.14f, 14, 7,
-CUBIC_TO, 14, 10.86f, 10.86f, 14, 7, 14,
-CUBIC_TO, 3.14f, 14, 0, 10.86f, 0, 7,
-CUBIC_TO, 0, 3.14f, 3.14f, 0, 7, 0,
-CLOSE,
-MOVE_TO, 9, 1.29f,
-CUBIC_TO, 8.41f, 1.1f, 7.77f, 1, 7.12f, 1,
-CUBIC_TO, 3.74f, 1, 1, 3.69f, 1, 7,
-CUBIC_TO, 1, 10.31f, 3.74f, 13, 7.12f, 13,
-CUBIC_TO, 7.77f, 13, 8.41f, 12.9f, 9, 12.71f,
-CUBIC_TO, 6.54f, 11.93f, 4.76f, 9.67f, 4.76f, 7,
-CUBIC_TO, 4.76f, 4.33f, 6.54f, 2.07f, 9, 1.29f,
-LINE_TO, 9, 1.29f,
-CLOSE
diff --git a/ash/shelf/kiosk_next_shelf_view.cc b/ash/shelf/kiosk_next_shelf_view.cc
deleted file mode 100644
index 46f25af..0000000
--- a/ash/shelf/kiosk_next_shelf_view.cc
+++ /dev/null
@@ -1,166 +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.
-
-#include "ash/shelf/kiosk_next_shelf_view.h"
-
-#include <memory>
-
-#include "ash/display/screen_orientation_controller.h"
-#include "ash/home_screen/home_screen_controller.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/shelf/back_button.h"
-#include "ash/shelf/home_button.h"
-#include "ash/shelf/overflow_button.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_button_delegate.h"
-#include "ash/shelf/shelf_constants.h"
-#include "ash/shelf/shelf_widget.h"
-#include "ash/shell.h"
-#include "ash/system/status_area_widget.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "base/logging.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/insets.h"
-#include "ui/gfx/geometry/insets_f.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_mask.h"
-#include "ui/views/animation/ink_drop_ripple.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-namespace {
-
-// Kiosk Next back button with permanent round rectangle background.
-class KioskNextBackButton : public BackButton {
- public:
-  explicit KioskNextBackButton(ShelfButtonDelegate* shelf_button_delegate)
-      : BackButton(shelf_button_delegate) {}
-  ~KioskNextBackButton() override = default;
-
- private:
-  // views::BackButton:
-  void PaintButtonContents(gfx::Canvas* canvas) override {
-    PaintBackground(canvas, GetContentsBounds());
-    BackButton::PaintButtonContents(canvas);
-  }
-
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
-    return std::make_unique<views::RoundRectInkDropMask>(
-        size(), gfx::InsetsF(), kKioskNextShelfControlWidthDp / 2);
-  }
-
-  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override {
-    return std::make_unique<views::FloodFillInkDropRipple>(
-        size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
-        GetInkDropBaseColor(), ink_drop_visible_opacity());
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextBackButton);
-};
-
-// Kiosk Next home button with permanent round rectangle background.
-class KioskNextHomeButton : public HomeButton {
- public:
-  explicit KioskNextHomeButton(ShelfButtonDelegate* shelf_button_delegate)
-      : HomeButton(shelf_button_delegate) {}
-  ~KioskNextHomeButton() override = default;
-
- private:
-  // views::HomeButton:
-  void PaintButtonContents(gfx::Canvas* canvas) override {
-    PaintBackground(canvas, GetContentsBounds());
-    HomeButton::PaintButtonContents(canvas);
-  }
-
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
-    return std::make_unique<views::RoundRectInkDropMask>(
-        size(), gfx::InsetsF(), kKioskNextShelfControlWidthDp / 2);
-  }
-
-  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override {
-    return std::make_unique<views::FloodFillInkDropRipple>(
-        size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
-        GetInkDropBaseColor(), ink_drop_visible_opacity());
-  }
-
-  void OnPressed(app_list::AppListShowSource show_source,
-                 base::TimeTicks time_stamp) override {
-    Shell::Get()->home_screen_controller()->GoHome(GetDisplayId());
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextHomeButton);
-};
-
-bool IsTabletModeEnabled() {
-  // This check is needed, because tablet mode controller is destroyed before
-  // shelf widget. See https://crbug.com/967149 for more details.
-  return Shell::Get()->tablet_mode_controller() &&
-         Shell::Get()->tablet_mode_controller()->InTabletMode();
-}
-
-}  // namespace
-
-KioskNextShelfView::KioskNextShelfView(ShelfModel* model,
-                                       Shelf* shelf,
-                                       ShelfWidget* shelf_widget)
-    : ShelfView(model, shelf, shelf_widget) {
-  // Kiosk next shelf has 2 navigation buttons (back and home), no app items
-  // in its shelf model, and it is expected to be used in tablet mode with
-  // bottom shelf alignment. It can be adapted to different requirements, but
-  // they should be explicitly verified first.
-  DCHECK(IsTabletModeEnabled());
-  DCHECK(shelf->IsHorizontalAlignment());
-  DCHECK_EQ(0, model->item_count());
-}
-
-KioskNextShelfView::~KioskNextShelfView() = default;
-
-void KioskNextShelfView::Init() {
-  ShelfView::Init();
-
-  // Needs to be called after base class call that creates below objects.
-  // TODO(agawronska): Separator and overflow button are not needed in Kiosk
-  // Next shelf. They should be moved to DefaultShelfView subclass and the below
-  // code should be removed.
-  DCHECK(overflow_button());
-  overflow_button()->SetVisible(false);
-}
-
-void KioskNextShelfView::CalculateIdealBounds() {
-  DCHECK(shelf()->IsHorizontalAlignment());
-  DCHECK_EQ(0, model()->item_count());
-  DCHECK_GE(ShelfConstants::shelf_size(), kKioskNextShelfControlHeightDp);
-
-  // TODO(https://crbug.com/965690): Button spacing might be relative to shelf
-  // width. Reevaluate this piece once visual spec is available.
-  const int control_buttons_spacing =
-      IsCurrentScreenOrientationLandscape()
-          ? kKioskNextShelfControlSpacingLandscapeDp
-          : kKioskNextShelfControlSpacingPortraitDp;
-  const int total_shelf_width =
-      shelf_widget()->GetWindowBoundsInScreen().width();
-  int x = total_shelf_width / 2 - kKioskNextShelfControlWidthDp -
-          control_buttons_spacing / 2;
-  int y = (ShelfConstants::shelf_size() - kKioskNextShelfControlHeightDp) / 2;
-
-  GetBackButton()->set_ideal_bounds(gfx::Rect(
-      x, y, kKioskNextShelfControlWidthDp, kKioskNextShelfControlHeightDp));
-  x += (kKioskNextShelfControlWidthDp + control_buttons_spacing);
-  GetHomeButton()->set_ideal_bounds(gfx::Rect(
-      x, y, kKioskNextShelfControlWidthDp, kKioskNextShelfControlHeightDp));
-}
-
-std::unique_ptr<BackButton> KioskNextShelfView::CreateBackButton() {
-  return std::make_unique<KioskNextBackButton>(this);
-}
-
-std::unique_ptr<HomeButton> KioskNextShelfView::CreateHomeButton() {
-  return std::make_unique<KioskNextHomeButton>(this);
-}
-
-}  // namespace ash
diff --git a/ash/shelf/kiosk_next_shelf_view.h b/ash/shelf/kiosk_next_shelf_view.h
deleted file mode 100644
index 31e9394..0000000
--- a/ash/shelf/kiosk_next_shelf_view.h
+++ /dev/null
@@ -1,46 +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 ASH_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
-#define ASH_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
-
-#include <memory>
-
-#include "ash/ash_export.h"
-#include "ash/shelf/shelf_view.h"
-#include "base/macros.h"
-
-namespace ash {
-
-class Shelf;
-class ShelfModel;
-class ShelfWidget;
-
-// Shelf view used in Kiosk Next mode.
-// This shelf view is much simpler than the default shelf view. It is always
-// bottom aligned. It shows centered control buttons (back and home). It does
-// not display apps' buttons. Because of this alignment overflow mode is not
-// needed. This shelf view is using ShelfModel, but it could be removed in the
-// future.
-class ASH_EXPORT KioskNextShelfView : public ShelfView {
- public:
-  KioskNextShelfView(ShelfModel* model,
-                     Shelf* shelf,
-                     ShelfWidget* shelf_widget);
-  ~KioskNextShelfView() override;
-
-  // All ShelfView overrides are public to keep them together.
-  // ShelfView:
-  void Init() override;
-  void CalculateIdealBounds() override;
-  std::unique_ptr<BackButton> CreateBackButton() override;
-  std::unique_ptr<HomeButton> CreateHomeButton() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(KioskNextShelfView);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
diff --git a/ash/shelf/shelf_constants.h b/ash/shelf/shelf_constants.h
index ac3847cf..107dd4be 100644
--- a/ash/shelf/shelf_constants.h
+++ b/ash/shelf/shelf_constants.h
@@ -89,18 +89,6 @@
 constexpr float kShelfTooltipPreviewMaxRatio = 1.5;    // = 3/2
 constexpr float kShelfTooltipPreviewMinRatio = 0.666;  // = 2/3
 
-// Kiosk Next shelf constants.
-// TODO(agawronska): Make it a part of theme.
-
-// Size of the space between control buttons on the shelf. Changes within
-// orientation.
-constexpr int kKioskNextShelfControlSpacingPortraitDp = 96;
-constexpr int kKioskNextShelfControlSpacingLandscapeDp = 122;
-
-// Size of the shelf control buttons (back and home).
-constexpr int kKioskNextShelfControlWidthDp = 64;
-constexpr int kKioskNextShelfControlHeightDp = 40;
-
 class ShelfConstants {
  public:
   // Size of the shelf when visible (height when the shelf is horizontal and
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index 6bf23e5..ead6f15 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -15,10 +15,6 @@
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/focus_cycler.h"
 #include "ash/ime/ime_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
-#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
-#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
-#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_prefs.h"
@@ -3894,124 +3890,4 @@
   // This should have brought focus to the first element on the shelf.
   EXPECT_TRUE(shelf_view_->GetHomeButton()->HasFocus());
 }
-
-class KioskNextShelfViewTest : public ShelfViewTest {
- public:
-  KioskNextShelfViewTest() {
-    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
-  }
-
-  void SetUp() override {
-    set_start_session(false);
-    ShelfViewTest::SetUp();
-    client_ = std::make_unique<MockKioskNextShellClient>();
-  }
-
-  void TearDown() override {
-    client_.reset();
-    ShelfViewTest::TearDown();
-  }
-
- protected:
-  void LogInKioskNextUserInternal() {
-    LogInKioskNextUser(GetSessionControllerClient());
-
-    // The shelf_view_ in ShelfWidget will be replaced. Therefore, we need
-    // to update |shelf_view_|.
-    shelf_view_ = GetPrimaryShelf()->GetShelfViewForTesting();
-    ASSERT_GE(shelf_view_->width(), 500);
-
-    test_api_.reset(new ShelfViewTestAPI(shelf_view_));
-    test_api_->SetAnimationDuration(1);  // Speeds up animation for test.
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockKioskNextShellClient> client_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextShelfViewTest);
-};
-
-TEST_F(KioskNextShelfViewTest, AppButtonHidden) {
-  // When a KioskNextUser is not logged in, the shelf model is not hosted
-  // in KioskNextSellController.
-  EXPECT_FALSE(shelf_view_->model() ==
-               Shell::Get()->kiosk_next_shell_controller()->shelf_model());
-
-  LogInKioskNextUserInternal();
-
-  // When a KiosknextUser is logged in, the shelf model for the shelf view
-  // is hosted in KioskNextShellController.
-  EXPECT_TRUE(shelf_view_->model() ==
-              Shell::Get()->kiosk_next_shell_controller()->shelf_model());
-
-  // The home and back buttons are always visible.
-  EXPECT_TRUE(shelf_view_->GetHomeButton()->GetVisible());
-  EXPECT_TRUE(shelf_view_->GetBackButton()->GetVisible());
-
-  ASSERT_FALSE(shelf_view_->GetOverflowButton()->GetVisible());
-  EXPECT_EQ(-1, shelf_view_->last_visible_index());
-}
-
-// Tests that control buttons (back/home) are positioned correctly in Kiosk Next
-// mode. They should be centered, but exact position depends on the screen
-// orientation.
-TEST_F(KioskNextShelfViewTest, ControlButtonsCentered) {
-  // Setup internal display, otherwise setting display rotation will not take
-  // any effect.
-  const int64_t display_id =
-      display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayManager* display_manager = Shell::Get()->display_manager();
-  display::test::ScopedSetInternalDisplayId internal_display(display_manager,
-                                                             display_id);
-
-  ScreenOrientationControllerTestApi screen_orientation_test_api(
-      Shell::Get()->screen_orientation_controller());
-
-  LogInKioskNextUserInternal();
-
-  auto test_controls_bounds = [&](display::Display::Rotation rotation,
-                                  bool is_landscape) {
-    SCOPED_TRACE(base::StringPrintf("Rotation: %d", rotation));
-
-    screen_orientation_test_api.SetDisplayRotation(
-        rotation, display::Display::RotationSource::ACTIVE);
-
-    // Get shelf bounds from the widget - buttons should be centered
-    // relatively to the whole shelf area (consisting of shelf view and
-    // status area).
-    const gfx::Rect shelf_bounds = GetPrimaryShelf()
-                                       ->GetShelfViewForTesting()
-                                       ->GetWidget()
-                                       ->GetWindowBoundsInScreen();
-
-    // Switch to local shelf coordinates - buttons bounds are checked in
-    // relation to shelf.
-    gfx::Rect expected_button_area_bounds = gfx::Rect(shelf_bounds.size());
-    const gfx::Size expected_button_area_size =
-        gfx::Size(2 * kKioskNextShelfControlWidthDp +
-                      (is_landscape ? kKioskNextShelfControlSpacingLandscapeDp
-                                    : kKioskNextShelfControlSpacingPortraitDp),
-                  ShelfConstants::shelf_size());
-    expected_button_area_bounds.ClampToCenteredSize(expected_button_area_size);
-
-    const gfx::Rect back_button_bounds =
-        shelf_view_->GetBackButton()->ideal_bounds();
-    EXPECT_FALSE(back_button_bounds.IsEmpty());
-    EXPECT_TRUE(expected_button_area_bounds.Contains(back_button_bounds));
-
-    const gfx::Rect home_button_bounds =
-        shelf_view_->GetHomeButton()->ideal_bounds();
-    EXPECT_FALSE(home_button_bounds.IsEmpty());
-    EXPECT_TRUE(expected_button_area_bounds.Contains(home_button_bounds));
-
-    EXPECT_FALSE(back_button_bounds.Intersects(home_button_bounds));
-  };
-
-  test_controls_bounds(display::Display::ROTATE_0, true /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_90, false /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_180, true /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_270, false /*is_landscape*/);
-}
-
 }  // namespace ash
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 644dfae..47e3e27 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -9,7 +9,6 @@
 #include "ash/animation/animation_change_type.h"
 #include "ash/focus_cycler.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shelf_model.h"
@@ -18,7 +17,6 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/default_shelf_view.h"
 #include "ash/shelf/home_button.h"
-#include "ash/shelf/kiosk_next_shelf_view.h"
 #include "ash/shelf/login_shelf_view.h"
 #include "ash/shelf/overflow_bubble.h"
 #include "ash/shelf/overflow_bubble_view.h"
@@ -345,14 +343,6 @@
 
   background_animator_.AddObserver(delegate_view_);
   shelf_->AddObserver(this);
-
-  // KioskNextShell controller may have already notified its observers that
-  // it has been enabled by the time this ShelfWidget is being created.
-  if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled()) {
-    OnKioskNextEnabled();
-  } else {
-    Shell::Get()->kiosk_next_shell_controller()->AddObserver(this);
-  }
 }
 
 ShelfWidget::~ShelfWidget() {
@@ -380,9 +370,6 @@
   background_animator_.RemoveObserver(delegate_view_);
   shelf_->RemoveObserver(this);
 
-  if (Shell::Get()->kiosk_next_shell_controller())
-    Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
-
   // Don't need to observe focus/activation during shutdown.
   Shell::Get()->focus_cycler()->RemoveWidget(this);
   SetFocusCycler(nullptr);
@@ -577,18 +564,6 @@
   login_shelf_view_->UpdateAfterSessionChange();
 }
 
-void ShelfWidget::OnKioskNextEnabled() {
-  // Hide the shelf view and delete/remove it.
-  shelf_view_->SetVisible(false);
-  delete shelf_view_;
-
-  shelf_view_ = new KioskNextShelfView(
-      Shell::Get()->kiosk_next_shell_controller()->shelf_model(), shelf_, this);
-  shelf_view_->Init();
-  GetContentsView()->AddChildView(shelf_view_);
-  shelf_view_->SetVisible(true);
-}
-
 SkColor ShelfWidget::GetShelfBackgroundColor() const {
   return delegate_view_->GetShelfBackgroundColor();
 }
diff --git a/ash/shelf/shelf_widget.h b/ash/shelf/shelf_widget.h
index fd0ab4b..4ae412e2 100644
--- a/ash/shelf/shelf_widget.h
+++ b/ash/shelf/shelf_widget.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "ash/ash_export.h"
-#include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/session/session_observer.h"
 #include "ash/shelf/shelf_background_animator.h"
@@ -38,8 +37,7 @@
 class ASH_EXPORT ShelfWidget : public views::Widget,
                                public ShelfLayoutManagerObserver,
                                public ShelfObserver,
-                               public SessionObserver,
-                               public KioskNextShellObserver {
+                               public SessionObserver {
  public:
   ShelfWidget(aura::Window* shelf_container, Shelf* shelf);
   ~ShelfWidget() override;
@@ -120,9 +118,6 @@
   void OnSessionStateChanged(session_manager::SessionState state) override;
   void OnUserSessionAdded(const AccountId& account_id) override;
 
-  // KioskNextShellObserver:
-  void OnKioskNextEnabled() override;
-
   SkColor GetShelfBackgroundColor() const;
   bool GetHitTestRects(aura::Window* target,
                        gfx::Rect* hit_test_rect_mouse,
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index f207157..2f7dbae 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -75,11 +75,11 @@
   SkColor light_color, dark_color;
   switch (type) {
     case BaseLayerType::kTransparentWithBlur:
-      light_color = SkColorSetA(SK_ColorWHITE, 0xC7);
-      dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xC7);
+      light_color = SkColorSetA(SK_ColorWHITE, 0xBC);  // 74%
+      dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xBC);
       break;
     case BaseLayerType::kTransparentWithoutBlur:
-      light_color = SkColorSetA(SK_ColorWHITE, 0xE6);
+      light_color = SkColorSetA(SK_ColorWHITE, 0xE6);  // 90%
       dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xE6);
       break;
     case BaseLayerType::kOpaque:
@@ -94,16 +94,16 @@
   SkColor light_color, dark_color;
   switch (type) {
     case PlusOneLayerType::kHairLine:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x24);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x24);  // 14%
       dark_color = SkColorSetA(SK_ColorWHITE, 0x24);
       break;
     case PlusOneLayerType::kSeparator:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x24);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x24);  // 14%
       dark_color = SkColorSetA(SK_ColorWHITE, 0x24);
       break;
     case PlusOneLayerType::kInActive:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x0D);
-      dark_color = SkColorSetA(SK_ColorWHITE, 0x1A);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x0D);  // 5%
+      dark_color = SkColorSetA(SK_ColorWHITE, 0x1A);   // 10%
       break;
     case PlusOneLayerType::kActive:
       light_color = gfx::kGoogleBlue600;
diff --git a/ash/system/night_light/night_light_toggle_button.cc b/ash/system/night_light/night_light_toggle_button.cc
deleted file mode 100644
index 2679fc4d..0000000
--- a/ash/system/night_light/night_light_toggle_button.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/system/night_light/night_light_toggle_button.h"
-
-#include "ash/shell.h"
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/night_light/night_light_controller_impl.h"
-#include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_popup_item_style.h"
-#include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/gfx/vector_icon_types.h"
-
-namespace ash {
-
-namespace {
-
-gfx::ImageSkia GetNightLightNormalStateIcon(bool night_light_enabled) {
-  if (night_light_enabled)
-    return gfx::CreateVectorIcon(kSystemMenuNightLightOnIcon, kMenuIconColor);
-
-  // Use the same icon theme used for inactive items in the tray when
-  // NightLight is not active.
-  return gfx::CreateVectorIcon(kSystemMenuNightLightOffIcon,
-                               TrayPopupItemStyle::GetIconColor(
-                                   TrayPopupItemStyle::ColorStyle::INACTIVE));
-}
-
-gfx::ImageSkia GetNightLightDisabledStateIcon(bool night_light_enabled) {
-  return gfx::CreateVectorIcon(night_light_enabled
-                                   ? kSystemMenuNightLightOnIcon
-                                   : kSystemMenuNightLightOffIcon,
-                               kMenuIconColorDisabled);
-}
-
-base::string16 GetNightLightTooltipText(bool night_light_enabled) {
-  return l10n_util::GetStringFUTF16(
-      IDS_ASH_STATUS_TRAY_NIGHT_LIGHT,
-      l10n_util::GetStringUTF16(
-          night_light_enabled ? IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ON_STATE
-                              : IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_OFF_STATE));
-}
-
-}  // namespace
-
-NightLightToggleButton::NightLightToggleButton(views::ButtonListener* listener)
-    : SystemMenuButton(listener,
-                       kSystemMenuNightLightOffIcon,
-                       IDS_ASH_STATUS_TRAY_NIGHT_LIGHT) {
-  Update();
-}
-
-void NightLightToggleButton::Toggle() {
-  Shell::Get()->night_light_controller()->Toggle();
-  Update();
-  NotifyAccessibilityEvent(ax::mojom::Event::kAriaAttributeChanged, true);
-}
-
-const char* NightLightToggleButton::GetClassName() const {
-  return "NightLightToggleButton";
-}
-
-void NightLightToggleButton::Update() {
-  const bool night_light_enabled =
-      Shell::Get()->night_light_controller()->GetEnabled();
-
-  SetTooltipText(GetNightLightTooltipText(night_light_enabled));
-  SetImage(views::Button::STATE_NORMAL,
-           GetNightLightNormalStateIcon(night_light_enabled));
-  SetImage(views::Button::STATE_DISABLED,
-           GetNightLightDisabledStateIcon(night_light_enabled));
-}
-
-void NightLightToggleButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  const bool is_enabled = Shell::Get()->night_light_controller()->GetEnabled();
-  node_data->SetName(GetNightLightTooltipText(is_enabled));
-  node_data->role = ax::mojom::Role::kToggleButton;
-  node_data->SetCheckedState(is_enabled ? ax::mojom::CheckedState::kTrue
-                                        : ax::mojom::CheckedState::kFalse);
-}
-
-}  // namespace ash
diff --git a/ash/system/night_light/night_light_toggle_button.h b/ash/system/night_light/night_light_toggle_button.h
deleted file mode 100644
index 696e40c..0000000
--- a/ash/system/night_light/night_light_toggle_button.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
-#define ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
-
-#include "ash/system/tray/system_menu_button.h"
-#include "base/macros.h"
-
-namespace ash {
-
-// The NightLight toggle button in the system tray.
-class NightLightToggleButton : public SystemMenuButton {
- public:
-  explicit NightLightToggleButton(views::ButtonListener* listener);
-  ~NightLightToggleButton() override = default;
-
-  // Toggles the status of NightLight.
-  void Toggle();
-
-  // views::View:
-  const char* GetClassName() const override;
-
- private:
-  // Updates the icon and its style based on the status of NightLight.
-  void Update();
-
-  // views::View:
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
-  DISALLOW_COPY_AND_ASSIGN(NightLightToggleButton);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
diff --git a/ash/system/power/power_status_view.cc b/ash/system/power/power_status_view.cc
deleted file mode 100644
index 0907825c..0000000
--- a/ash/system/power/power_status_view.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2012 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 "ash/system/power/power_status_view.h"
-
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/power/tray_power.h"
-#include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_popup_item_style.h"
-#include "ash/system/tray/tray_popup_utils.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/geometry/insets.h"
-#include "ui/native_theme/native_theme.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/grid_layout.h"
-
-namespace ash {
-
-PowerStatusView::PowerStatusView()
-    : percentage_label_(new views::Label),
-      separator_label_(new views::Label),
-      time_status_label_(new views::Label) {
-  SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
-
-  percentage_label_->SetEnabledColor(kHeaderTextColorNormal);
-  separator_label_->SetEnabledColor(kHeaderTextColorNormal);
-  separator_label_->SetText(base::ASCIIToUTF16(" - "));
-
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kHorizontal, gfx::Insets(0, 12),
-      kTrayPopupPaddingBetweenItems));
-
-  AddChildView(percentage_label_);
-  AddChildView(separator_label_);
-  AddChildView(time_status_label_);
-
-  PowerStatus::Get()->AddObserver(this);
-  OnPowerStatusChanged();
-}
-
-PowerStatusView::~PowerStatusView() {
-  PowerStatus::Get()->RemoveObserver(this);
-}
-
-void PowerStatusView::OnPowerStatusChanged() {
-  UpdateText();
-}
-
-void PowerStatusView::UpdateText() {
-  base::string16 battery_percentage;
-  base::string16 battery_time_status;
-
-  std::tie(battery_percentage, battery_time_status) =
-      PowerStatus::Get()->GetStatusStrings();
-
-  percentage_label_->SetVisible(!battery_percentage.empty());
-  percentage_label_->SetText(battery_percentage);
-  separator_label_->SetVisible(!battery_percentage.empty() &&
-                               !battery_time_status.empty());
-  time_status_label_->SetVisible(!battery_time_status.empty());
-  time_status_label_->SetText(battery_time_status);
-
-  accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(true);
-
-  TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::SYSTEM_INFO);
-  style.SetupLabel(percentage_label_);
-  style.SetupLabel(separator_label_);
-  style.SetupLabel(time_status_label_);
-}
-
-void PowerStatusView::ChildPreferredSizeChanged(views::View* child) {
-  PreferredSizeChanged();
-}
-
-void PowerStatusView::Layout() {
-  views::View::Layout();
-
-  // Move the time_status_label_, separator_label_, and percentage_label_
-  // closer to each other.
-  if (percentage_label_ && separator_label_ && time_status_label_ &&
-      percentage_label_->GetVisible() && time_status_label_->GetVisible()) {
-    separator_label_->SetX(percentage_label_->bounds().right() + 1);
-    time_status_label_->SetX(separator_label_->bounds().right() + 1);
-  }
-}
-
-void PowerStatusView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  node_data->role = ax::mojom::Role::kLabelText;
-  node_data->SetName(accessible_name_);
-}
-
-const char* PowerStatusView::GetClassName() const {
-  return "PowerStatusView";
-}
-
-}  // namespace ash
diff --git a/ash/system/power/power_status_view.h b/ash/system/power/power_status_view.h
deleted file mode 100644
index c603576..0000000
--- a/ash/system/power/power_status_view.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2012 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 ASH_SYSTEM_POWER_POWER_STATUS_VIEW_H_
-#define ASH_SYSTEM_POWER_POWER_STATUS_VIEW_H_
-
-#include "ash/ash_export.h"
-#include "ash/system/power/power_status.h"
-#include "base/macros.h"
-#include "base/strings/string16.h"
-#include "ui/views/view.h"
-
-namespace views {
-class Label;
-}
-
-namespace ash {
-
-class ASH_EXPORT PowerStatusView : public views::View,
-                                   public PowerStatus::Observer {
- public:
-  PowerStatusView();
-  ~PowerStatusView() override;
-
-  // views::View:
-  void Layout() override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  const char* GetClassName() const override;
-
-  // PowerStatus::Observer:
-  void OnPowerStatusChanged() override;
-
- private:
-  friend class PowerStatusViewTest;
-
-  void UpdateText();
-
-  // views::View:
-  void ChildPreferredSizeChanged(views::View* child) override;
-
-  views::Label* percentage_label_;
-  views::Label* separator_label_;
-  views::Label* time_status_label_;
-
-  base::string16 accessible_name_;
-
-  DISALLOW_COPY_AND_ASSIGN(PowerStatusView);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_POWER_POWER_STATUS_VIEW_H_
diff --git a/ash/system/power/power_status_view_unittest.cc b/ash/system/power/power_status_view_unittest.cc
deleted file mode 100644
index c17889f9..0000000
--- a/ash/system/power/power_status_view_unittest.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2014 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 "ash/system/power/power_status_view.h"
-
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/power/power_status.h"
-#include "ash/test/ash_test_base.h"
-#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/l10n/time_format.h"
-#include "ui/views/controls/label.h"
-
-using power_manager::PowerSupplyProperties;
-
-namespace ash {
-
-class PowerStatusViewTest : public AshTestBase {
- public:
-  PowerStatusViewTest() = default;
-  ~PowerStatusViewTest() override = default;
-
-  // Overridden from testing::Test:
-  void SetUp() override {
-    AshTestBase::SetUp();
-    view_.reset(new PowerStatusView());
-  }
-
-  void TearDown() override {
-    view_.reset();
-    AshTestBase::TearDown();
-  }
-
- protected:
-  void UpdatePowerStatus(const power_manager::PowerSupplyProperties& proto) {
-    PowerStatus::Get()->SetProtoForTesting(proto);
-    view_->OnPowerStatusChanged();
-  }
-
-  bool IsPercentageVisible() const {
-    return view_->percentage_label_->GetVisible();
-  }
-
-  bool IsTimeStatusVisible() const {
-    return view_->time_status_label_->GetVisible();
-  }
-
-  base::string16 RemainingTimeInView() const {
-    return view_->time_status_label_->GetText();
-  }
-
- private:
-  std::unique_ptr<PowerStatusView> view_;
-
-  DISALLOW_COPY_AND_ASSIGN(PowerStatusViewTest);
-};
-
-TEST_F(PowerStatusViewTest, Basic) {
-  EXPECT_FALSE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-
-  // Disconnect the power.
-  PowerSupplyProperties prop;
-  prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
-  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
-  prop.set_battery_percent(99.0);
-  prop.set_battery_time_to_empty_sec(120);
-  prop.set_is_calculating_battery_time(true);
-  UpdatePowerStatus(prop);
-
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-
-  prop.set_is_calculating_battery_time(false);
-  UpdatePowerStatus(prop);
-  EXPECT_NE(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-  EXPECT_NE(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  prop.set_external_power(PowerSupplyProperties::AC);
-  prop.set_battery_state(PowerSupplyProperties::CHARGING);
-  prop.set_battery_time_to_full_sec(120);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_NE(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-  EXPECT_NE(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  prop.set_external_power(PowerSupplyProperties::USB);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_EQ(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  // Tricky -- connected to non-USB but still discharging. Not likely happening
-  // on production though.
-  prop.set_external_power(PowerSupplyProperties::AC);
-  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
-  prop.set_battery_time_to_full_sec(120);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_FALSE(IsTimeStatusVisible());
-}
-
-}  // namespace ash
diff --git a/ash/system/tray/tray_constants.cc b/ash/system/tray/tray_constants.cc
index 7fc1444..36fadb71 100644
--- a/ash/system/tray/tray_constants.cc
+++ b/ash/system/tray/tray_constants.cc
@@ -27,7 +27,6 @@
 
 const int kTrayPopupAutoCloseDelayInSeconds = 2;
 const int kTrayPopupPaddingHorizontal = 18;
-const int kTrayPopupPaddingBetweenItems = 10;
 const int kTrayPopupButtonEndMargin = 10;
 const int kTrayPopupLabelHorizontalPadding = 4;
 const int kTrayPopupSliderHorizontalPadding = 16;
@@ -49,8 +48,6 @@
 
 const SkColor kHeaderBackgroundColor = SkColorSetRGB(0xf5, 0xf5, 0xf5);
 
-const SkColor kHeaderTextColorNormal = SkColorSetARGB(0x7f, 0, 0, 0);
-
 const SkColor kMobileNotConnectedXIconColor = SkColorSetRGB(0xb2, 0xb2, 0xb2);
 
 const SkColor kTrayIconColor = gfx::kGoogleGrey200;
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h
index e42301a61..ad5e228 100644
--- a/ash/system/tray/tray_constants.h
+++ b/ash/system/tray/tray_constants.h
@@ -34,7 +34,6 @@
 
 extern const int kTrayPopupAutoCloseDelayInSeconds;
 extern const int kTrayPopupPaddingHorizontal;
-extern const int kTrayPopupPaddingBetweenItems;
 extern const int kTrayPopupButtonEndMargin;
 
 // The padding used on the left and right of labels. This applies to all labels
@@ -72,8 +71,6 @@
 
 extern const SkColor kHeaderBackgroundColor;
 
-extern const SkColor kHeaderTextColorNormal;
-
 // Constants for the title row.
 constexpr int kTitleRowVerticalPadding = 4;
 constexpr int kTitleRowProgressBarHeight = 2;
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index fc9fa38..8377a911 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -221,21 +221,6 @@
   view->layer()->SetFillsBoundsOpaquely(false);
 }
 
-void TrayPopupUtils::ShowStickyHeaderSeparator(views::View* view,
-                                               bool show_separator) {
-  if (show_separator) {
-    view->SetBorder(views::CreatePaddedBorder(
-        views::CreateSolidSidedBorder(0, 0, kTraySeparatorWidth, 0,
-                                      kMenuSeparatorColor),
-        gfx::Insets(kMenuSeparatorVerticalPadding, 0,
-                    kMenuSeparatorVerticalPadding - kTraySeparatorWidth, 0)));
-  } else {
-    view->SetBorder(views::CreateEmptyBorder(
-        gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
-  }
-  view->SchedulePaint();
-}
-
 void TrayPopupUtils::ConfigureContainer(TriView::Container container,
                                         views::View* container_view) {
   container_view->SetLayoutManager(CreateDefaultLayoutManager(container));
@@ -348,14 +333,6 @@
   return separator;
 }
 
-views::Separator* TrayPopupUtils::CreateListSubHeaderSeparator() {
-  views::Separator* separator = new views::Separator();
-  separator->SetColor(kMenuSeparatorColor);
-  separator->SetBorder(views::CreateEmptyBorder(
-      kMenuSeparatorVerticalPadding - views::Separator::kThickness, 0, 0, 0));
-  return separator;
-}
-
 bool TrayPopupUtils::CanOpenWebUISettings() {
   return Shell::Get()->session_controller()->ShouldEnableSettings();
 }
diff --git a/ash/system/tray/tray_popup_utils.h b/ash/system/tray/tray_popup_utils.h
index 548f0d2..f422be6 100644
--- a/ash/system/tray/tray_popup_utils.h
+++ b/ash/system/tray/tray_popup_utils.h
@@ -125,9 +125,6 @@
   // Sets up |view| to be a sticky header in a tray detail scroll view.
   static void ConfigureAsStickyHeader(views::View* view);
 
-  // Configures a |view| to have a visible separator below.
-  static void ShowStickyHeaderSeparator(views::View* view, bool show_separator);
-
   // Configures |container_view| just like CreateDefaultRowView() would
   // configure |container| on its returned TriView. To be used when mutliple
   // targetable areas are required within a single row.
@@ -188,11 +185,6 @@
   // ownership of the returned separator.
   static views::Separator* CreateListItemSeparator(bool left_inset);
 
-  // Creates and returns a horizontal separator line to be drawn between rows
-  // in a detailed view above the sub-header rows. Caller assumes ownership of
-  // the returned separator.
-  static views::Separator* CreateListSubHeaderSeparator();
-
   // Returns true if it is possible to open WebUI settings in a browser window,
   // i.e. the user is logged in, not on the lock screen, not adding a secondary
   // user, and not in the supervised user creation flow.
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index b33aade..d7ccaa5 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -541,6 +541,10 @@
   // can accept text input and it resizes correctly with the a11y keyboard.
   keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
 
+  // Prevent toggling overview during the split view divider snap animation.
+  if (Shell::Get()->split_view_controller()->IsDividerAnimating())
+    return true;
+
   auto windows =
       Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
 
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 03595dd2..3691ce3 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -555,6 +555,10 @@
     return (work_area_bounds_in_screen.height() - divider_size.height()) * 0.5f;
 }
 
+bool SplitViewController::IsDividerAnimating() {
+  return divider_snap_animation_ && divider_snap_animation_->is_animating();
+}
+
 void SplitViewController::StartResize(const gfx::Point& location_in_screen) {
   DCHECK(InSplitViewMode());
 
@@ -1442,10 +1446,6 @@
   return fix_position;
 }
 
-bool SplitViewController::IsDividerAnimating() {
-  return divider_snap_animation_ && divider_snap_animation_->is_animating();
-}
-
 void SplitViewController::StopAndShoveAnimatedDivider() {
   DCHECK(IsDividerAnimating());
 
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 67bb129..a35e2fe 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -136,6 +136,9 @@
   // Gets the default value of |divider_position_|.
   int GetDefaultDividerPosition(aura::Window* window) const;
 
+  // Returns true during the divider snap animation.
+  bool IsDividerAnimating();
+
   void StartResize(const gfx::Point& location_in_screen);
   void Resize(const gfx::Point& location_in_screen);
   void EndResize(const gfx::Point& location_in_screen);
@@ -269,9 +272,6 @@
   // Returns the closest fix location for |divider_position_|.
   int GetClosestFixedDividerPosition();
 
-  // Returns true during the divider snap animation.
-  bool IsDividerAnimating();
-
   // While the divider is animating to somewhere, stop it and shove it there.
   void StopAndShoveAnimatedDivider();
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index dea8fe5..dd70645 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -8,7 +8,6 @@
 #include <string>
 #include <utility>
 
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/fps_counter.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -303,8 +302,6 @@
             base::Unretained(this)));
   }
 
-  Shell::Get()->kiosk_next_shell_controller()->AddObserver(this);
-
   chromeos::PowerManagerClient* power_manager_client =
       chromeos::PowerManagerClient::Get();
   power_manager_client->AddObserver(this);
@@ -332,7 +329,6 @@
                             tab_drag_in_splitview_count_);
 
   Shell::Get()->RemoveShellObserver(this);
-  Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
 
   if (ShouldInitTabletModeController()) {
     Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
@@ -618,11 +614,6 @@
   HandlePointingDeviceAddedOrRemoved();
 }
 
-void TabletModeController::OnKioskNextEnabled() {
-  tablet_mode_behavior_ = kLockInCurrentMode;
-  AttemptEnterTabletMode();
-}
-
 void TabletModeController::OnLayerAnimationStarted(
     ui::LayerAnimationSequence* sequence) {}
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index 4c25a3c1..ebd32aef 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -12,7 +12,6 @@
 #include "ash/ash_export.h"
 #include "ash/bluetooth_devices_observer.h"
 #include "ash/display/window_tree_host_manager.h"
-#include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/session/session_observer.h"
 #include "ash/shell_observer.h"
@@ -71,7 +70,6 @@
       public WindowTreeHostManager::Observer,
       public SessionObserver,
       public ui::InputDeviceEventObserver,
-      public KioskNextShellObserver,
       public ui::LayerAnimationObserver {
  public:
   // Enable or disable using a screenshot for testing as it makes the
@@ -149,9 +147,6 @@
   void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
   void OnDeviceListsComplete() override;
 
-  // KioskNextShellObserver:
-  void OnKioskNextEnabled() override;
-
   // ui::LayerAnimationObserver:
   void OnLayerAnimationStarted(ui::LayerAnimationSequence* sequence) override;
   void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 4a95c375..51ce305 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -1483,35 +1483,6 @@
   EXPECT_TRUE(Shell::Get()->overview_controller()->StartOverview());
 }
 
-// Test that when OnKioskNextEnabled() is called the UI mode changes into
-// TabletMode. Ensure that UI mode keeps staying in Tablet Mode.
-TEST_F(TabletModeControllerTest, TestKioskNextModeUI) {
-  ui::DeviceDataManagerTestApi().SetMouseDevices({});
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices({});
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
-
-  tablet_mode_controller()->OnKioskNextEnabled();
-  EXPECT_TRUE(IsTabletModeStarted());
-
-  // Attach a mouse. Check that we are still in Tablet Mode.
-  ui::DeviceDataManagerTestApi().SetMouseDevices(
-      {ui::InputDevice(0, ui::InputDeviceType::INPUT_DEVICE_USB, "mouse")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetMouseDevices({});
-
-  // Attach Touchpad
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices(
-      {ui::InputDevice(1, ui::InputDeviceType::INPUT_DEVICE_USB, "touchpad")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices({});
-
-  // Attach Keyboard
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices(
-      {ui::InputDevice(2, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
-}
-
 // Test that tablet mode controller does not respond to the input device changes
 // during its suspend.
 TEST_F(TabletModeControllerTest, DoNotObserverInputDeviceChangeDuringSuspend) {
diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java
index af2b5600..c3388192 100644
--- a/base/android/java/src/org/chromium/base/library_loader/Linker.java
+++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -269,7 +269,7 @@
         assertLinkerTestsAreEnabled();
 
         synchronized (sLock) {
-            sSingleton.mTestRunnerClassName = testRunnerClassName;
+            Linker.getInstance().mTestRunnerClassName = testRunnerClassName;
         }
     }
 
diff --git a/base/android/jni_generator/jni_refactorer.py b/base/android/jni_generator/jni_refactorer.py
index de0da86..8502207 100755
--- a/base/android/jni_generator/jni_refactorer.py
+++ b/base/android/jni_generator/jni_refactorer.py
@@ -138,7 +138,7 @@
     else:
       import_insert = match.end() + 1
 
-  return "%s%s\n%s" % (contents[:import_insert], JCALLER_IMPORT_STRING,
+  return "%s%s\n%s" % (contents[:import_insert], import_string,
                        contents[import_insert:])
 
 
@@ -370,6 +370,10 @@
       ' instead of converting static methods to new jni.')
   arg_parser.add_argument(
       '--verbose', default=False, action='store_true', help='')
+  arg_parser.add_argument(
+      '--ignored-paths',
+      action='append',
+      help='Paths to ignore during conversion.')
 
   args = arg_parser.parse_args()
 
@@ -383,29 +387,21 @@
       java_file_paths = pickle.load(file)
       print('Found %s java paths.' % len(java_file_paths))
   elif args.recursive:
-    ignored_paths = [
-        'third_party', 'src/out', 'out/Debug', 'library_loader', '.cipd',
-        'jni_generator', 'media', 'accessibility', '/vr', 'website',
-        'gcm_driver', 'preferences'
-    ]
-
-    for root, dirs, files in os.walk(os.path.abspath('.')):
-
-      def getPaths():
-        for ignored_path in ignored_paths:
-          if ignored_path in root:
-            return
-
-          java_file_paths.extend(
-              ['%s/%s' % (root, f) for f in files if f.endswith('.java')])
-
-      getPaths()
+    for root, _, files in os.walk(os.path.abspath('.')):
+      java_file_paths.extend(
+          ['%s/%s' % (root, f) for f in files if f.endswith('.java')])
 
   else:
     # Get all java files in current dir.
     java_file_paths = filter(lambda x: x.endswith('.java'),
                              map(os.path.abspath, os.listdir('.')))
 
+  if args.ignored_paths:
+    java_file_paths = [
+        path for path in java_file_paths
+        if all(p not in path for p in args.ignored_paths)
+    ]
+
   if args.cache:
     with open(PICKLE_LOCATION, 'w') as file:
       pickle.dump(filter_files_with_natives(java_file_paths), file)
diff --git a/base/mac/mach_port_rendezvous.cc b/base/mac/mach_port_rendezvous.cc
index 5e71440..43e5806 100644
--- a/base/mac/mach_port_rendezvous.cc
+++ b/base/mac/mach_port_rendezvous.cc
@@ -258,11 +258,15 @@
 
 // static
 MachPortRendezvousClient* MachPortRendezvousClient::GetInstance() {
-  static MachPortRendezvousClient* client = new MachPortRendezvousClient();
-  if (!client->did_acquire_ports()) {
-    bool ok = client->AcquirePorts();
-    DCHECK(ok);
+  static MachPortRendezvousClient* client = []() -> auto* {
+    auto* client = new MachPortRendezvousClient();
+    if (!client->AcquirePorts()) {
+      delete client;
+      client = nullptr;
+    }
+    return client;
   }
+  ();
   return client;
 }
 
@@ -296,8 +300,6 @@
 bool MachPortRendezvousClient::AcquirePorts() {
   AutoLock lock(lock_);
 
-  did_acquire_ports_ = true;
-
   mac::ScopedMachSendRight server_port;
   std::string bootstrap_name = GetBootstrapName();
   kern_return_t kr = bootstrap_look_up(
diff --git a/base/mac/mach_port_rendezvous.h b/base/mac/mach_port_rendezvous.h
index 422b2df30..5fd9d08 100644
--- a/base/mac/mach_port_rendezvous.h
+++ b/base/mac/mach_port_rendezvous.h
@@ -82,7 +82,9 @@
  public:
   // Returns the instance of the server. Upon the first call to this method,
   // the server is created, which registers an endpoint in the Mach bootstrap
-  // namespace.
+  // namespace. If the rendezvous fails, which can happen if the server is not
+  // available, this returns null. Acquiring zero ports from the exchange is
+  // not considered a failure.
   static MachPortRendezvousServer* GetInstance();
 
   // Registers a collection of Mach ports |ports| to be acquirable by the
@@ -202,13 +204,8 @@
   // MachRendezvousPort with MACH_PORT_NULL is returned.
   MachRendezvousPort PortForKey(MachPortsForRendezvous::key_type key);
 
-  bool did_acquire_ports() { return did_acquire_ports_; }
-
   // Lock for the below data members.
   Lock lock_;
-  // Flag for if the client has attempted to acquire ports. If the client
-  // experienced an error in doing so, this will still be true.
-  bool did_acquire_ports_ = false;
   // The collection of ports that was acquired.
   MachPortsForRendezvous ports_;
 
diff --git a/base/mac/mach_port_rendezvous_unittest.cc b/base/mac/mach_port_rendezvous_unittest.cc
index 5af2a86..a66c1cef 100644
--- a/base/mac/mach_port_rendezvous_unittest.cc
+++ b/base/mac/mach_port_rendezvous_unittest.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/at_exit.h"
+#include "base/mac/foundation_util.h"
 #include "base/mac/mach_logging.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/multiprocess_test.h"
@@ -206,4 +207,25 @@
   }
 }
 
+MULTIPROCESS_TEST_MAIN(FailToRendezvous) {
+  // The rendezvous system uses the BaseBundleID to construct the bootstrap
+  // server name, so changing it will result in a failure to look it up.
+  base::mac::SetBaseBundleID("org.chromium.totallyfake");
+  CHECK_EQ(nullptr, base::MachPortRendezvousClient::GetInstance());
+  return 0;
+}
+
+TEST_F(MachPortRendezvousServerTest, FailToRendezvous) {
+  auto* server = MachPortRendezvousServer::GetInstance();
+  ASSERT_TRUE(server);
+
+  Process child = SpawnChild("FailToRendezvous");
+
+  int exit_code;
+  ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+      child, TestTimeouts::action_timeout(), &exit_code));
+
+  EXPECT_EQ(0, exit_code);
+}
+
 }  // namespace base
diff --git a/base/memory/shared_memory.h b/base/memory/shared_memory.h
index bd97f11b..f095516c 100644
--- a/base/memory/shared_memory.h
+++ b/base/memory/shared_memory.h
@@ -98,9 +98,6 @@
   // Closes a shared memory handle.
   static void CloseHandle(const SharedMemoryHandle& handle);
 
-  // Returns the maximum number of handles that can be open at once per process.
-  static size_t GetHandleLimit();
-
   // Duplicates The underlying OS primitive. Returns an invalid handle on
   // failure. The caller is responsible for destroying the duplicated OS
   // primitive.
diff --git a/base/memory/shared_memory_fuchsia.cc b/base/memory/shared_memory_fuchsia.cc
index a386913a..878906a 100644
--- a/base/memory/shared_memory_fuchsia.cc
+++ b/base/memory/shared_memory_fuchsia.cc
@@ -38,13 +38,6 @@
   handle.Close();
 }
 
-// static
-size_t SharedMemory::GetHandleLimit() {
-  // Duplicated from the internal Magenta kernel constant kMaxHandleCount
-  // (kernel/lib/zircon/zircon.cpp).
-  return 256 * 1024u;
-}
-
 bool SharedMemory::CreateAndMapAnonymous(size_t size) {
   return CreateAnonymous(size) && Map(size);
 }
diff --git a/base/memory/shared_memory_mac.cc b/base/memory/shared_memory_mac.cc
index fc1af800..82dfeae0 100644
--- a/base/memory/shared_memory_mac.cc
+++ b/base/memory/shared_memory_mac.cc
@@ -99,11 +99,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  return GetMaxFds();
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc
index 0988b5ad..f92a916 100644
--- a/base/memory/shared_memory_posix.cc
+++ b/base/memory/shared_memory_posix.cc
@@ -60,11 +60,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  return GetMaxFds();
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/memory/shared_memory_win.cc b/base/memory/shared_memory_win.cc
index 88e3cbbc..b0adabe 100644
--- a/base/memory/shared_memory_win.cc
+++ b/base/memory/shared_memory_win.cc
@@ -163,13 +163,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  // Rounded down from value reported here:
-  // http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx
-  return static_cast<size_t>(1 << 23);
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/message_loop/message_pump_fuchsia.cc b/base/message_loop/message_pump_fuchsia.cc
index 25b255dd..948785d 100644
--- a/base/message_loop/message_pump_fuchsia.cc
+++ b/base/message_loop/message_pump_fuchsia.cc
@@ -92,14 +92,6 @@
 
   controller->handler = nullptr;
 
-  // |signal| can include other spurious things, in particular, that an fd
-  // is writable, when we only asked to know when it was readable. In that
-  // case, we don't want to call both the CanWrite and CanRead callback,
-  // when the caller asked for only, for example, readable callbacks. So,
-  // mask with the events that we actually wanted to know about.
-  zx_signals_t signals = signal->trigger & signal->observed;
-  DCHECK_NE(0u, signals);
-
   // In the case of a persistent Watch, the Watch may be stopped and
   // potentially deleted by the caller within the callback, in which case
   // |controller| should not be accessed again, and we mustn't continue the
@@ -108,7 +100,7 @@
   bool was_stopped = false;
   controller->was_stopped_ = &was_stopped;
 
-  controller->watcher_->OnZxHandleSignalled(wait->object, signals);
+  controller->watcher_->OnZxHandleSignalled(wait->object, signal->observed);
 
   if (was_stopped)
     return;
@@ -125,6 +117,14 @@
   uint32_t events;
   fdio_unsafe_wait_end(io_, signals, &events);
 
+  // |events| can include other spurious things, in particular, that an fd
+  // is writable, when we only asked to know when it was readable. In that
+  // case, we don't want to call both the CanWrite and CanRead callback,
+  // when the caller asked for only, for example, readable callbacks. So,
+  // mask with the events that we actually wanted to know about.
+  events &= desired_events_;
+  DCHECK_NE(0u, events);
+
   // Each |watcher_| callback we invoke may stop or delete |this|. The pump has
   // set |was_stopped_| to point to a safe location on the calling stack, so we
   // can use that to detect being stopped mid-callback and avoid doing further
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index b2335a7..09eb38fb 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -1217,6 +1217,8 @@
   win::ScopedHandle scoped_handle(handle);
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
   auto* rendezvous = MachPortRendezvousClient::GetInstance();
+  if (!rendezvous)
+    return ReadOnlySharedMemoryRegion();
   mac::ScopedMachSendRight scoped_handle =
       rendezvous->TakeSendRight(field_trial_handle);
   if (!scoped_handle.is_valid())
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index f445125..2599109 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -254,6 +254,9 @@
 // at once. If the number is unavailable, a conservative best guess is returned.
 BASE_EXPORT size_t GetMaxFds();
 
+// Returns the maximum number of handles that can be open at once per process.
+BASE_EXPORT size_t GetHandleLimit();
+
 #if defined(OS_POSIX)
 // Increases the file descriptor soft limit to |max_descriptors| or the OS hard
 // limit, whichever is lower. If the limit is already higher than
diff --git a/base/process/process_metrics_fuchsia.cc b/base/process/process_metrics_fuchsia.cc
index ddfa9c6..3c7d14c 100644
--- a/base/process/process_metrics_fuchsia.cc
+++ b/base/process/process_metrics_fuchsia.cc
@@ -12,6 +12,12 @@
   return FDIO_MAX_FD;
 }
 
+size_t GetHandleLimit() {
+  // Duplicated from the internal Magenta kernel constant kMaxHandleCount
+  // (zircon/kernel/object/handle.cc).
+  return 256 * 1024u;
+}
+
 size_t GetSystemCommitCharge() {
   // TODO(https://crbug.com/926581): Fuchsia does not support this.
   return 0;
diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc
index a09bbf2..5763432 100644
--- a/base/process/process_metrics_posix.cc
+++ b/base/process/process_metrics_posix.cc
@@ -71,6 +71,15 @@
   return static_cast<size_t>(max_fds);
 }
 
+size_t GetHandleLimit() {
+#if defined(OS_MACOSX)
+  // Taken from a small test that allocated ports in a loop.
+  return static_cast<size_t>(1 << 18);
+#else
+  return GetMaxFds();
+#endif
+}
+
 void IncreaseFdLimitTo(unsigned int max_descriptors) {
   struct rlimit limits;
   if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc
index fcf69acc..5cf0943 100644
--- a/base/process/process_metrics_win.cc
+++ b/base/process/process_metrics_win.cc
@@ -129,6 +129,12 @@
   return std::numeric_limits<size_t>::max();
 }
 
+size_t GetHandleLimit() {
+  // Rounded down from value reported here:
+  // http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx
+  return static_cast<size_t>(1 << 23);
+}
+
 // static
 std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
     ProcessHandle process) {
diff --git a/base/task/promise/dependent_list.cc b/base/task/promise/dependent_list.cc
index 81cc763..6cbbf345 100644
--- a/base/task/promise/dependent_list.cc
+++ b/base/task/promise/dependent_list.cc
@@ -47,7 +47,7 @@
 
 DependentList::Node::Node() = default;
 
-DependentList::Node::Node(Node&& other) {
+DependentList::Node::Node(Node&& other) noexcept {
   prerequisite_ = other.prerequisite_.load(std::memory_order_relaxed);
   other.prerequisite_ = 0;
   dependent_ = std::move(other.dependent_);
diff --git a/base/task/promise/finally_executor.cc b/base/task/promise/finally_executor.cc
index 27cc023bf..52da5678 100644
--- a/base/task/promise/finally_executor.cc
+++ b/base/task/promise/finally_executor.cc
@@ -7,7 +7,8 @@
 namespace base {
 namespace internal {
 
-FinallyExecutorCommon::FinallyExecutorCommon(internal::CallbackBase&& callback)
+FinallyExecutorCommon::FinallyExecutorCommon(
+    internal::CallbackBase&& callback) noexcept
     : callback_(std::move(callback)) {}
 
 FinallyExecutorCommon::~FinallyExecutorCommon() = default;
diff --git a/base/task/promise/helpers.cc b/base/task/promise/helpers.cc
index 5008363..35dc37d 100644
--- a/base/task/promise/helpers.cc
+++ b/base/task/promise/helpers.cc
@@ -40,7 +40,7 @@
     const scoped_refptr<TaskRunner>& task_runner,
     const Location& from_here,
     AbstractPromise* prerequsite,
-    internal::PromiseExecutor::Data&& executor_data) {
+    internal::PromiseExecutor::Data&& executor_data) noexcept {
   return internal::AbstractPromise::Create(
       task_runner, from_here,
       std::make_unique<AbstractPromise::AdjacencyList>(prerequsite),
diff --git a/base/task/sequence_manager/task_queue.cc b/base/task/sequence_manager/task_queue.cc
index a69f9d30..35ff9d7 100644
--- a/base/task/sequence_manager/task_queue.cc
+++ b/base/task/sequence_manager/task_queue.cc
@@ -329,6 +329,10 @@
   impl_->SetObserver(observer);
 }
 
+void TaskQueue::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
+  impl_->SetShouldReportPostedTasksWhenDisabled(should_report);
+}
+
 bool TaskQueue::IsOnMainThread() const {
   return associated_thread_->IsBoundToCurrentThread();
 }
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
index f57b4a1..d863308 100644
--- a/base/task/sequence_manager/task_queue.h
+++ b/base/task/sequence_manager/task_queue.h
@@ -325,6 +325,12 @@
 
   void SetObserver(Observer* observer);
 
+  // Controls whether or not the queue will emit traces events when tasks are
+  // posted to it while disabled. This only applies for the current or next
+  // period during which the queue is disabled. When the queue is re-enabled
+  // this will revert back to the default value of false.
+  void SetShouldReportPostedTasksWhenDisabled(bool should_report);
+
   // Create a task runner for this TaskQueue which will annotate all
   // posted tasks with the given task type.
   // May be called on any thread.
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index 0ad98e8..a46b72de 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -136,6 +136,9 @@
 
 TaskQueueImpl::AnyThread::~AnyThread() = default;
 
+TaskQueueImpl::AnyThread::TracingOnly::TracingOnly() = default;
+TaskQueueImpl::AnyThread::TracingOnly::~TracingOnly() = default;
+
 TaskQueueImpl::MainThreadOnly::MainThreadOnly(TaskQueueImpl* task_queue,
                                               TimeDomain* time_domain)
     : time_domain(time_domain),
@@ -143,10 +146,7 @@
           new WorkQueue(task_queue, "delayed", WorkQueue::QueueType::kDelayed)),
       immediate_work_queue(new WorkQueue(task_queue,
                                          "immediate",
-                                         WorkQueue::QueueType::kImmediate)),
-      is_enabled(true),
-      blame_context(nullptr),
-      is_enabled_for_test(true) {}
+                                         WorkQueue::QueueType::kImmediate)) {}
 
 TaskQueueImpl::MainThreadOnly::~MainThreadOnly() = default;
 
@@ -290,6 +290,8 @@
 
     sequence_manager_->WillQueueTask(
         &any_thread_.immediate_incoming_queue.back(), name_);
+    MaybeReportIpcTaskQueuedFromAnyThreadLocked(
+        &any_thread_.immediate_incoming_queue.back(), name_);
 
     // If this queue was completely empty, then the SequenceManager needs to be
     // informed so it can reload the work queue and add us to the
@@ -386,8 +388,10 @@
   pending_task.cross_thread_ = false;
 #endif
 
-  if (notify_task_annotator)
+  if (notify_task_annotator) {
     sequence_manager_->WillQueueTask(&pending_task, name_);
+    MaybeReportIpcTaskQueuedFromMainThread(&pending_task, name_);
+  }
   main_thread_only().delayed_incoming_queue.push(std::move(pending_task));
 
   LazyNow lazy_now(now);
@@ -398,6 +402,7 @@
 
 void TaskQueueImpl::PushOntoDelayedIncomingQueue(Task pending_task) {
   sequence_manager_->WillQueueTask(&pending_task, name_);
+  MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(&pending_task, name_);
 
 #if DCHECK_IS_ON()
   pending_task.cross_thread_ = true;
@@ -880,29 +885,45 @@
 }
 
 void TaskQueueImpl::SetQueueEnabled(bool enabled) {
-  if (main_thread_only().is_enabled != enabled) {
-    main_thread_only().is_enabled = enabled;
-    EnableOrDisableWithSelector(enabled);
-  }
-}
-
-void TaskQueueImpl::EnableOrDisableWithSelector(bool enable) {
-  // |sequence_manager_| can be null in tests.
-  if (!sequence_manager_)
+  if (main_thread_only().is_enabled == enabled)
     return;
 
+  // Update the |main_thread_only_| struct.
+  main_thread_only().is_enabled = enabled;
+  main_thread_only().disabled_time = nullopt;
+  if (!enabled) {
+    bool tracing_enabled = false;
+    TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                       &tracing_enabled);
+    main_thread_only().disabled_time = main_thread_only().time_domain->Now();
+  } else {
+    // Override reporting if the queue is becoming enabled again.
+    main_thread_only().should_report_posted_tasks_when_disabled = false;
+  }
+
   LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
   UpdateDelayedWakeUp(&lazy_now);
 
-  bool has_pending_immediate_work;
+  bool has_pending_immediate_work = false;
 
   {
     base::internal::CheckedAutoLock lock(any_thread_lock_);
     UpdateCrossThreadQueueStateLocked();
     has_pending_immediate_work = HasPendingImmediateWorkLocked();
+
+    // Copy over the task-reporting related state.
+    any_thread_.tracing_only.is_enabled = enabled;
+    any_thread_.tracing_only.disabled_time = main_thread_only().disabled_time;
+    any_thread_.tracing_only.should_report_posted_tasks_when_disabled =
+        main_thread_only().should_report_posted_tasks_when_disabled;
   }
 
-  if (enable) {
+  // |sequence_manager_| can be null in tests.
+  if (!sequence_manager_)
+    return;
+
+  // Finally, enable or disable the queue with the selector.
+  if (enabled) {
     if (has_pending_immediate_work && main_thread_only().task_queue_observer) {
       // Delayed work notification will be issued via time domain.
       main_thread_only().task_queue_observer->OnQueueNextWakeUpChanged(
@@ -917,6 +938,30 @@
   }
 }
 
+void TaskQueueImpl::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
+  if (main_thread_only().should_report_posted_tasks_when_disabled ==
+      should_report)
+    return;
+
+  // Only observe transitions turning the reporting on if tracing is enabled.
+  if (should_report) {
+    bool tracing_enabled = false;
+    TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                       &tracing_enabled);
+    if (!tracing_enabled)
+      return;
+  }
+
+  main_thread_only().should_report_posted_tasks_when_disabled = should_report;
+
+  // Mirror the state to the AnyThread struct as well.
+  {
+    base::internal::CheckedAutoLock lock(any_thread_lock_);
+    any_thread_.tracing_only.should_report_posted_tasks_when_disabled =
+        should_report;
+  }
+}
+
 void TaskQueueImpl::UpdateCrossThreadQueueStateLocked() {
   any_thread_.immediate_work_queue_empty =
       main_thread_only().immediate_work_queue->Empty();
@@ -1139,6 +1184,107 @@
   return false;
 }
 
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromMainThread(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  // It's possible that tracing was just enabled and no disabled time has been
+  // stored. In that case, skip emitting the event.
+  if (!main_thread_only().disabled_time)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  if (main_thread_only().is_enabled ||
+      !main_thread_only().should_report_posted_tasks_when_disabled) {
+    return;
+  }
+
+  base::TimeDelta time_since_disabled =
+      main_thread_only().time_domain->Now() -
+      main_thread_only().disabled_time.value();
+
+  ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+bool TaskQueueImpl::ShouldReportIpcTaskQueuedFromAnyThreadLocked(
+    base::TimeDelta* time_since_disabled) {
+  // It's possible that tracing was just enabled and no disabled time has been
+  // stored. In that case, skip emitting the event.
+  if (!any_thread_.tracing_only.disabled_time)
+    return false;
+
+  if (any_thread_.tracing_only.is_enabled ||
+      any_thread_.tracing_only.should_report_posted_tasks_when_disabled) {
+    return false;
+  }
+
+  *time_since_disabled = any_thread_.time_domain->Now() -
+                         any_thread_.tracing_only.disabled_time.value();
+  return true;
+}
+
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromAnyThreadLocked(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  base::TimeDelta time_since_disabled;
+  if (ShouldReportIpcTaskQueuedFromAnyThreadLocked(&time_since_disabled))
+    ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  base::TimeDelta time_since_disabled;
+  bool should_report = false;
+  {
+    base::internal::CheckedAutoLock lock(any_thread_lock_);
+    should_report =
+        ShouldReportIpcTaskQueuedFromAnyThreadLocked(&time_since_disabled);
+  }
+
+  ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+void TaskQueueImpl::ReportIpcTaskQueued(
+    Task* pending_task,
+    const char* task_queue_name,
+    const base::TimeDelta& time_since_disabled) {
+  // Use a begin/end event pair so we can get 4 fields in the event.
+  TRACE_EVENT_BEGIN2(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                     "task_posted_to_disabled_queue", "task_queue_name",
+                     task_queue_name, "time_since_disabled_ms",
+                     time_since_disabled.InMilliseconds());
+  TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                   "task_posted_to_disabled_queue", "ipc_hash",
+                   pending_task->ipc_hash, "location",
+                   pending_task->posted_from.program_counter());
+}
+
 TaskQueueImpl::DelayedIncomingQueue::DelayedIncomingQueue() = default;
 TaskQueueImpl::DelayedIncomingQueue::~DelayedIncomingQueue() = default;
 
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
index d5a4ea6..ed5489dd 100644
--- a/base/task/sequence_manager/task_queue_impl.h
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -102,6 +102,7 @@
   const char* GetName() const;
   bool IsQueueEnabled() const;
   void SetQueueEnabled(bool enabled);
+  void SetShouldReportPostedTasksWhenDisabled(bool should_report);
   bool IsEmpty() const;
   size_t GetNumberOfPendingTasks() const;
   bool HasTaskToRunImmediately() const;
@@ -340,8 +341,8 @@
     DelayedIncomingQueue delayed_incoming_queue;
     ObserverList<TaskObserver>::Unchecked task_observers;
     base::internal::HeapHandle heap_handle;
-    bool is_enabled;
-    trace_event::BlameContext* blame_context;  // Not owned.
+    bool is_enabled = true;
+    trace_event::BlameContext* blame_context = nullptr;  // Not owned.
     EnqueueOrder current_fence;
     Optional<TimeTicks> delayed_fence;
     OnTaskStartedHandler on_task_started_handler;
@@ -350,7 +351,13 @@
     // excessive calls.
     Optional<DelayedWakeUp> scheduled_wake_up;
     // If false, queue will be disabled. Used only for tests.
-    bool is_enabled_for_test;
+    bool is_enabled_for_test = true;
+    // The time at which the task queue was disabled, if it is currently
+    // disabled.
+    Optional<TimeTicks> disabled_time;
+    // Whether or not the task queue should emit tracing events for tasks
+    // posted to this queue when it is disabled.
+    bool should_report_posted_tasks_when_disabled = false;
   };
 
   void PostTask(PostedTask task);
@@ -394,8 +401,6 @@
                               TimeTicks now,
                               trace_event::TracedValue* state);
 
-  void EnableOrDisableWithSelector(bool enable);
-
   // Schedules delayed work on time domain and calls the observer.
   void UpdateDelayedWakeUp(LazyNow* lazy_now);
   void UpdateDelayedWakeUpImpl(LazyNow* lazy_now,
@@ -411,6 +416,23 @@
   void MaybeLogPostTask(PostedTask* task);
   void MaybeAdjustTaskDelay(PostedTask* task, CurrentThread current_thread);
 
+  // Reports the task if it was due to IPC and was posted to a disabled queue.
+  // This should be called after WillQueueTask has been called for the task.
+  void MaybeReportIpcTaskQueuedFromMainThread(Task* pending_task,
+                                              const char* task_queue_name);
+  bool ShouldReportIpcTaskQueuedFromAnyThreadLocked(
+      base::TimeDelta* time_since_disabled)
+      EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
+  void MaybeReportIpcTaskQueuedFromAnyThreadLocked(Task* pending_task,
+                                                   const char* task_queue_name)
+      EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
+  void MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(
+      Task* pending_task,
+      const char* task_queue_name);
+  void ReportIpcTaskQueued(Task* pending_task,
+                           const char* task_queue_name,
+                           const base::TimeDelta& time_since_disabled);
+
   const char* name_;
   SequenceManagerImpl* const sequence_manager_;
 
@@ -421,6 +443,16 @@
   mutable base::internal::CheckedLock any_thread_lock_;
 
   struct AnyThread {
+    // Mirrored from MainThreadOnly. These are only used for tracing.
+    struct TracingOnly {
+      TracingOnly();
+      ~TracingOnly();
+
+      bool is_enabled = true;
+      Optional<TimeTicks> disabled_time;
+      bool should_report_posted_tasks_when_disabled = false;
+    };
+
     explicit AnyThread(TimeDomain* time_domain);
     ~AnyThread();
 
@@ -441,12 +473,14 @@
     bool unregistered = false;
 
 #if DCHECK_IS_ON()
-    // A cached of |immediate_work_queue->work_queue_set_index()| which is used
+    // A cache of |immediate_work_queue->work_queue_set_index()| which is used
     // to index into
     // SequenceManager::Settings::per_priority_cross_thread_task_delay to apply
     // a priority specific delay for debugging purposes.
     int queue_set_index = 0;
 #endif
+
+    TracingOnly tracing_only;
   };
 
   AnyThread any_thread_ GUARDED_BY(any_thread_lock_);
diff --git a/base/task/thread_pool/task_source.cc b/base/task/thread_pool/task_source.cc
index de0fcd3..b978251 100644
--- a/base/task/thread_pool/task_source.cc
+++ b/base/task/thread_pool/task_source.cc
@@ -15,7 +15,7 @@
 namespace base {
 namespace internal {
 
-TaskSource::RunIntent::RunIntent(RunIntent&& other)
+TaskSource::RunIntent::RunIntent(RunIntent&& other) noexcept
     : task_source_(other.task_source_),
       concurrency_status_(other.concurrency_status_) {
   other.task_source_ = nullptr;
@@ -111,8 +111,8 @@
 RegisteredTaskSource::RegisteredTaskSource(std::nullptr_t)
     : RegisteredTaskSource() {}
 
-RegisteredTaskSource::RegisteredTaskSource(RegisteredTaskSource&& other) =
-    default;
+RegisteredTaskSource::RegisteredTaskSource(
+    RegisteredTaskSource&& other) noexcept = default;
 
 RegisteredTaskSource::~RegisteredTaskSource() {
   Unregister();
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
index 2225bfc..426860c 100644
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -177,6 +177,7 @@
   X(TRACE_DISABLED_BY_DEFAULT("gpu.service"))                            \
   X(TRACE_DISABLED_BY_DEFAULT("ipc.flow"))                               \
   X(TRACE_DISABLED_BY_DEFAULT("layer-element"))                          \
+  X(TRACE_DISABLED_BY_DEFAULT("lifecycles"))                             \
   X(TRACE_DISABLED_BY_DEFAULT("loading"))                                \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra"))                           \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra.v8.code_stats"))             \
diff --git a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
index efefcbc8..56b84845 100644
--- a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
+++ b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
@@ -113,15 +113,20 @@
             writer = new ClassWriter(reader, 0);
         }
         ClassVisitor chain = writer;
-        /* DEBUGGING:
-         To see the bytecode for a specific class:
-           if (entry.getName().contains("YourClassName")) {
-             chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
-           }
+        /* DEBUGGING:}
          To see objectweb.asm code that will generate bytecode for a given class:
-           java -cp "third_party/ow2_asm/lib/asm-5.0.1.jar:third_party/ow2_asm/lib/"\
-               "asm-util-5.0.1.jar:out/Debug/lib.java/jar_containing_yourclass.jar" \
-               org.objectweb.asm.util.ASMifier org.package.YourClassName
+
+         java -cp
+         "third_party/ow2_asm/lib/asm.jar:third_party/ow2_asm/lib/asm-util.jar:out/Debug/lib.java/jar_containing_yourclass.jar"
+         org.objectweb.asm.util.ASMifier org.package.YourClassName
+
+         See this pdf for more details: https://asm.ow2.io/asm4-guide.pdf
+
+         To see the bytecode for a specific class, uncomment this code with your class name:
+
+        if (entry.getName().contains("YOUR_CLASS_NAME")) {
+          chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
+        }
         */
         if (sShouldUseThreadAnnotations) {
             chain = new ThreadAssertionClassAdapter(chain);
diff --git a/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java b/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
index 8d6ae69..d8aa284 100644
--- a/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
+++ b/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
@@ -6,6 +6,7 @@
 
 import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
 import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.RETURN;
@@ -34,6 +35,9 @@
 
     private static final String MODULE_INSTALLER_CLASS_NAME =
             "org/chromium/components/module_installer/ModuleInstaller";
+    private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
+    private static final String GET_INSTANCE_DESCRIPTOR =
+            TypeUtils.getMethodDescriptor(MODULE_INSTALLER_CLASS_NAME);
     private static final String INIT_ACTIVITY_METHOD_NAME = "initActivity";
     private static final String INIT_ACTIVITY_DESCRIPTOR =
             TypeUtils.getMethodDescriptor(VOID, CONTEXT);
@@ -107,7 +111,7 @@
      * <pre>
      * protected void attachBaseContext(Context base) {
      *     super.attachBaseContext(base);
-     *     ModuleInstaller.initActivity(this);
+     *     ModuleInstaller.getInstance().initActivity(this);
      * }
      * </pre>
      */
@@ -115,16 +119,30 @@
         MethodVisitor mv = super.visitMethod(ACC_PROTECTED, ATTACH_BASE_CONTEXT_METHOD_NAME,
                 ATTACH_BASE_CONTEXT_DESCRIPTOR, null, null);
         mv.visitCode();
-        mv.visitVarInsn(ALOAD, 0); // load "this" on stack
-        mv.visitVarInsn(ALOAD, 1); // load first method parameter on stack (Context)
+        // Push "this" on stack.
+        mv.visitVarInsn(ALOAD, 0);
+        // Push first method parameter on stack (Context).
+        mv.visitVarInsn(ALOAD, 1);
+        // Pop argument from stack (Context).
+        // Pop target object from stack ("this").
+        // Calls attachBaseContext.
         mv.visitMethodInsn(INVOKESPECIAL, ANDROID_APP_ACTIVITY_CLASS_NAME,
-                ATTACH_BASE_CONTEXT_METHOD_NAME,
-                ATTACH_BASE_CONTEXT_DESCRIPTOR); // invoke super's attach base context
-        mv.visitVarInsn(ALOAD, 0); // load "this" on stack
-        mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
-                INIT_ACTIVITY_DESCRIPTOR);
+                ATTACH_BASE_CONTEXT_METHOD_NAME, ATTACH_BASE_CONTEXT_DESCRIPTOR, false);
+        // Push return value on stack (ModuleInstaller).
+        // Calls getInstance.
+        mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, GET_INSTANCE_METHOD_NAME,
+                GET_INSTANCE_DESCRIPTOR, false);
+        // Push "this" on stack.
+        mv.visitVarInsn(ALOAD, 0);
+        // Pop argument from stack ("this").
+        // Pop target object from stack (ModuleInstaller).
+        // Calls initActivity.
+        mv.visitMethodInsn(INVOKEINTERFACE, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
+                INIT_ACTIVITY_DESCRIPTOR, true);
         mv.visitInsn(RETURN);
-        mv.visitMaxs(2, 2); // max stack size - 2, max locals - 2
+        // Max stack size = 2 (Only push at most 2 before popping).
+        // Max locals = 2 ("this" and 1 parameter).
+        mv.visitMaxs(2, 2);
         mv.visitEnd();
     }
 
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index 57efc8b..199d8bcb 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -139,6 +139,13 @@
       help='Use resource IDs generated by aapt --emit-ids.')
 
   input_opts.add_argument(
+      '--extra-main-r-text-files',
+      help='Additional R.txt files that will be added to the root R.java file, '
+      'but not packaged in the generated resources.arsc. If these resources '
+      'entries contain duplicate resources with the generated R.txt file, they '
+      'must be identical.')
+
+  input_opts.add_argument(
       '--support-zh-hk',
       action='store_true',
       help='Use zh-rTW resources for zh-rHK.')
@@ -157,7 +164,8 @@
       required=True,
       help="android:targetSdkVersion for APK.")
   input_opts.add_argument(
-      '--max-sdk-version', help="android:maxSdkVersion for APK.")
+      '--max-sdk-version',
+      help="android:maxSdkVersion expected in AndroidManifest.xml.")
   input_opts.add_argument(
       '--manifest-package', help='Package name of the AndroidManifest.xml.')
 
@@ -244,6 +252,8 @@
       options.shared_resources_whitelist_locales)
   options.resource_blacklist_exceptions = build_utils.ParseGnList(
       options.resource_blacklist_exceptions)
+  options.extra_main_r_text_files = build_utils.ParseGnList(
+      options.extra_main_r_text_files)
 
   if options.optimized_proto_path and not options.proto_path:
     # We could write to a temp file, but it's simpler to require it.
@@ -467,8 +477,13 @@
       options.android_manifest)
 
   manifest_utils.AssertUsesSdk(manifest_node, options.min_sdk_version,
-                               options.target_sdk_version,
-                               options.max_sdk_version)
+                               options.target_sdk_version)
+  # We explicitly check that maxSdkVersion is set in the manifest since we don't
+  # add it later like minSdkVersion and targetSdkVersion.
+  manifest_utils.AssertUsesSdk(
+      manifest_node,
+      max_sdk_version=options.max_sdk_version,
+      fail_if_not_exist=True)
   manifest_utils.AssertPackage(manifest_node, options.manifest_package)
 
   manifest_node.set('platformBuildVersionCode', version_code)
@@ -966,7 +981,8 @@
     resource_utils.CreateRJavaFiles(
         build.srcjar_dir, None, build.r_txt_path, options.extra_res_packages,
         options.extra_r_text_files, rjava_build_options, options.srcjar_out,
-        custom_root_package_name, grandparent_custom_package_name)
+        custom_root_package_name, grandparent_custom_package_name,
+        options.extra_main_r_text_files)
 
     build_utils.ZipDir(build.srcjar_path, build.srcjar_dir)
 
diff --git a/build/android/gyp/merge_manifest.py b/build/android/gyp/merge_manifest.py
index a7b2eff1..5680ad9 100755
--- a/build/android/gyp/merge_manifest.py
+++ b/build/android/gyp/merge_manifest.py
@@ -30,14 +30,15 @@
 
 @contextlib.contextmanager
 def _ProcessManifest(manifest_path, min_sdk_version, target_sdk_version,
-                     manifest_package):
+                     max_sdk_version, manifest_package):
   """Patches an Android manifest to always include the 'tools' namespace
   declaration, as it is not propagated by the manifest merger from the SDK.
 
   See https://issuetracker.google.com/issues/63411481
   """
   doc, manifest, _ = manifest_utils.ParseManifest(manifest_path)
-  manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version)
+  manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version,
+                               max_sdk_version)
   assert manifest_utils.GetPackage(manifest) or manifest_package, \
             'Must set manifest package in GN or in AndroidManifest.xml'
   manifest_utils.AssertPackage(manifest, manifest_package)
@@ -80,6 +81,8 @@
       required=True,
       help='android:targetSdkVersion for merging.')
   parser.add_argument(
+      '--max-sdk-version', help='android:maxSdkVersion for merging.')
+  parser.add_argument(
       '--manifest-package',
       help='Package name of the merged AndroidManifest.xml.')
   args = parser.parse_args(argv)
@@ -95,14 +98,24 @@
         _MANIFEST_MERGER_MAIN_CLASS,
         '--out',
         output.name,
+        '--property',
+        'MIN_SDK_VERSION=' + args.min_sdk_version,
+        '--property',
+        'TARGET_SDK_VERSION=' + args.target_sdk_version,
     ]
 
+    if args.max_sdk_version:
+      cmd += [
+          '--property',
+          'MAX_SDK_VERSION=' + args.max_sdk_version,
+      ]
+
     extras = build_utils.ParseGnList(args.extras)
     if extras:
       cmd += ['--libs', ':'.join(extras)]
 
     with _ProcessManifest(args.root_manifest, args.min_sdk_version,
-                          args.target_sdk_version,
+                          args.target_sdk_version, args.max_sdk_version,
                           args.manifest_package) as tup:
       root_manifest, package = tup
       cmd += [
@@ -110,10 +123,6 @@
           root_manifest,
           '--property',
           'PACKAGE=' + package,
-          '--property',
-          'MIN_SDK_VERSION=' + args.min_sdk_version,
-          '--property',
-          'TARGET_SDK_VERSION=' + args.target_sdk_version,
       ]
       build_utils.CheckOutput(cmd,
         # https://issuetracker.google.com/issues/63514300:
diff --git a/build/android/gyp/util/manifest_utils.py b/build/android/gyp/util/manifest_utils.py
index e036014..309230f 100644
--- a/build/android/gyp/util/manifest_utils.py
+++ b/build/android/gyp/util/manifest_utils.py
@@ -66,13 +66,15 @@
 
 
 def AssertUsesSdk(manifest_node,
-                  min_sdk_version,
+                  min_sdk_version=None,
                   target_sdk_version=None,
-                  max_sdk_version=None):
+                  max_sdk_version=None,
+                  fail_if_not_exist=False):
   """Asserts values of attributes of <uses-sdk> element.
 
-  Will only assert if both the passed value is not None and the value of
-  attribute exist.
+  Unless |fail_if_not_exist| is true, will only assert if both the passed value
+  is not None and the value of attribute exist. If |fail_if_not_exist| is true
+  will fail if passed value is not None but attribute does not exist.
   """
   uses_sdk_node = manifest_node.find('./uses-sdk')
   if uses_sdk_node is None:
@@ -81,6 +83,10 @@
                                                          target_sdk_version),
                               ('max', max_sdk_version)):
     value = uses_sdk_node.get('{%s}%sSdkVersion' % (ANDROID_NAMESPACE, prefix))
+    if fail_if_not_exist and not value and sdk_version:
+      assert False, (
+          '%sSdkVersion in Android manifest does not exist but we expect %s' %
+          (prefix, sdk_version))
     if not value or not sdk_version:
       continue
     assert value == sdk_version, (
diff --git a/build/android/gyp/util/resource_utils.py b/build/android/gyp/util/resource_utils.py
index 633d600d..65061f5 100644
--- a/build/android/gyp/util/resource_utils.py
+++ b/build/android/gyp/util/resource_utils.py
@@ -335,7 +335,8 @@
                      rjava_build_options,
                      srcjar_out,
                      custom_root_package_name=None,
-                     grandparent_custom_package_name=None):
+                     grandparent_custom_package_name=None,
+                     extra_main_r_text_files=None):
   """Create all R.java files for a set of packages and R.txt files.
 
   Args:
@@ -358,6 +359,7 @@
       as the grandparent_custom_package_name. The format of this package name
       is identical to custom_root_package_name.
       (eg. for vr grandparent_custom_package_name would be "base")
+    extra_main_r_text_files: R.txt files to be added to the root R.java file.
   Raises:
     Exception if a package name appears several times in |extra_res_packages|
   """
@@ -378,12 +380,23 @@
   all_resources = {}
   all_resources_by_type = collections.defaultdict(list)
 
-  for entry in _ParseTextSymbolsFile(main_r_txt_file, fix_package_ids=True):
-    all_resources[(entry.resource_type, entry.name)] = entry
-    all_resources_by_type[entry.resource_type].append(entry)
-    assert entry.resource_type in _ALL_RESOURCE_TYPES, (
-        'Unknown resource type: %s, add to _ALL_RESOURCE_TYPES!' %
-        entry.resource_type)
+  main_r_text_files = [main_r_txt_file]
+  if extra_main_r_text_files:
+    main_r_text_files.extend(extra_main_r_text_files)
+  for r_txt_file in main_r_text_files:
+    for entry in _ParseTextSymbolsFile(r_txt_file, fix_package_ids=True):
+      entry_key = (entry.resource_type, entry.name)
+      if entry_key in all_resources:
+        assert entry == all_resources[entry_key], (
+            'Input R.txt %s provided a duplicate resource with a different '
+            'entry value. Got %s, expected %s.' % (r_txt_file, entry,
+                                                   all_resources[entry_key]))
+      else:
+        all_resources[entry_key] = entry
+        all_resources_by_type[entry.resource_type].append(entry)
+        assert entry.resource_type in _ALL_RESOURCE_TYPES, (
+            'Unknown resource type: %s, add to _ALL_RESOURCE_TYPES!' %
+            entry.resource_type)
 
   if custom_root_package_name:
     # Custom package name is available, thus use it for root_r_java_package.
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index b4d8c71..40de1fe 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -120,22 +120,17 @@
 [`android_app_bundle_module`](#target_android_app_bundle_module)
 type. Whether or not this module is the base module for some bundle.
 
-# Top-level `resources` dictionary:
-
-This dictionary only appears for a few target types that can contain or
-relate to Android resources (e.g. `android_resources` or `android_apk`):
-
-* `resources['dependency_zips']`:
+* `deps_info['dependency_zips']`:
 List of `deps_info['resources_zip']` entries for all `android_resources`
 dependencies for the current target.
 
-* `resource['extra_package_names']`:
+* `deps_info['extra_package_names']`:
 Always empty for `android_resources` types. Otherwise,
 the list of `deps_info['package_name']` entries for all `android_resources`
 dependencies for the current target. Computed automatically by
 `write_build_config.py`.
 
-* `resources['extra_r_text_files']`:
+* `deps_info['extra_r_text_files']`:
 Always empty for `android_resources` types. Otherwise, the list of
 `deps_info['r_text']` entries for all `android_resources` dependencies for
 the current target. Computed automatically.
@@ -840,7 +835,6 @@
   # android_resources options
   parser.add_option('--srcjar', help='Path to target\'s resources srcjar.')
   parser.add_option('--resources-zip', help='Path to target\'s resources zip.')
-  parser.add_option('--r-text', help='Path to target\'s R.txt file.')
   parser.add_option('--package-name',
       help='Java package name for these resources.')
   parser.add_option('--android-manifest', help='Path to android manifest.')
@@ -929,6 +923,19 @@
   parser.add_option('--incremental-install-json-path',
                     help="Path to the target's generated incremental install "
                     "json.")
+  parser.add_option(
+      '--tested-apk-config',
+      help='Path to the build config of the tested apk (for an instrumentation '
+      'test apk).')
+  parser.add_option(
+      '--proguard-enabled',
+      action='store_true',
+      help='Whether proguard is enabled for this apk or bundle module.')
+  parser.add_option(
+      '--proguard-configs',
+      help='GN-list of proguard flag files to use in final apk.')
+  parser.add_option(
+      '--proguard-mapping-path', help='Path to jar created by ProGuard step')
 
   # apk options that are static library specific
   parser.add_option(
@@ -936,22 +943,13 @@
       help='GN list of .build_configs of targets that use this target as a '
       'static library.')
   parser.add_option(
-      '--static-library-jar-path',
-      help='Equivalent to what normally would be the jar_path for the APK.')
-  parser.add_option(
       '--resource-ids-provider',
       help='Path to the .build_config for the APK that this static library '
       'target uses to generate stable resource IDs.')
 
-  parser.add_option('--tested-apk-config',
-      help='Path to the build config of the tested apk (for an instrumentation '
-      'test apk).')
-  parser.add_option('--proguard-enabled', action='store_true',
-      help='Whether proguard is enabled for this apk or bundle module.')
-  parser.add_option('--proguard-configs',
-      help='GN-list of proguard flag files to use in final apk.')
-  parser.add_option('--proguard-mapping-path',
-      help='Path to jar created by ProGuard step')
+  # options shared between android_resources and apk targets
+  parser.add_option('--r-text-path', help='Path to target\'s R.txt file.')
+
   parser.add_option('--fail',
       help='GN-list of error message lines to fail with.')
 
@@ -966,9 +964,6 @@
       '--module-pathmap-path',
       help='Path to pathmap file for resource paths in a bundle module.')
   parser.add_option(
-      '--module-rtxt-path',
-      help='Path to R.txt file for resources in a bundle module.')
-  parser.add_option(
       '--base-whitelist-rtxt-path',
       help='Path to R.txt file for the base resources whitelist.')
   parser.add_option(
@@ -1033,9 +1028,6 @@
     if options.apk_proto_resources:
       raise Exception('--apk-proto-resources can only be used with '
                       '--type=android_app_bundle_module')
-    if options.module_rtxt_path:
-      raise Exception('--module-rxt-path can only be used with '
-                      '--type=android_app_bundle_module')
     if options.module_pathmap_path:
       raise Exception('--module-pathmap-path can only be used with '
                       '--type=android_app_bundle_module')
@@ -1077,9 +1069,6 @@
     if options.type != 'android_apk':
       raise Exception(
           '--static-library-dependent-configs only supports --type=android_apk')
-    if not options.static_library_jar_path:
-      raise Exception('Can\'t have --static-library-dependent-configs without '
-                      '--static-library-jar-path')
   options.static_library_dependent_configs = build_utils.ParseGnList(
       options.static_library_dependent_configs)
   static_library_dependent_configs_by_path = {
@@ -1163,6 +1152,9 @@
       else:
         gradle['dependent_java_projects'].append(c['path'])
 
+  if options.r_text_path:
+    deps_info['r_text_path'] = options.r_text_path
+
   # TODO(tiborg): Remove creation of JNI info for type group and java_library
   # once we can generate the JNI registration based on APK / module targets as
   # opposed to groups and libraries.
@@ -1177,9 +1169,6 @@
     if options.apk_proto_resources:
       deps_info['proto_resources_path'] = options.apk_proto_resources
 
-    if options.module_rtxt_path:
-      deps_info['module_rtxt_path'] = options.module_rtxt_path
-
     if options.module_pathmap_path:
       deps_info['module_pathmap_path'] = options.module_pathmap_path
     else:
@@ -1256,8 +1245,6 @@
       deps_info['package_name'] = manifest.GetPackageName()
     if options.package_name:
       deps_info['package_name'] = options.package_name
-    if options.r_text:
-      deps_info['r_text'] = options.r_text
 
     deps_info['resources_dirs'] = []
     if options.resource_dirs:
@@ -1304,7 +1291,6 @@
   if options.type in (
       'android_resources', 'android_apk', 'junit_binary', 'resource_rewriter',
       'dist_aar', 'android_app_bundle_module'):
-    config['resources'] = {}
 
     dependency_zips = [
         c['resources_zip'] for c in all_resources_deps if c['resources_zip']
@@ -1316,32 +1302,33 @@
       extra_package_names = [
           c['package_name'] for c in all_resources_deps if 'package_name' in c]
       extra_r_text_files = [
-          c['r_text'] for c in all_resources_deps if 'r_text' in c]
+          c['r_text_path'] for c in all_resources_deps if 'r_text_path' in c
+      ]
 
     # For feature modules, remove any resources that already exist in the base
     # module.
     if base_module_build_config:
       dependency_zips = [
           c for c in dependency_zips
-          if c not in base_module_build_config['resources']['dependency_zips']
+          if c not in base_module_build_config['deps_info']['dependency_zips']
       ]
       extra_package_names = [
           c for c in extra_package_names if c not in
-          base_module_build_config['resources']['extra_package_names']
+          base_module_build_config['deps_info']['extra_package_names']
       ]
       extra_r_text_files = [
           c for c in extra_r_text_files if c not in
-          base_module_build_config['resources']['extra_r_text_files']
+          base_module_build_config['deps_info']['extra_r_text_files']
       ]
 
-    config['resources']['dependency_zips'] = dependency_zips
-    config['resources']['extra_package_names'] = extra_package_names
-    config['resources']['extra_r_text_files'] = extra_r_text_files
+    config['deps_info']['dependency_zips'] = dependency_zips
+    config['deps_info']['extra_package_names'] = extra_package_names
+    config['deps_info']['extra_r_text_files'] = extra_r_text_files
     if options.type == 'android_apk' and options.tested_apk_config:
-      config['resources']['arsc_package_name'] = (
+      config['deps_info']['arsc_package_name'] = (
           tested_apk_config['package_name'])
     if options.res_size_info:
-      config['resources']['size_info'] = options.res_size_info
+      config['deps_info']['res_size_info'] = options.res_size_info
 
   if is_apk_or_module_target:
     deps_dex_files = [c['dex_path'] for c in all_library_deps]
@@ -1446,38 +1433,43 @@
     assert len(base_module_configs) == 1, 'Must have exactly 1 base module!'
     deps_info['base_module_config'] = base_module_configs[0]['path']
 
+  # Map configs to classpath entries that should be included in their final dex.
+  classpath_entries_by_owning_config = collections.defaultdict(list)
+  extra_main_r_text_files = []
   if is_static_library_dex_provider_target:
     # Map classpath entries to configs that include them in their classpath.
     configs_by_classpath_entry = collections.defaultdict(list)
     static_lib_jar_paths = {}
-    for config_path, dep_config in (
-        static_library_dependent_configs_by_path.iteritems()):
+    for config_path, dep_config in (sorted(
+        static_library_dependent_configs_by_path.iteritems())):
       # For bundles, only the jar path and jni sources of the base module
       # are relevant for proguard. Should be updated when bundle feature
       # modules support JNI.
       base_config = dep_config
       if dep_config['type'] == 'android_app_bundle':
         base_config = GetDepConfig(dep_config['base_module_config'])
+      extra_main_r_text_files.append(base_config['r_text_path'])
       static_lib_jar_paths[config_path] = base_config['jar_path']
       all_configs.extend(dep_config['proguard_all_configs'])
       extra_proguard_classpath_jars.extend(
           dep_config['proguard_classpath_jars'])
       all_java_sources.extend(base_config['jni']['all_source'])
+
+      # The srcjars containing the generated R.java files are excluded for APK
+      # targets the use static libraries, so we add them here to ensure the
+      # union of resource IDs are available in the static library APK.
+      for r_text in base_config['extra_r_text_files']:
+        if r_text not in extra_r_text_files:
+          extra_r_text_files.append(r_text)
+      for package in base_config['extra_package_names']:
+        if package not in extra_package_names:
+          extra_package_names.append(package)
       for cp_entry in dep_config['java_runtime_classpath']:
-        # The APK Java targets for the static library dependent targets will
-        # have some of the same classes (R.java) due to shared resource
-        # dependencies. To avoid Proguard failures due to duplicate classes, we
-        # merge the APK jars into the static library's jar_path as a
-        # preprocessing build step.
-        if cp_entry != base_config['jar_path']:
-          configs_by_classpath_entry[cp_entry].append(config_path)
+        configs_by_classpath_entry[cp_entry].append(config_path)
 
     for cp_entry in java_full_classpath:
       configs_by_classpath_entry[cp_entry].append(options.build_config)
 
-    # Map configs to classpath entries that should be included in their final
-    # dex.
-    classpath_entries_by_owning_config = collections.defaultdict(list)
     for cp_entry, candidate_configs in configs_by_classpath_entry.iteritems():
       config_path = (candidate_configs[0]
                      if len(candidate_configs) == 1 else options.build_config)
@@ -1485,32 +1477,16 @@
       java_full_classpath.append(cp_entry)
 
     java_full_classpath = sorted(set(java_full_classpath))
-    deps_info['static_library_dependent_classpath_configs'] = {
-        path: sorted(set(classpath))
-        for path, classpath in classpath_entries_by_owning_config.iteritems()
-    }
 
-    # resource_ids_provider's jar must go first to ensure the correct R.java
-    # IDs are used.
-    ordered_static_lib_jar_paths = []
-    if options.resource_ids_provider:
-      assert (options.resource_ids_provider in options.
-              static_library_dependent_configs), (
-                  '--resource-ids-provider must be in '
-                  '--static-library-dependent-configs')
-      ordered_static_lib_jar_paths.append(
-          static_lib_jar_paths[options.resource_ids_provider])
-
-    ordered_static_lib_jar_paths.extend(
-        x for x in static_lib_jar_paths.itervalues()
-        if x not in ordered_static_lib_jar_paths)
-    ordered_static_lib_jar_paths.append(options.static_library_jar_path)
-    deps_info[
-        'static_library_dependent_apk_jars'] = ordered_static_lib_jar_paths
-    deps_info['static_library_proguard_mapping_output_paths'] = [
-        d['proguard_mapping_path']
-        for d in static_library_dependent_configs_by_path.itervalues()
-    ]
+  deps_info['static_library_proguard_mapping_output_paths'] = sorted([
+      d['proguard_mapping_path']
+      for d in static_library_dependent_configs_by_path.itervalues()
+  ])
+  deps_info['static_library_dependent_classpath_configs'] = {
+      path: sorted(set(classpath))
+      for path, classpath in classpath_entries_by_owning_config.iteritems()
+  }
+  deps_info['extra_main_r_text_files'] = sorted(extra_main_r_text_files)
 
   if is_apk_or_module_target or options.type in ('group', 'java_library',
                                                  'junit_binary'):
diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
index f94f1a8..47933c2 100644
--- a/build/android/pylib/linker/test_case.py
+++ b/build/android/pylib/linker/test_case.py
@@ -27,11 +27,7 @@
        Host-driven tests have also been tried, but since they're really
        sub-classes of instrumentation tests, they didn't work well either.
 
-   To build and run the linker tests, do the following:
-
-     ninja -C out/Debug chromium_linker_test_apk
-     out/Debug/bin/run_chromium_linker_test_apk
-
+   To build and run, refer to android_linker_testing.md.
 """
 # pylint: disable=R0201
 
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index a9273aa..abf6922 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -309,7 +309,7 @@
     }
     if (defined(invoker.r_text)) {
       args += [
-        "--r-text",
+        "--r-text-path",
         rebase_path(invoker.r_text, root_build_dir),
       ]
     }
@@ -328,9 +328,9 @@
           rebase_path(invoker.proto_resources_path, root_build_dir)
       args += [ "--apk-proto-resources=$_rebased_proto_resources" ]
     }
-    if (defined(invoker.module_rtxt_path)) {
-      _rebased_rtxt_path = rebase_path(invoker.module_rtxt_path, root_build_dir)
-      args += [ "--module-rtxt-path=$_rebased_rtxt_path" ]
+    if (defined(invoker.r_text_path)) {
+      _rebased_rtxt_path = rebase_path(invoker.r_text_path, root_build_dir)
+      args += [ "--r-text-path=$_rebased_rtxt_path" ]
     }
     if (defined(invoker.module_pathmap_path)) {
       _rebased_pathmap_path =
@@ -456,11 +456,6 @@
       args += [ "--proguard-configs=$_rebased_proguard_configs" ]
     }
     if (defined(invoker.static_library_dependent_targets)) {
-      assert(defined(invoker.static_library_jar_path))
-      args += [
-        "--static-library-jar-path",
-        rebase_path(invoker.static_library_jar_path, root_build_dir),
-      ]
       _dependent_configs = []
       foreach(_target, invoker.static_library_dependent_targets) {
         _target_name = _target.name
@@ -1754,6 +1749,10 @@
       if (defined(invoker.manifest_package)) {
         args += [ "--manifest-package=${invoker.manifest_package}" ]
       }
+
+      if (defined(invoker.max_sdk_version)) {
+        args += [ "--max-sdk-version=${invoker.max_sdk_version}" ]
+      }
     }
   }
 
@@ -1860,9 +1859,9 @@
         "--include-resources=@FileArg($_rebased_build_config:android:sdk_jars)",
         "--aapt-path",
         rebase_path(_android_aapt_path, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--extra-res-packages=@FileArg($_rebased_build_config:resources:extra_package_names)",
-        "--extra-r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--extra-res-packages=@FileArg($_rebased_build_config:deps_info:extra_package_names)",
+        "--extra-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
       ]
 
       if (defined(invoker.android_manifest)) {
@@ -2108,9 +2107,10 @@
         "--include-resources=@FileArg($_rebased_build_config:android:sdk_jars)",
         "--aapt2-path",
         rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--extra-res-packages=@FileArg($_rebased_build_config:resources:extra_package_names)",
-        "--extra-r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--extra-res-packages=@FileArg($_rebased_build_config:deps_info:extra_package_names)",
+        "--extra-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
+        "--extra-main-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_main_r_text_files)",
         "--min-sdk-version=${invoker.min_sdk_version}",
         "--target-sdk-version=${invoker.target_sdk_version}",
       ]
@@ -2337,6 +2337,7 @@
         args += [ "--use-resource-ids-path=$_rebased_ids_path" ]
         deps += [ _compile_res_dep ]
       }
+
       if (defined(invoker.max_sdk_version)) {
         _max_sdk_version = invoker.max_sdk_version
         args += [ "--max-sdk-version=$_max_sdk_version" ]
@@ -2389,7 +2390,7 @@
           args += [
             "--r-text-whitelist",
             rebase_path(invoker.shared_resources_whitelist, root_build_dir),
-            "--r-text",
+            "--r-text-path",
             rebase_path(invoker.r_text_out_path, root_build_dir),
           ]
         }
@@ -2454,7 +2455,7 @@
           args += [
             "--jar-files=@FileArg($_rebased_build_config:deps_info:unprocessed_jar_path)",
             "--jar-files=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)",
-            "--in-res-info-path=@FileArg($_rebased_build_config:resources:size_info)",
+            "--in-res-info-path=@FileArg($_rebased_build_config:deps_info:res_size_info)",
             "--assets=@FileArg($_rebased_build_config:assets)",
             "--uncompressed-assets=@FileArg($_rebased_build_config:uncompressed_assets)",
           ]
@@ -3038,8 +3039,6 @@
   #    java_files is empty. If not
   #  jar_path: Optional path to a prebuilt .jar file for this target.
   #    Mutually exclusive with java_files and srcjar_deps.
-  #  intermediate_jar_path: Optional path to the output .jar file. If used,
-  #    final_jar_path must be created by another target.
   #  final_jar_path: Optional path to the final output .jar file (after
   #    processing). If not provided, the output will go under
   #    $root_build_dir/lib.java/
@@ -3125,8 +3124,8 @@
   #  proto_resources_path: The path of an zip archive containing the APK's
   #    resources compiled to the protocol buffer format (instead of regular
   #    binary xml + resources.arsc).
-  #  module_rtxt_path: The path of the R.txt file generated when compiling the
-  #    resources for the bundle module.
+  #  r_text_path: The path of the R.txt file generated when compiling the
+  #    resources for this target.
   #  module_pathmap_path: The path of the pathmap file generated when compiling
   #    the resources for the bundle module, if path shortening is enabled.
   #  base_whitelist_rtxt_path: The path of the R.txt file containing the
@@ -3213,9 +3212,6 @@
       # for the ijar as well, but this is only used for APK targets where
       # the ijar path isn't actually used.
       _build_config_jar_path = _final_jar_path
-      if (defined(invoker.intermediate_jar_path)) {
-        _final_jar_path = invoker.intermediate_jar_path
-      }
       _final_ijar_path =
           get_path_info(_final_jar_path, "dir") + "/" +
           get_path_info(_final_jar_path, "name") + ".interface.jar"
@@ -3334,6 +3330,7 @@
                                "proguard_configs",
                                "proguard_enabled",
                                "proguard_mapping_path",
+                               "r_text_path",
                                "secondary_abi_loadable_modules",
                                "type",
                              ])
@@ -3370,7 +3367,6 @@
                                  "base_module_target",
                                  "is_base_module",
                                  "module_pathmap_path",
-                                 "module_rtxt_path",
                                  "proto_resources_path",
                                ])
       }
@@ -3385,9 +3381,6 @@
       bypass_platform_checks = defined(invoker.bypass_platform_checks) &&
                                invoker.bypass_platform_checks
 
-      if (defined(invoker.intermediate_jar_path)) {
-        static_library_jar_path = invoker.intermediate_jar_path
-      }
       if (defined(_final_jar_path)) {
         jar_path = _build_config_jar_path
         ijar_path = _final_ijar_path
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index bcf645ca..4481c20 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1757,8 +1757,8 @@
         rebase_path(depfile, root_build_dir),
         "--output",
         rebase_path(invoker.output, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
         "--proguard-configs=@FileArg($_rebased_build_config:deps_info:proguard_all_configs)",
       ]
       if (_direct_deps_only) {
@@ -2308,7 +2308,6 @@
     _is_static_library_provider =
         defined(invoker.static_library_dependent_targets) && _proguard_enabled
     if (_is_static_library_provider) {
-      _static_library_apk_java_target_output = _jar_path
       _static_library_sync_dex_path =
           "$_gen_dir/static_library_synchronized_proguard.classes.dex.zip"
       _resource_ids_provider_deps = []
@@ -2344,7 +2343,11 @@
         "$target_gen_dir/${_template_name}_manifest/AndroidManifest.xml"
     _merge_manifest_target = "${_template_name}__merge_manifests"
     merge_manifests(_merge_manifest_target) {
-      forward_variables_from(invoker, [ "manifest_package" ])
+      forward_variables_from(invoker,
+                             [
+                               "manifest_package",
+                               "max_sdk_version",
+                             ])
       input_manifest = _android_root_manifest
       output_manifest = _android_manifest
       build_config = _build_config
@@ -2457,13 +2460,21 @@
                _android_sdk_dep,
              ]
 
+      # The static library uses the R.txt files generated by the
+      # static_library_dependent_targets when generating the final R.java file.
+      if (_is_static_library_provider) {
+        foreach(_dep, invoker.static_library_dependent_targets) {
+          deps += [ "${_dep.name}__compile_resources" ]
+        }
+      }
+
       if (defined(invoker.apk_under_test)) {
         # Set the arsc package name to match the apk_under_test package name
         # So that test resources can references under_test resources via
         # @type/name syntax.
         r_java_root_package_name = "test"
         arsc_package_name =
-            "@FileArg($_rebased_build_config:resources:arsc_package_name)"
+            "@FileArg($_rebased_build_config:deps_info:arsc_package_name)"
 
         # Passing in the --emit-ids mapping will cause aapt2 to assign resources
         # IDs that do not conflict with those from apk_under_test.
@@ -2572,7 +2583,20 @@
       }
     }
 
-    _srcjar_deps += [ ":$_compile_resources_target" ]
+    # The static library will provide all R.java files, but we still need to
+    # have a dep on the compile_resources() target from the java_library_impl()
+    # target below. Using a group to rename the target avoids the naming
+    # restrictions described in crbug.com/908819.
+    if (_uses_static_library) {
+      group("${_compile_resources_target}__compile_only_dep") {
+        public_deps = [
+          ":$_compile_resources_target",
+        ]
+      }
+      _deps += [ ":${_compile_resources_target}__compile_only_dep" ]
+    } else {
+      _srcjar_deps += [ ":$_compile_resources_target" ]
+    }
 
     if (_native_libs_deps != [] || _secondary_abi_native_libs_deps != []) {
       _enable_chromium_linker_tests = false
@@ -2769,24 +2793,19 @@
       } else {
         type = "android_apk"
       }
+      r_text_path = _compile_resources_rtxt_out
       main_target_name = _template_name
       supports_android = true
       requires_android = true
       min_sdk_version = _min_sdk_version
       deps = _deps
-
       srcjar_deps = _srcjar_deps
       final_jar_path = _jar_path
-      if (_is_static_library_provider) {
-        intermediate_jar_path = "$_base_path.intermediate.jar"
-        final_jar_path = _static_library_apk_java_target_output
-      }
       dex_path = _lib_dex_path
       final_dex_path = _final_dex_path
 
       if (_is_bundle_module) {
         proto_resources_path = _proto_resources_path
-        module_rtxt_path = _compile_resources_rtxt_out
         if (_optimize_resources) {
           proto_resources_path = _optimized_proto_resources_path
           if (_short_resource_paths) {
@@ -2855,38 +2874,6 @@
       }
     }
 
-    if (_is_static_library_provider) {
-      _static_library_apk_java_target = "${_template_name}__combine_apk_jars"
-
-      # Since some of the static_libary_dependent_targets may have overlapping
-      # resource dependencies, we can't include all jars created by the
-      # static_library_dependent_targets or else proguard will fail with
-      # duplicate class definitions. This step combines these jars into
-      # a single jar with duplicates ignored.
-      action_with_pydeps(_static_library_apk_java_target) {
-        script = "//build/android/gyp/zip.py"
-        deps = [
-          ":$_build_config_target",
-          ":$_java_target",
-        ]
-        foreach(_dep, invoker.static_library_dependent_targets) {
-          _target_label = get_label_info(_dep.name, "label_no_toolchain")
-          deps += [ "${_target_label}__java" ]
-        }
-        inputs = [
-          _build_config,
-        ]
-        outputs = [
-          _static_library_apk_java_target_output,
-        ]
-        args = [
-          "--input-zips=@FileArg($_rebased_build_config:deps_info:static_library_dependent_apk_jars)",
-          "--output",
-          rebase_path(_static_library_apk_java_target_output, root_build_dir),
-        ]
-      }
-    }
-
     if (_proguard_enabled && _uses_static_library) {
       _final_dex_target_dep = "${invoker.static_library_provider}__dexsplitter"
     } else if (!(_is_bundle_module && _proguard_enabled)) {
@@ -2909,9 +2896,6 @@
           ":$_build_config_target",
           ":$_java_target",
         ]
-        if (_is_static_library_provider) {
-          deps += [ ":$_static_library_apk_java_target" ]
-        }
         if (_proguard_enabled) {
           forward_variables_from(invoker, [ "proguard_jar_path" ])
           deps += _deps + [ ":$_compile_resources_target" ]
@@ -2927,6 +2911,13 @@
         }
 
         if (_is_static_library_provider) {
+          # The list of input jars is already recorded in the .build_config, but
+          # we need to explicitly add the java deps here to ensure they're
+          # available to be used as inputs to the dex step.
+          foreach(_dep, invoker.static_library_dependent_targets) {
+            _target_label = get_label_info(_dep.name, "label_no_toolchain")
+            deps += [ "${_target_label}__java" ]
+          }
           output = _static_library_sync_dex_path
           is_static_library = true
         } else {
@@ -4506,6 +4497,11 @@
     group("${target_name}__java") {
       deps = _sync_module_java_targets
     }
+    group("${target_name}__compile_resources") {
+      deps = [
+        "${invoker.base_module_target}__compile_resources",
+      ]
+    }
 
     _build_config = "$target_gen_dir/${target_name}.build_config"
     _rebased_build_config = rebase_path(_build_config, root_build_dir)
@@ -4712,7 +4708,8 @@
       if (_enable_language_splits) {
         args += [
           "--base-whitelist-rtxt-path=@FileArg(" + "${_rebased_base_module_build_config}:deps_info:base_whitelist_rtxt_path)",
-          "--base-module-rtxt-path=@FileArg(" + "${_rebased_base_module_build_config}:deps_info:module_rtxt_path)",
+          "--base-module-rtxt-path=@FileArg(" +
+              "${_rebased_base_module_build_config}:deps_info:r_text_path)",
         ]
       }
 
@@ -4722,7 +4719,7 @@
           "--uncompressed-assets=@FileArg(" +
               "$_rebased_build_config:uncompressed_assets)",
           "--rtxt-in-paths=@FileArg(" +
-              "$_rebased_build_config:deps_info:module_rtxt_path)",
+              "$_rebased_build_config:deps_info:r_text_path)",
           "--pathmap-in-paths=@FileArg(" +
               "$_rebased_build_config:deps_info:module_pathmap_path)",
         ]
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 28ca2a5..7afac95 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8908254478258311552
\ No newline at end of file
+8908200315658417376
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 097742f5..be0f203 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8908254542434342448
\ No newline at end of file
+8908199400534799360
\ No newline at end of file
diff --git a/buildtools/ensure_gn_version.py b/buildtools/ensure_gn_version.py
index 9c01f58..acb1c1d 100755
--- a/buildtools/ensure_gn_version.py
+++ b/buildtools/ensure_gn_version.py
@@ -18,6 +18,7 @@
 from __future__ import print_function
 
 import argparse
+import errno
 import io
 import os
 import re
@@ -67,14 +68,22 @@
   except subprocess.CalledProcessError as e:
     print('`%s` returned %d:\n%s' % (cmd_str, e.returncode, e.output))
     return 1
+  except OSError as e:
+    if e.errno != errno.ENOENT:
+      print('`%s` failed:\n%s' % (cmd_str, e))
+      return 1
+
+    # The tool doesn't exist, so redownload it.
+    out = ''
   except Exception as e:
-    print('`%s` failed:\n%s' % (cmd_str, e.message))
+    print('`%s` failed:\n%s' % (cmd_str, e))
     return 1
 
-  current_revision = re.findall(r'\((.*)\)', out)[0]
-  if desired_revision.startswith(current_revision):
-    # We're on the right version, so we're done.
-    return 0
+  if out:
+    current_revision = re.findall(r'\((.*)\)', out)[0]
+    if desired_revision.startswith(current_revision):
+      # We're on the right version, so we're done.
+      return 0
 
   print("`%s` returned '%s', which wasn't what we were expecting."
           % (cmd_str, out.strip()))
@@ -96,7 +105,7 @@
     zf = zipfile.ZipFile(io.BytesIO(zipdata))
     zf.extract(member, os.path.join(BUILDTOOLS_DIR, dest_dir))
   except Exception as e:
-    print('Failed to extract the binary:\n%s\n' % e.msg)
+    print('Failed to extract the binary:\n%s\n' % e)
     return 1
 
   try:
diff --git a/cc/layers/mirror_layer_impl.cc b/cc/layers/mirror_layer_impl.cc
index b36f293..7a916bf 100644
--- a/cc/layers/mirror_layer_impl.cc
+++ b/cc/layers/mirror_layer_impl.cc
@@ -41,8 +41,8 @@
   viz::SharedQuadState* shared_quad_state =
       render_pass->CreateAndAppendSharedQuadState();
   PopulateScaledSharedQuadStateWithContentRects(
-      shared_quad_state, GetIdealContentsScale(), content_rect, content_rect,
-      contents_opaque);
+      shared_quad_state, mirrored_layer->GetIdealContentsScale(), content_rect,
+      content_rect, contents_opaque);
 
   AppendDebugBorderQuad(render_pass, content_rect, shared_quad_state,
                         append_quads_data);
@@ -75,7 +75,10 @@
 }
 
 gfx::Rect MirrorLayerImpl::GetEnclosingRectInTargetSpace() const {
-  return GetScaledEnclosingRectInTargetSpace(GetIdealContentsScale());
+  const LayerImpl* mirrored_layer =
+      layer_tree_impl()->LayerById(mirrored_layer_id_);
+  return GetScaledEnclosingRectInTargetSpace(
+      mirrored_layer->GetIdealContentsScale());
 }
 
 const char* MirrorLayerImpl::LayerTypeAsString() const {
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 3e181f6..0ffd87e 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -298,15 +298,9 @@
                             true) {}
 };
 
-SkMatrix CreateMatrix(const SkSize& scale, bool is_decomposable) {
+SkMatrix CreateMatrix(const SkSize& scale) {
   SkMatrix matrix;
   matrix.setScale(scale.width(), scale.height());
-
-  if (!is_decomposable) {
-    // Perspective is not decomposable, add it.
-    matrix[SkMatrix::kMPersp0] = 0.1f;
-  }
-
   return matrix;
 }
 
@@ -420,6 +414,28 @@
                            gfx::ColorSpace::TransferID::LINEAR);
   }
 
+  DrawImage CreateDrawImageInternal(
+      const PaintImage& paint_image,
+      const SkMatrix& matrix = SkMatrix::I(),
+      gfx::ColorSpace* color_space = nullptr,
+      SkFilterQuality filter_quality = kMedium_SkFilterQuality,
+      SkIRect* src_rect = nullptr,
+      size_t frame_index = PaintImage::kDefaultFrameIndex) {
+    SkIRect src_rectangle;
+    gfx::ColorSpace cs;
+    if (!src_rect) {
+      src_rectangle =
+          SkIRect::MakeWH(paint_image.width(), paint_image.height());
+      src_rect = &src_rectangle;
+    }
+    if (!color_space) {
+      cs = DefaultColorSpace();
+      color_space = &cs;
+    }
+    return DrawImage(paint_image, *src_rect, filter_quality, matrix,
+                     frame_index, *color_space);
+  }
+
   GPUImageDecodeTestMockContextProvider* context_provider() {
     return context_provider_.get();
   }
@@ -524,22 +540,16 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageSameImage) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
   EXPECT_TRUE(result.task);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage another_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(another_result.need_unref);
@@ -555,13 +565,8 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageSmallerScale) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -571,10 +576,8 @@
   EXPECT_EQ(result.task->dependencies().size(), 1u);
   EXPECT_TRUE(result.task->dependencies()[0]);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage another_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
 
@@ -597,21 +600,15 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLowerQuality) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
-
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kHigh_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
+  DrawImage draw_image = CreateDrawImageInternal(image, matrix);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
   EXPECT_TRUE(result.task);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kLow_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage another_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(another_result.need_unref);
@@ -626,25 +623,18 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageDifferentImage) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
   PaintImage second_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage second_draw_image(
-      second_image,
-      SkIRect::MakeWH(second_image.width(), second_image.height()), quality,
-      CreateMatrix(SkSize::Make(0.25f, 0.25f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      second_image, CreateMatrix(SkSize::Make(0.25f, 0.25f)));
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -662,14 +652,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLargerScale) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -680,20 +666,15 @@
 
   cache->UnrefImage(first_draw_image);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -708,33 +689,23 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLargerScaleNoReuse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -752,14 +723,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageHigherQuality) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
-
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      kLow_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -770,10 +737,8 @@
 
   cache->UnrefImage(first_draw_image);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, matrix, nullptr /* color_space */, kMedium_SkFilterQuality);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -787,14 +752,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedAndLocked) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -829,14 +789,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedNotLocked) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -871,14 +826,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyUploaded) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -902,14 +852,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageCanceledGetsNewTask) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -945,14 +890,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageCanceledWhileReffedGetsNewTask) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -992,14 +932,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageUploadCanceledButDecodeRun) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1021,14 +956,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, NoTaskForImageAlreadyFailedDecoding) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1051,14 +981,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1085,14 +1010,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetLargeDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1119,16 +1039,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawAtRasterDecode) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1155,14 +1070,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawLargerScale) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
+                              nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1171,10 +1082,8 @@
   TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
   TestTileTaskRunner::ProcessTask(result.task.get());
 
-  DrawImage larger_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage larger_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult larger_result = cache->GetTaskForImageAndRef(
       larger_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(larger_result.need_unref);
@@ -1210,13 +1119,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawHigherQuality) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable);
-
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.5f, 0.5f));
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1225,10 +1131,7 @@
   TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
   TestTileTaskRunner::ProcessTask(result.task.get());
 
-  DrawImage higher_quality_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage higher_quality_draw_image = CreateDrawImageInternal(image, matrix);
   ImageDecodeCache::TaskResult hq_result = cache->GetTaskForImageAndRef(
       higher_quality_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(hq_result.need_unref);
@@ -1263,14 +1166,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawNegative) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(-0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(-0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1301,15 +1199,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetLargeScaledDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback(
       gfx::Size(GetLargeImageSize().width(), GetLargeImageSize().height() * 2));
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
+                              nullptr /* color_space */, kHigh_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1342,17 +1236,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, AtRasterUsedDirectlyIfSpaceAllows) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1386,16 +1274,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        GetDecodedImageForDrawAtRasterDecodeMultipleTimes) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
 
   // Must hold context lock before calling GetDecodedImageForDraw /
   // DrawWithImageFinished.
@@ -1424,16 +1307,10 @@
 TEST_P(GpuImageDecodeCacheTest,
        GetLargeDecodedImageForDrawAtRasterDecodeMultipleTimes) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   // Must hold context lock before calling GetDecodedImageForDraw /
   // DrawWithImageFinished.
@@ -1463,14 +1340,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ZeroSizedImagesAreSkipped) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.f, 0.f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.f, 0.f)));
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1489,15 +1361,12 @@
 
 TEST_P(GpuImageDecodeCacheTest, NonOverlappingSrcRectImagesAreSkipped) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
   DrawImage draw_image(image,
                        SkIRect::MakeXYWH(image.width() + 1, image.height() + 1,
                                          image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
+                       kMedium_SkFilterQuality,
+                       CreateMatrix(SkSize::Make(1.f, 1.f)),
                        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
   ImageDecodeCache::TaskResult result =
@@ -1517,14 +1386,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, CanceledTasksDoNotCountAgainstBudget) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeXYWH(0, 0, image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkIRect src_rect = SkIRect::MakeXYWH(0, 0, image.width(), image.height());
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.f, 1.f)), nullptr /* color_space */,
+      kMedium_SkFilterQuality, &src_rect);
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1543,14 +1409,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ShouldAggressivelyFreeResources) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   {
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
@@ -1604,15 +1465,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedImagesFreeOnReachingZeroRefs) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -1624,10 +1480,8 @@
 
   // Create a larger version of |first_image|, this should immediately free the
   // memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -1652,15 +1506,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedZeroRefImagesImmediatelyDeleted) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -1676,10 +1525,7 @@
 
   // Create a larger version of |first_image|, this should immediately free the
   // memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -1699,14 +1545,11 @@
 TEST_P(GpuImageDecodeCacheTest, QualityCappedAtMedium) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
 
   // Create an image with kLow_FilterQuality.
-  DrawImage low_draw_image(image,
-                           SkIRect::MakeWH(image.width(), image.height()),
-                           kLow_SkFilterQuality, matrix,
-                           PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage low_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult low_result = cache->GetTaskForImageAndRef(
       low_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(low_result.need_unref);
@@ -1714,21 +1557,16 @@
 
   // Get the same image at kMedium_SkFilterQuality. We can't re-use low, so we
   // should get a new task/ref.
-  DrawImage medium_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kMedium_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage medium_draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult medium_result = cache->GetTaskForImageAndRef(
       medium_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(medium_result.need_unref);
   EXPECT_TRUE(medium_result.task.get());
   EXPECT_FALSE(low_result.task.get() == medium_result.task.get());
 
-  // Get the same image at kHigh_FilterQuality. We should re-use medium.
-  DrawImage high_quality_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  // Get the same image at kHigh_SkFilterQuality. We should re-use medium.
+  DrawImage high_quality_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kHigh_SkFilterQuality);
   ImageDecodeCache::TaskResult high_quality_result =
       cache->GetTaskForImageAndRef(high_quality_draw_image,
                                    ImageDecodeCache::TracingInfo());
@@ -1749,15 +1587,9 @@
 // cache entry creation doesn't cause a buffer overflow/crash.
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawMipUsageChange) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create an image decode task and cache entry that does not need mips.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1776,10 +1608,8 @@
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
 
   // Do an at-raster decode of the above image that *does* require mips.
-  DrawImage draw_image_mips(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image_mips =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image_mips));
   cache->DrawWithImageFinished(draw_image_mips, decoded_draw_image);
@@ -1787,13 +1617,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OutOfRasterDecodeTask) {
   auto cache = CreateCache();
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkMatrix matrix = CreateMatrix(SkSize::Make(1.0f, 1.0f));
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
 
   ImageDecodeCache::TaskResult result =
       cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
@@ -1812,16 +1639,10 @@
 TEST_P(GpuImageDecodeCacheTest, ZeroCacheNormalWorkingSet) {
   SetCachedTexturesLimit(0);
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Add an image to the cache-> Due to normal working set, this should produce
   // a task and a ref.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1865,19 +1686,13 @@
   // Cache will fit one image.
   SetCachedTexturesLimit(1);
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   PaintImage image2 = CreatePaintImageInternal(GetNormalImageSize());
   DrawImage draw_image2(
-      image2, SkIRect::MakeWH(image2.width(), image2.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+      image2, SkIRect::MakeWH(image2.width(), image2.height()),
+      kMedium_SkFilterQuality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
   // Add an image to the cache and un-ref it.
@@ -1947,15 +1762,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ClearCache) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   for (int i = 0; i < 10; ++i) {
     PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -1977,15 +1786,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ClearCacheInUse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create an image but keep it reffed so it can't be immediately freed.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2012,36 +1815,27 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageDifferentColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   gfx::ColorSpace color_space_a = gfx::ColorSpace::CreateSRGB();
   gfx::ColorSpace color_space_b = gfx::ColorSpace::CreateXYZD50();
 
   PaintImage first_image = CreatePaintImageInternal(gfx::Size(100, 100));
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_a);
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_a);
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_b);
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_b);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_a);
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_a);
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -2059,15 +1853,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForLargeImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateXYZD50();
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2081,7 +1870,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, CacheDecodesExpectedFrames) {
   auto cache = CreateCache();
-
   std::vector<FrameMetadata> frames = {
       FrameMetadata(true, base::TimeDelta::FromMilliseconds(2)),
       FrameMetadata(true, base::TimeDelta::FromMilliseconds(3)),
@@ -2103,12 +1891,10 @@
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
 
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
+  SkFilterQuality quality = kMedium_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       1u, DefaultColorSpace());
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)), 1u,
+                       DefaultColorSpace());
   auto decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2135,8 +1921,7 @@
   ASSERT_LT(subset_height, test_image_size.height());
   DrawImage subset_draw_image(
       image, SkIRect::MakeWH(subset_width, subset_height), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable), 3u,
-      DefaultColorSpace());
+      CreateMatrix(SkSize::Make(1.0f, 1.0f)), 3u, DefaultColorSpace());
   decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(subset_draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2148,15 +1933,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedDataCancelledWhileReplaced) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -2167,10 +1947,7 @@
 
   // Create a larger version of |first_image|, this should immediately free
   // the memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -2203,15 +1980,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, AlreadyBudgetedImagesAreNotAtRaster) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   const size_t bytes_for_test_image =
       GetBytesNeededForSingleImage(test_image_size);
 
@@ -2244,8 +2016,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, ImageBudgetingByCount) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   // Allow a single image by count. Use a high byte limit as we want to test the
@@ -2255,10 +2025,7 @@
   cache->SetWorkingSetLimitsForTesting(
       bytes_for_test_image * 100 /* max_bytes */, 1u /* max_items */);
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   // The image counts against our budget.
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
@@ -2268,11 +2035,9 @@
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
 
   // Try another image, it shouldn't be budgeted and should be at-raster.
-  DrawImage second_draw_image(
-      CreatePaintImageInternal(GetNormalImageSize()),
-      SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  PaintImage second_paint_image =
+      CreatePaintImageInternal(GetNormalImageSize());
+  DrawImage second_draw_image = CreateDrawImageInternal(second_paint_image);
 
   // Should be at raster.
   ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
@@ -2291,15 +2056,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, ImageBudgetingBySize) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   const size_t bytes_for_test_image =
       GetBytesNeededForSingleImage(test_image_size);
@@ -2317,11 +2077,8 @@
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
 
   // Try another image, it shouldn't be budgeted and should be at-raster.
-  DrawImage second_draw_image(
-      CreatePaintImageInternal(test_image_size),
-      SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  PaintImage test_paint_image = CreatePaintImageInternal(test_image_size);
+  DrawImage second_draw_image = CreateDrawImageInternal(test_paint_image);
 
   // Should be at raster.
   ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
@@ -2341,15 +2098,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        ColorConversionDuringDecodeForLargeImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateXYZD50();
 
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2396,15 +2149,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        ColorConversionDuringUploadForSmallImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateDisplayP3D65();
 
   PaintImage image = CreatePaintImageInternal(gfx::Size(11, 12));
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2449,14 +2198,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2474,14 +2218,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   auto result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2498,14 +2237,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   auto result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2526,14 +2260,10 @@
   const bool should_cache_sw_image =
       cache->SupportsColorSpaceConversion() && !use_transfer_cache_;
 
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateBitmapImageInternal(GetLargeImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, gfx::ColorSpace::CreateDisplayP3D65());
+  gfx::ColorSpace color_space = gfx::ColorSpace::CreateDisplayP3D65();
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2545,9 +2275,8 @@
   auto sw_image = cache->GetSWImageDecodeForTesting(draw_image);
   ASSERT_EQ(!!sw_image, should_cache_sw_image);
   if (should_cache_sw_image) {
-    EXPECT_TRUE(SkColorSpace::Equals(
-        sw_image->colorSpace(),
-        gfx::ColorSpace::CreateDisplayP3D65().ToSkColorSpace().get()));
+    EXPECT_TRUE(SkColorSpace::Equals(sw_image->colorSpace(),
+                                     color_space.ToSkColorSpace().get()));
   }
 }
 
@@ -2557,14 +2286,10 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2575,8 +2300,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, KeepOnlyLast2ContentIds) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   const PaintImage::Id paint_image_id = PaintImage::GetNextId();
@@ -2586,10 +2309,8 @@
   for (int i = 0; i < 10; ++i) {
     PaintImage image = CreatePaintImageInternal(
         GetNormalImageSize(), SkColorSpace::MakeSRGB(), paint_image_id);
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), quality,
-        CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
     DecodedDrawImage decoded_draw_image =
         EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
 
@@ -2625,8 +2346,6 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kMedium_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   const SkISize full_size = SkISize::Make(100, 100);
@@ -2643,10 +2362,8 @@
                                .set_paint_image_generator(generator)
                                .TakePaintImage();
 
-  DrawImage draw_image(
-      paint_image, SkIRect::MakeWH(paint_image.width(), paint_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5, 0.5), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(
+      paint_image, CreateMatrix(SkSize::Make(0.5, 0.5)));
   DecodedDrawImage decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   const int expected_width =
@@ -2672,8 +2389,6 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kNone_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   SkISize full_size = SkISize::Make(100, 100);
@@ -2690,10 +2405,9 @@
                                .set_paint_image_generator(generator)
                                .TakePaintImage();
 
-  DrawImage draw_image(
-      paint_image, SkIRect::MakeWH(paint_image.width(), paint_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5, 0.5), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(paint_image, CreateMatrix(SkSize::Make(0.5, 0.5)),
+                              nullptr /* color_space */, kNone_SkFilterQuality);
   DecodedDrawImage decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2716,12 +2430,10 @@
                                       SkSize scale, gfx::ColorSpace color_space,
                                       bool should_have_mips) {
     auto cache = CreateCache();
-    bool is_decomposable = true;
 
     PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-    DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                         filter_quality, CreateMatrix(scale, is_decomposable),
-                         PaintImage::kDefaultFrameIndex, color_space);
+    DrawImage draw_image = CreateDrawImageInternal(
+        image, CreateMatrix(scale), &color_space, filter_quality);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2777,9 +2489,6 @@
   // Medium filter quality == mips
   decode_and_check_mips(kMedium_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
                         DefaultColorSpace(), true);
-  // High filter quality == mips
-  decode_and_check_mips(kHigh_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
-                        DefaultColorSpace(), true);
   // Color conversion preserves mips
   decode_and_check_mips(kMedium_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
                         gfx::ColorSpace::CreateXYZD50(), true);
@@ -2787,17 +2496,12 @@
 
 TEST_P(GpuImageDecodeCacheTest, MipsAddedSubsequentDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  auto filter_quality = kMedium_SkFilterQuality;
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
 
   // Create an image with no scaling. It will not have mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2839,10 +2543,8 @@
   // no new task (re-uses the existing image), but mips should have been
   // added.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2882,9 +2584,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, MipsAddedWhileOriginalInUse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  auto filter_quality = kMedium_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
 
   struct Decode {
@@ -2895,10 +2594,7 @@
 
   // Create an image with no scaling. It will not have mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2933,10 +2629,8 @@
 
   // Second decode with mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -3019,15 +2713,14 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
   SkFilterQuality filter_quality = kMedium_SkFilterQuality;
   SkSize requires_decode_at_original_scale = SkSize::Make(0.8f, 0.8f);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-      CreateMatrix(requires_decode_at_original_scale, is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       filter_quality,
+                       CreateMatrix(requires_decode_at_original_scale),
+                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3068,15 +2761,13 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
   SkFilterQuality filter_quality = kMedium_SkFilterQuality;
   SkSize less_than_half_scale = SkSize::Make(0.45f, 0.45f);
 
   gfx::Size image_size = GetNormalImageSize();
   PaintImage image = CreatePaintImageInternal(image_size);
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       filter_quality,
-                       CreateMatrix(less_than_half_scale, is_decomposable),
+                       filter_quality, CreateMatrix(less_than_half_scale),
                        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3122,17 +2813,10 @@
   // We will create a texture that's at the maximum size the GPU says it can
   // support for uploads.
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage almost_too_large_image =
       CreatePaintImageInternal(gfx::Size(max_texture_size_, max_texture_size_));
-  DrawImage draw_image(almost_too_large_image,
-                       SkIRect::MakeWH(almost_too_large_image.width(),
-                                       almost_too_large_image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(almost_too_large_image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
 
@@ -3227,12 +2911,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3264,12 +2946,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3305,12 +2985,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3347,11 +3025,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
@@ -3373,11 +3049,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3402,11 +3076,9 @@
   const PaintImage image = CreatePaintImageForDecodeAcceleration(
       image_size, image_color_space,
       false /* is_eligible_for_accelerated_decoding */);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3431,11 +3103,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3459,12 +3129,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
diff --git a/cc/trees/layer_tree_host_pixeltest_mirror.cc b/cc/trees/layer_tree_host_pixeltest_mirror.cc
index cbd67e31..02ab6cc 100644
--- a/cc/trees/layer_tree_host_pixeltest_mirror.cc
+++ b/cc/trees/layer_tree_host_pixeltest_mirror.cc
@@ -17,14 +17,14 @@
 
 class LayerTreeHostMirrorPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<
+          ::testing::tuple<LayerTreeTest::RendererType, bool>> {
  protected:
-  RendererType renderer_type() { return GetParam(); }
+  RendererType renderer_type() { return std::get<0>(GetParam()); }
 
   void InitializeSettings(LayerTreeSettings* settings) override {
-    // MirrorLayer is only used by UI compositor; so, match its behavior by
-    // setting layer_transforms_should_scale_layer_contents to false.
-    settings->layer_transforms_should_scale_layer_contents = false;
+    settings->layer_transforms_should_scale_layer_contents =
+        std::get<1>(GetParam());
   }
 };
 
@@ -37,9 +37,11 @@
 #endif
 };
 
-INSTANTIATE_TEST_SUITE_P(,
-                         LayerTreeHostMirrorPixelTest,
-                         ::testing::ValuesIn(kRendererTypes));
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    LayerTreeHostMirrorPixelTest,
+    ::testing::Combine(::testing::ValuesIn(kRendererTypes),
+                       /*layer_transforms_scale_content=*/testing::Bool()));
 
 // Verifies that a mirror layer with a scale mirrors another layer correctly.
 TEST_P(LayerTreeHostMirrorPixelTest, MirrorLayer) {
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 109bcf5..38c7314 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -424,7 +424,6 @@
       "//content/app/resources",
       "//content/public/common:service_names",
       "//crypto",
-      "//headless:headless_shell_browser_lib",
       "//net:net_resources",
       "//ppapi/buildflags",
       "//services/service_manager/embedder",
@@ -445,19 +444,16 @@
       "/DELAYLOAD:dbghelp.dll",
       "/DELAYLOAD:dhcpcsvc.dll",
       "/DELAYLOAD:dwmapi.dll",
-      "/DELAYLOAD:dwrite.dll",
       "/DELAYLOAD:dxgi.dll",
       "/DELAYLOAD:esent.dll",
       "/DELAYLOAD:gdi32.dll",
       "/DELAYLOAD:hid.dll",
       "/DELAYLOAD:imagehlp.dll",
       "/DELAYLOAD:imm32.dll",
-      "/DELAYLOAD:iphlpapi.dll",
       "/DELAYLOAD:msi.dll",
       "/DELAYLOAD:netapi32.dll",
       "/DELAYLOAD:ole32.dll",
       "/DELAYLOAD:oleacc.dll",
-      "/DELAYLOAD:oleaut32.dll",
       "/DELAYLOAD:ncrypt.dll",
       "/DELAYLOAD:propsys.dll",
       "/DELAYLOAD:psapi.dll",
@@ -474,16 +470,29 @@
       "/DELAYLOAD:wevtapi.dll",
       "/DELAYLOAD:winhttp.dll",
       "/DELAYLOAD:wininet.dll",
-      "/DELAYLOAD:winmm.dll",
       "/DELAYLOAD:winspool.drv",
       "/DELAYLOAD:wintrust.dll",
       "/DELAYLOAD:winusb.dll",
-      "/DELAYLOAD:ws2_32.dll",
       "/DELAYLOAD:wsock32.dll",
       "/DELAYLOAD:wtsapi32.dll",
       "/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll",
     ]
 
+    if (is_multi_dll_chrome) {
+      ldflags += [
+        "/DELAYLOAD:dwrite.dll",
+        "/DELAYLOAD:iphlpapi.dll",
+        "/DELAYLOAD:oleaut32.dll",
+        "/DELAYLOAD:winmm.dll",
+        "/DELAYLOAD:ws2_32.dll",
+      ]
+    } else {
+      ldflags += [
+        "/DELAYLOAD:dxva2.dll",
+        "/DELAYLOAD:rstrtmgr.dll",
+      ]
+    }
+
     if (!is_component_build) {
       # This is a large module that can't do incremental linking in some
       # cases.
@@ -498,7 +507,10 @@
 
     if (is_multi_dll_chrome) {
       defines += [ "CHROME_MULTIPLE_DLL_BROWSER" ]
-      deps += [ "//content/public/app:browser" ]
+      deps += [
+        "//content/public/app:browser",
+        "//headless:headless_shell_browser_lib",
+      ]
       assert_no_deps = [
         # The browser DLL may not depend on blink or v8.
         "//third_party/blink/public:blink",
@@ -512,6 +524,7 @@
       deps += [
         ":child_dependencies",
         "//content/public/app:both",
+        "//headless:headless_shell_lib",
       ]
     }
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 88b6bd2..c357fcd 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -569,7 +569,7 @@
     "//chrome/browser/ntp_snippets/ntp_snippets_metrics.h",
     "//chrome/browser/profiles/profile_metrics.h",
     "//chrome/browser/translate/android/translate_utils.h",
-    "//chrome/browser/ui/android/bluetooth_scanning_prompt_android.h",
+    "//chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h",
     "//chrome/browser/ui/android/infobars/infobar_android.h",
   ]
 }
@@ -852,7 +852,6 @@
     "//third_party/android_deps:com_android_support_preference_v7_java",
     "//third_party/android_deps:com_android_support_recyclerview_v7_java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
-    "//third_party/android_deps:com_google_ar_core_java",
     "//third_party/android_deps:com_google_protobuf_protobuf_lite_java",
     "//third_party/android_sdk:android_test_base_java",
     "//third_party/android_sdk:android_test_mock_java",
@@ -1039,6 +1038,7 @@
             "//third_party/gvr-android-sdk:gvr_common_java",
             ":chrome_test_util_java",
             "//components/module_installer/android:module_installer_java",
+            "//components/module_installer/android:module_installer_stub_java",
             "//components/module_installer/android:module_installer_test_java",
           ]
 
@@ -1756,6 +1756,8 @@
     android_manifest_dep = ":trichrome_library_android_manifest"
 
     if (trichrome_synchronized_proguard) {
+      shared_resources_whitelist_target = "//android_webview:system_webview_apk"
+      shared_resources_whitelist_locales = locales
       static_library_dependent_targets = [
         {
           name = "//android_webview:trichrome_webview_apk"
@@ -1777,6 +1779,8 @@
     android_manifest_dep = ":trichrome_library_android_manifest"
 
     if (trichrome_synchronized_proguard) {
+      shared_resources_whitelist_target = "//android_webview:system_webview_apk"
+      shared_resources_whitelist_locales = locales
       static_library_dependent_targets = [
         {
           name = "//android_webview:trichrome_webview_for_bundle_apk"
@@ -2424,8 +2428,6 @@
     "java/src/org/chromium/chrome/browser/AppHooks.java",
     "java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
     "java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java",
-    "java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java",
-    "java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java",
     "java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
     "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
     "java/src/org/chromium/chrome/browser/ChromeFeatureList.java",
@@ -2438,7 +2440,6 @@
     "java/src/org/chromium/chrome/browser/SearchGeolocationDisclosureTabHelper.java",
     "java/src/org/chromium/chrome/browser/ServiceTabLauncher.java",
     "java/src/org/chromium/chrome/browser/ShortcutHelper.java",
-    "java/src/org/chromium/chrome/browser/UsbChooserDialog.java",
     "java/src/org/chromium/chrome/browser/WarmupManager.java",
     "java/src/org/chromium/chrome/browser/WebContentsFactory.java",
     "java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java",
@@ -2495,6 +2496,9 @@
     "java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java",
     "java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ModuleMetrics.java",
     "java/src/org/chromium/chrome/browser/database/SQLiteCursor.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java",
     "java/src/org/chromium/chrome/browser/document/DocumentWebContentsDelegate.java",
     "java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerServiceFactory.java",
     "java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 251b6cd..7047ad6 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -14,8 +14,6 @@
   "java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
   "java/src/org/chromium/chrome/browser/AssistStatusHandler.java",
   "java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java",
-  "java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java",
-  "java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java",
   "java/src/org/chromium/chrome/browser/BrowserRestartActivity.java",
   "java/src/org/chromium/chrome/browser/ChromeActionModeCallback.java",
   "java/src/org/chromium/chrome/browser/ChromeActivity.java",
@@ -40,15 +38,12 @@
   "java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java",
   "java/src/org/chromium/chrome/browser/DevToolsServer.java",
   "java/src/org/chromium/chrome/browser/DeviceConditions.java",
-  "java/src/org/chromium/chrome/browser/DeviceItemAdapter.java",
-  "java/src/org/chromium/chrome/browser/DeviceItemRow.java",
   "java/src/org/chromium/chrome/browser/FileProviderHelper.java",
   "java/src/org/chromium/chrome/browser/GlobalDiscardableReferencePool.java",
   "java/src/org/chromium/chrome/browser/InsetObserverView.java",
   "java/src/org/chromium/chrome/browser/IntentHandler.java",
   "java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java",
   "java/src/org/chromium/chrome/browser/IntentHelper.java",
-  "java/src/org/chromium/chrome/browser/ItemChooserDialog.java",
   "java/src/org/chromium/chrome/browser/KeyboardShortcuts.java",
   "java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java",
   "java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java",
@@ -66,7 +61,6 @@
   "java/src/org/chromium/chrome/browser/SynchronousInitializationActivity.java",
   "java/src/org/chromium/chrome/browser/TabThemeColorProvider.java",
   "java/src/org/chromium/chrome/browser/ThemeColorProvider.java",
-  "java/src/org/chromium/chrome/browser/UsbChooserDialog.java",
   "java/src/org/chromium/chrome/browser/WarmupManager.java",
   "java/src/org/chromium/chrome/browser/WebContentsFactory.java",
   "java/src/org/chromium/chrome/browser/WindowDelegate.java",
@@ -447,6 +441,12 @@
   "java/src/org/chromium/chrome/browser/dependency_injection/ChromeCommonQualifiers.java",
   "java/src/org/chromium/chrome/browser/dependency_injection/ModuleFactoryOverrides.java",
   "java/src/org/chromium/chrome/browser/device/DeviceClassManager.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java",
   "java/src/org/chromium/chrome/browser/directactions/ChromeDirectActionIds.java",
   "java/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandler.java",
   "java/src/org/chromium/chrome/browser/directactions/DirectActionCoordinator.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index d16003f7..a92cc41 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -226,9 +226,6 @@
     if (_target_type == "android_app_bundle_module") {
       deps +=
           [ "//components/module_installer/android:module_installer_impl_java" ]
-    } else if (_target_type == "instrumentation_test_apk") {
-      deps +=
-          [ "//components/module_installer/android:module_installer_test_java" ]
     } else {
       deps +=
           [ "//components/module_installer/android:module_installer_stub_java" ]
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index d02d0795..5b9f9cc 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -9,8 +9,6 @@
   "javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java",
   "javatests/src/org/chromium/chrome/browser/AudioTest.java",
   "javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java",
-  "javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java",
-  "javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java",
@@ -21,7 +19,6 @@
   "javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java",
   "javatests/src/org/chromium/chrome/browser/InstalledAppTest.java",
   "javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java",
-  "javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java",
   "javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java",
   "javatests/src/org/chromium/chrome/browser/MockSafeBrowsingApiHandler.java",
@@ -47,7 +44,6 @@
   "javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java",
   "javatests/src/org/chromium/chrome/browser/TabsTest.java",
   "javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java",
-  "javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/VideoFullscreenOrientationLockChromeTest.java",
   "javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java",
@@ -79,6 +75,10 @@
   "javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java",
   "javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java",
   "javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/DirectActionTestRule.java",
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index a3f2d624..919d5a7 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -72,7 +72,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantTagsForTesting.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
@@ -162,6 +162,7 @@
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPaymentRequestUiTest.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java",
+    "javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java",
   ]
 
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
index ac3e29a..3237350 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
@@ -257,7 +257,8 @@
 
     /** Request showing the Assistant bottom bar view and expand the sheet. */
     public void showAndExpand() {
-        BottomSheetUtils.showContentAndExpand(mBottomSheetController, mContent);
+        BottomSheetUtils.showContentAndExpand(
+                mBottomSheetController, mContent, /* animate= */ true);
     }
 
     /** Hide the Assistant bottom bar view. */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
index f9d27be..776880d6 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.autofill_assistant;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.text.SpannableString;
 import android.text.method.LinkMovementMethod;
 import android.view.LayoutInflater;
@@ -13,8 +14,15 @@
 import android.widget.TextView;
 
 import org.chromium.base.Callback;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.text.NoUnderlineClickableSpan;
 import org.chromium.ui.text.SpanApplier;
 
@@ -27,24 +35,112 @@
 class AssistantOnboardingCoordinator {
     private static final String SMALL_ONBOARDING_EXPERIMENT_ID = "4257013";
 
+    private final String mExperimentIds;
+    private final Context mContext;
+    private final BottomSheetController mController;
+    @Nullable
+    private final Tab mTab;
+
+    @Nullable
+    private AssistantOverlayCoordinator mOverlayCoordinator;
+
+    @Nullable
+    private AssistantBottomSheetContent mContent;
+    private boolean mAnimate = true;
+
+    AssistantOnboardingCoordinator(String experimentIds, Context context,
+            BottomSheetController controller, @Nullable Tab tab) {
+        mExperimentIds = experimentIds;
+        mContext = context;
+        mController = controller;
+        mTab = tab;
+    }
+
     /**
-     * Set the content of {@code bottomSheetContent} to be the Autofill Assistant onboarding. {@code
-     * callback} will be called with true or false when the user accepts or cancels the onboarding
-     * (respectively).
+     * Shows onboarding and provides the result to the given callback.
+     *
+     * <p>The {@code callback} will be called with true or false when the user accepts or cancels
+     * the onboarding (respectively).
+     *
+     * <p>Note that the bottom sheet will be hidden after the callback returns. Call, from the
+     * callback, {@link #hide} to hide it earlier or {@link #transferControls} to take ownership of
+     * it and possibly keep it past the end of the callback.
      */
-    static void setOnboardingContent(String experimentIds, Context context,
-            AssistantBottomSheetContent bottomSheetContent, Callback<Boolean> callback) {
-        ScrollView initView = (ScrollView) LayoutInflater.from(context).inflate(
+    void show(Callback<Boolean> callback) {
+        AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_SHOWN);
+
+        if (mTab != null) {
+            // If there's a tab, cover it with an overlay.
+            AssistantOverlayModel overlayModel = new AssistantOverlayModel();
+            mOverlayCoordinator = new AssistantOverlayCoordinator(mTab.getActivity(), overlayModel);
+            overlayModel.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
+        }
+        mContent = new AssistantBottomSheetContent(mContext);
+        initContent(callback);
+        BottomSheetUtils.showContentAndExpand(mController, mContent, mAnimate);
+    }
+
+    /**
+     * Transfers ownership of the controls to the caller, returns the overlay coordinator, if one
+     * was created.
+     *
+     * <p>This call is only useful when called from inside a callback provided to {@link #show}, as
+     * before that there are no controls and after that the coordinator automatically hides them.
+     * This call allows callbacks to reuse the controls setup for onboarding and provide a smooth
+     * transition.
+     */
+    @Nullable
+    AssistantOverlayCoordinator transferControls() {
+        assert isInProgress();
+
+        AssistantOverlayCoordinator coordinator = mOverlayCoordinator;
+        mOverlayCoordinator = null;
+        mContent = null;
+        return coordinator;
+    }
+
+    /** Hides the UI, if one is shown. */
+    void hide() {
+        if (mOverlayCoordinator != null) {
+            mOverlayCoordinator.destroy();
+            mOverlayCoordinator = null;
+        }
+
+        if (mContent != null) {
+            mController.hideContent(mContent, /* animate= */ mAnimate);
+            mContent = null;
+        }
+    }
+
+    /**
+     * Returns {@code true} between the time {@link #show} is called and the time
+     * the callback has returned.
+     */
+    boolean isInProgress() {
+        return mContent != null;
+    }
+
+    /** Don't animate the bottom sheet expansion. */
+    @VisibleForTesting
+    void disableAnimationForTesting() {
+        mAnimate = false;
+    }
+
+    /**
+     * Set the content of the bottom sheet to be the Autofill Assistant onboarding.
+     */
+    private void initContent(Callback<Boolean> callback) {
+        ScrollView initView = (ScrollView) LayoutInflater.from(mContext).inflate(
                 R.layout.autofill_assistant_onboarding, /* root= */ null);
 
         TextView termsTextView = initView.findViewById(R.id.google_terms_message);
-        String termsString = context.getApplicationContext().getString(
+        String termsString = mContext.getApplicationContext().getString(
                 R.string.autofill_assistant_google_terms_description);
 
-        NoUnderlineClickableSpan termsSpan = new NoUnderlineClickableSpan(context.getResources(),
+        NoUnderlineClickableSpan termsSpan = new NoUnderlineClickableSpan(mContext.getResources(),
                 (widget)
-                        -> CustomTabActivity.showInfoPage(context.getApplicationContext(),
-                                context.getApplicationContext().getString(
+                        -> CustomTabActivity.showInfoPage(mContext.getApplicationContext(),
+                                mContext.getApplicationContext().getString(
                                         R.string.autofill_assistant_google_terms_url)));
         SpannableString spannableMessage = SpanApplier.applySpans(
                 termsString, new SpanApplier.SpanInfo("<link>", "</link>", termsSpan));
@@ -59,24 +155,27 @@
         initView.findViewById(R.id.button_init_not_ok)
                 .setOnClickListener(unusedView -> onClicked(false, callback));
         initView.setContentDescription(
-                context.getString(R.string.autofill_assistant_first_run_accessibility));
+                mContext.getString(R.string.autofill_assistant_first_run_accessibility));
 
         // Hide views that should not be displayed when showing the small onboarding.
-        if (Arrays.asList(experimentIds.split(",")).contains(SMALL_ONBOARDING_EXPERIMENT_ID)) {
+        if (Arrays.asList(mExperimentIds.split(",")).contains(SMALL_ONBOARDING_EXPERIMENT_ID)) {
             hide(initView, R.id.onboarding_image);
             hide(initView, R.id.onboarding_subtitle);
             hide(initView, R.id.onboarding_separator);
         }
 
-        bottomSheetContent.setContent(initView, initView);
+        mContent.setContent(initView, initView);
     }
 
     private static void hide(View root, int resId) {
         root.findViewById(resId).setVisibility(View.GONE);
     }
 
-    private static void onClicked(boolean accept, Callback<Boolean> callback) {
+    private void onClicked(boolean accept, Callback<Boolean> callback) {
         AutofillAssistantPreferencesUtil.setInitialPreferences(accept);
+        AutofillAssistantMetrics.recordOnBoarding(
+                accept ? OnBoarding.OB_ACCEPTED : OnBoarding.OB_CANCELLED);
         callback.onResult(accept);
+        hide();
     }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java
deleted file mode 100644
index 2eba277e1..0000000
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java
+++ /dev/null
@@ -1,89 +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.
-
-package org.chromium.chrome.browser.autofill_assistant;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.chromium.base.annotations.UsedByReflection;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
-import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
-import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
-import org.chromium.content_public.browser.WebContents;
-
-import java.util.Map;
-
-/**
- * Factory implementation to create AutofillAssistantClient as
- * as AutofillAssistantModuleEntry to serve as interface between
- * base module and assistant DFM.
- */
-@UsedByReflection("AutofillAssistantModuleEntryProvider.java")
-public class AutofillAssistantModuleEntryFactoryImpl
-        implements AutofillAssistantModuleEntryFactory {
-    @Override
-    public AutofillAssistantModuleEntry createEntry(
-            @NonNull ChromeActivity activity, @NonNull WebContents webContents) {
-        return new AutofillAssistantModuleEntryImpl(activity, webContents);
-    }
-
-    private static class AutofillAssistantModuleEntryImpl implements AutofillAssistantModuleEntry {
-        private final ChromeActivity mActivity;
-        private final WebContents mWebContents;
-
-        private AutofillAssistantModuleEntryImpl(ChromeActivity activity, WebContents webContents) {
-            mActivity = activity;
-            mWebContents = webContents;
-        }
-
-        @Override
-        public void start(boolean skipOnboarding, String initialUrl, Map<String, String> parameters,
-                String experimentIds, Bundle intentExtras) {
-            if (skipOnboarding) {
-                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_NOT_SHOWN);
-
-                start(initialUrl, parameters, experimentIds, intentExtras, null);
-            } else {
-                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_SHOWN);
-
-                BottomSheetController controller = mActivity.getBottomSheetController();
-                AssistantBottomSheetContent content = new AssistantBottomSheetContent(mActivity);
-                AssistantOverlayModel overlayModel = new AssistantOverlayModel();
-                AssistantOverlayCoordinator overlayCoordinator =
-                        new AssistantOverlayCoordinator(mActivity, overlayModel);
-                overlayModel.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
-                AssistantOnboardingCoordinator.setOnboardingContent(
-                        experimentIds, mActivity, content, accepted -> {
-                            if (accepted) {
-                                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_ACCEPTED);
-
-                                // We transfer the ownership of the overlay to #start() and the
-                                // bottom sheet content will be replaced.
-                                start(initialUrl, parameters, experimentIds, intentExtras,
-                                        overlayCoordinator);
-                            } else {
-                                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_CANCELLED);
-                                overlayCoordinator.destroy();
-                                controller.hideContent(content, /* animate= */ true);
-                                AutofillAssistantMetrics.recordDropOut(DropOutReason.DECLINED);
-                            }
-                        });
-
-                BottomSheetUtils.showContentAndExpand(controller, content);
-            }
-        }
-
-        private void start(String initialUrl, Map<String, String> parameters, String experimentIds,
-                Bundle intentExtras, @Nullable AssistantOverlayCoordinator overlayCoordinator) {
-            AutofillAssistantClient client = AutofillAssistantClient.fromWebContents(mWebContents);
-            client.start(initialUrl, parameters, experimentIds, intentExtras, overlayCoordinator);
-        }
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
new file mode 100644
index 0000000..f415368
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
@@ -0,0 +1,46 @@
+// 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.
+
+package org.chromium.chrome.browser.autofill_assistant;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import org.chromium.base.annotations.UsedByReflection;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.WebContents;
+
+import java.util.Map;
+
+/**
+ * Implementation of {@link AutofillAssistantModuleEntry}. This is the entry point into the
+ * assistant DFM.
+ */
+@UsedByReflection("AutofillAssistantModuleEntryProvider.java")
+public class AutofillAssistantModuleEntryImpl implements AutofillAssistantModuleEntry {
+    @Override
+    public void start(@NonNull Tab tab, @NonNull WebContents webContents, boolean skipOnboarding,
+            String initialUrl, Map<String, String> parameters, String experimentIds,
+            Bundle intentExtras) {
+        if (skipOnboarding) {
+            AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_NOT_SHOWN);
+            AutofillAssistantClient.fromWebContents(tab.getWebContents())
+                    .start(initialUrl, parameters, experimentIds, intentExtras, null);
+            return;
+        }
+
+        ChromeActivity activity = tab.getActivity();
+        AssistantOnboardingCoordinator onboardingCoordinator = new AssistantOnboardingCoordinator(
+                experimentIds, activity, activity.getBottomSheetController(), tab);
+        onboardingCoordinator.show(accepted -> {
+            if (!accepted) return;
+
+            AutofillAssistantClient.fromWebContents(tab.getWebContents())
+                    .start(initialUrl, parameters, experimentIds, intentExtras,
+                            onboardingCoordinator.transferControls());
+        });
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
index 3c2d6dd..695c588 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
@@ -13,8 +13,8 @@
 
 class BottomSheetUtils {
     /** Request {@code controller} to show {@code content} and expand the sheet when it is shown. */
-    static void showContentAndExpand(
-            BottomSheetController controller, AssistantBottomSheetContent content) {
+    static void showContentAndExpand(BottomSheetController controller,
+            AssistantBottomSheetContent content, boolean animate) {
         // Add an observer that makes sure the bottom sheet content is always shown, even in the
         // peek state.
         BottomSheet bottomSheet = controller.getBottomSheet();
@@ -62,7 +62,7 @@
         });
 
         // Show the content.
-        if (controller.requestShowContent(content, /* animate= */ true)) {
+        if (controller.requestShowContent(content, animate)) {
             controller.expandSheet();
         } else {
             // If the content is not directly shown, add an observer that will expand the sheet when
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
index 3165381..f593c37 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
@@ -137,7 +137,7 @@
         view.mProfileIconMenu.setOnMenuItemClickListener(item -> {
             int itemId = item.getItemId();
             if (itemId == R.id.settings) {
-                PreferencesLauncher.launchSettingsPage(
+                PreferencesLauncher.launchSettingsPageCompat(
                         view.mHeader.getContext(), AutofillAssistantPreferences.class);
                 return true;
             } else if (itemId == R.id.send_feedback) {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
new file mode 100644
index 0000000..330d373e
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
@@ -0,0 +1,153 @@
+// 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.
+
+package org.chromium.chrome.browser.autofill_assistant;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import android.support.annotation.IdRes;
+import android.support.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests {@link AssistantOnboardingCoordinator}
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
+public class AssistantOnboardingCoordinatorTest {
+    @Rule
+    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    Callback<Boolean> mCallback;
+
+    private ChromeActivity mActivity;
+    private BottomSheetController mBottomSheetController;
+    private Tab mTab;
+
+    @Before
+    public void setUp() throws Exception {
+        AutofillAssistantUiTestUtil.startOnBlankPage(mCustomTabActivityTestRule);
+        mActivity = mCustomTabActivityTestRule.getActivity();
+        mBottomSheetController = ThreadUtils.runOnUiThreadBlocking(
+                () -> AutofillAssistantUiTestUtil.createBottomSheetController(mActivity));
+        mTab = mActivity.getTabModelSelector().getCurrentTab();
+    }
+
+    private AssistantOnboardingCoordinator createCoordinator(Tab tab) {
+        AssistantOnboardingCoordinator coordinator =
+                new AssistantOnboardingCoordinator("", mActivity, mBottomSheetController, mTab);
+        coordinator.disableAnimationForTesting();
+        return coordinator;
+    }
+
+    @Test
+    @MediumTest
+    public void testAcceptOnboarding() throws Exception {
+        testOnboarding(R.id.button_init_ok, true);
+    }
+
+    @Test
+    @MediumTest
+    public void testRejectOnboarding() throws Exception {
+        testOnboarding(R.id.button_init_not_ok, false);
+    }
+
+    private void testOnboarding(@IdRes int buttonToClick, boolean expectAccept) throws Exception {
+        AutofillAssistantPreferencesUtil.setInitialPreferences(!expectAccept);
+
+        AssistantOnboardingCoordinator coordinator = createCoordinator(mTab);
+
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(mCallback));
+
+        assertTrue(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+        onView(is(mActivity.getScrim())).check(matches(isDisplayed()));
+        onView(withId(buttonToClick)).perform(click());
+
+        verify(mCallback).onResult(expectAccept);
+
+        assertFalse(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+        assertEquals(expectAccept, AutofillAssistantPreferencesUtil.isAutofillOnboardingAccepted());
+    }
+
+    @Test
+    @MediumTest
+    public void testOnboardingWithNoTabs() throws Exception {
+        AssistantOnboardingCoordinator coordinator = createCoordinator(/* tab= */ null);
+
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(mCallback));
+
+        onView(withId(R.id.button_init_ok)).perform(click());
+
+        verify(mCallback).onResult(true);
+    }
+
+    @Test
+    @MediumTest
+    public void testTransfertControls() throws Exception {
+        AssistantOnboardingCoordinator coordinator = createCoordinator(mTab);
+
+        List<AssistantOverlayCoordinator> capturedOverlays =
+                Collections.synchronizedList(new ArrayList<>());
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show((accepted) -> {
+            capturedOverlays.add(coordinator.transferControls());
+        }));
+
+        onView(withId(R.id.button_init_ok)).perform(click());
+        assertFalse(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+
+        // An overlay was captured, and it is still shown.
+        onView(is(mActivity.getScrim())).check(matches(isDisplayed()));
+        assertEquals(1, capturedOverlays.size());
+        AssistantOverlayCoordinator overlay = capturedOverlays.get(0);
+        assertNotNull(overlay);
+        assertEquals(
+                AssistantOverlayState.FULL, overlay.getModel().get(AssistantOverlayModel.STATE));
+
+        // The bottom sheet content is still the assistant one.
+        assertThat(mBottomSheetController.getBottomSheet().getCurrentSheetContent(),
+                instanceOf(AssistantBottomSheetContent.class));
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
index 540c4b6..fd52f00 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
@@ -12,15 +12,11 @@
 
 import static org.hamcrest.Matchers.not;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.Intent;
-import android.support.annotation.IdRes;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -55,8 +51,6 @@
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
-import org.chromium.chrome.browser.snackbar.BottomContainer;
-import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -111,53 +105,10 @@
         return mCustomTabActivityTestRule.getActivity();
     }
 
-    // Copied from {@link ChromeActivity#initializeBottomSheet}.
     protected BottomSheetController initializeBottomSheet() {
-        CustomTabActivity activity = getActivity();
-        ViewGroup coordinator = activity.findViewById(R.id.coordinator);
-        LayoutInflater.from(activity).inflate(R.layout.bottom_sheet, coordinator);
-        BottomSheet bottomSheet = coordinator.findViewById(R.id.bottom_sheet);
-        bottomSheet.init(coordinator, activity);
-
-        ((BottomContainer) activity.findViewById(R.id.bottom_container))
-                .setBottomSheet(bottomSheet);
-
-        return new BottomSheetController(activity, activity.getLifecycleDispatcher(),
-                activity.getActivityTabProvider(), activity.getScrim(), bottomSheet,
-                activity.getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
-                /* suppressSheetForContextualSearch= */ false);
+        return AutofillAssistantUiTestUtil.createBottomSheetController(getActivity());
     }
 
-    @Test
-    @MediumTest
-    public void testAcceptOnboarding() throws Exception {
-        testOnboarding(true, R.id.button_init_ok);
-    }
-
-    @Test
-    @MediumTest
-    public void testDeclineOnboarding() throws Exception {
-        testOnboarding(false, R.id.button_init_not_ok);
-    }
-
-    private void testOnboarding(boolean expectedAccepted, @IdRes int buttonToClick)
-            throws Exception {
-        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
-        Context context = getActivity();
-        AssistantBottomSheetContent bottomSheetContent = new AssistantBottomSheetContent(context);
-
-        AssistantOnboardingCoordinator.setOnboardingContent(
-                /* experimentIds= */ "", context, bottomSheetContent, accepted -> {
-                    Assert.assertEquals(expectedAccepted, accepted);
-                    mRunnableMock.run();
-                });
-
-        View button = bottomSheetContent.getContentView().findViewById(buttonToClick);
-        Assert.assertNotNull(button);
-
-        TestThreadUtils.runOnUiThreadBlocking(button::performClick);
-        TestThreadUtils.runOnUiThreadBlocking(() -> verify(mRunnableMock).run());
-    }
 
     // TODO(crbug.com/806868): Add more UI details test and check, like payment request UI,
     // highlight chips and so on.
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
index 45d18ee..7ec9b81 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
@@ -9,6 +9,7 @@
 import android.support.design.widget.CoordinatorLayout;
 import android.support.test.InstrumentationRegistry;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -18,11 +19,16 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
 import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
+import org.chromium.chrome.browser.snackbar.BottomContainer;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 
 import jp.tomorrowkey.android.gifplayer.BaseGifImage;
 
@@ -79,6 +85,29 @@
     }
 
     /**
+     * Creates a {@link BottomSheetController} for the activity, suitable for testing.
+     *
+     * <p>The returned controller is different from the one returned by {@link
+     * ChromeActivity#getBottomSheetController}.
+     */
+    static BottomSheetController createBottomSheetController(ChromeActivity activity) {
+        // Copied from {@link ChromeActivity#initializeBottomSheet}.
+
+        ViewGroup coordinator = activity.findViewById(R.id.coordinator);
+        LayoutInflater.from(activity).inflate(R.layout.bottom_sheet, coordinator);
+        BottomSheet bottomSheet = coordinator.findViewById(R.id.bottom_sheet);
+        bottomSheet.init(coordinator, activity);
+
+        ((BottomContainer) activity.findViewById(R.id.bottom_container))
+                .setBottomSheet(bottomSheet);
+
+        return new BottomSheetController(activity, activity.getLifecycleDispatcher(),
+                activity.getActivityTabProvider(), activity.getScrim(), bottomSheet,
+                activity.getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
+                /* suppressSheetForContextualSearch= */ false);
+    }
+
+    /**
      * Attaches the specified view to the Chrome coordinator. Must be called from the UI thread.
      */
     public static void attachToCoordinator(CustomTabActivity activity, View view) {
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 8a92e67..8b0df5d 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -11,12 +11,15 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.BuildInfo;
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeActivity.ActivityType;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
 import org.chromium.chrome.browser.directactions.DirectActionHandler;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.widget.ScrimView;
@@ -104,18 +107,20 @@
 
         // Have an "attempted starts" baseline for the drop out histogram.
         AutofillAssistantMetrics.recordDropOut(DropOutReason.AA_START);
-        AutofillAssistantModuleEntryProvider.getModuleEntry(activity, (moduleEntry) -> {
-            if (moduleEntry == null) {
-                AutofillAssistantMetrics.recordDropOut(DropOutReason.DFM_INSTALL_FAILED);
-                return;
-            }
+        waitForTabWithWebContents(activity, tab -> {
+            AutofillAssistantModuleEntryProvider.getModuleEntry(activity, tab, (moduleEntry) -> {
+                if (moduleEntry == null) {
+                    AutofillAssistantMetrics.recordDropOut(DropOutReason.DFM_INSTALL_FAILED);
+                    return;
+                }
 
-            Bundle bundleExtras = activity.getInitialIntent().getExtras();
-            Map<String, String> parameters = extractParameters(bundleExtras);
-            parameters.remove(PARAMETER_ENABLED);
-            String initialUrl = activity.getInitialIntent().getDataString();
-            moduleEntry.start(canStartWithoutOnboarding, initialUrl, parameters, experimentIds,
-                    activity.getInitialIntent().getExtras());
+                Bundle bundleExtras = activity.getInitialIntent().getExtras();
+                Map<String, String> parameters = extractParameters(bundleExtras);
+                parameters.remove(PARAMETER_ENABLED);
+                String initialUrl = activity.getInitialIntent().getDataString();
+                moduleEntry.start(tab, tab.getWebContents(), canStartWithoutOnboarding, initialUrl,
+                        parameters, experimentIds, activity.getInitialIntent().getExtras());
+            });
         });
     }
 
@@ -227,4 +232,25 @@
         }
         return false;
     }
+
+    /** Provides the callback with a tab that has a web contents, waits if necessary. */
+    private static void waitForTabWithWebContents(ChromeActivity activity, Callback<Tab> callback) {
+        if (activity.getActivityTab() != null
+                && activity.getActivityTab().getWebContents() != null) {
+            callback.onResult(activity.getActivityTab());
+            return;
+        }
+
+        // The tab is not yet available. We need to register as listener and wait for it.
+        activity.getActivityTabProvider().addObserverAndTrigger(
+                new ActivityTabProvider.HintlessActivityTabObserver() {
+                    @Override
+                    public void onActivityTabChanged(Tab tab) {
+                        if (tab == null) return;
+                        activity.getActivityTabProvider().removeObserver(this);
+                        assert tab.getWebContents() != null;
+                        callback.onResult(tab);
+                    }
+                });
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
index 44b802e2..590dc7b 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
@@ -5,19 +5,28 @@
 package org.chromium.chrome.browser.autofill_assistant;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.module_installer.ModuleInterface;
+import org.chromium.content_public.browser.WebContents;
 
 import java.util.Map;
 
 /**
- * Interface for base module to start the autofill assistant
- * experience in dynamic feature module.
+ * Interface between base module and assistant DFM.
  */
+@ModuleInterface(module = "autofill_assistant",
+        impl = "org.chromium.chrome.browser.autofill_assistant.AutofillAssistantModuleEntryImpl")
 interface AutofillAssistantModuleEntry {
     /**
-     * Launches Autofill Assistant on the current web contents, expecting autostart. If {@code
-     * skipOnboarding} is false, the onboarding will first be shown and the Autofill Assistant will
-     * start only if the user accepts to proceed.
+     * Starts Autofill Assistant on the current tab of the given chrome activity.
+     *
+     * <p>When started this way, Autofill Assistant appears immediately in the bottom sheet, expects
+     * a single autostartable script for the tab's current URL, runs that script until the end and
+     * disappears.
      */
-    void start(boolean skipOnboarding, String initialUrl, Map<String, String> parameters,
-            String experimentIds, Bundle intentExtras);
-}
\ No newline at end of file
+    void start(@NonNull Tab tab, @NonNull WebContents webContents, boolean skipOnboarding,
+            String initialUrl, Map<String, String> parameters, String experimentIds,
+            Bundle intentExtras);
+}
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java
deleted file mode 100644
index 214ce76..0000000
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java
+++ /dev/null
@@ -1,23 +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.
-
-package org.chromium.chrome.browser.autofill_assistant;
-
-import android.support.annotation.NonNull;
-
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.components.module_installer.ModuleInterface;
-import org.chromium.content_public.browser.WebContents;
-
-/**
- * Interface to create AutofillAssistantModuleEntry as inferface
- * between base module and assistant DFM.
- */
-@ModuleInterface(module = "autofill_assistant",
-        impl = "org.chromium.chrome.browser.autofill_assistant."
-                + "AutofillAssistantModuleEntryFactoryImpl")
-interface AutofillAssistantModuleEntryFactory {
-    AutofillAssistantModuleEntry createEntry(
-            @NonNull ChromeActivity activity, @NonNull WebContents webContents);
-}
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
index 517615c9..991c3f0 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
@@ -4,13 +4,14 @@
 
 package org.chromium.chrome.browser.autofill_assistant;
 
+import android.content.Context;
+import android.support.annotation.Nullable;
+
 import org.chromium.base.BundleUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ActivityTabProvider;
-import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.modules.ModuleInstallUi;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.module_installer.ModuleInstaller;
@@ -22,21 +23,26 @@
 public class AutofillAssistantModuleEntryProvider {
     private static final String TAG = "AutofillAssistant";
 
-    /**
-     * Returns AutofillAssistantModuleEntry by using it as argument to the
-     * passed in callback, or null if DFM loading fails.
-     */
+    /* Returns the AA module entry, if it is already installed. */
+    @Nullable
+    /* package */ static AutofillAssistantModuleEntry getModuleEntryIfInstalled(Context context) {
+        // Required to access resources in DFM using this activity as context.
+        ModuleInstaller.getInstance().initActivity(context);
+        if (AutofillAssistantModule.isInstalled()) {
+            return AutofillAssistantModule.getImpl();
+        }
+        return null;
+    }
+
+    /** Gets the AA module entry, installing it if necessary. */
     /* package */ static void getModuleEntry(
-            ChromeActivity activity, Callback<AutofillAssistantModuleEntry> callback) {
-        getTab(activity, tab -> {
-            // Required to access resources in DFM using this activity as context.
-            ModuleInstaller.initActivity(activity);
-            if (AutofillAssistantModule.isInstalled()) {
-                callback.onResult(createEntry(tab));
-                return;
-            }
-            loadDynamicModuleWithUi(activity, tab, callback);
-        });
+            Context context, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
+        AutofillAssistantModuleEntry entry = getModuleEntryIfInstalled(context);
+        if (entry != null) {
+            callback.onResult(entry);
+            return;
+        }
+        loadDynamicModuleWithUi(context, tab, callback);
     }
 
     /**
@@ -70,13 +76,8 @@
         AutofillAssistantModule.installDeferred();
     }
 
-    private static AutofillAssistantModuleEntry createEntry(Tab tab) {
-        AutofillAssistantModuleEntryFactory factory = AutofillAssistantModule.getImpl();
-        return factory.createEntry(tab.getActivity(), tab.getWebContents());
-    }
-
     private static void loadDynamicModuleWithUi(
-            ChromeActivity activity, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
+            Context activity, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
         ModuleInstallUi ui = new ModuleInstallUi(tab, R.string.autofill_assistant_module_title,
                 new ModuleInstallUi.FailureUiListener() {
                     @Override
@@ -91,37 +92,17 @@
                 });
         // Shows toast informing user about install start.
         ui.showInstallStartUi();
-        ModuleInstaller.install("autofill_assistant", (success) -> {
+        ModuleInstaller.getInstance().install("autofill_assistant", (success) -> {
             if (success) {
                 // Clean install of chrome will have issues here without initializing
                 // after installation of DFM.
-                ModuleInstaller.initActivity(activity);
+                ModuleInstaller.getInstance().initActivity(activity);
                 // Don't show success UI from DFM, transition to autobot UI directly.
-                callback.onResult(createEntry(tab));
+                callback.onResult(AutofillAssistantModule.getImpl());
                 return;
             }
             // Show inforbar to ask user if they want to retry or cancel.
             ui.showInstallFailureUi();
         });
     }
-
-    private static void getTab(ChromeActivity activity, Callback<Tab> callback) {
-        if (activity.getActivityTab() != null
-                && activity.getActivityTab().getWebContents() != null) {
-            callback.onResult(activity.getActivityTab());
-            return;
-        }
-
-        // The tab is not yet available. We need to register as listener and wait for it.
-        activity.getActivityTabProvider().addObserverAndTrigger(
-                new ActivityTabProvider.HintlessActivityTabObserver() {
-                    @Override
-                    public void onActivityTabChanged(Tab tab) {
-                        if (tab == null) return;
-                        activity.getActivityTabProvider().removeObserver(this);
-                        assert tab.getWebContents() != null;
-                        callback.onResult(tab);
-                    }
-                });
-    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java_sources.gni b/chrome/android/features/autofill_assistant/public/java_sources.gni
index 1d79e4d..357f8ea 100644
--- a/chrome/android/features/autofill_assistant/public/java_sources.gni
+++ b/chrome/android/features/autofill_assistant/public/java_sources.gni
@@ -6,7 +6,6 @@
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantMetrics.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java",
-  "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java",
 ]
diff --git a/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml b/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
index a06df67..5ea4e1a 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
+++ b/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
@@ -15,37 +15,12 @@
     android:layout_marginBottom="@dimen/keyboard_accessory_sheet_bottom_margin"
     android:orientation="vertical">
 
-    <LinearLayout
+    <org.chromium.ui.widget.ChipView
+        android:id="@+id/name_full"
         android:gravity="center_vertical|start"
-        android:fillViewport="true"
-        android:layout_height="@dimen/keyboard_accessory_suggestion_height"
-        android:layout_width="match_parent"
-        android:orientation="horizontal">
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_first"
-            android:gravity="center_vertical|start"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_middle"
-            android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_last"
-            android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-    </LinearLayout>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/InputChip" />
 
     <org.chromium.ui.widget.ChipView
         android:id="@+id/company_name"
@@ -78,6 +53,7 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_state"
             android:gravity="center_vertical|start"
+            android:layout_marginEnd="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
@@ -85,7 +61,7 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_city"
             android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
+            android:layout_marginEnd="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
@@ -93,7 +69,6 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_zip"
             android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
index 272ae28..1460611 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
@@ -18,9 +18,7 @@
  * This view represents a section of user data in the address tab of the keyboard accessory.
  */
 class AddressAccessoryInfoView extends LinearLayout {
-    private ChipView mNameFirst;
-    private ChipView mNameMiddle;
-    private ChipView mNameLast;
+    private ChipView mNameFull;
     private ChipView mCompanyName;
     private ChipView mAddressHomeLine1;
     private ChipView mAddressHomeLine2;
@@ -61,9 +59,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mNameFirst = findViewById(R.id.name_first);
-        mNameMiddle = findViewById(R.id.name_middle);
-        mNameLast = findViewById(R.id.name_last);
+        mNameFull = findViewById(R.id.name_full);
         mCompanyName = findViewById(R.id.company_name);
         mAddressHomeLine1 = findViewById(R.id.address_home_line_1);
         mAddressHomeLine2 = findViewById(R.id.address_home_line_2);
@@ -75,16 +71,8 @@
         mEmailAddress = findViewById(R.id.email_address);
     }
 
-    public ChipView getNameFirst() {
-        return mNameFirst;
-    }
-
-    public ChipView getNameMiddle() {
-        return mNameMiddle;
-    }
-
-    public ChipView getNameLast() {
-        return mNameLast;
+    public ChipView getNameFull() {
+        return mNameFull;
     }
 
     public ChipView getCompanyName() {
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
index 4ef49e9..dda8664 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
@@ -46,18 +46,16 @@
 
         @Override
         protected void bind(KeyboardAccessoryData.UserInfo info, AddressAccessoryInfoView view) {
-            bindChipView(view.getNameFirst(), info.getFields().get(0));
-            bindChipView(view.getNameMiddle(), info.getFields().get(1));
-            bindChipView(view.getNameLast(), info.getFields().get(2));
-            bindChipView(view.getCompanyName(), info.getFields().get(3));
-            bindChipView(view.getAddressHomeLine1(), info.getFields().get(4));
-            bindChipView(view.getAddressHomeLine2(), info.getFields().get(5));
-            bindChipView(view.getAddressHomeZip(), info.getFields().get(6));
-            bindChipView(view.getAddressHomeCity(), info.getFields().get(7));
-            bindChipView(view.getAddressHomeState(), info.getFields().get(8));
-            bindChipView(view.getAddressHomeCountry(), info.getFields().get(9));
-            bindChipView(view.getPhoneHomeWholeNumber(), info.getFields().get(10));
-            bindChipView(view.getEmailAddress(), info.getFields().get(11));
+            bindChipView(view.getNameFull(), info.getFields().get(0));
+            bindChipView(view.getCompanyName(), info.getFields().get(1));
+            bindChipView(view.getAddressHomeLine1(), info.getFields().get(2));
+            bindChipView(view.getAddressHomeLine2(), info.getFields().get(3));
+            bindChipView(view.getAddressHomeZip(), info.getFields().get(4));
+            bindChipView(view.getAddressHomeCity(), info.getFields().get(5));
+            bindChipView(view.getAddressHomeState(), info.getFields().get(6));
+            bindChipView(view.getAddressHomeCountry(), info.getFields().get(7));
+            bindChipView(view.getPhoneHomeWholeNumber(), info.getFields().get(8));
+            bindChipView(view.getEmailAddress(), info.getFields().get(9));
         }
 
         void bindChipView(ChipView chip, UserInfoField field) {
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
index 63ce6aed2..4b069887 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
@@ -114,7 +114,7 @@
 
         // Focus the field to bring up the accessory.
         mHelper.focusPasswordField();
-        mHelper.waitForKeyboardAccessoryToBeShown(true);
+        mHelper.waitForKeyboardAccessoryToBeShown();
 
         // Click the tab to show the sheet and hide the keyboard.
         whenDisplayed(allOf(withContentDescription(R.string.address_accessory_sheet_toggle),
@@ -142,9 +142,10 @@
         whenDisplayed(withId(R.id.addresses_sheet));
 
         // Click a suggestion.
-        whenDisplayed(withText("McSpartangregor")).perform(click());
+        whenDisplayed(withText("Marcus McSpartangregor")).perform(click());
 
-        CriteriaHelper.pollInstrumentationThread(
-                () -> { return mHelper.getFieldText("NAME_FIRST").equals("McSpartangregor"); });
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            return mHelper.getFieldText("NAME_FIRST").equals("Marcus McSpartangregor");
+        });
     }
 }
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
index 8a0d98e..1fddc52b 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
@@ -115,9 +115,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.add(new AccessorySheetDataPiece(
                     createInfo(
-                            /*nameFirst=*/"Maya",
-                            /*nameMiddle=*/"J.",
-                            /*nameLast=*/"Park",
+                            /*nameFull=*/"Maya J. Park",
                             /*companyName=*/"",
                             /*addressHomeLine1=*/"100 Test Str.",
                             /*addressHomeLine2=*/"",
@@ -138,9 +136,7 @@
         CriteriaHelper.pollUiThread(() -> mView.get().getChildCount() > 0);
 
         // Check that the titles are correct:
-        assertThat(getChipText(R.id.name_first), is("Maya"));
-        assertThat(getChipText(R.id.name_middle), is("J."));
-        assertThat(getChipText(R.id.name_last), is("Park"));
+        assertThat(getChipText(R.id.name_full), is("Maya J. Park"));
         assertThat(getChipText(R.id.company_name), is(""));
         assertThat(getChipText(R.id.address_home_line_1), is("100 Test Str."));
         assertThat(getChipText(R.id.address_home_line_2), is(""));
@@ -155,25 +151,20 @@
         assertThat(findChipView(R.id.company_name).isShown(), is(false));
 
         // Chips are clickable:
-        TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.name_first)::performClick);
+        TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.name_full)::performClick);
         assertThat(clicked.get(), is(true));
         clicked.set(false);
         TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.email_address)::performClick);
         assertThat(clicked.get(), is(true));
     }
 
-    private UserInfo createInfo(String nameFirst, String nameMiddle, String nameLast,
-            String companyName, String addressHomeLine1, String addressHomeLine2,
-            String addressHomeZip, String addressHomeCity, String addressHomeState,
-            String addressHomeCountry, String phoneHomeWholeNumber, String emailAddress,
-            AtomicBoolean clickRecorder) {
+    private UserInfo createInfo(String nameFull, String companyName, String addressHomeLine1,
+            String addressHomeLine2, String addressHomeZip, String addressHomeCity,
+            String addressHomeState, String addressHomeCountry, String phoneHomeWholeNumber,
+            String emailAddress, AtomicBoolean clickRecorder) {
         UserInfo info = new UserInfo("", null);
-        info.addField(new UserInfoField(
-                nameFirst, nameFirst, "", false, item -> clickRecorder.set(true)));
-        info.addField(new UserInfoField(
-                nameMiddle, nameMiddle, "", false, item -> clickRecorder.set(true)));
         info.addField(
-                new UserInfoField(nameLast, nameLast, "", false, item -> clickRecorder.set(true)));
+                new UserInfoField(nameFull, nameFull, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(
                 companyName, companyName, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
index 0dc6ce19..f3ef0c3 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
@@ -119,7 +119,7 @@
 
         // Focus the field to bring up the accessory.
         mHelper.focusPasswordField();
-        mHelper.waitForKeyboardAccessoryToBeShown(true);
+        mHelper.waitForKeyboardAccessoryToBeShown();
 
         // Click the tab to show the sheet and hide the keyboard.
         whenDisplayed(allOf(withContentDescription(R.string.credit_card_accessory_sheet_toggle),
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
index 1a268a5..29f9031f 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
@@ -142,6 +142,6 @@
 
     @NativeMethods
     /* package */ interface Natives {
-        void onUserConsentResult(long nativeArcoreConsentPrompt, boolean allowed);
+        void onUserConsentResult(long nativeArCoreConsentPrompt, boolean allowed);
     }
 }
diff --git a/chrome/android/java/res/layout/location_status_icon.xml b/chrome/android/java/res/layout/location_status_icon.xml
index 7f23056..0310480 100644
--- a/chrome/android/java/res/layout/location_status_icon.xml
+++ b/chrome/android/java/res/layout/location_status_icon.xml
@@ -8,11 +8,8 @@
 
     <org.chromium.ui.widget.ChromeImageButton
         android:id="@+id/location_bar_status_icon"
-        style="@style/LocationBarButton"
-        android:layout_width="@dimen/location_bar_start_icon_width"
-        android:layout_height="match_parent"
+        style="@style/OmniboxIcon"
         android:layout_gravity="center"
-        android:paddingEnd="4dp"
         android:src="@android:color/transparent"
         android:visibility="gone"
         android:contentDescription="@string/accessibility_toolbar_btn_site_info"
diff --git a/chrome/android/java/res/layout/new_tab_page_layout.xml b/chrome/android/java/res/layout/new_tab_page_layout.xml
index 9a138cd6..7ab7a8b 100644
--- a/chrome/android/java/res/layout/new_tab_page_layout.xml
+++ b/chrome/android/java/res/layout/new_tab_page_layout.xml
@@ -40,6 +40,13 @@
         android:background="@drawable/ntp_search_box"
         android:orientation="horizontal"
         android:paddingStart="@dimen/location_bar_lateral_padding" >
+        <ImageView
+            style="@style/OmniboxIcon"
+            android:id="@+id/search_engine_logo"
+            android:visibility="gone"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_logo_googleg_24dp"
+            android:scaleType="centerInside" />
         <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
         <EditText
             tools:ignore="Autofill,LabelFor"
diff --git a/chrome/android/java/res/layout/revamped_context_menu_header.xml b/chrome/android/java/res/layout/revamped_context_menu_header.xml
index 47916bd..2cdbba33 100644
--- a/chrome/android/java/res/layout/revamped_context_menu_header.xml
+++ b/chrome/android/java/res/layout/revamped_context_menu_header.xml
@@ -13,9 +13,7 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:paddingStart="@dimen/revamped_context_menu_list_lateral_padding"
-    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding"
-    android:paddingTop="@dimen/revamped_context_menu_header_vertical_padding"
-    android:paddingBottom="@dimen/revamped_context_menu_header_vertical_padding">
+    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding">
 
     <FrameLayout
         android:layout_width="wrap_content"
@@ -28,7 +26,9 @@
             android:background="@drawable/tile_view_icon_background_modern"
             android:layout_width="@dimen/revamped_context_menu_header_circle_bg_diameter"
             android:layout_height="@dimen/revamped_context_menu_header_circle_bg_diameter"
-            android:layout_margin="@dimen/revamped_context_menu_header_circle_bg_margin"
+            android:layout_marginTop="@dimen/revamped_context_menu_header_circle_bg_vertical_margin"
+            android:layout_marginStart="@dimen/revamped_context_menu_header_circle_bg_lateral_margin"
+            android:layout_marginEnd="@dimen/revamped_context_menu_header_circle_bg_lateral_margin"
             android:visibility="invisible" />
 
         <org.chromium.ui.widget.RoundedCornerImageView
@@ -37,6 +37,8 @@
             android:layout_height="@dimen/revamped_context_menu_header_image_max_size"
             android:scaleType="centerInside"
             android:importantForAccessibility="no"
+            android:layout_marginTop="@dimen/revamped_context_menu_header_vertical_padding"
+            android:layout_marginBottom="@dimen/revamped_context_menu_header_vertical_padding"
             app:cornerRadiusTopStart="@dimen/default_rounded_corner_radius"
             app:cornerRadiusTopEnd="@dimen/default_rounded_corner_radius"
             app:cornerRadiusBottomStart="@dimen/default_rounded_corner_radius"
@@ -45,10 +47,13 @@
     </FrameLayout>
 
     <LinearLayout
+        android:id="@+id/title_and_url"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:layout_gravity="center_vertical">
+        android:layout_gravity="center_vertical"
+        android:paddingTop="@dimen/revamped_context_menu_header_vertical_padding"
+        android:paddingBottom="@dimen/revamped_context_menu_header_vertical_padding">
 
         <org.chromium.ui.widget.TextViewWithLeading
             android:id="@+id/menu_header_title"
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index 8f9ac197..88c1c9a 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -1009,6 +1009,12 @@
         <item name="tint">@color/default_icon_color</item>
     </style>
 
+    <style name="OmniboxIcon" parent="LocationBarButton">
+        <item name="android:layout_width">@dimen/location_bar_start_icon_width</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginEnd">4dp</item>
+    </style>
+
     <!-- Revamped context menu -->
     <style name="RevampedContextMenuItemText">
         <item name="android:background">?attr/selectableItemBackground</item>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index dded95b..5582f203 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -573,7 +573,8 @@
     <dimen name="revamped_context_menu_divider_padding">8dp</dimen>
     <dimen name="revamped_context_menu_header_image_max_size">60dp</dimen>
     <dimen name="revamped_context_menu_header_circle_bg_diameter">48dp</dimen>
-    <dimen name="revamped_context_menu_header_circle_bg_margin">6dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_lateral_margin">6dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_vertical_margin">22dp</dimen>
     <dimen name="revamped_context_menu_header_monogram_text_size">16dp</dimen>
     <dimen name="revamped_context_menu_header_monogram_size">26dp</dimen>
     <!-- Reader Mode dimensions -->
diff --git a/chrome/android/java/res/xml/contextual_search_preferences.xml b/chrome/android/java/res/xml/contextual_search_preferences.xml
index f20942c4..4bd9f7b 100644
--- a/chrome/android/java/res/xml/contextual_search_preferences.xml
+++ b/chrome/android/java/res/xml/contextual_search_preferences.xml
@@ -3,17 +3,17 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<PreferenceScreen
+<android.support.v7.preference.PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <org.chromium.chrome.browser.preferences.ChromeSwitchPreference
+    <org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat
         android:key="contextual_search_switch"
         android:summaryOn="@string/text_on"
-        android:summaryOff="@string/text_off"
-        app:drawDivider="true" />
+        android:summaryOff="@string/text_off" />
 
-    <org.chromium.chrome.browser.preferences.TextMessagePreference
-        android:title="@string/contextual_search_description" />
+    <org.chromium.chrome.browser.preferences.TextMessagePreferenceCompat
+        android:title="@string/contextual_search_description"
+        app:allowDividerBelow="false" />
 
-</PreferenceScreen>
+</android.support.v7.preference.PreferenceScreen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
index cf2a02f..9fcf46b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
@@ -121,12 +121,12 @@
 
             // Record via UMA all modules that have been requested and are currently installed. This
             // will tell us the install penetration of each module over time.
-            ModuleInstaller.recordModuleAvailability();
+            ModuleInstaller.getInstance().recordModuleAvailability();
         }
 
         // Write installed modules to crash keys. This needs to be done as early as possible so that
         // these values are set before any crashes are reported.
-        ModuleInstaller.updateCrashKeys();
+        ModuleInstaller.getInstance().updateCrashKeys();
 
         BuildInfo.setFirebaseAppId(FirebaseConfig.getFirebaseAppId());
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 81433226..1c1b4a07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -284,6 +284,7 @@
     public static final String OMNIBOX_SHOW_SUGGESTION_FAVICONS =
             "OmniboxUIExperimentShowSuggestionFavicons";
     public static final String OMNIBOX_SPARE_RENDERER = "OmniboxSpareRenderer";
+    public static final String OMNIBOX_SEARCH_ENGINE_LOGO = "OmniboxSearchEngineLogo";
     public static final String OVERLAY_NEW_LAYOUT = "OverlayNewLayout";
     public static final String OVERSCROLL_HISTORY_NAVIGATION = "OverscrollHistoryNavigation";
     public static final String PASSWORD_EDITING_ANDROID = "PasswordEditingAndroid";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
index 15343d48..e742931 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
@@ -773,6 +773,7 @@
         List<String> names = new ArrayList<>();
         List<String> shortNames = new ArrayList<>();
         List<String> packageNames = new ArrayList<>();
+        List<String> ids = new ArrayList<>();
         List<Integer> shellApkVersions = new ArrayList<>();
         List<Integer> versionCodes = new ArrayList<>();
         List<String> uris = new ArrayList<>();
@@ -785,6 +786,7 @@
         List<Long> backgroundColors = new ArrayList<>();
         List<Long> lastUpdateCheckTimesMs = new ArrayList<>();
         List<Boolean> relaxUpdates = new ArrayList<>();
+        List<String> updateStatuses = new ArrayList<>();
 
         Context context = ContextUtils.getApplicationContext();
         PackageManager packageManager = context.getPackageManager();
@@ -799,6 +801,7 @@
                     names.add(webApkInfo.name());
                     shortNames.add(webApkInfo.shortName());
                     packageNames.add(webApkInfo.webApkPackageName());
+                    ids.add(webApkInfo.id());
                     shellApkVersions.add(webApkInfo.shellApkVersion());
                     versionCodes.add(packageInfo.versionCode);
                     uris.add(webApkInfo.uri().toString());
@@ -821,12 +824,13 @@
                     }
                     lastUpdateCheckTimesMs.add(lastUpdateCheckTimeMsForStorage);
                     relaxUpdates.add(relaxUpdatesForStorage);
+                    updateStatuses.add(storage.getUpdateStatus());
                 }
             }
         }
         nativeOnWebApksRetrieved(callbackPointer, names.toArray(new String[0]),
                 shortNames.toArray(new String[0]), packageNames.toArray(new String[0]),
-                CollectionUtil.integerListToIntArray(shellApkVersions),
+                ids.toArray(new String[0]), CollectionUtil.integerListToIntArray(shellApkVersions),
                 CollectionUtil.integerListToIntArray(versionCodes), uris.toArray(new String[0]),
                 scopes.toArray(new String[0]), manifestUrls.toArray(new String[0]),
                 manifestStartUrls.toArray(new String[0]),
@@ -835,13 +839,23 @@
                 CollectionUtil.longListToLongArray(themeColors),
                 CollectionUtil.longListToLongArray(backgroundColors),
                 CollectionUtil.longListToLongArray(lastUpdateCheckTimesMs),
-                CollectionUtil.booleanListToBooleanArray(relaxUpdates));
+                CollectionUtil.booleanListToBooleanArray(relaxUpdates),
+                updateStatuses.toArray(new String[0]));
+    }
+
+    @CalledByNative
+    public static void setForceWebApkUpdate(String id) {
+        WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
+        if (storage != null) {
+            storage.setShouldForceUpdate(true);
+        }
     }
 
     private static native void nativeOnWebappDataStored(long callbackPointer);
     private static native void nativeOnWebApksRetrieved(long callbackPointer, String[] names,
-            String[] shortNames, String[] packageName, int[] shellApkVersions, int[] versionCodes,
-            String[] uris, String[] scopes, String[] manifestUrls, String[] manifestStartUrls,
-            int[] displayModes, int[] orientations, long[] themeColors, long[] backgroundColors,
-            long[] lastUpdateCheckTimesMs, boolean[] relaxUpdates);
+            String[] shortNames, String[] packageNames, String[] ids, int[] shellApkVersions,
+            int[] versionCodes, String[] uris, String[] scopes, String[] manifestUrls,
+            String[] manifestStartUrls, int[] displayModes, int[] orientations, long[] themeColors,
+            long[] backgroundColors, long[] lastUpdateCheckTimesMs, boolean[] relaxUpdates,
+            String[] updateStatuses);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
index f28bcc3..2955971 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
@@ -386,7 +386,7 @@
         new Handler().post(new Runnable() {
             @Override
             public void run() {
-                PreferencesLauncher.launchSettingsPage(
+                PreferencesLauncher.launchSettingsPageCompat(
                         getContext(), ContextualSearchPreferenceFragment.class);
             }
         });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
index 1470c4d6..420e5a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
@@ -34,7 +34,7 @@
                             ? View.GONE
                             : View.VISIBLE);
         } else if (propertyKey == RevampedContextMenuHeaderProperties.URL_CLICK_LISTENER) {
-            view.findViewById(R.id.menu_header_url)
+            view.findViewById(R.id.title_and_url)
                     .setOnClickListener(
                             model.get(RevampedContextMenuHeaderProperties.URL_CLICK_LISTENER));
         } else if (propertyKey == RevampedContextMenuHeaderProperties.URL_MAX_LINES) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
index 913a37a..44e5778 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.Manifest;
 import android.app.Activity;
@@ -26,6 +26,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.location.LocationUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
index 572c3126..bcdb010e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.app.Dialog;
@@ -27,6 +27,7 @@
 import org.chromium.base.annotations.JCaller;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.util.MathUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
index ebd7bd1..42710ea2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
index 2e907e2..8b2ffe4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.graphics.drawable.Drawable;
 import android.os.Build;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
similarity index 91%
rename from chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
index 4ff29f0..77205cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.app.Dialog;
@@ -18,6 +18,7 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
+import android.widget.AdapterView;
 import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.ListView;
@@ -26,6 +27,7 @@
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.MathUtils;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.widget.TextViewWithClickableSpans;
@@ -170,20 +172,40 @@
         mConfirmButton = (Button) dialogContainer.findViewById(R.id.positive);
         mConfirmButton.setText(labels.positiveButton);
         mConfirmButton.setEnabled(false);
-        mConfirmButton.setOnClickListener(v -> {
-            mItemSelectedCallback.onItemSelected(mItemAdapter.getSelectedItemKey());
-            mDialog.setOnDismissListener(null);
-            mDialog.dismiss();
-        });
+
+        View.OnClickListener clickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mItemSelectedCallback.onItemSelected(mItemAdapter.getSelectedItemKey());
+                mDialog.setOnDismissListener(null);
+                mDialog.dismiss();
+            }
+        };
 
         mItemAdapter = new DeviceItemAdapter(
                 mActivity, /*itemsSelectable=*/true, R.layout.item_chooser_dialog_row);
         mItemAdapter.setNotifyOnChange(true);
         mItemAdapter.setObserver(this);
+
+        if (FeatureUtilities.isNoTouchModeEnabled()) {
+            // TODO(crbug.com/982869): ideally we would port to using the modal dialog
+            // manager. Until then, we will treat clicking on the items as selecting them.
+            mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                @Override
+                public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
+                    mItemAdapter.onItemClick(adapter, view, position, id);
+                    clickListener.onClick(null);
+                }
+            });
+            mConfirmButton.setVisibility(View.GONE);
+        } else {
+            mConfirmButton.setOnClickListener(clickListener);
+            mListView.setOnItemClickListener(mItemAdapter);
+        }
+
         mListView.setAdapter(mItemAdapter);
         mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mListView.setEmptyView(mEmptyMessage);
-        mListView.setOnItemClickListener(mItemAdapter);
         mListView.setDivider(null);
         setState(State.STARTING);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
new file mode 100644
index 0000000..298cd1c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
@@ -0,0 +1,2 @@
+juncai@chromium.org
+reillyg@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
index 02668b0..23cd8ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.text.SpannableString;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index a854524..2a6c9dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
+import org.chromium.chrome.browser.omnibox.LocationBarLayout;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
@@ -282,6 +283,16 @@
 
         manager.addDestructionObserver(NewTabPageLayout.this::onDestroy);
 
+        // Native will be initialized at this time (see NativePageFactory.java).
+        // TODO(crbug.com/982430): Check for changes to the DSE and react accordingly.
+        // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+        if (LocationBarLayout.shouldShowGoogleLogo()) {
+            ImageView logoView = findViewById(R.id.search_engine_logo);
+
+            assert logoView != null;
+            logoView.setVisibility(VISIBLE);
+        }
+
         mInitialized = true;
 
         TraceEvent.end(TAG + ".initialize()");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 6db6ed6..f5ae08cd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -32,6 +32,7 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.chrome.browser.locale.LocaleManager;
@@ -85,8 +86,6 @@
     protected ToolbarDataProvider mToolbarDataProvider;
     private ObserverList<UrlFocusChangeListener> mUrlFocusChangeListeners = new ObserverList<>();
 
-    protected boolean mNativeInitialized;
-
     private final List<Runnable> mDeferredNativeRunnables = new ArrayList<Runnable>();
 
     protected StatusViewCoordinator mStatusViewCoordinator;
@@ -96,11 +95,12 @@
     private WindowAndroid mWindowAndroid;
     private WindowDelegate mWindowDelegate;
 
-    private boolean mUrlHasFocus;
     protected boolean mUrlFocusChangeInProgress;
+    protected boolean mNativeInitialized;
+    protected boolean mShouldShowGoogleLogo;
+    private boolean mUrlHasFocus;
     private boolean mUrlFocusedFromFakebox;
     private boolean mUrlFocusedWithoutAnimations;
-
     private boolean mVoiceSearchEnabled;
 
     private OmniboxPrerender mOmniboxPrerender;
@@ -140,6 +140,7 @@
             return false;
         }
     }
+
     public LocationBarLayout(Context context, AttributeSet attrs) {
         this(context, attrs, R.layout.location_bar);
     }
@@ -289,6 +290,16 @@
     }
 
     /**
+     * Encapsulates complicated boolean check for reuse and readability.
+     */
+    public static boolean shouldShowGoogleLogo() {
+        return !LocaleManager.getInstance().needToCheckForSearchEnginePromo()
+                && TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()
+                && ChromeFeatureList.isInitialized()
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO);
+    }
+
+    /**
      * Handles native dependent initialization for this class.
      */
     @Override
@@ -308,6 +319,15 @@
         }
         mDeferredNativeRunnables.clear();
 
+        // Show the DSE logo when the url has focus.
+        // TODO(crbug.com/982430): Check for changes to the DSE and react accordingly.
+        mShouldShowGoogleLogo = LocationBarLayout.shouldShowGoogleLogo();
+        mStatusViewCoordinator.setShouldShowGoogleLogo(mShouldShowGoogleLogo);
+        mToolbarDataProvider.setShouldShowGoogleLogo(mShouldShowGoogleLogo);
+        if (mShouldShowGoogleLogo) {
+            mStatusViewCoordinator.setShowIconsWhenUrlFocused(true);
+        }
+
         updateVisualsForState();
 
         updateMicButtonVisibility(mUrlFocusChangePercent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index bd0dd563..7defcab6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -39,7 +39,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mFirstVisibleFocusedView = findViewById(R.id.url_bar);
+        // Assign the first visible view here only if it hasn't been set by the DSE icon experiment.
+        // See onNativeLibrary ready for when this variable is set for the DSE icon case.
+        mFirstVisibleFocusedView = mFirstVisibleFocusedView == null ? findViewById(R.id.url_bar)
+                                                                    : mFirstVisibleFocusedView;
 
         Rect delegateArea = new Rect();
         mUrlActionContainer.getHitRect(delegateArea);
@@ -49,6 +52,16 @@
         setTouchDelegate(touchDelegate);
     }
 
+    @Override
+    public void onNativeLibraryReady() {
+        super.onNativeLibraryReady();
+
+        // The search logo will be the first visible view when the google logo is showing.
+        if (mShouldShowGoogleLogo) {
+            mFirstVisibleFocusedView = findViewById(R.id.location_bar_status);
+        }
+    }
+
     /**
      * @return The first view visible when the location bar is focused.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index 4830f8d..37a8795 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -27,6 +27,7 @@
     private boolean mVerboseStatusSpaceAvailable;
     private boolean mPageIsPreview;
     private boolean mPageIsOffline;
+    private boolean mShouldShowGoogleLogo;
 
     private boolean mShowStatusIconWhenUrlFocused;
 
@@ -281,6 +282,14 @@
     }
 
     /**
+     * Turn on/off the google logo in the omnibox.
+     */
+    void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mShouldShowGoogleLogo = shouldShowGoogleLogo;
+        updateLocationBarIcon();
+    }
+
+    /**
      * Update selection of icon presented on the location bar.
      *
      * - Navigation button is:
@@ -301,10 +310,15 @@
 
         if (mUrlHasFocus) {
             if (mShowStatusIconWhenUrlFocused) {
-                icon = mFirstSuggestionIsSearchQuery ? R.drawable.omnibox_search
-                                                     : R.drawable.ic_omnibox_page;
-                tint = mNavigationIconTintRes;
-                description = R.string.accessibility_toolbar_btn_site_info;
+                if (mShouldShowGoogleLogo) {
+                    // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+                    icon = R.drawable.ic_logo_googleg_24dp;
+                } else {
+                    icon = mFirstSuggestionIsSearchQuery ? R.drawable.omnibox_search
+                                                         : R.drawable.ic_omnibox_page;
+                    tint = mNavigationIconTintRes;
+                    description = R.string.accessibility_toolbar_btn_site_info;
+                }
             }
         } else if (mSecurityIconRes != 0) {
             mIsSecurityButtonShown = true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
index f8151774..1fab538 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
@@ -194,4 +194,11 @@
     public void setFirstSuggestionIsSearchType(boolean firstSuggestionIsSearchQuery) {
         mMediator.setFirstSuggestionIsSearchType(firstSuggestionIsSearchQuery);
     }
+
+    /**
+     * Turn on/off the google logo in the omnibox.
+     */
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mMediator.setShouldShowGoogleLogo(shouldShowGoogleLogo);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
index 2d7de927..5d866a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
@@ -391,6 +391,11 @@
     }
 
     @Override
+    public void onVerificationError(String errorMessage) {
+        mCallback.onGetPaymentAppsError(errorMessage);
+    }
+
+    @Override
     public void onFinishedVerification() {
         mPendingVerifiersCount--;
         if (mPendingVerifiersCount != 0) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
index 06d181d..3946823 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
@@ -38,6 +38,12 @@
         void onPaymentAppCreated(PaymentApp paymentApp);
 
         /**
+         * Called when an error has occurred.
+         * @param errorMessage Developer facing error message.
+         */
+        void onGetPaymentAppsError(String errorMessage);
+
+        /**
          * Called when the factory is finished creating payment apps.
          */
         void onAllPaymentAppsCreated();
@@ -119,6 +125,11 @@
                 }
 
                 @Override
+                public void onGetPaymentAppsError(String errorMessage) {
+                    callback.onGetPaymentAppsError(errorMessage);
+                }
+
+                @Override
                 public void onAllPaymentAppsCreated() {
                     mPendingTasks.remove(additionalFactory);
                     if (mPendingTasks.isEmpty()) callback.onAllPaymentAppsCreated();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
index 8c0ec0b..d2e1dba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
@@ -70,6 +70,13 @@
         void onAllOriginsSupported(URI methodName);
 
         /**
+         * Called when a part of verification has failed.
+         *
+         * @param errorMessage Developer facing error message.
+         */
+        void onVerificationError(String errorMessage);
+
+        /**
          * Called when the manifest has been fully verified. No more valid apps or origins will
          * be found after this call.
          */
@@ -524,10 +531,12 @@
     }
 
     @Override
-    public void onManifestDownloadFailure() {
+    public void onManifestDownloadFailure(String errorMessage) {
         if (mAtLeastOneManifestFailedToDownloadOrParse) return;
         mAtLeastOneManifestFailedToDownloadOrParse = true;
 
+        mCallback.onVerificationError(errorMessage);
+
         if (mIsManifestCacheStaleOrUnusable) mCallback.onFinishedVerification();
         mCallback.onFinishedUsingResources();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 20bd792b..1b2eb70 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -409,7 +409,13 @@
     private PaymentResponseHelper mPaymentResponseHelper;
 
     /** If not empty, use this error message for rejecting PaymentRequest.show(). */
-    private String mProhibitedOriginOrInvalidSslErrorMessage;
+    private String mRejectShowErrorMessage;
+
+    /**
+     * True when Payment Request is invoked on a prohibited origin (e.g., blob:) or with an invalid
+     * SSL certificate (e.g., self-signed).
+     */
+    private boolean mIsProhibitedOriginOrInvalidSsl;
 
     /**
      * Builds the PaymentRequest service implementation.
@@ -488,8 +494,9 @@
         // TODO(crbug.com/978471): Improve architecture for handling prohibited origins and invalid
         // SSL certificates.
         if (!UrlUtil.isOriginAllowedToUseWebPaymentApis(mWebContents.getLastCommittedUrl())) {
-            mProhibitedOriginOrInvalidSslErrorMessage = ErrorStrings.PROHIBITED_ORIGIN;
-            Log.d(TAG, mProhibitedOriginOrInvalidSslErrorMessage);
+            mIsProhibitedOriginOrInvalidSsl = true;
+            mRejectShowErrorMessage = ErrorStrings.PROHIBITED_ORIGIN;
+            Log.d(TAG, mRejectShowErrorMessage);
             Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
             // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
             // "NotSupportedError".
@@ -500,10 +507,11 @@
         mJourneyLogger.setRequestedInformation(
                 mRequestShipping, mRequestPayerEmail, mRequestPayerPhone, mRequestPayerName);
 
-        mProhibitedOriginOrInvalidSslErrorMessage =
-                mDelegate.getInvalidSslCertificateErrorMessage(mWebContents);
-        if (!TextUtils.isEmpty(mProhibitedOriginOrInvalidSslErrorMessage)) {
-            Log.d(TAG, mProhibitedOriginOrInvalidSslErrorMessage);
+        assert mRejectShowErrorMessage == null;
+        mRejectShowErrorMessage = mDelegate.getInvalidSslCertificateErrorMessage(mWebContents);
+        if (!TextUtils.isEmpty(mRejectShowErrorMessage)) {
+            mIsProhibitedOriginOrInvalidSsl = true;
+            Log.d(TAG, mRejectShowErrorMessage);
             Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
             // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
             // "NotSupportedError".
@@ -811,6 +819,11 @@
     }
 
     @Override
+    public void onGetPaymentAppsError(String errorMessage) {
+        if (TextUtils.isEmpty(mRejectShowErrorMessage)) mRejectShowErrorMessage = errorMessage;
+    }
+
+    @Override
     public void onAllPaymentAppsCreated() {
         if (mClient == null) return;
 
@@ -2117,11 +2130,11 @@
             mJourneyLogger.setNotShown(mArePaymentMethodsSupported
                             ? NotShownReason.NO_MATCHING_PAYMENT_METHOD
                             : NotShownReason.NO_SUPPORTED_PAYMENT_METHOD);
-            if (!TextUtils.isEmpty(mProhibitedOriginOrInvalidSslErrorMessage)) {
+            if (mIsProhibitedOriginOrInvalidSsl) {
                 if (mNativeObserverForTest != null) mNativeObserverForTest.onNotSupportedError();
                 // Chrome always refuses payments with invalid SSL and in prohibited origin types.
-                disconnectFromClientWithDebugMessage(mProhibitedOriginOrInvalidSslErrorMessage,
-                        PaymentErrorReason.NOT_SUPPORTED);
+                disconnectFromClientWithDebugMessage(
+                        mRejectShowErrorMessage, PaymentErrorReason.NOT_SUPPORTED);
             } else if (mIsIncognito) {
                 // If the user is in the incognito mode, hide the absence of their payment methods
                 // from the merchant site.
@@ -2130,7 +2143,10 @@
             } else {
                 if (mNativeObserverForTest != null) mNativeObserverForTest.onNotSupportedError();
                 disconnectFromClientWithDebugMessage(
-                        ErrorStrings.GENERIC_PAYMENT_METHOD_NOT_SUPPORTED_MESSAGE,
+                        ErrorStrings.GENERIC_PAYMENT_METHOD_NOT_SUPPORTED_MESSAGE
+                                + (TextUtils.isEmpty(mRejectShowErrorMessage)
+                                                ? ""
+                                                : " " + mRejectShowErrorMessage),
                         PaymentErrorReason.NOT_SUPPORTED);
             }
             if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index 504944c..913e3b13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -403,6 +403,14 @@
     }
 
     @CalledByNative
+    private static void onGetPaymentAppsError(
+            PaymentAppFactory.PaymentAppCreatedCallback callback, String errorMessage) {
+        ThreadUtils.assertOnUiThread();
+
+        callback.onGetPaymentAppsError(errorMessage);
+    }
+
+    @CalledByNative
     private static void onHasServiceWorkerPaymentApps(
             HasServiceWorkerPaymentAppsCallback callback, boolean hasPaymentApps) {
         ThreadUtils.assertOnUiThread();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
index 07e93f1..7a667fca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
@@ -4,44 +4,42 @@
 
 package org.chromium.chrome.browser.preferences.autofill_assistant;
 
+import android.content.Context;
 import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceFragmentCompat;
+import android.support.v7.preference.PreferenceScreen;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 
 /** The "Autofill Assistant" preferences screen in Settings. */
-public class AutofillAssistantPreferences extends PreferenceFragment {
+public class AutofillAssistantPreferences extends PreferenceFragmentCompat {
     /** Autofill Assistant switch preference key name. */
     public static final String PREF_AUTOFILL_ASSISTANT_SWITCH = "autofill_assistant_switch";
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         getActivity().setTitle(R.string.prefs_autofill_assistant_title);
-        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+
+        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getStyledContext());
+        setPreferenceScreen(screen);
         createAutofillAssistantSwitch();
     }
 
     private void createAutofillAssistantSwitch() {
-        ChromeSwitchPreference autofillAssistantSwitch =
-                new ChromeSwitchPreference(getActivity(), null);
+        ChromeSwitchPreferenceCompat autofillAssistantSwitch =
+                new ChromeSwitchPreferenceCompat(getStyledContext(), null);
         autofillAssistantSwitch.setKey(PREF_AUTOFILL_ASSISTANT_SWITCH);
         autofillAssistantSwitch.setTitle(R.string.prefs_autofill_assistant_switch);
         autofillAssistantSwitch.setSummaryOn(R.string.text_on);
         autofillAssistantSwitch.setSummaryOff(R.string.text_off);
-        autofillAssistantSwitch.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                ContextUtils.getAppSharedPreferences()
-                        .edit()
-                        .putBoolean(PREF_AUTOFILL_ASSISTANT_SWITCH, (boolean) newValue)
-                        .apply();
-                return true;
-            }
+        autofillAssistantSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+            ContextUtils.getAppSharedPreferences()
+                    .edit()
+                    .putBoolean(PREF_AUTOFILL_ASSISTANT_SWITCH, (boolean) newValue)
+                    .apply();
+            return true;
         });
         getPreferenceScreen().addPreference(autofillAssistantSwitch);
 
@@ -52,4 +50,8 @@
         autofillAssistantSwitch.setChecked(ContextUtils.getAppSharedPreferences().getBoolean(
                 PREF_AUTOFILL_ASSISTANT_SWITCH, true));
     }
+
+    private Context getStyledContext() {
+        return getPreferenceManager().getContext();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
index 10355f7e..3988888 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
@@ -5,26 +5,22 @@
 package org.chromium.chrome.browser.preferences.privacy;
 
 import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceFragmentCompat;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.preferences.PreferenceUtils;
 
 /**
  * Fragment to manage the Contextual Search preference and to explain to the user what it does.
  */
-public class ContextualSearchPreferenceFragment extends PreferenceFragment {
-
+public class ContextualSearchPreferenceFragment extends PreferenceFragmentCompat {
     private static final String PREF_CONTEXTUAL_SEARCH_SWITCH = "contextual_search_switch";
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         PreferenceUtils.addPreferencesFromResource(this, R.xml.contextual_search_preferences);
         getActivity().setTitle(R.string.contextual_search_title);
         setHasOptionsMenu(true);
@@ -32,23 +28,19 @@
     }
 
     private void initContextualSearchSwitch() {
-        ChromeSwitchPreference contextualSearchSwitch =
-                (ChromeSwitchPreference) findPreference(PREF_CONTEXTUAL_SEARCH_SWITCH);
+        ChromeSwitchPreferenceCompat contextualSearchSwitch =
+                (ChromeSwitchPreferenceCompat) findPreference(PREF_CONTEXTUAL_SEARCH_SWITCH);
 
         boolean isContextualSearchEnabled =
                 !PrefServiceBridge.getInstance().isContextualSearchDisabled();
         contextualSearchSwitch.setChecked(isContextualSearchEnabled);
 
-        contextualSearchSwitch.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                PrefServiceBridge.getInstance().setContextualSearchState((boolean) newValue);
-                ContextualSearchUma.logPreferenceChange((boolean) newValue);
-                return true;
-            }
+        contextualSearchSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+            PrefServiceBridge.getInstance().setContextualSearchState((boolean) newValue);
+            ContextualSearchUma.logPreferenceChange((boolean) newValue);
+            return true;
         });
         contextualSearchSwitch.setManagedPreferenceDelegate(
                 preference -> PrefServiceBridge.getInstance().isContextualSearchDisabledByPolicy());
     }
-
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
index cde33f2..8d645c1e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
@@ -123,4 +123,7 @@
     public @ColorRes int getSecurityIconColorStateList() {
         return 0;
     }
+
+    @Override
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
index 5276129..4e6de776 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
@@ -85,21 +85,25 @@
 
     @Override
     public void addBlacklistedUrl(String url) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeAddOrRemoveBlacklistedUrl(mNativeMostVisitedSitesBridge, url, true);
     }
 
     @Override
     public void removeBlacklistedUrl(String url) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeAddOrRemoveBlacklistedUrl(mNativeMostVisitedSitesBridge, url, false);
     }
 
     @Override
     public void recordPageImpression(int tilesCount) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordPageImpression(mNativeMostVisitedSitesBridge, tilesCount);
     }
 
     @Override
     public void recordTileImpression(Tile tile) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordTileImpression(mNativeMostVisitedSitesBridge, tile.getIndex(), tile.getType(),
                 tile.getIconType(), tile.getTitleSource(), tile.getSource(),
                 tile.getData().dataGenerationTime.getTime(), tile.getUrl());
@@ -107,6 +111,7 @@
 
     @Override
     public void recordOpenedMostVisitedItem(Tile tile) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordOpenedMostVisitedItem(mNativeMostVisitedSitesBridge, tile.getIndex(),
                 tile.getType(), tile.getTitleSource(), tile.getSource(),
                 tile.getData().dataGenerationTime.getTime());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
index 01fe3ca2f..c787370 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
@@ -460,9 +460,10 @@
      * thread.
      * @param bundle Bundle to write the tab's state to.
      * @param state State object obtained from from {@link Tab#getState()}.
+     * @return Whether the tab state was successfully saved.
      */
-    public static void saveState(Bundle bundle, TabState state) {
-        if (state == null || state.contentsState == null) return;
+    public static boolean saveState(Bundle bundle, TabState state) {
+        if (state == null || state.contentsState == null) return false;
 
         byte[] contentsStateBytes = getContentStateByteArray(state.contentsState.buffer());
 
@@ -479,6 +480,7 @@
         bundle.putInt(VERSION, state.contentsState.version());
         bundle.putInt(THEME_COLOR, state.themeColor);
         bundle.putBoolean(IS_INCOGNITO, state.isIncognito());
+        return true;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index d94d1e8..e92934f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -47,11 +47,13 @@
     private final Context mContext;
 
     private Tab mTab;
-    private boolean mIsIncognito;
     private int mPrimaryColor;
+    private OverviewModeBehavior mOverviewModeBehavior;
+
+    private boolean mIsIncognito;
     private boolean mIsUsingBrandColor;
     private boolean mShouldShowOmniboxInOverviewMode;
-    private OverviewModeBehavior mOverviewModeBehavior;
+    private boolean mShouldShowGoogleLogo;
 
     private long mNativeLocationBarModelAndroid;
 
@@ -362,8 +364,17 @@
         // If we're showing a query in the omnibox, and the security level is high enough to show
         // the search icon, return that instead of the security icon.
         if (getDisplaySearchTerms() != null) {
-            return R.drawable.omnibox_search;
+            if (mShouldShowGoogleLogo) {
+                // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+                return R.drawable.ic_logo_googleg_24dp;
+            } else {
+                return R.drawable.omnibox_search;
+            }
+        } else if (getNewTabPageForCurrentTab() != null && mShouldShowGoogleLogo) {
+            // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+            return R.drawable.ic_logo_googleg_24dp;
         }
+
         return getSecurityIconResource(getSecurityLevel(), !isTablet, isOfflinePage(), isPreview());
     }
 
@@ -415,6 +426,12 @@
 
     @Override
     public @ColorRes int getSecurityIconColorStateList() {
+        // Don't apply tint to the search logo, which is shown on the NTP and the SRP pages.
+        if ((getNewTabPageForCurrentTab() != null || getDisplaySearchTerms() != null)
+                && mShouldShowGoogleLogo) {
+            return 0;
+        }
+
         int securityLevel = getSecurityLevel();
         int color = getPrimaryColor();
         boolean needLightIcon = ColorUtils.shouldUseLightForegroundOnBackground(color);
@@ -477,6 +494,11 @@
         return nativeGetURLForDisplay(mNativeLocationBarModelAndroid);
     }
 
+    @Override
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mShouldShowGoogleLogo = shouldShowGoogleLogo;
+    }
+
     private native long nativeInit();
     private native void nativeDestroy(long nativeLocationBarModelAndroid);
     private native String nativeGetFormattedFullURL(long nativeLocationBarModelAndroid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
index d4bcfa94..44e23ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
@@ -153,4 +153,9 @@
     default public String getDisplaySearchTerms() {
         return null;
     }
+
+    /**
+     * Turn on/off the google logo in the omnibox
+     */
+    void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 2593b38b..1e57a77a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -27,7 +27,6 @@
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -114,7 +113,6 @@
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
 import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -1698,22 +1696,34 @@
      * @param activityName Simple class name for the activity this toolbar belongs to.
      */
     public void onDeferredStartup(final long activityCreationTimeMs, final String activityName) {
-        // Record startup performance statistics
+        recordStartupHistograms(activityCreationTimeMs, activityName);
+        mLocationBar.onDeferredStartup();
+    }
+
+    /**
+     * Record histograms covering Chrome startup.
+     * This method will collect metrics no sooner than RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS since
+     * Activity creation to ensure availability of collected data.
+     *
+     * Histograms will not be collected if Chrome is destroyed before the above timeout passed.
+     */
+    private void recordStartupHistograms(
+            final long activityCreationTimeMs, final String activityName) {
+        // Schedule call to self if minimum time since activity creation has not yet passed.
         long elapsedTime = SystemClock.elapsedRealtime() - activityCreationTimeMs;
         if (elapsedTime < RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS) {
-            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
-                onDeferredStartup(activityCreationTimeMs, activityName);
-            }, RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime);
+            // clang-format off
+            mHandler.postDelayed(
+                    () -> recordStartupHistograms(activityCreationTimeMs, activityName),
+                    RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime);
+            // clang-format on
             return;
         }
+
         RecordHistogram.recordTimesHistogram("MobileStartup.ToolbarFirstDrawTime2." + activityName,
                 mToolbar.getFirstDrawTime() - activityCreationTimeMs);
 
-        // mOmniboxStartupMetrics might be null. ie. ToolbarManager is destroyed. See
-        // https://crbug.com/860449
-        if (mOmniboxStartupMetrics != null) mOmniboxStartupMetrics.maybeRecordHistograms();
-
-        mLocationBar.onDeferredStartup();
+        mOmniboxStartupMetrics.maybeRecordHistograms();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index 47c94f9f..c5dfdc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -315,6 +315,9 @@
             public @ColorRes int getSecurityIconColorStateList() {
                 return 0;
             }
+
+            @Override
+            public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
         };
 
         // Set menu button background in case it was previously called before inflation
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java
new file mode 100644
index 0000000..7394834
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java
@@ -0,0 +1,266 @@
+// 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.
+
+package org.chromium.chrome.browser.vr;
+
+import android.app.Activity;
+
+import org.chromium.base.BundleUtils;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
+import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
+import org.chromium.chrome.browser.modules.ModuleInstallUi;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.module_installer.ModuleInstaller;
+
+/**
+ * Installs AR DFM and ArCore runtimes.
+ */
+@JNINamespace("vr")
+public class ArCoreInstallUtils implements ModuleInstallUi.FailureUiListener {
+    private static final String TAG = "ArCoreInstallUtils";
+
+    private long mNativeArCoreInstallUtils;
+
+    private Tab mTab;
+
+    // Instance that requested installation of ARCore.
+    // Should be non-null only if there is a pending request to install ARCore.
+    private static ArCoreInstallUtils sRequestInstallInstance;
+
+    // Cached ArCoreShim instance - valid only after AR module was installed and
+    // getArCoreShimInstance() was called.
+    private static ArCoreShim sArCoreInstance;
+
+    private static ArCoreShim getArCoreShimInstance() {
+        if (sArCoreInstance != null) return sArCoreInstance;
+
+        try {
+            sArCoreInstance =
+                    (ArCoreShim) Class.forName("org.chromium.chrome.browser.vr.ArCoreShimImpl")
+                            .newInstance();
+        } catch (ClassNotFoundException e) {
+            // shouldn't happen - we should only call this method once AR module is installed.
+            throw new RuntimeException(e);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+
+        return sArCoreInstance;
+    }
+
+    private ArCoreInstallUtils(long nativeArCoreInstallUtils) {
+        mNativeArCoreInstallUtils = nativeArCoreInstallUtils;
+        // Need to be called before trying to access the AR module.
+        ModuleInstaller.getInstance().init();
+    }
+
+    @Override
+    public void onRetry() {
+        if (mNativeArCoreInstallUtils == 0) return;
+        requestInstallArModule(mTab);
+    }
+
+    @Override
+    public void onCancel() {
+        if (mNativeArCoreInstallUtils == 0) return;
+        ArCoreInstallUtilsJni.get().onRequestInstallArModuleResult(
+                mNativeArCoreInstallUtils, false);
+    }
+
+    @CalledByNative
+    private boolean canRequestInstallArModule() {
+        // We can only try to install the AR module if we are in a bundle mode.
+        return BundleUtils.isBundle();
+    }
+
+    @CalledByNative
+    private boolean shouldRequestInstallArModule() {
+        try {
+            // Try to find class in AR module that has not been obfuscated.
+            Class.forName("com.google.ar.core.ArCoreApk");
+            return false;
+        } catch (ClassNotFoundException e) {
+            return true;
+        }
+    }
+
+    @CalledByNative
+    private void requestInstallArModule(Tab tab) {
+        mTab = tab;
+        ModuleInstallUi ui = new ModuleInstallUi(mTab, R.string.ar_module_title, this);
+        ui.showInstallStartUi();
+        ModuleInstaller.getInstance().install("ar", success -> {
+            assert shouldRequestInstallArModule() != success;
+
+            if (success) {
+                // As per documentation, it's recommended to issue a call to
+                // ArCoreApk.checkAvailability() early in application lifecycle & ignore the result
+                // so that subsequent calls can return cached result:
+                // https://developers.google.com/ar/develop/java/enable-arcore
+                // This is as early in the app lifecycle as it gets for us - just after installing
+                // AR module.
+                getArCoreInstallStatus();
+            }
+
+            if (mNativeArCoreInstallUtils != 0) {
+                if (success) {
+                    ui.showInstallSuccessUi();
+                    ArCoreInstallUtilsJni.get().onRequestInstallArModuleResult(
+                            mNativeArCoreInstallUtils, success);
+                } else {
+                    ui.showInstallFailureUi();
+                    // early exit - user will be offered a choice to retry & install flow will
+                    // continue from onRetry / onCancel
+                    return;
+                }
+            }
+        });
+    }
+
+    private @ArCoreShim.Availability int getArCoreInstallStatus() {
+        return getArCoreShimInstance().checkAvailability(ContextUtils.getApplicationContext());
+    }
+
+    @CalledByNative
+    private boolean shouldRequestInstallSupportedArCore() {
+        @ArCoreShim.Availability
+        int availability = getArCoreInstallStatus();
+        // Skip ARCore installation if we are certain that it is already installed.
+        // In all other cases, we might as well try to install it and handle installation failures.
+        return availability != ArCoreShim.Availability.SUPPORTED_INSTALLED;
+    }
+
+    @CalledByNative
+    private void requestInstallSupportedArCore(final Tab tab) {
+        assert shouldRequestInstallSupportedArCore();
+
+        @ArCoreShim.Availability
+        int arCoreAvailability = getArCoreInstallStatus();
+        final Activity activity = tab.getActivity();
+        String infobarText = null;
+        String buttonText = null;
+        switch (arCoreAvailability) {
+            case ArCoreShim.Availability.UNSUPPORTED_DEVICE_NOT_CAPABLE:
+                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                break;
+            case ArCoreShim.Availability.UNKNOWN_CHECKING:
+            case ArCoreShim.Availability.UNKNOWN_ERROR:
+            case ArCoreShim.Availability.UNKNOWN_TIMED_OUT:
+            case ArCoreShim.Availability.SUPPORTED_NOT_INSTALLED:
+                infobarText = activity.getString(R.string.ar_core_check_infobar_install_text);
+                buttonText = activity.getString(R.string.app_banner_install);
+                break;
+            case ArCoreShim.Availability.SUPPORTED_APK_TOO_OLD:
+                infobarText = activity.getString(R.string.ar_core_check_infobar_update_text);
+                buttonText = activity.getString(R.string.update_from_market);
+                break;
+            case ArCoreShim.Availability.SUPPORTED_INSTALLED:
+                assert false;
+                break;
+        }
+
+        SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
+            @Override
+            public void onInfoBarDismissed() {
+                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(
+                        !shouldRequestInstallSupportedArCore());
+            }
+
+            @Override
+            public boolean onInfoBarButtonClicked(boolean isPrimary) {
+                try {
+                    assert sRequestInstallInstance == null;
+                    @ArCoreShim.InstallStatus
+                    int installStatus = getArCoreShimInstance().requestInstall(activity, true);
+
+                    if (installStatus == ArCoreShim.InstallStatus.INSTALL_REQUESTED) {
+                        // Install flow will resume in onArCoreRequestInstallReturned, mark that
+                        // there is active request. Native code notification will be deferred until
+                        // our activity gets resumed.
+                        sRequestInstallInstance = ArCoreInstallUtils.this;
+                    } else if (installStatus == ArCoreShim.InstallStatus.INSTALLED) {
+                        // No need to install - notify native code.
+                        maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
+                    }
+
+                } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
+                    sRequestInstallInstance = null;
+                    Log.w(TAG, "ARCore installation request failed with exception: %s",
+                            e.toString());
+
+                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
+                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                }
+
+                return false;
+            }
+
+            @Override
+            public boolean onInfoBarLinkClicked() {
+                return false;
+            }
+        };
+        // TODO(ijamardo, https://crbug.com/838833): Add icon for AR info bar.
+        SimpleConfirmInfoBarBuilder.create(tab, listener, InfoBarIdentifier.AR_CORE_UPGRADE_ANDROID,
+                R.drawable.ic_error_outline_googblue_24dp, infobarText, buttonText, null, null,
+                true);
+    }
+
+    /**
+     * Helper used to notify native code about the result of the request to install ARCore.
+     */
+    private void maybeNotifyNativeOnRequestInstallSupportedArCoreResult(boolean success) {
+        if (mNativeArCoreInstallUtils != 0) {
+            ArCoreInstallUtilsJni.get().onRequestInstallSupportedArCoreResult(
+                    mNativeArCoreInstallUtils, success);
+        }
+    }
+
+    private void onArCoreRequestInstallReturned(Activity activity) {
+        try {
+            // Since |userRequestedInstall| parameter is false, the below call should
+            // throw if ARCore is still not installed - no need to check the result.
+            getArCoreShimInstance().requestInstall(activity, false);
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
+        } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
+            Log.w(TAG, "Exception thrown when trying to validate install state of ARCore: %s",
+                    e.toString());
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+        } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+        }
+    }
+
+    /**
+     * This method should be called by the Activity that gets resumed.
+     * We are only interested in the cases where our current Activity got paused
+     * as a result of a call to ArCoreApk.requestInstall() method.
+     */
+    public static void onResumeActivityWithNative(Activity activity) {
+        if (sRequestInstallInstance != null) {
+            sRequestInstallInstance.onArCoreRequestInstallReturned(activity);
+            sRequestInstallInstance = null;
+        }
+    }
+
+    public static void installArCoreDeviceProviderFactory() {
+        ArCoreInstallUtilsJni.get().installArCoreDeviceProviderFactory();
+    }
+
+    @NativeMethods
+    /* package */ interface ArConsentPromptNative {
+        void onRequestInstallArModuleResult(long nativeArCoreConsentPrompt, boolean success);
+        void onRequestInstallSupportedArCoreResult(long nativeArCoreConsentPrompt, boolean success);
+        void installArCoreDeviceProviderFactory();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
index ad81f10..8b8dc7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
@@ -4,81 +4,31 @@
 
 package org.chromium.chrome.browser.vr;
 
-import android.app.Activity;
 import android.content.Context;
 import android.view.Surface;
 
 import dalvik.system.BaseDexClassLoader;
 
-import org.chromium.base.BundleUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
-import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
-import org.chromium.chrome.browser.modules.ModuleInstallUi;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.module_installer.ModuleInstaller;
 
 /**
  * Provides ARCore classes access to java-related app functionality.
  */
 @JNINamespace("vr")
-public class ArCoreJavaUtils implements ModuleInstallUi.FailureUiListener {
+public class ArCoreJavaUtils {
     private static final String TAG = "ArCoreJavaUtils";
     private static final boolean DEBUG_LOGS = false;
 
     private long mNativeArCoreJavaUtils;
-    private boolean mAppInfoInitialized;
-    private Tab mTab;
 
     private ArImmersiveOverlay mArImmersiveOverlay;
 
-    // Instance that requested installation of ARCore.
-    // Should be non-null only if there is a pending request to install ARCore.
-    private static ArCoreJavaUtils sRequestInstallInstance;
-
-    // Cached ArCoreShim instance - valid only after AR module was installed and
-    // getArCoreShimInstance() was called.
-    private static ArCoreShim sArCoreInstance;
-
-    private static ArCoreShim getArCoreShimInstance() {
-        if (sArCoreInstance != null) return sArCoreInstance;
-
-        try {
-            sArCoreInstance =
-                    (ArCoreShim) Class.forName("org.chromium.chrome.browser.vr.ArCoreShimImpl")
-                            .newInstance();
-        } catch (ClassNotFoundException e) {
-            // shouldn't happen - we should only call this method once AR module is installed.
-            throw new RuntimeException(e);
-        } catch (InstantiationException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-
-        return sArCoreInstance;
-    }
-
-    public static void installArCoreDeviceProviderFactory() {
-        nativeInstallArCoreDeviceProviderFactory();
-    }
-
-    /**
-     * Gets the current application context.
-     *
-     * @return Context The application context.
-     */
-    @CalledByNative
-    private static Context getApplicationContext() {
-        return ContextUtils.getApplicationContext();
-    }
-
     @CalledByNative
     private static ArCoreJavaUtils create(long nativeArCoreJavaUtils) {
         ThreadUtils.assertOnUiThread();
@@ -93,22 +43,19 @@
         }
     }
 
-    @Override
-    public void onRetry() {
-        if (mNativeArCoreJavaUtils == 0) return;
-        requestInstallArModule(mTab);
-    }
-
-    @Override
-    public void onCancel() {
-        if (mNativeArCoreJavaUtils == 0) return;
-        nativeOnRequestInstallArModuleResult(mNativeArCoreJavaUtils, /* success = */ false);
+    /**
+     * Gets the current application context.
+     *
+     * @return Context The application context.
+     */
+    @CalledByNative
+    private static Context getApplicationContext() {
+        return ContextUtils.getApplicationContext();
     }
 
     private ArCoreJavaUtils(long nativeArCoreJavaUtils) {
         if (DEBUG_LOGS) Log.i(TAG, "constructor, nativeArCoreJavaUtils=" + nativeArCoreJavaUtils);
         mNativeArCoreJavaUtils = nativeArCoreJavaUtils;
-        initializeAppInfo();
     }
 
     @CalledByNative
@@ -151,194 +98,6 @@
         mNativeArCoreJavaUtils = 0;
     }
 
-    private void initializeAppInfo() {
-        mAppInfoInitialized = true;
-
-        // Need to be called before trying to access the AR module.
-        ModuleInstaller.init();
-    }
-
-    private @ArCoreShim.Availability int getArCoreInstallStatus() {
-        return getArCoreShimInstance().checkAvailability(getApplicationContext());
-    }
-
-    @CalledByNative
-    private boolean shouldRequestInstallSupportedArCore() {
-        @ArCoreShim.Availability
-        int availability = getArCoreInstallStatus();
-        // Skip ARCore installation if we are certain that it is already installed.
-        // In all other cases, we might as well try to install it and handle installation failures.
-        return availability != ArCoreShim.Availability.SUPPORTED_INSTALLED;
-    }
-
-    @CalledByNative
-    private void requestInstallArModule(Tab tab) {
-        mTab = tab;
-        ModuleInstallUi ui = new ModuleInstallUi(mTab, R.string.ar_module_title, this);
-        ui.showInstallStartUi();
-        ModuleInstaller.install("ar", success -> {
-            assert shouldRequestInstallArModule() != success;
-
-            if (success) {
-                // As per documentation, it's recommended to issue a call to
-                // ArCoreApk.checkAvailability() early in application lifecycle & ignore the result
-                // so that subsequent calls can return cached result:
-                // https://developers.google.com/ar/develop/java/enable-arcore
-                // This is as early in the app lifecycle as it gets for us - just after installing
-                // AR module.
-                getArCoreInstallStatus();
-            }
-
-            if (mNativeArCoreJavaUtils != 0) {
-                if (success) {
-                    ui.showInstallSuccessUi();
-                    nativeOnRequestInstallArModuleResult(mNativeArCoreJavaUtils, success);
-                } else {
-                    ui.showInstallFailureUi();
-                    // early exit - user will be offered a choice to retry & install flow will
-                    // continue from onRetry / onCancel
-                    return;
-                }
-            }
-        });
-    }
-
-    @CalledByNative
-    private void requestInstallSupportedArCore(final Tab tab) {
-        assert shouldRequestInstallSupportedArCore();
-
-        @ArCoreShim.Availability
-        int arCoreAvailability = getArCoreInstallStatus();
-        final Activity activity = tab.getActivity();
-        String infobarText = null;
-        String buttonText = null;
-        switch (arCoreAvailability) {
-            case ArCoreShim.Availability.UNSUPPORTED_DEVICE_NOT_CAPABLE:
-                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                break;
-            case ArCoreShim.Availability.UNKNOWN_CHECKING:
-            case ArCoreShim.Availability.UNKNOWN_ERROR:
-            case ArCoreShim.Availability.UNKNOWN_TIMED_OUT:
-            case ArCoreShim.Availability.SUPPORTED_NOT_INSTALLED:
-                infobarText = activity.getString(R.string.ar_core_check_infobar_install_text);
-                buttonText = activity.getString(R.string.app_banner_install);
-                break;
-            case ArCoreShim.Availability.SUPPORTED_APK_TOO_OLD:
-                infobarText = activity.getString(R.string.ar_core_check_infobar_update_text);
-                buttonText = activity.getString(R.string.update_from_market);
-                break;
-            case ArCoreShim.Availability.SUPPORTED_INSTALLED:
-                assert false;
-                break;
-        }
-
-        SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
-            @Override
-            public void onInfoBarDismissed() {
-                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(
-                        !shouldRequestInstallSupportedArCore());
-            }
-
-            @Override
-            public boolean onInfoBarButtonClicked(boolean isPrimary) {
-                try {
-                    assert sRequestInstallInstance == null;
-                    @ArCoreShim.InstallStatus
-                    int installStatus = getArCoreShimInstance().requestInstall(activity, true);
-
-                    if (installStatus == ArCoreShim.InstallStatus.INSTALL_REQUESTED) {
-                        // Install flow will resume in onArCoreRequestInstallReturned, mark that
-                        // there is active request. Native code notification will be deferred until
-                        // our activity gets resumed.
-                        sRequestInstallInstance = ArCoreJavaUtils.this;
-                    } else if (installStatus == ArCoreShim.InstallStatus.INSTALLED) {
-                        // No need to install - notify native code.
-                        maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
-                    }
-
-                } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
-                    sRequestInstallInstance = null;
-                    Log.w(TAG, "ARCore installation request failed with exception: %s",
-                            e.toString());
-
-                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
-                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                }
-
-                return false;
-            }
-
-            @Override
-            public boolean onInfoBarLinkClicked() {
-                return false;
-            }
-        };
-        // TODO(ijamardo, https://crbug.com/838833): Add icon for AR info bar.
-        SimpleConfirmInfoBarBuilder.create(tab, listener, InfoBarIdentifier.AR_CORE_UPGRADE_ANDROID,
-                R.drawable.ic_error_outline_googblue_24dp, infobarText, buttonText, null, null,
-                true);
-    }
-
-    @CalledByNative
-    private boolean canRequestInstallArModule() {
-        // We can only try to install the AR module if we are in a bundle mode.
-        return BundleUtils.isBundle();
-    }
-
-    @CalledByNative
-    private boolean shouldRequestInstallArModule() {
-        try {
-            // Try to find class in AR module that has not been obfuscated.
-            Class.forName("com.google.ar.core.ArCoreApk");
-            return false;
-        } catch (ClassNotFoundException e) {
-            return true;
-        }
-    }
-
-    private void onArCoreRequestInstallReturned(Activity activity) {
-        try {
-            // Since |userRequestedInstall| parameter is false, the below call should
-            // throw if ARCore is still not installed - no need to check the result.
-            getArCoreShimInstance().requestInstall(activity, false);
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
-        } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
-            Log.w(TAG, "Exception thrown when trying to validate install state of ARCore: %s",
-                    e.toString());
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-        } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-        }
-    }
-
-    /**
-     * This method should be called by the Activity that gets resumed.
-     * We are only interested in the cases where our current Activity got paused
-     * as a result of a call to ArCoreApk.requestInstall() method.
-     */
-    public static void onResumeActivityWithNative(Activity activity) {
-        if (sRequestInstallInstance != null) {
-            sRequestInstallInstance.onArCoreRequestInstallReturned(activity);
-            sRequestInstallInstance = null;
-        }
-    }
-
-    /**
-     * Helper used to notify native code about the result of the request to install ARCore.
-     */
-    private void maybeNotifyNativeOnRequestInstallSupportedArCoreResult(boolean success) {
-        if (mNativeArCoreJavaUtils != 0) {
-            nativeOnRequestInstallSupportedArCoreResult(mNativeArCoreJavaUtils, success);
-        }
-    }
-
-    private static native void nativeInstallArCoreDeviceProviderFactory();
-    private native void nativeOnRequestInstallArModuleResult(
-            long nativeArCoreJavaUtils, boolean success);
-    private native void nativeOnRequestInstallSupportedArCoreResult(
-            long nativeArCoreJavaUtils, boolean success);
-
     private native void nativeOnDrawingSurfaceReady(
             long nativeArCoreJavaUtils, Surface surface, int rotation, int width, int height);
     private native void nativeOnDrawingSurfaceTouch(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
index 9fc202e..500cb1c5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
@@ -19,11 +19,11 @@
 
     @Override
     public void init() {
-        ArCoreJavaUtils.installArCoreDeviceProviderFactory();
+        ArCoreInstallUtils.installArCoreDeviceProviderFactory();
     }
 
     @Override
     public void registerOnResumeActivity(Activity activity) {
-        ArCoreJavaUtils.onResumeActivityWithNative(activity);
+        ArCoreInstallUtils.onResumeActivityWithNative(activity);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index d66d815..d1275006 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -110,6 +110,9 @@
         @WebApkUpdateReason
         int updateReason = needsUpdate(mInfo, fetchedInfo, primaryIconUrl, badgeIconUrl);
         boolean needsUpgrade = (updateReason != WebApkUpdateReason.NONE);
+        if (mStorage.shouldForceUpdate() && needsUpgrade) {
+            updateReason = WebApkUpdateReason.MANUALLY_TRIGGERED;
+        }
         Log.i(TAG, "Got Manifest: " + gotManifest);
         Log.i(TAG, "WebAPK upgrade needed: " + needsUpgrade);
 
@@ -130,7 +133,7 @@
         }
 
         if (!needsUpgrade) {
-            if (!mStorage.didPreviousUpdateSucceed()) {
+            if (!mStorage.didPreviousUpdateSucceed() || mStorage.shouldForceUpdate()) {
                 onFinishedUpdate(mStorage, WebApkInstallResult.SUCCESS, false /* relaxUpdates */);
             }
             return;
@@ -178,18 +181,30 @@
     /** Schedules update for when WebAPK is not running. */
     private void scheduleUpdate() {
         WebApkUma.recordUpdateRequestQueued(1);
+        TaskInfo updateTask;
+        if (mStorage.shouldForceUpdate()) {
+            // Start an update task ASAP for forced updates.
+            updateTask = TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID,
+                                         WebApkUpdateTask.class, 0 /* windowEndTimeMs */)
+                                 .setUpdateCurrent(true)
+                                 .setIsPersisted(true)
+                                 .build();
+            mStorage.setUpdateScheduled(true);
+            mStorage.setShouldForceUpdate(false);
+        } else {
+            // The task deadline should be before {@link WebappDataStorage#RETRY_UPDATE_DURATION}
+            updateTask =
+                    TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID, WebApkUpdateTask.class,
+                                    DateUtils.HOUR_IN_MILLIS, DateUtils.HOUR_IN_MILLIS * 23)
+                            .setRequiredNetworkType(TaskInfo.NetworkType.UNMETERED)
+                            .setUpdateCurrent(true)
+                            .setIsPersisted(true)
+                            .setRequiresCharging(true)
+                            .build();
+        }
 
-        // The task deadline should be before {@link WebappDataStorage#RETRY_UPDATE_DURATION}
-        TaskInfo taskInfo =
-                TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID, WebApkUpdateTask.class,
-                                DateUtils.HOUR_IN_MILLIS, DateUtils.HOUR_IN_MILLIS * 23)
-                        .setRequiredNetworkType(TaskInfo.NetworkType.UNMETERED)
-                        .setUpdateCurrent(true)
-                        .setIsPersisted(true)
-                        .setRequiresCharging(true)
-                        .build();
         BackgroundTaskSchedulerFactory.getScheduler().schedule(
-                ContextUtils.getApplicationContext(), taskInfo);
+                ContextUtils.getApplicationContext(), updateTask);
     }
 
     /** Sends update request to the WebAPK Server. Should be called when WebAPK is not running. */
@@ -284,6 +299,8 @@
      */
     private static void onFinishedUpdate(
             WebappDataStorage storage, @WebApkInstallResult int result, boolean relaxUpdates) {
+        storage.setShouldForceUpdate(false);
+        storage.setUpdateScheduled(false);
         recordUpdate(storage, result, relaxUpdates);
         storage.deletePendingUpdateRequestFile();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
index 76d62c8..2a181020 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
@@ -4,17 +4,15 @@
 
 package org.chromium.chrome.browser.webapps;
 
-import android.app.Activity;
 import android.content.Context;
-import android.text.TextUtils;
 
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.StrictModeContext;
 import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
 import org.chromium.components.background_task_scheduler.BackgroundTask.TaskFinishedCallback;
 import org.chromium.components.background_task_scheduler.TaskIds;
 import org.chromium.components.background_task_scheduler.TaskParameters;
 
+import java.lang.ref.WeakReference;
 import java.util.List;
 
 /**
@@ -41,7 +39,9 @@
         List<String> ids = WebappRegistry.getInstance().findWebApksWithPendingUpdate();
         for (String id : ids) {
             WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
-            if (!isWebApkActivityRunning(storage.getWebApkPackageName())) {
+            WeakReference<WebappActivity> activity =
+                    WebappActivity.findRunningWebappActivityWithId(storage.getId());
+            if (activity == null || activity.get() == null) {
                 mStorageToUpdate = storage;
                 mMoreToUpdate = ids.size() > 1;
                 return StartBeforeNativeResult.LOAD_NATIVE;
@@ -79,19 +79,4 @@
 
     @Override
     public void reschedule(Context context) {}
-
-    /** Returns whether a WebApkActivity with {@link webApkPackageName} is running. */
-    private static boolean isWebApkActivityRunning(String webApkPackageName) {
-        for (Activity activity : ApplicationStatus.getRunningActivities()) {
-            if (!(activity instanceof WebApkActivity)) {
-                continue;
-            }
-            WebApkActivity webApkActivity = (WebApkActivity) activity;
-            if (webApkActivity != null
-                    && TextUtils.equals(webApkPackageName, webApkActivity.getWebApkPackageName())) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
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 09a30313..abfe27122 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
@@ -127,6 +127,21 @@
         return null;
     }
 
+    /** Returns the WebappActivity with the given {@link webappId}. */
+    public static WeakReference<WebappActivity> findRunningWebappActivityWithId(String webappId) {
+        for (Activity activity : ApplicationStatus.getRunningActivities()) {
+            if (!(activity instanceof WebappActivity)) {
+                continue;
+            }
+            WebappActivity webappActivity = (WebappActivity) activity;
+            if (webappActivity != null
+                    && TextUtils.equals(webappId, webappActivity.getWebappInfo().id())) {
+                return new WeakReference<>(webappActivity);
+            }
+        }
+        return null;
+    }
+
     /**
      * Construct all the variables that shouldn't change.  We do it here both to clarify when the
      * objects are created and to ensure that they exist throughout the parallelized initialization
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index b95b84c6..3228a615 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -77,6 +77,12 @@
     // The path where serialized update data is written before uploading to the WebAPK server.
     static final String KEY_PENDING_UPDATE_FILE_PATH = "pending_update_file_path";
 
+    // Whether to force an update.
+    static final String KEY_SHOULD_FORCE_UPDATE = "should_force_update";
+
+    // Whether an update has been scheduled.
+    static final String KEY_UPDATE_SCHEDULED = "update_scheduled";
+
     // Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed.
     public static final long UPDATE_INTERVAL = DateUtils.DAY_IN_MILLIS;
 
@@ -514,6 +520,33 @@
         return mPreferences.getBoolean(KEY_RELAX_UPDATES, false);
     }
 
+    /** Sets whether an update has been scheduled. */
+    public void setUpdateScheduled(boolean isUpdateScheduled) {
+        mPreferences.edit().putBoolean(KEY_UPDATE_SCHEDULED, isUpdateScheduled).apply();
+    }
+
+    /** Gets whether an update has been scheduled. */
+    public boolean isUpdateScheduled() {
+        return mPreferences.getBoolean(KEY_UPDATE_SCHEDULED, false);
+    }
+
+    /** Sets whether an update should be forced. */
+    public void setShouldForceUpdate(boolean forceUpdate) {
+        mPreferences.edit().putBoolean(KEY_SHOULD_FORCE_UPDATE, forceUpdate).apply();
+    }
+
+    /** Whether to force an update. */
+    public boolean shouldForceUpdate() {
+        return mPreferences.getBoolean(KEY_SHOULD_FORCE_UPDATE, false);
+    }
+
+    /** Returns the update status. */
+    public String getUpdateStatus() {
+        if (isUpdateScheduled()) return "Scheduled";
+        if (shouldForceUpdate()) return "Pending";
+        return didPreviousUpdateSucceed() ? "Succeeded" : "Failed";
+    }
+
     /**
      * Returns file where WebAPK update data should be stored and stores the file name in
      * SharedPreferences.
@@ -556,6 +589,7 @@
 
     /** Returns whether we should check for update. */
     boolean shouldCheckForUpdate() {
+        if (shouldForceUpdate()) return true;
         long checkUpdatesInterval =
                 shouldRelaxUpdates() ? RELAXED_UPDATE_INTERVAL : UPDATE_INTERVAL;
         long now = sClock.currentTimeMillis();
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index cadb698..0802da7 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1147,7 +1147,7 @@
 
       <!-- Single site settings -->
       <message name="IDS_NO_SAVED_WEBSITE_SETTINGS" desc="Text to display when there are no saved website settings.">
-        You have no saved website settings.
+        No storage data here
       </message>
 
       <!-- Languages preferences -->
@@ -1814,7 +1814,7 @@
 
       <!-- Bluetooth Scanning Prompt strings -->
       <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ORIGIN" desc="The label that is used to introduce Bluetooth scanning prompt details to the user when it is from a website.">
-        <ph name="SITE">%1$s<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices, the following devices have been found:
+        <ph name="SITE">%1$s<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices. The following devices have been found:
       </message>
       <message name="IDS_BLUETOOTH_SCANNING_DEVICE_UNKNOWN" desc="Text to identify Bluetooth devices of unknown or unsupported class.">
         Unknown or unsupported device (<ph name="DEVICE_ID">%1$s<ex>A1:B2:C3:D4:E5:F6</ex></ph>)
@@ -2248,7 +2248,7 @@
 
       <!-- Page info popup -->
       <message name="IDS_PAGE_INFO_SITE_SETTINGS_BUTTON" desc="Text in the button that opens a website's Site Settings from the Page Info dialog.">
-        Site Settings
+        Site settings
       </message>
       <message name="IDS_PAGE_INFO_INSTANT_APP_BUTTON" desc="Text in the button that opens an Android Instant app that is associated with the website's URL.">
         Open Instant App
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1
new file mode 100644
index 0000000..4d3c5db
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1
@@ -0,0 +1 @@
+61c67160243523857d934b35eb74acf104e05675
\ No newline at end of file
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index a1b751d..a860903c6 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -44,6 +44,7 @@
 
 if (enable_arcore) {
   chrome_java_sources += [
+    "java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java",
     "java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java",
     "java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java",
     "java/src/org/chromium/chrome/browser/vr/ArCoreShim.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
index 41879a7..00f0b40 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
@@ -43,6 +43,7 @@
     private View mHeaderView;
     private TextView mTitle;
     private TextView mUrl;
+    private View mTitleAndUrl;
     private RoundedCornerImageView mImage;
     private View mCircleBg;
     private PropertyModel mModel;
@@ -61,6 +62,7 @@
             mHeaderView = getActivity().findViewById(android.R.id.content);
             mTitle = mHeaderView.findViewById(R.id.menu_header_title);
             mUrl = mHeaderView.findViewById(R.id.menu_header_url);
+            mTitleAndUrl = mHeaderView.findViewById(R.id.title_and_url);
             mImage = mHeaderView.findViewById(R.id.menu_header_image);
             mCircleBg = mHeaderView.findViewById(R.id.circle_background);
         });
@@ -131,8 +133,10 @@
     @SmallTest
     @UiThreadTest
     public void testUrlClick() {
+        // Even though the click event expands/shrinks the url, the click target is the LinearLayout
+        // that contains the title and the url to give the user more area to touch.
         assertFalse("URL has onClickListeners when it shouldn't, yet, have.",
-                mUrl.hasOnClickListeners());
+                mTitleAndUrl.hasOnClickListeners());
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.set(RevampedContextMenuHeaderProperties.URL, URL_STRING);
@@ -146,14 +150,14 @@
                             RevampedContextMenuHeaderProperties.URL_MAX_LINES, Integer.MAX_VALUE);
                 }
             });
-            mUrl.callOnClick();
+            mTitleAndUrl.callOnClick();
         });
 
         assertThat("Incorrect max line count for URL.", mUrl.getMaxLines(),
                 equalTo(Integer.MAX_VALUE));
         assertNull("URL is ellipsized when it shouldn't be.", mUrl.getEllipsize());
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrl.callOnClick(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mTitleAndUrl.callOnClick(); });
 
         assertThat("Incorrect max line count for URL.", mUrl.getMaxLines(), equalTo(URL_MAX_COUNT));
         assertThat("Incorrect URL ellipsize mode.", mUrl.getEllipsize(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
similarity index 98%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
index 2904da0e..6d59149b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.Manifest;
 import android.app.Dialog;
@@ -24,6 +24,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.location.LocationUtils;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
similarity index 96%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
index 5d431fc..f984d86 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.support.test.filters.LargeTest;
@@ -20,6 +20,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
similarity index 99%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
index 6dafc3af..c5986a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.graphics.drawable.Drawable;
@@ -26,6 +26,8 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.Criteria;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS
new file mode 100644
index 0000000..19c774f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
similarity index 97%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
index 69b47c943..4f40fb4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
@@ -2,7 +2,7 @@
 // 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;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.support.test.filters.LargeTest;
@@ -19,6 +19,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
index 26714cd..a318f90c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
@@ -257,6 +257,9 @@
         public @ColorRes int getSecurityIconColorStateList() {
             return 0;
         }
+
+        @Override
+        public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
index 3036629..127ae5b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
@@ -94,6 +94,10 @@
 
     // PaymentAppCreatedCallback
     @Override
+    public void onGetPaymentAppsError(String errorMessage) {}
+
+    // PaymentAppCreatedCallback
+    @Override
     public void onAllPaymentAppsCreated() {
         mAllPaymentAppsCreated = true;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
index 0b8aadd5..cb687dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
@@ -62,6 +62,7 @@
     private boolean mDownloadPaymentMethodManifestSuccess;
     private boolean mDownloadWebAppManifestSuccess;
     private boolean mDownloadFailure;
+    private String mErrorMessage;
     private String mPaymentMethodManifest;
     private String mWebAppManifest;
 
@@ -80,9 +81,10 @@
     }
 
     @Override
-    public void onManifestDownloadFailure() {
+    public void onManifestDownloadFailure(String errorMessage) {
         mDownloadComplete = true;
         mDownloadFailure = true;
+        mErrorMessage = errorMessage;
     }
 
     @Before
@@ -96,6 +98,7 @@
         mDownloadPaymentMethodManifestSuccess = false;
         mDownloadWebAppManifestSuccess = false;
         mDownloadFailure = false;
+        mErrorMessage = "";
         mPaymentMethodManifest = null;
         mWebAppManifest = null;
     }
@@ -139,6 +142,8 @@
         });
 
         Assert.assertTrue("Web app manifest should not have been downloaded.", mDownloadFailure);
+        Assert.assertEquals(
+                "Unable to download payment manifest \"" + uri.toString() + "\".", mErrorMessage);
     }
 
     @Test
@@ -174,6 +179,9 @@
 
         Assert.assertTrue(
                 "Payment method manifest should have not have been downloaded.", mDownloadFailure);
+        Assert.assertEquals("Unable to make a HEAD request to \"" + uri.toString()
+                        + "\" for payment method manifest.",
+                mErrorMessage);
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
index 9535cf9..558e913 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
@@ -23,7 +23,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.history.HistoryActivity;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 import org.chromium.chrome.browser.preferences.MainPreferences;
 import org.chromium.chrome.browser.preferences.Preferences;
 import org.chromium.chrome.browser.preferences.PreferencesTest;
@@ -85,15 +85,15 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AutofillAssistantPreferences autofillAssistantPrefs =
-                    (AutofillAssistantPreferences) preferences.getMainFragment();
-            ChromeSwitchPreference onOffSwitch =
-                    (ChromeSwitchPreference) autofillAssistantPrefs.findPreference(
+                    (AutofillAssistantPreferences) preferences.getMainFragmentCompat();
+            ChromeSwitchPreferenceCompat onOffSwitch =
+                    (ChromeSwitchPreferenceCompat) autofillAssistantPrefs.findPreference(
                             AutofillAssistantPreferences.PREF_AUTOFILL_ASSISTANT_SWITCH);
             Assert.assertTrue(onOffSwitch.isChecked());
 
-            PreferencesTest.clickPreference(autofillAssistantPrefs, onOffSwitch);
+            onOffSwitch.performClick();
             Assert.assertFalse(getAutofillAssistantSwitch(true));
-            PreferencesTest.clickPreference(autofillAssistantPrefs, onOffSwitch);
+            onOffSwitch.performClick();
             Assert.assertTrue(getAutofillAssistantSwitch(false));
 
             preferences.finish();
@@ -105,9 +105,9 @@
                         AutofillAssistantPreferences.class.getName());
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AutofillAssistantPreferences autofillAssistantPrefs =
-                    (AutofillAssistantPreferences) preferences2.getMainFragment();
-            ChromeSwitchPreference onOffSwitch =
-                    (ChromeSwitchPreference) autofillAssistantPrefs.findPreference(
+                    (AutofillAssistantPreferences) preferences2.getMainFragmentCompat();
+            ChromeSwitchPreferenceCompat onOffSwitch =
+                    (ChromeSwitchPreferenceCompat) autofillAssistantPrefs.findPreference(
                             AutofillAssistantPreferences.PREF_AUTOFILL_ASSISTANT_SWITCH);
             Assert.assertFalse(onOffSwitch.isChecked());
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
index 0d6109b..175f5dd5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
@@ -29,12 +29,17 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.components.module_installer.ModuleInstaller;
+import org.chromium.components.module_installer.ModuleInstallerRule;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Callable;
 
 /**
  * End-to-end tests for installing the VR DFM on Daydream-ready phones on startup.
+ *
+ * TODO(agrieve): This test may be better as a robolectric test.
  */
 @RunWith(ParameterizedRunner.class)
 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@@ -48,12 +53,24 @@
     @Rule
     public RuleChain mRuleChain;
 
+    private ModuleInstallerRule mModuleInstallerRule;
+
     private ChromeActivityTestRule mVrTestRule;
 
+    private final Set<String> mModulesRequestedDeferred = new HashSet<>();
+
     public VrDaydreamReadyModuleInstallTest(Callable<ChromeActivityTestRule> callable)
             throws Exception {
         mVrTestRule = callable.call();
-        mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule);
+        mModuleInstallerRule = new ModuleInstallerRule(new ModuleInstaller() {
+            @Override
+            public void installDeferred(String moduleName) {
+                mModulesRequestedDeferred.add(moduleName);
+            }
+        });
+        mRuleChain =
+                RuleChain.outerRule(mModuleInstallerRule)
+                        .around(VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule));
     }
 
     /** Tests that the install is requested deferred. */
@@ -62,8 +79,8 @@
     @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
     @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM})
     @VrModuleNotInstalled
-    public void testDeferredRequestOnStartup() throws InterruptedException {
+    public void testDeferredRequestOnStartup() {
         Assert.assertTrue("VR module should have been deferred installed at startup",
-                ModuleInstaller.didRequestDeferred("vr"));
+                mModulesRequestedDeferred.contains("vr"));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
index 3ac117c4..7d432e4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
@@ -32,6 +32,8 @@
 @RunWith(RobolectricTestRunner.class)
 @Config(sdk = 21, manifest = Config.NONE)
 public class PaymentManifestVerifierTest {
+    private static final String ERROR_MESSAGE = "This is an error message.";
+
     private final URI mMethodName;
     private final ResolveInfo mAlicePay;
     private final ResolveInfo mBobPay;
@@ -139,7 +141,7 @@
                     @Override
                     public void downloadPaymentMethodManifest(
                             URI uri, ManifestDownloadCallback callback) {
-                        callback.onManifestDownloadFailure();
+                        callback.onManifestDownloadFailure(ERROR_MESSAGE);
                     }
 
                     @Override
@@ -167,7 +169,7 @@
 
                     @Override
                     public void downloadWebAppManifest(URI uri, ManifestDownloadCallback callback) {
-                        callback.onManifestDownloadFailure();
+                        callback.onManifestDownloadFailure(ERROR_MESSAGE);
                     }
 
                     @Override
@@ -288,7 +290,7 @@
             @Override
             public void downloadWebAppManifest(URI uri, ManifestDownloadCallback callback) {
                 if (mDownloadWebAppManifestCounter++ == 0) {
-                    callback.onManifestDownloadFailure();
+                    callback.onManifestDownloadFailure(ERROR_MESSAGE);
                 } else {
                     callback.onWebAppManifestDownloadSuccess("some content");
                 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
index 7bd08e103..942de06 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
@@ -1094,4 +1094,68 @@
         onGotManifestData(updateManager, defaultManifestData());
         assertTrue(updateManager.updateRequested());
     }
+
+    /**
+     * Tests that a forced update is requested and performed immediately if there is a material
+     * change to the manifest.
+     */
+    @Test
+    public void testForcedUpdateSuccess() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotDifferentData(updateManager);
+        assertTrue(updateManager.updateRequested());
+        tryCompletingUpdate(updateManager, storage, WebApkInstallResult.SUCCESS);
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update is requested, but not performed if there is no material change to
+     * the manifest.
+     */
+    @Test
+    public void testForcedUpdateNotNeeded() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, defaultManifestData());
+        assertFalse(updateManager.updateRequested());
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update handles failure gracefully.
+     */
+    @Test
+    public void testForcedUpdateFailure() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotDifferentData(updateManager);
+        assertTrue(updateManager.updateRequested());
+        tryCompletingUpdate(updateManager, storage, WebApkInstallResult.FAILURE);
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update handles failing to retrieve the manifest.
+     */
+    @Test
+    public void testForcedUpdateManifestNotRetrieved() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, null);
+        assertFalse(updateManager.updateRequested());
+        assertEquals(false, storage.shouldForceUpdate());
+    }
 }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index a295d15..6a82b52 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3849.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3850.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
index ef04459..d3cd6e7 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
@@ -241,8 +241,9 @@
         Tab tab = getActivityTab();
         if (tab == null || tab.getUrl() == null || tab.getUrl().isEmpty()) return;
         long time = SystemClock.elapsedRealtime();
-        outState.putInt(BUNDLE_TAB_ID, tab.getId());
-        TabState.saveState(outState, TabState.from(tab));
+        if (TabState.saveState(outState, TabState.from(tab))) {
+            outState.putInt(BUNDLE_TAB_ID, tab.getId());
+        }
         RecordHistogram.recordTimesHistogram("Android.StrictMode.NoTouchActivitySaveState",
                 SystemClock.elapsedRealtime() - time);
     }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
index 1afba35..ceaf2212 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
-import org.chromium.chrome.browser.native_page.ContextMenuManager.ContextMenuItemId;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames;
@@ -63,6 +62,8 @@
 
     private final ChromeActivity mActivity;
     private final ModalDialogManager mDialogManager;
+    // This field should be set when showing a dialog, and nulled out when the dialog is closed. We
+    // can check if it is null to determine whether we're currently showing our dialog.
     private PropertyModel mTouchlessMenuModel;
     private ModalDialogManager mModalDialogManager;
 
@@ -84,6 +85,11 @@
      */
     public void showTouchlessContextMenu(
             ModalDialogManager modalDialogManager, ContextMenuManager.Delegate delegate) {
+        // Don't create a new dialog if we're already showing one.
+        if (mTouchlessMenuModel != null) {
+            return;
+        }
+
         ArrayList<PropertyModel> menuItems = new ArrayList<>();
         for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) {
             if (!shouldShowItem(itemId, delegate)) continue;
@@ -195,7 +201,9 @@
                             public void onClick(PropertyModel model, int buttonType) {}
 
                             @Override
-                            public void onDismiss(PropertyModel model, int dismissalCause) {}
+                            public void onDismiss(PropertyModel model, int dismissalCause) {
+                                mTouchlessMenuModel = null;
+                            }
                         })
                 .with(TouchlessDialogProperties.ACTION_NAMES, names)
                 .with(TouchlessDialogProperties.CANCEL_ACTION, (v) -> closeTouchlessContextMenu())
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
index 439993b..5c497ff 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
@@ -28,6 +28,7 @@
     private boolean mSkipShowingOnNextActivityStart;
 
     private static final int MINIMUM_DISPLAY_DURATION_MS = 3 * 1000;
+    private static final int MAXIMUM_PROGRESS = 100;
 
     ProgressBarMediator(PropertyModel model, ActivityTabProvider activityTabProvider) {
         mProgressBarTabObserver = new ProgressBarTabObserver(activityTabProvider);
@@ -73,11 +74,16 @@
         show();
     }
 
-    private void stopLoadProgress() {
+    private void finishLoadProgress() {
         mCanHideProgressBar = true;
         hide();
     }
 
+    private void updateLoadProgress(int progress) {
+        mModel.set(ProgressBarProperties.PROGRESS_FRACTION, progress / ((float) MAXIMUM_PROGRESS));
+        if (progress == MAXIMUM_PROGRESS) finishLoadProgress();
+    }
+
     void destroy() {
         if (mHideTask != null) mHideTask.cancel(false);
         mProgressBarTabObserver.destroy();
@@ -94,15 +100,23 @@
         public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
             if (!navigation.isInMainFrame()) return;
 
+            if (tab.getWebContents() != null
+                    && tab.getWebContents().getNavigationController() != null
+                    && tab.getWebContents().getNavigationController().isInitialNavigation()) {
+                updateUrl(tab);
+            }
+
+            if (navigation.isSameDocument()) return;
+
             if (NativePageFactory.isNativePageUrl(navigation.getUrl(), tab.isIncognito())) {
                 mModel.set(ProgressBarProperties.IS_ENABLED, false);
-                stopLoadProgress();
+                finishLoadProgress();
                 return;
             }
 
             mModel.set(ProgressBarProperties.IS_ENABLED, true);
-            updateUrl(tab);
             startLoadProgress();
+            updateLoadProgress(tab.getProgress());
         }
 
         @Override
@@ -117,15 +131,21 @@
 
         @Override
         public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
-            updateUrl(tab);
-            stopLoadProgress();
+            if (!toDifferentDocument) return;
+
+            // If we made some progress, fast-forward to complete.
+            if (tab.getProgress() < MAXIMUM_PROGRESS) {
+                updateLoadProgress(MAXIMUM_PROGRESS);
+            } else {
+                finishLoadProgress();
+            }
         }
 
         @Override
         public void onLoadProgressChanged(Tab tab, int progress) {
             if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) return;
 
-            mModel.set(ProgressBarProperties.PROGRESS_FRACTION, progress / 100f);
+            updateLoadProgress(progress);
         }
 
         @Override
@@ -133,12 +153,12 @@
             if (!didStartLoad) return;
 
             updateUrl(tab);
-            if (didFinishLoad) stopLoadProgress();
+            if (didFinishLoad) finishLoadProgress();
         }
 
         @Override
         public void onCrash(Tab tab) {
-            stopLoadProgress();
+            finishLoadProgress();
         }
 
         private void updateUrl(Tab tab) {
diff --git a/chrome/android/trichrome.gni b/chrome/android/trichrome.gni
index 758f1ca..abefaaf 100644
--- a/chrome/android/trichrome.gni
+++ b/chrome/android/trichrome.gni
@@ -49,6 +49,8 @@
                              "apk_name",
                              "min_sdk_version",
                              "proguard_jar_path",
+                             "shared_resources_whitelist_target",
+                             "shared_resources_whitelist_locales",
                              "static_library_dependent_targets",
                              "target_sdk_version",
                            ])
@@ -59,8 +61,12 @@
     no_build_hooks = true
 
     alternative_android_sdk_dep = webview_framework_dep
-    app_as_shared_lib = true
-    r_java_root_package_name = "trichrome_lib"
+    if (!trichrome_synchronized_proguard) {
+      # TODO(crbug.com/901465): Remove r_java_root_package_name once shared
+      # Java code is moved to the shared library even in debug.
+      r_java_root_package_name = "trichrome_lib"
+      app_as_shared_lib = true
+    }
     use_chromium_linker = false
     uncompress_shared_libraries = true
     uncompress_dex = use_uncompressed_dex
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index aeb27b76..2f1700b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9203,7 +9203,7 @@
       No nearby devices found.
     </message>
     <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ORIGIN" desc="The label that is used to introduce Bluetooth scanning prompt details to the user when it is from a website.">
-      <ph name="Origin">$1<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices, the following devices have been found:
+      <ph name="Origin">$1<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices. The following devices have been found:
     </message>
     <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ALLOW_BUTTON_TEXT" desc="Label on the button that allows Bluetooth scanning." formatter_data="android_java">
       Allow
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index a69266f..e404aef1 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -15,7 +15,7 @@
     Languages and input
   </message>
 
-  <!-- Personalizaton Page (OS settings) -->
+  <!-- Personalization Page (OS settings) -->
   <message name="IDS_OS_SETTINGS_PERSONALIZATION" desc="Name of the OS settings page which displays personalization preferences.">
     Personalization
   </message>
@@ -32,5 +32,10 @@
   <message name="IDS_OS_SETTINGS_ASSISTANT" desc="Name of the settings section for the Google Assistant.">
     Assistant
   </message>
+
+  <!-- Files Page (OS settings) -->
+  <message name="IDS_OS_SETTINGS_FILES" desc="Name of the settings page which displays file preferences.">
+    Files
+  </message>
 </if>
 </grit-part>
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 1a4b27e..64705d8a 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -262,7 +262,7 @@
         <structure type="chrome_scaled_image" name="IDR_LOGO_AVATAR_CIRCLE_BLUE_COLOR" file="cros/logo_avatar_circle_blue_color.png" />
         <structure type="chrome_scaled_image" name="IDR_LOGO_GOOGLE_COLOR_90" file="cros/logo_google_color_90.png" />
       </if>
-      <if expr="not_is_android">
+      <if expr="not is_android">
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_FRAME" file="common/supervised_user_theme/theme_frame_supervised.png" />
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_FRAME_INACTIVE" file="common/supervised_user_theme/theme_frame_supervised_inactive.png" />
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_TAB_BACKGROUND" file="common/supervised_user_theme/theme_tab_background_supervised.png" />
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3a7ca7f..3d7c37f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -814,6 +814,8 @@
     "native_file_system/chrome_native_file_system_permission_context.h",
     "native_file_system/native_file_system_permission_context_factory.cc",
     "native_file_system/native_file_system_permission_context_factory.h",
+    "native_file_system/native_file_system_permission_request_manager.cc",
+    "native_file_system/native_file_system_permission_request_manager.h",
     "native_window_notification_source.h",
     "navigation_predictor/navigation_predictor.cc",
     "navigation_predictor/navigation_predictor.h",
@@ -1078,6 +1080,8 @@
     "performance_manager/persistence/site_data/feature_usage.h",
     "performance_manager/persistence/site_data/leveldb_site_data_store.cc",
     "performance_manager/persistence/site_data/leveldb_site_data_store.h",
+    "performance_manager/persistence/site_data/non_recording_site_data_cache.cc",
+    "performance_manager/persistence/site_data/non_recording_site_data_cache.h",
     "performance_manager/persistence/site_data/noop_site_data_writer.cc",
     "performance_manager/persistence/site_data/noop_site_data_writer.h",
     "performance_manager/persistence/site_data/site_data_cache.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3c73a70..aab04ed1 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2333,6 +2333,12 @@
      flag_descriptions::kArcVpnDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kVpnFeature)},
 #endif  // OS_CHROMEOS
+#if defined(OS_WIN)
+    {"enable-winrt-sensor-implementation",
+     flag_descriptions::kWinrtSensorsImplementationName,
+     flag_descriptions::kWinrtSensorsImplementationDescription, kOsWin,
+     FEATURE_VALUE_TYPE(features::kWinrtSensorsImplementation)},
+#endif
     {"enable-generic-sensor", flag_descriptions::kEnableGenericSensorName,
      flag_descriptions::kEnableGenericSensorDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kGenericSensor)},
@@ -2503,6 +2509,11 @@
      flag_descriptions::kOmniboxOnDeviceHeadSuggestionsName,
      flag_descriptions::kOmniboxOnDeviceHeadSuggestionsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(omnibox::kOnDeviceHeadProvider)},
+
+    {"omnibox-search-engine-logo",
+     flag_descriptions::kOmniboxSearchEngineLogoName,
+     flag_descriptions::kOmniboxSearchEngineLogoDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(omnibox::kOmniboxSearchEngineLogo)},
 #endif  // defined(OS_ANDROID)
 
     {"omnibox-rich-entity-suggestions",
@@ -4093,6 +4104,10 @@
      flag_descriptions::kEnableAutofillSaveCardShowNoThanksDescription, kOsAll,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillSaveCardShowNoThanks)},
 
+    {"enable-defer-all-script", flag_descriptions::kEnableDeferAllScriptName,
+     flag_descriptions::kEnableDeferAllScriptDescription, kOsAll,
+     FEATURE_VALUE_TYPE(previews::features::kDeferAllScriptPreviews)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 993cb504..1c52b61 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -208,6 +208,7 @@
     &omnibox::kOmniboxRichEntitySuggestions,
     &omnibox::kQueryInOmnibox,
     &omnibox::kUIExperimentShowSuggestionFavicons,
+    &omnibox::kOmniboxSearchEngineLogo,
     &password_manager::features::kGooglePasswordManager,
     &password_manager::features::kPasswordEditingAndroid,
     &password_manager::features::kTouchToFillAndroid,
diff --git a/chrome/browser/android/find_in_page/find_in_page_bridge.cc b/chrome/browser/android/find_in_page/find_in_page_bridge.cc
index 84b237df..6bcdff4 100644
--- a/chrome/browser/android/find_in_page/find_in_page_bridge.cc
+++ b/chrome/browser/android/find_in_page/find_in_page_bridge.cc
@@ -7,6 +7,7 @@
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/FindInPageBridge_jni.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "content/public/browser/web_contents.h"
 
 using base::android::ConvertUTF16ToJavaString;
@@ -40,9 +41,9 @@
 void FindInPageBridge::StopFinding(JNIEnv* env,
                                    const JavaParamRef<jobject>& obj,
                                    jboolean clearSelection) {
-  FindTabHelper::FromWebContents(web_contents_)->
-      StopFinding(clearSelection ? FindBarController::kClearSelectionOnPage
-          : FindBarController::kKeepSelectionOnPage);
+  FindTabHelper::FromWebContents(web_contents_)
+      ->StopFinding(clearSelection ? FindOnPageSelectionAction::kClear
+                                   : FindOnPageSelectionAction::kKeep);
 }
 
 ScopedJavaLocalRef<jstring> FindInPageBridge::GetPreviousFindText(
diff --git a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
index 5e44a549..656b87f 100644
--- a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
@@ -59,9 +59,15 @@
     const JavaRef<jobject>& jcallback,
     content::PaymentAppProvider::PaymentApps apps,
     payments::ServiceWorkerPaymentAppFactory::InstallablePaymentApps
-        installable_apps) {
+        installable_apps,
+    const std::string& error_message) {
   JNIEnv* env = AttachCurrentThread();
 
+  if (!error_message.empty()) {
+    Java_ServiceWorkerPaymentAppBridge_onGetPaymentAppsError(
+        env, jcallback, ConvertUTF8ToJavaString(env, error_message));
+  }
+
   for (const auto& app_info : apps) {
     // Sends related application Ids to java side if the app prefers related
     // applications.
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index 10704ea..4da3d1e 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -358,6 +358,7 @@
     const JavaParamRef<jobjectArray>& jnames,
     const JavaParamRef<jobjectArray>& jshort_names,
     const JavaParamRef<jobjectArray>& jpackage_names,
+    const JavaParamRef<jobjectArray>& jids,
     const JavaParamRef<jintArray>& jshell_apk_versions,
     const JavaParamRef<jintArray>& jversion_codes,
     const JavaParamRef<jobjectArray>& juris,
@@ -369,7 +370,8 @@
     const JavaParamRef<jlongArray>& jtheme_colors,
     const JavaParamRef<jlongArray>& jbackground_colors,
     const JavaParamRef<jlongArray>& jlast_update_check_times_ms,
-    const JavaParamRef<jbooleanArray>& jrelax_updates) {
+    const JavaParamRef<jbooleanArray>& jrelax_updates,
+    const JavaParamRef<jobjectArray>& jupdateStatuses) {
   DCHECK(jcallback_pointer);
   std::vector<std::string> names;
   base::android::AppendJavaStringArrayToStringVector(env, jnames, &names);
@@ -379,6 +381,8 @@
   std::vector<std::string> package_names;
   base::android::AppendJavaStringArrayToStringVector(env, jpackage_names,
                                                      &package_names);
+  std::vector<std::string> ids;
+  base::android::AppendJavaStringArrayToStringVector(env, jids, &ids);
   std::vector<int> shell_apk_versions;
   base::android::JavaIntArrayToIntVector(env, jshell_apk_versions,
                                          &shell_apk_versions);
@@ -409,9 +413,13 @@
   std::vector<bool> relax_updates;
   base::android::JavaBooleanArrayToBoolVector(env, jrelax_updates,
                                               &relax_updates);
+  std::vector<std::string> update_statuses;
+  base::android::AppendJavaStringArrayToStringVector(env, jupdateStatuses,
+                                                     &update_statuses);
 
   DCHECK(short_names.size() == names.size());
   DCHECK(short_names.size() == package_names.size());
+  DCHECK(short_names.size() == ids.size());
   DCHECK(short_names.size() == shell_apk_versions.size());
   DCHECK(short_names.size() == version_codes.size());
   DCHECK(short_names.size() == uris.size());
@@ -424,21 +432,22 @@
   DCHECK(short_names.size() == background_colors.size());
   DCHECK(short_names.size() == last_update_check_times_ms.size());
   DCHECK(short_names.size() == relax_updates.size());
+  DCHECK(short_names.size() == update_statuses.size());
 
   std::vector<WebApkInfo> webapk_list;
   webapk_list.reserve(short_names.size());
   for (size_t i = 0; i < short_names.size(); ++i) {
     webapk_list.push_back(WebApkInfo(
         std::move(names[i]), std::move(short_names[i]),
-        std::move(package_names[i]), shell_apk_versions[i], version_codes[i],
-        std::move(uris[i]), std::move(scopes[i]), std::move(manifest_urls[i]),
-        std::move(manifest_start_urls[i]),
+        std::move(package_names[i]), std::move(ids[i]), shell_apk_versions[i],
+        version_codes[i], std::move(uris[i]), std::move(scopes[i]),
+        std::move(manifest_urls[i]), std::move(manifest_start_urls[i]),
         static_cast<blink::WebDisplayMode>(display_modes[i]),
         static_cast<blink::WebScreenOrientationLockType>(orientations[i]),
         JavaColorToOptionalSkColor(theme_colors[i]),
         JavaColorToOptionalSkColor(background_colors[i]),
         base::Time::FromJavaTime(last_update_check_times_ms[i]),
-        relax_updates[i]));
+        relax_updates[i], std::move(update_statuses[i])));
   }
 
   ShortcutHelper::WebApkInfoCallback* webapk_list_callback =
@@ -446,3 +455,9 @@
   webapk_list_callback->Run(webapk_list);
   delete webapk_list_callback;
 }
+
+void ShortcutHelper::SetForceWebApkUpdate(const std::string& id) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_ShortcutHelper_setForceWebApkUpdate(
+      env, base::android::ConvertUTF8ToJavaString(env, id));
+}
diff --git a/chrome/browser/android/shortcut_helper.h b/chrome/browser/android/shortcut_helper.h
index 3edc5160..126359a 100644
--- a/chrome/browser/android/shortcut_helper.h
+++ b/chrome/browser/android/shortcut_helper.h
@@ -104,6 +104,10 @@
   // the info to the |callback|.
   static void RetrieveWebApks(const WebApkInfoCallback& callback);
 
+  // Sets a flag to force an update for the WebAPK corresponding to |id| on next
+  // launch.
+  static void SetForceWebApkUpdate(const std::string& id);
+
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutHelper);
 };
diff --git a/chrome/browser/android/signin/signin_manager_android.cc b/chrome/browser/android/signin/signin_manager_android.cc
index 54a9427..9936197 100644
--- a/chrome/browser/android/signin/signin_manager_android.cc
+++ b/chrome/browser/android/signin/signin_manager_android.cc
@@ -11,12 +11,11 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "chrome/android/chrome_jni_headers/SigninManager_jni.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/primary_account_manager.h"
 #include "components/signin/public/base/account_consistency_method.h"
+#include "components/signin/public/base/signin_client.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -25,28 +24,34 @@
 
 namespace {
 // Clears the information about the last signed-in user from |profile|.
-void ClearLastSignedInUserForProfile(Profile* profile) {
-  profile->GetPrefs()->ClearPref(prefs::kGoogleServicesLastAccountId);
-  profile->GetPrefs()->ClearPref(prefs::kGoogleServicesLastUsername);
+void ClearLastSignedInUserForProfile(SigninClient* signin_client) {
+  signin_client->GetPrefs()->ClearPref(prefs::kGoogleServicesLastAccountId);
+  signin_client->GetPrefs()->ClearPref(prefs::kGoogleServicesLastUsername);
 }
 }  // namespace
 
 SigninManagerAndroid::SigninManagerAndroid(
-    Profile* profile,
+    SigninClient* signin_client,
+    PrefService* local_state_pref_service,
     identity::IdentityManager* identity_manager,
     std::unique_ptr<SigninManagerDelegate> signin_manager_delegate)
-    : profile_(profile),
+    : signin_client_(signin_client),
       identity_manager_(identity_manager),
       signin_manager_delegate_(std::move(signin_manager_delegate)) {
-  DCHECK(profile_);
+  DCHECK(signin_client_);
+  DCHECK(local_state_pref_service);
   DCHECK(identity_manager_);
   DCHECK(signin_manager_delegate_);
   identity_manager_->AddObserver(this);
-  pref_change_registrar_.Init(profile_->GetPrefs());
-  pref_change_registrar_.Add(
-      prefs::kSigninAllowed,
+
+  signin_allowed_.Init(
+      prefs::kSigninAllowed, signin_client_->GetPrefs(),
       base::Bind(&SigninManagerAndroid::OnSigninAllowedPrefChanged,
                  base::Unretained(this)));
+
+  force_browser_signin_.Init(prefs::kForceBrowserSignin,
+                             local_state_pref_service);
+
   java_signin_manager_ = Java_SigninManager_create(
       base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
       signin_manager_delegate_->GetJavaObject(),
@@ -98,7 +103,7 @@
 void SigninManagerAndroid::ClearLastSignedInUser(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
-  ClearLastSignedInUserForProfile(profile_);
+  ClearLastSignedInUserForProfile(signin_client_);
 }
 
 void SigninManagerAndroid::LogInSignedInUser(JNIEnv* env,
@@ -109,18 +114,20 @@
   identity_manager_->LegacyReloadAccountsFromSystem();
 }
 
+bool SigninManagerAndroid::IsSigninAllowed() const {
+  return signin_allowed_.GetValue();
+}
+
 jboolean SigninManagerAndroid::IsSigninAllowedByPolicy(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj) {
-  return profile_->GetPrefs()->GetBoolean(prefs::kSigninAllowed);
+    const JavaParamRef<jobject>& obj) const {
+  return IsSigninAllowed();
 }
 
 jboolean SigninManagerAndroid::IsForceSigninEnabled(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
-  // prefs::kForceBrowserSignin is set in Local State, not in user prefs.
-  PrefService* prefs = g_browser_process->local_state();
-  return prefs->GetBoolean(prefs::kForceBrowserSignin);
+  return force_browser_signin_.GetValue();
 }
 
 jboolean SigninManagerAndroid::IsSignedInOnNative(
@@ -136,10 +143,10 @@
                                      java_signin_manager_);
 }
 
-void SigninManagerAndroid::OnSigninAllowedPrefChanged() {
+void SigninManagerAndroid::OnSigninAllowedPrefChanged() const {
   Java_SigninManager_onSigninAllowedByPolicyChanged(
       base::android::AttachCurrentThread(), java_signin_manager_,
-      profile_->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
+      IsSigninAllowed());
 }
 
 base::android::ScopedJavaLocalRef<jstring> JNI_SigninManager_ExtractDomainName(
diff --git a/chrome/browser/android/signin/signin_manager_android.h b/chrome/browser/android/signin/signin_manager_android.h
index 47de7dc..a03d97e 100644
--- a/chrome/browser/android/signin/signin_manager_android.h
+++ b/chrome/browser/android/signin/signin_manager_android.h
@@ -13,9 +13,10 @@
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/android/signin/signin_manager_delegate.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_member.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
-class Profile;
+class SigninClient;
 
 // Android wrapper of Chrome's C++ identity management code which provides
 // access from the Java layer. Note that on Android, there's only a single
@@ -28,7 +29,8 @@
 class SigninManagerAndroid : public identity::IdentityManager::Observer {
  public:
   SigninManagerAndroid(
-      Profile* profile,
+      SigninClient* signin_client,
+      PrefService* local_state_prefs_service,
       identity::IdentityManager* identity_manager,
       std::unique_ptr<SigninManagerDelegate> signin_manager_delegate);
 
@@ -56,7 +58,7 @@
 
   jboolean IsSigninAllowedByPolicy(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj);
+      const base::android::JavaParamRef<jobject>& obj) const;
 
   jboolean IsForceSigninEnabled(
       JNIEnv* env,
@@ -70,9 +72,17 @@
       const CoreAccountInfo& previous_primary_account_info) override;
 
  private:
-  void OnSigninAllowedPrefChanged();
+  void OnSigninAllowedPrefChanged() const;
+  bool IsSigninAllowed() const;
 
-  Profile* profile_;
+  SigninClient* signin_client_;
+
+  // Handler for prefs::kSigninAllowed set in user's profile.
+  BooleanPrefMember signin_allowed_;
+
+  // Handler for prefs::kForceBrowserSignin. This preference is set in Local
+  // State, not in user prefs.
+  BooleanPrefMember force_browser_signin_;
 
   identity::IdentityManager* identity_manager_;
 
@@ -81,8 +91,6 @@
   // Java-side SigninManager object.
   base::android::ScopedJavaGlobalRef<jobject> java_signin_manager_;
 
-  PrefChangeRegistrar pref_change_registrar_;
-
   base::ThreadChecker thread_checker_;
 
   DISALLOW_COPY_AND_ASSIGN(SigninManagerAndroid);
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index 2449a42..4dc70e81 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/android/feature_utilities.h"
 #include "chrome/browser/android/hung_renderer_infobar_delegate.h"
 #include "chrome/browser/banners/app_banner_manager_android.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/sound_content_setting_observer.h"
 #include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -32,8 +31,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/resource_coordinator/tab_load_tracker.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
-#include "chrome/browser/ui/android/bluetooth_chooser_android.h"
-#include "chrome/browser/ui/android/bluetooth_scanning_prompt_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h"
 #include "chrome/browser/ui/android/infobars/framebust_block_infobar.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "chrome/browser/ui/blocked_content/popup_blocker.h"
@@ -53,9 +52,6 @@
 #include "components/security_state/content/content_utils.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -135,9 +131,7 @@
     : WebContentsDelegateAndroid(env, obj) {
 }
 
-TabWebContentsDelegateAndroid::~TabWebContentsDelegateAndroid() {
-  notification_registrar_.RemoveAll();
-}
+TabWebContentsDelegateAndroid::~TabWebContentsDelegateAndroid() = default;
 
 void TabWebContentsDelegateAndroid::RunFileChooser(
     content::RenderFrameHost* render_frame_host,
@@ -174,14 +168,9 @@
 
 void TabWebContentsDelegateAndroid::CloseContents(
     WebContents* web_contents) {
-  // Prevent dangling registrations assigned to closed web contents.
-  if (notification_registrar_.IsRegistered(this,
-      chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-      content::Source<WebContents>(web_contents))) {
-    notification_registrar_.Remove(this,
-        chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-        content::Source<WebContents>(web_contents));
-  }
+  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
+  if (find_result_observer_.IsObserving(find_tab_helper))
+    find_result_observer_.Remove(find_tab_helper);
 
   WebContentsDelegateAndroid::CloseContents(web_contents);
 }
@@ -202,16 +191,6 @@
   return false;
 }
 
-void TabWebContentsDelegateAndroid::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, type);
-  OnFindResultAvailable(
-      content::Source<WebContents>(source).ptr(),
-      content::Details<FindNotificationDetails>(details).ptr());
-}
-
 blink::WebDisplayMode TabWebContentsDelegateAndroid::GetDisplayMode(
     const WebContents* web_contents) {
   JNIEnv* env = base::android::AttachCurrentThread();
@@ -231,15 +210,10 @@
     const gfx::Rect& selection_rect,
     int active_match_ordinal,
     bool final_update) {
-  if (!notification_registrar_.IsRegistered(this,
-      chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-      content::Source<WebContents>(web_contents))) {
-    notification_registrar_.Add(this,
-        chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-        content::Source<WebContents>(web_contents));
-  }
-
   FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
+  if (!find_result_observer_.IsObserving(find_tab_helper))
+    find_result_observer_.Add(find_tab_helper);
+
   find_tab_helper->HandleFindReply(request_id,
                                    number_of_matches,
                                    selection_rect,
@@ -247,28 +221,6 @@
                                    final_update);
 }
 
-void TabWebContentsDelegateAndroid::OnFindResultAvailable(
-    WebContents* web_contents,
-    const FindNotificationDetails* find_result) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
-  if (obj.is_null())
-    return;
-
-  ScopedJavaLocalRef<jobject> selection_rect =
-      JNI_TabWebContentsDelegateAndroid_CreateJavaRect(
-          env, find_result->selection_rect());
-
-  // Create the details object.
-  ScopedJavaLocalRef<jobject> details_object =
-      Java_TabWebContentsDelegateAndroid_createFindNotificationDetails(
-          env, find_result->number_of_matches(), selection_rect,
-          find_result->active_match_ordinal(), find_result->final_update());
-
-  Java_TabWebContentsDelegateAndroid_onFindResultAvailable(env, obj,
-                                                           details_object);
-}
-
 void TabWebContentsDelegateAndroid::FindMatchRectsReply(
     WebContents* web_contents,
     int version,
@@ -508,6 +460,30 @@
 }
 #endif
 
+void TabWebContentsDelegateAndroid::OnFindResultAvailable(
+    WebContents* web_contents) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
+  if (obj.is_null())
+    return;
+
+  const FindNotificationDetails& find_result =
+      FindTabHelper::FromWebContents(web_contents)->find_result();
+
+  ScopedJavaLocalRef<jobject> selection_rect =
+      JNI_TabWebContentsDelegateAndroid_CreateJavaRect(
+          env, find_result.selection_rect());
+
+  // Create the details object.
+  ScopedJavaLocalRef<jobject> details_object =
+      Java_TabWebContentsDelegateAndroid_createFindNotificationDetails(
+          env, find_result.number_of_matches(), selection_rect,
+          find_result.active_match_ordinal(), find_result.final_update());
+
+  Java_TabWebContentsDelegateAndroid_onFindResultAvailable(env, obj,
+                                                           details_object);
+}
+
 bool TabWebContentsDelegateAndroid::ShouldEnableEmbeddedMediaExperience()
     const {
   JNIEnv* env = base::android::AttachCurrentThread();
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.h b/chrome/browser/android/tab_web_contents_delegate_android.h
index bf26bb54..bd8141e3 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.h
+++ b/chrome/browser/android/tab_web_contents_delegate_android.h
@@ -6,15 +6,15 @@
 #define CHROME_BROWSER_ANDROID_TAB_WEB_CONTENTS_DELEGATE_ANDROID_H_
 
 #include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/ui/find_bar/find_result_observer.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
 #include "components/embedder_support/android/delegate/web_contents_delegate_android.h"
 #include "content/public/browser/bluetooth_chooser.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/common/frame/blocked_navigation_types.h"
 
-class FindNotificationDetails;
-
 namespace content {
 struct FileChooserParams;
 class WebContents;
@@ -32,7 +32,7 @@
 // the Chromium Android port but not to be shared with WebView.
 class TabWebContentsDelegateAndroid
     : public web_contents_delegate_android::WebContentsDelegateAndroid,
-      public content::NotificationObserver {
+      public FindResultObserver {
  public:
   TabWebContentsDelegateAndroid(JNIEnv* env, jobject obj);
   ~TabWebContentsDelegateAndroid() override;
@@ -112,21 +112,18 @@
       content::RenderFrameHost* subframe_host) const override;
 #endif
 
+  // FindResultObserver:
+  void OnFindResultAvailable(content::WebContents* web_contents) override;
+
   bool ShouldEnableEmbeddedMediaExperience() const;
   bool IsPictureInPictureEnabled() const;
   bool IsNightModeEnabled() const;
   const GURL GetManifestScope() const;
 
  private:
-  // NotificationObserver implementation.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  ScopedObserver<FindTabHelper, FindResultObserver> find_result_observer_{this};
 
-  void OnFindResultAvailable(content::WebContents* web_contents,
-                             const FindNotificationDetails* find_result);
-
-  content::NotificationRegistrar notification_registrar_;
+  DISALLOW_COPY_AND_ASSIGN(TabWebContentsDelegateAndroid);
 };
 
 }  // namespace android
diff --git a/chrome/browser/android/usb/web_usb_chooser_android.cc b/chrome/browser/android/usb/web_usb_chooser_android.cc
index d715f49b..62f5c70 100644
--- a/chrome/browser/android/usb/web_usb_chooser_android.cc
+++ b/chrome/browser/android/usb/web_usb_chooser_android.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "chrome/browser/ui/android/usb_chooser_dialog_android.h"
+#include "chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h"
 #include "chrome/browser/usb/usb_chooser_controller.h"
 
 WebUsbChooserAndroid::WebUsbChooserAndroid(
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index e17977b..d6c5c83 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -83,9 +83,9 @@
       "arcore_device/arcore_gl_thread.h",
       "arcore_device/arcore_impl.cc",
       "arcore_device/arcore_impl.h",
-      "arcore_device/arcore_install_utils.h",
       "arcore_device/arcore_java_utils.cc",
       "arcore_device/arcore_java_utils.h",
+      "arcore_device/arcore_session_utils.h",
       "arcore_device/arcore_shim.cc",
       "arcore_device/arcore_shim.h",
       "arcore_device/type_converters.cc",
@@ -222,6 +222,7 @@
 if (enable_arcore) {
   generate_jni("ar_jni_headers") {
     sources = [
+      "//chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java",
       "//chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java",
     ]
   }
diff --git a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
index b1fe89a5..5b368c01 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/bind.h"
 #include "chrome/android/features/vr/jni_headers/ArConsentDialog_jni.h"
 #include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/android/vr/ar_jni_headers/ArCoreInstallUtils_jni.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "device/vr/android/arcore/arcore_device_provider_factory.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ScopedJavaLocalRef;
@@ -19,7 +23,23 @@
 
 namespace {
 
-ArcoreConsentPrompt* g_instance = nullptr;
+ArCoreConsentPrompt* g_instance = nullptr;
+
+class ArCoreDeviceProviderFactoryImpl
+    : public device::ArCoreDeviceProviderFactory {
+ public:
+  ArCoreDeviceProviderFactoryImpl() = default;
+  ~ArCoreDeviceProviderFactoryImpl() override = default;
+  std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactoryImpl);
+};
+
+std::unique_ptr<device::VRDeviceProvider>
+ArCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
+  return std::make_unique<device::ArCoreDeviceProvider>();
+}
 
 base::android::ScopedJavaLocalRef<jobject> GetTabFromRenderer(
     int render_process_id,
@@ -44,42 +64,173 @@
 
 }  // namespace
 
-ArcoreConsentPrompt::ArcoreConsentPrompt() = default;
+ArCoreConsentPrompt::ArCoreConsentPrompt() : weak_ptr_factory_(this) {}
 
-ArcoreConsentPrompt::~ArcoreConsentPrompt() = default;
+ArCoreConsentPrompt::~ArCoreConsentPrompt() = default;
 
-void ArcoreConsentPrompt::GetUserPermission(
+void ArCoreConsentPrompt::GetUserPermission(
     int render_process_id,
     int render_frame_id,
     base::OnceCallback<void(bool)> response_callback) {
   on_user_consent_callback_ = std::move(response_callback);
+  render_process_id_ = render_process_id;
+  render_frame_id_ = render_frame_id;
 
   JNIEnv* env = AttachCurrentThread();
-  ScopedJavaLocalRef<jobject> jdelegate = Java_ArConsentDialog_showDialog(
+  jdelegate_ = Java_ArConsentDialog_showDialog(
       env, reinterpret_cast<jlong>(this),
-      GetTabFromRenderer(render_process_id, render_frame_id));
-  if (jdelegate.is_null()) {
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+  if (jdelegate_.is_null()) {
     std::move(on_user_consent_callback_).Run(false);
   }
 }
 
-void ArcoreConsentPrompt::OnUserConsentResult(
+void ArCoreConsentPrompt::OnUserConsentResult(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& j_caller,
     jboolean is_granted) {
-  DCHECK(on_user_consent_callback_);
-  std::move(on_user_consent_callback_).Run(is_granted);
+  jdelegate_.Reset();
+
+  if (!on_user_consent_callback_)
+    return;
+
+  if (!is_granted) {
+    CallDeferredUserConsentCallback(false);
+    return;
+  }
+
+  RequestArModule();
 }
 
 // static
-void ArcoreConsentPrompt::ShowConsentPrompt(
+void ArCoreConsentPrompt::ShowConsentPrompt(
     int render_process_id,
     int render_frame_id,
     base::OnceCallback<void(bool)> response_callback) {
   if (!g_instance)
-    g_instance = new ArcoreConsentPrompt();
+    g_instance = new ArCoreConsentPrompt();
   g_instance->GetUserPermission(render_process_id, render_frame_id,
                                 std::move(response_callback));
 }
 
+bool ArCoreConsentPrompt::CanRequestInstallArModule() {
+  return Java_ArCoreInstallUtils_canRequestInstallArModule(
+      AttachCurrentThread(), java_install_utils_);
+}
+
+bool ArCoreConsentPrompt::ShouldRequestInstallArModule() {
+  return Java_ArCoreInstallUtils_shouldRequestInstallArModule(
+      AttachCurrentThread(), java_install_utils_);
+}
+
+void ArCoreConsentPrompt::RequestInstallArModule() {
+  Java_ArCoreInstallUtils_requestInstallArModule(
+      AttachCurrentThread(), java_install_utils_,
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+}
+
+bool ArCoreConsentPrompt::ShouldRequestInstallSupportedArCore() {
+  JNIEnv* env = AttachCurrentThread();
+  return Java_ArCoreInstallUtils_shouldRequestInstallSupportedArCore(
+      env, java_install_utils_);
+}
+
+void ArCoreConsentPrompt::RequestInstallSupportedArCore() {
+  DCHECK(ShouldRequestInstallSupportedArCore());
+
+  JNIEnv* env = AttachCurrentThread();
+  Java_ArCoreInstallUtils_requestInstallSupportedArCore(
+      env, java_install_utils_,
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+}
+
+void ArCoreConsentPrompt::OnRequestInstallArModuleResult(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (on_request_ar_module_result_callback_) {
+    std::move(on_request_ar_module_result_callback_).Run(success);
+  }
+}
+
+void ArCoreConsentPrompt::OnRequestInstallSupportedArCoreResult(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(on_request_arcore_install_or_update_result_callback_);
+
+  std::move(on_request_arcore_install_or_update_result_callback_).Run(success);
+}
+
+void ArCoreConsentPrompt::RequestArModule() {
+  DVLOG(1) << __func__;
+  if (ShouldRequestInstallArModule()) {
+    if (!CanRequestInstallArModule()) {
+      OnRequestArModuleResult(false);
+      return;
+    }
+
+    on_request_ar_module_result_callback_ = base::BindOnce(
+        &ArCoreConsentPrompt::OnRequestArModuleResult, GetWeakPtr());
+    RequestInstallArModule();
+    return;
+  }
+
+  OnRequestArModuleResult(true);
+}
+
+void ArCoreConsentPrompt::OnRequestArModuleResult(bool success) {
+  DVLOG(3) << __func__ << ": success=" << success;
+
+  if (!success) {
+    CallDeferredUserConsentCallback(false);
+    return;
+  }
+
+  RequestArCoreInstallOrUpdate();
+}
+
+void ArCoreConsentPrompt::RequestArCoreInstallOrUpdate() {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!on_request_arcore_install_or_update_result_callback_);
+
+  if (ShouldRequestInstallSupportedArCore()) {
+    // ARCore is not installed or requires an update. Store the callback to be
+    // processed later once installation/update is complete or got cancelled.
+    on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
+        &ArCoreConsentPrompt::OnRequestArCoreInstallOrUpdateResult,
+        GetWeakPtr());
+
+    RequestInstallSupportedArCore();
+    return;
+  }
+
+  OnRequestArCoreInstallOrUpdateResult(true);
+}
+
+void ArCoreConsentPrompt::OnRequestArCoreInstallOrUpdateResult(bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  CallDeferredUserConsentCallback(success);
+}
+
+void ArCoreConsentPrompt::CallDeferredUserConsentCallback(
+    bool is_permission_granted) {
+  if (on_user_consent_callback_)
+    std::move(on_user_consent_callback_).Run(is_permission_granted);
+}
+
+static void JNI_ArCoreInstallUtils_InstallArCoreDeviceProviderFactory(
+    JNIEnv* env) {
+  device::ArCoreDeviceProviderFactory::Install(
+      std::make_unique<ArCoreDeviceProviderFactoryImpl>());
+}
+
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
index 257d5abc..3e24ec6 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
@@ -7,21 +7,24 @@
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
 #include "chrome/browser/vr/service/arcore_consent_prompt_interface.h"
 #include "chrome/browser/vr/vr_export.h"
 
 namespace vr {
 
-class VR_EXPORT ArcoreConsentPrompt : public ArcoreConsentPromptInterface {
+class VR_EXPORT ArCoreConsentPrompt : public ArCoreConsentPromptInterface {
  public:
   void ShowConsentPrompt(
       int render_process_id,
       int render_frame_id,
       base::OnceCallback<void(bool)> response_callback) override;
 
-  ArcoreConsentPrompt();
-  ~ArcoreConsentPrompt();
+  ArCoreConsentPrompt();
+  ~ArCoreConsentPrompt();
 
   // device::VrDevicePermissionProvider:
   void GetUserPermission(int render_process_id,
@@ -32,8 +35,51 @@
                            const base::android::JavaParamRef<jobject>& j_caller,
                            jboolean is_granted);
 
+  // Returns true if AR module installation is supported, false otherwise.
+  virtual bool CanRequestInstallArModule();
+  // Returns true if AR module is not installed, false otherwise.
+  virtual bool ShouldRequestInstallArModule();
+  virtual void RequestInstallArModule();
+  virtual bool ShouldRequestInstallSupportedArCore();
+  virtual void RequestInstallSupportedArCore();
+
+  // Called from Java end.
+  void OnRequestInstallArModuleResult(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      bool success);
+  void OnRequestInstallSupportedArCoreResult(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      bool success);
+
  private:
+  void RequestArModule();
+  void OnRequestArModuleResult(bool success);
+  void RequestArCoreInstallOrUpdate();
+  void OnRequestArCoreInstallOrUpdateResult(bool success);
+
+  void CallDeferredUserConsentCallback(bool is_permission_granted);
+
+  base::WeakPtr<ArCoreConsentPrompt> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   base::OnceCallback<void(bool)> on_user_consent_callback_;
+
+  base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
+  base::OnceCallback<void(bool)>
+      on_request_arcore_install_or_update_result_callback_;
+
+  base::android::ScopedJavaLocalRef<jobject> jdelegate_;
+  int render_process_id_;
+  int render_frame_id_;
+
+  base::android::ScopedJavaGlobalRef<jobject> java_install_utils_;
+  THREAD_CHECKER(thread_checker_);
+
+  base::WeakPtrFactory<ArCoreConsentPrompt> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ArCoreConsentPrompt);
 };
 
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.cc b/chrome/browser/android/vr/arcore_device/arcore_device.cc
index 535231c7..205e130c 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.cc
@@ -14,8 +14,8 @@
 #include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_gl_thread.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_impl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "chrome/browser/permissions/permission_manager.h"
 #include "chrome/browser/permissions/permission_result.h"
@@ -74,13 +74,13 @@
     std::unique_ptr<ArCoreFactory> arcore_factory,
     std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
     std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
-    std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils)
+    std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils)
     : VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       arcore_factory_(std::move(arcore_factory)),
       ar_image_transport_factory_(std::move(ar_image_transport_factory)),
       mailbox_bridge_(std::move(mailbox_to_surface_bridge)),
-      arcore_install_utils_(std::move(arcore_install_utils)),
+      arcore_session_utils_(std::move(arcore_session_utils)),
       session_state_(std::make_unique<ArCoreDevice::SessionState>()),
       weak_ptr_factory_(this) {
   // Ensure display_info_ is set to avoid crash in CallDeferredSessionCallback
@@ -97,25 +97,16 @@
 }
 
 ArCoreDevice::ArCoreDevice()
-    : ArCoreDevice(
-          std::make_unique<ArCoreImplFactory>(),
-          std::make_unique<ArImageTransportFactory>(),
-          std::make_unique<vr::MailboxToSurfaceBridge>(),
-          std::make_unique<vr::ArCoreJavaUtils>(
-              base::BindRepeating(
-                  &ArCoreDevice::OnRequestInstallArModuleResult,
-                  base::Unretained(this)),  // unretained is fine for callbacks
-                                            // since ArCoreDevice owns the
-                                            // ArCoreJavaUtils instance
-              base::BindRepeating(
-                  &ArCoreDevice::OnRequestInstallSupportedArCoreResult,
-                  base::Unretained(this)))) {}
+    : ArCoreDevice(std::make_unique<ArCoreImplFactory>(),
+                   std::make_unique<ArImageTransportFactory>(),
+                   std::make_unique<vr::MailboxToSurfaceBridge>(),
+                   std::make_unique<vr::ArCoreJavaUtils>()) {}
 
 ArCoreDevice::~ArCoreDevice() {
   CallDeferredRequestSessionCallback(/*success=*/false);
   // The GL thread must be terminated since it uses our members. For example,
   // there might still be a posted Initialize() call in flight that uses
-  // arcore_install_utils_ and arcore_factory_. Ensure that the thread is
+  // arcore_session_utils_ and arcore_factory_. Ensure that the thread is
   // stopped before other members get destructed. Don't call Stop() here,
   // destruction calls Stop() and doing so twice is illegal (null pointer
   // dereference).
@@ -190,15 +181,6 @@
 
 void ArCoreDevice::RequestSessionAfterInitialization(int render_process_id,
                                                      int render_frame_id) {
-  session_state_->start_immersive_activity_callback_ =
-      base::BindOnce(&ArCoreDevice::RequestArSessionConsent, GetWeakPtr(),
-                     render_process_id, render_frame_id);
-
-  RequestArModule(render_process_id, render_frame_id);
-}
-
-void ArCoreDevice::RequestArSessionConsent(int render_process_id,
-                                           int render_frame_id) {
   auto ready_callback =
       base::BindRepeating(&ArCoreDevice::OnDrawingSurfaceReady, GetWeakPtr());
   auto touch_callback =
@@ -206,7 +188,7 @@
   auto destroyed_callback =
       base::BindOnce(&ArCoreDevice::OnDrawingSurfaceDestroyed, GetWeakPtr());
 
-  arcore_install_utils_->RequestArSession(
+  arcore_session_utils_->RequestArSession(
       render_process_id, render_frame_id, std::move(ready_callback),
       std::move(touch_callback), std::move(destroyed_callback));
 }
@@ -250,7 +232,7 @@
   DVLOG(1) << __func__;
 
   // This may be a no-op in case it's destroyed already.
-  arcore_install_utils_->DestroyDrawingSurface();
+  arcore_session_utils_->DestroyDrawingSurface();
 
   // The GL thread had initialized its context with a drawing_widget based on
   // the ArImmersiveOverlay's Surface, and the one it has is no longer valid.
@@ -276,88 +258,6 @@
   mailbox_bridge_ = nullptr;
 }
 
-void ArCoreDevice::RequestArModule(int render_process_id, int render_frame_id) {
-  DVLOG(1) << __func__;
-  if (arcore_install_utils_->ShouldRequestInstallArModule()) {
-    if (!arcore_install_utils_->CanRequestInstallArModule()) {
-      OnRequestArModuleResult(render_process_id, render_frame_id, false);
-      return;
-    }
-
-    on_request_ar_module_result_callback_ =
-        base::BindOnce(&ArCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
-                       render_process_id, render_frame_id);
-    arcore_install_utils_->RequestInstallArModule(render_process_id,
-                                                  render_frame_id);
-    return;
-  }
-
-  OnRequestArModuleResult(render_process_id, render_frame_id, true);
-}
-
-void ArCoreDevice::OnRequestArModuleResult(int render_process_id,
-                                           int render_frame_id,
-                                           bool success) {
-  DVLOG(3) << __func__ << ": success=" << success;
-
-  if (!success) {
-    CallDeferredRequestSessionCallback(/*success=*/false);
-    return;
-  }
-
-  RequestArCoreInstallOrUpdate(render_process_id, render_frame_id);
-}
-
-void ArCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
-                                                int render_frame_id) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-  DCHECK(!on_request_arcore_install_or_update_result_callback_);
-
-  if (arcore_install_utils_->ShouldRequestInstallSupportedArCore()) {
-    // ARCore is not installed or requires an update. Store the callback to be
-    // processed later once installation/update is complete or got cancelled.
-    on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
-        &ArCoreDevice::OnRequestArCoreInstallOrUpdateResult, GetWeakPtr());
-
-    arcore_install_utils_->RequestInstallSupportedArCore(render_process_id,
-                                                         render_frame_id);
-    return;
-  }
-
-  OnRequestArCoreInstallOrUpdateResult(true);
-}
-
-void ArCoreDevice::OnRequestArCoreInstallOrUpdateResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-
-  if (!success) {
-    CallDeferredRequestSessionCallback(/*success=*/false);
-    return;
-  }
-
-  DCHECK(session_state_->start_immersive_activity_callback_);
-  std::move(session_state_->start_immersive_activity_callback_).Run();
-}
-
-void ArCoreDevice::OnRequestInstallArModuleResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-
-  if (on_request_ar_module_result_callback_) {
-    std::move(on_request_ar_module_result_callback_).Run(success);
-  }
-}
-
-void ArCoreDevice::OnRequestInstallSupportedArCoreResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-  DCHECK(on_request_arcore_install_or_update_result_callback_);
-
-  std::move(on_request_arcore_install_or_update_result_callback_).Run(success);
-}
-
 void ArCoreDevice::CallDeferredRequestSessionCallback(bool success) {
   DVLOG(1) << __func__ << " success=" << success;
   DCHECK(IsOnMainThread());
@@ -430,7 +330,7 @@
   DCHECK(IsOnMainThread());
   DCHECK(session_state_->is_arcore_gl_thread_initialized_);
 
-  if (!arcore_install_utils_->EnsureLoaded()) {
+  if (!arcore_session_utils_->EnsureLoaded()) {
     DLOG(ERROR) << "ARCore was not loaded properly.";
     OnArCoreGlInitializationComplete(false);
     return;
@@ -445,7 +345,7 @@
     PostTaskToGlThread(base::BindOnce(
         &ArCoreGl::Initialize,
         session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
-        arcore_install_utils_.get(), arcore_factory_.get(), drawing_widget,
+        arcore_session_utils_.get(), arcore_factory_.get(), drawing_widget,
         frame_size, rotation,
         CreateMainThreadCallback(base::BindOnce(
             &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.h b/chrome/browser/android/vr/arcore_device/arcore_device.h
index fc2560b..64a4e79 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.h
@@ -22,7 +22,7 @@
 
 namespace vr {
 class MailboxToSurfaceBridge;
-class ArCoreInstallUtils;
+class ArCoreSessionUtils;
 }  // namespace vr
 
 namespace device {
@@ -37,7 +37,7 @@
       std::unique_ptr<ArCoreFactory> arcore_factory,
       std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
       std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
-      std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils);
+      std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils);
   ArCoreDevice();
   ~ArCoreDevice() override;
 
@@ -51,9 +51,6 @@
   }
 
  private:
-  void OnRequestInstallArModuleResult(bool success);
-  void OnRequestInstallSupportedArCoreResult(bool success);
-
   // VRDeviceBase implementation
   void OnMailboxBridgeReady();
   void OnArCoreGlThreadInitialized();
@@ -102,7 +99,6 @@
                                      int rotation,
                                      const gfx::Size& size);
   void OnArCoreGlInitializationComplete(bool success);
-  void RequestArSessionConsent(int render_process_id, int render_frame_id);
 
   void OnCreateSessionCallback(
       mojom::XRRuntime::RequestSessionCallback deferred_callback,
@@ -115,7 +111,7 @@
   std::unique_ptr<ArCoreFactory> arcore_factory_;
   std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
   std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
-  std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils_;
+  std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils_;
 
   // Encapsulates data with session lifetime.
   struct SessionState {
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
index cc514c7..72706d61 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
@@ -11,9 +11,8 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/arcore_device/fake_arcore.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
@@ -82,18 +81,10 @@
   base::OnceClosure callback_;
 };
 
-class StubArCoreInstallUtils : public vr::ArCoreInstallUtils {
+class StubArCoreSessionUtils : public vr::ArCoreSessionUtils {
  public:
-  StubArCoreInstallUtils() = default;
+  StubArCoreSessionUtils() = default;
 
-  bool CanRequestInstallArModule() override { return false; }
-  bool ShouldRequestInstallArModule() override { return false; }
-
-  void RequestInstallArModule(int render_process_id,
-                              int render_frame_id) override {}
-  bool ShouldRequestInstallSupportedArCore() override { return false; }
-  void RequestInstallSupportedArCore(int render_process_id,
-                                     int render_frame_id) override {}
   void RequestArSession(
       int render_process_id,
       int render_frame_id,
@@ -148,7 +139,7 @@
   }
 
   StubMailboxToSurfaceBridge* bridge;
-  StubArCoreInstallUtils* install_utils;
+  StubArCoreSessionUtils* session_utils;
   mojom::XRFrameDataProviderPtr frame_provider;
   mojom::XREnvironmentIntegrationProviderAssociatedPtr environment_provider;
   std::unique_ptr<base::RunLoop> run_loop;
@@ -159,13 +150,13 @@
     std::unique_ptr<StubMailboxToSurfaceBridge> bridge_ptr =
         std::make_unique<StubMailboxToSurfaceBridge>();
     bridge = bridge_ptr.get();
-    std::unique_ptr<StubArCoreInstallUtils> install_utils_ptr =
-        std::make_unique<StubArCoreInstallUtils>();
-    install_utils = install_utils_ptr.get();
+    std::unique_ptr<StubArCoreSessionUtils> session_utils_ptr =
+        std::make_unique<StubArCoreSessionUtils>();
+    session_utils = session_utils_ptr.get();
     device_ = std::make_unique<ArCoreDevice>(
         std::make_unique<FakeArCoreFactory>(),
         std::make_unique<StubArImageTransportFactory>(), std::move(bridge_ptr),
-        std::move(install_utils_ptr));
+        std::move(session_utils_ptr));
   }
 
   void CreateSession() {
@@ -245,7 +236,7 @@
                                        base::BindOnce(callback, &hit_results));
   // Have to get frame data to trigger the hit-test calculation.
   GetFrameData();
-  EXPECT_TRUE(hit_results.size() > 0);
+  EXPECT_FALSE(hit_results.empty());
 }
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index 1bfb8e0..a466b51 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -20,7 +20,7 @@
 #include "base/trace_event/traced_value.h"
 #include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_impl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/web_xr_presentation_state.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
@@ -119,7 +119,7 @@
   CloseBindingsIfOpen();
 }
 
-void ArCoreGl::Initialize(vr::ArCoreInstallUtils* install_utils,
+void ArCoreGl::Initialize(vr::ArCoreSessionUtils* session_utils,
                           ArCoreFactory* arcore_factory,
                           gfx::AcceleratedWidget drawing_widget,
                           const gfx::Size& frame_size,
@@ -141,7 +141,7 @@
 
   // Get the activity context.
   base::android::ScopedJavaLocalRef<jobject> application_context =
-      install_utils->GetApplicationContext();
+      session_utils->GetApplicationContext();
   if (!application_context.obj()) {
     DLOG(ERROR) << "Unable to retrieve the Java context/activity!";
     std::move(callback).Run(false);
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 0e13a647..0121a4d8 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -36,7 +36,7 @@
 }  // namespace gl
 
 namespace vr {
-class ArCoreInstallUtils;
+class ArCoreSessionUtils;
 class WebXrPresentationState;
 }  // namespace vr
 
@@ -63,7 +63,7 @@
   explicit ArCoreGl(std::unique_ptr<ArImageTransport> ar_image_transport);
   ~ArCoreGl() override;
 
-  void Initialize(vr::ArCoreInstallUtils* install_utils,
+  void Initialize(vr::ArCoreSessionUtils* session_utils,
                   ArCoreFactory* arcore_factory,
                   gfx::AcceleratedWidget drawing_widget,
                   const gfx::Size& frame_size,
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
index 111e9a31..54392cd 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
@@ -10,11 +10,9 @@
 #include "base/android/jni_string.h"
 #include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/android/vr/ar_jni_headers/ArCoreJavaUtils_jni.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_shim.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
-#include "device/vr/android/arcore/arcore_device_provider_factory.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ScopedJavaLocalRef;
@@ -23,29 +21,9 @@
 
 namespace {
 
-class ArCoreDeviceProviderFactoryImpl
-    : public device::ArCoreDeviceProviderFactory {
- public:
-  ArCoreDeviceProviderFactoryImpl() = default;
-  ~ArCoreDeviceProviderFactoryImpl() override = default;
-  std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactoryImpl);
-};
-
-std::unique_ptr<device::VRDeviceProvider>
-ArCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
-  return std::make_unique<device::ArCoreDeviceProvider>();
-}
-
 }  // namespace
 
-ArCoreJavaUtils::ArCoreJavaUtils(
-    base::RepeatingCallback<void(bool)> ar_module_installation_callback,
-    base::RepeatingCallback<void(bool)> ar_core_installation_callback)
-    : ar_module_installation_callback_(ar_module_installation_callback),
-      ar_core_installation_callback_(ar_core_installation_callback) {
+ArCoreJavaUtils::ArCoreJavaUtils() {
   JNIEnv* env = AttachCurrentThread();
   if (!env)
     return;
@@ -61,46 +39,6 @@
   Java_ArCoreJavaUtils_onNativeDestroy(env, j_arcore_java_utils_);
 }
 
-void ArCoreJavaUtils::OnRequestInstallSupportedArCoreResult(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    bool success) {
-  ar_core_installation_callback_.Run(success);
-}
-
-bool ArCoreJavaUtils::CanRequestInstallArModule() {
-  return Java_ArCoreJavaUtils_canRequestInstallArModule(AttachCurrentThread(),
-                                                        j_arcore_java_utils_);
-}
-
-bool ArCoreJavaUtils::ShouldRequestInstallArModule() {
-  return Java_ArCoreJavaUtils_shouldRequestInstallArModule(
-      AttachCurrentThread(), j_arcore_java_utils_);
-}
-
-void ArCoreJavaUtils::RequestInstallArModule(int render_process_id,
-                                             int render_frame_id) {
-  Java_ArCoreJavaUtils_requestInstallArModule(
-      AttachCurrentThread(), j_arcore_java_utils_,
-      getTabFromRenderer(render_process_id, render_frame_id));
-}
-
-bool ArCoreJavaUtils::ShouldRequestInstallSupportedArCore() {
-  JNIEnv* env = AttachCurrentThread();
-  return Java_ArCoreJavaUtils_shouldRequestInstallSupportedArCore(
-      env, j_arcore_java_utils_);
-}
-
-void ArCoreJavaUtils::RequestInstallSupportedArCore(int render_process_id,
-                                                    int render_frame_id) {
-  DCHECK(ShouldRequestInstallSupportedArCore());
-
-  JNIEnv* env = AttachCurrentThread();
-  Java_ArCoreJavaUtils_requestInstallSupportedArCore(
-      env, j_arcore_java_utils_,
-      getTabFromRenderer(render_process_id, render_frame_id));
-}
-
 void ArCoreJavaUtils::RequestArSession(
     int render_process_id,
     int render_frame_id,
@@ -161,13 +99,6 @@
   }
 }
 
-void ArCoreJavaUtils::OnRequestInstallArModuleResult(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    bool success) {
-  ar_module_installation_callback_.Run(success);
-}
-
 bool ArCoreJavaUtils::EnsureLoaded() {
   DCHECK(vr::IsArCoreSupported());
 
@@ -206,10 +137,4 @@
   return j_tab_android;
 }
 
-static void JNI_ArCoreJavaUtils_InstallArCoreDeviceProviderFactory(
-    JNIEnv* env) {
-  device::ArCoreDeviceProviderFactory::Install(
-      std::make_unique<ArCoreDeviceProviderFactoryImpl>());
-}
-
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
index 5a5afe5..9e9ac15b 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
@@ -11,39 +11,26 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 
 namespace vr {
 
-class ArCoreJavaUtils : public ArCoreInstallUtils {
+class ArCoreJavaUtils : public ArCoreSessionUtils {
  public:
-  explicit ArCoreJavaUtils(
-      base::RepeatingCallback<void(bool)> ar_module_installation_callback,
-      base::RepeatingCallback<void(bool)> ar_core_installation_callback);
+  ArCoreJavaUtils();
   ~ArCoreJavaUtils() override;
-  bool ShouldRequestInstallArModule() override;
-  bool CanRequestInstallArModule() override;
-  void RequestInstallArModule(int render_process_id,
-                              int render_frame_id) override;
-  bool ShouldRequestInstallSupportedArCore() override;
-  void RequestInstallSupportedArCore(int render_process_id,
-                                     int render_frame_id) override;
+
+  // ArCoreSessionUtils:
   void RequestArSession(int render_process_id,
                         int render_frame_id,
                         SurfaceReadyCallback ready_callback,
                         SurfaceTouchCallback touch_callback,
                         SurfaceDestroyedCallback destroyed_callback) override;
   void DestroyDrawingSurface() override;
+  bool EnsureLoaded() override;
+  base::android::ScopedJavaLocalRef<jobject> GetApplicationContext() override;
 
   // Methods called from the Java side.
-  void OnRequestInstallArModuleResult(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      bool success);
-  void OnRequestInstallSupportedArCoreResult(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      bool success);
   void OnDrawingSurfaceReady(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
@@ -60,17 +47,11 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
 
-  bool EnsureLoaded() override;
-  base::android::ScopedJavaLocalRef<jobject> GetApplicationContext() override;
-
  private:
   base::android::ScopedJavaLocalRef<jobject> getTabFromRenderer(
       int render_process_id,
       int render_frame_id);
 
-  base::RepeatingCallback<void(bool)> ar_module_installation_callback_;
-  base::RepeatingCallback<void(bool)> ar_core_installation_callback_;
-
   base::android::ScopedJavaGlobalRef<jobject> j_arcore_java_utils_;
 
   SurfaceReadyCallback surface_ready_callback_;
diff --git a/chrome/browser/android/vr/arcore_device/arcore_install_utils.h b/chrome/browser/android/vr/arcore_device/arcore_session_utils.h
similarity index 64%
rename from chrome/browser/android/vr/arcore_device/arcore_install_utils.h
rename to chrome/browser/android/vr/arcore_device/arcore_session_utils.h
index f1156983..1573fbe4 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_install_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_session_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
+#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
+#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
 
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/weak_ptr.h"
@@ -30,18 +30,9 @@
     base::RepeatingCallback<void(bool touching, const gfx::PointF& location)>;
 using SurfaceDestroyedCallback = base::OnceClosure;
 
-class ArCoreInstallUtils {
+class ArCoreSessionUtils {
  public:
-  virtual ~ArCoreInstallUtils() = default;
-  // Returns true if AR module installation is supported, false otherwise.
-  virtual bool CanRequestInstallArModule() = 0;
-  // Returns true if AR module is not installed, false otherwise.
-  virtual bool ShouldRequestInstallArModule() = 0;
-  virtual void RequestInstallArModule(int render_process_id,
-                                      int render_frame_id) = 0;
-  virtual bool ShouldRequestInstallSupportedArCore() = 0;
-  virtual void RequestInstallSupportedArCore(int render_process_id,
-                                             int render_frame_id) = 0;
+  virtual ~ArCoreSessionUtils() = default;
   virtual bool EnsureLoaded() = 0;
   virtual base::android::ScopedJavaLocalRef<jobject>
   GetApplicationContext() = 0;
@@ -56,4 +47,4 @@
 
 }  // namespace vr
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
+#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
diff --git a/chrome/browser/android/vr/vr_module_provider.cc b/chrome/browser/android/vr/vr_module_provider.cc
index 1004ae4..92abcf8 100644
--- a/chrome/browser/android/vr/vr_module_provider.cc
+++ b/chrome/browser/android/vr/vr_module_provider.cc
@@ -88,7 +88,7 @@
 static void JNI_VrModuleProvider_Init(JNIEnv* env) {
   GvrConsentHelper::SetInstance(std::make_unique<vr::GvrConsentHelperImpl>());
 #if BUILDFLAG(ENABLE_ARCORE)
-  ArcoreConsentPromptInterface::SetInstance(new ArcoreConsentPrompt());
+  ArCoreConsentPromptInterface::SetInstance(new ArCoreConsentPrompt());
 #endif
 }
 
diff --git a/chrome/browser/android/webapk/webapk.proto b/chrome/browser/android/webapk/webapk.proto
index 3afd167..e75cc63 100644
--- a/chrome/browser/android/webapk/webapk.proto
+++ b/chrome/browser/android/webapk/webapk.proto
@@ -46,6 +46,7 @@
     ORIENTATION_DIFFERS = 11;
     DISPLAY_MODE_DIFFERS = 12;
     WEB_SHARE_TARGET_DIFFERS = 13;
+    MANUALLY_TRIGGERED = 14;
   }
 
   // Package name of the WebAPK.
diff --git a/chrome/browser/android/webapk/webapk_info.cc b/chrome/browser/android/webapk/webapk_info.cc
index 9c77db24..f2d36b59 100644
--- a/chrome/browser/android/webapk/webapk_info.cc
+++ b/chrome/browser/android/webapk/webapk_info.cc
@@ -9,6 +9,7 @@
 WebApkInfo::WebApkInfo(std::string name,
                        std::string short_name,
                        std::string package_name,
+                       std::string id,
                        int shell_apk_version,
                        int version_code,
                        std::string uri,
@@ -20,10 +21,12 @@
                        base::Optional<SkColor> theme_color,
                        base::Optional<SkColor> background_color,
                        base::Time last_update_check_time,
-                       bool relax_updates)
+                       bool relax_updates,
+                       std::string update_status)
     : name(std::move(name)),
       short_name(std::move(short_name)),
       package_name(std::move(package_name)),
+      id(std::move(id)),
       shell_apk_version(shell_apk_version),
       version_code(version_code),
       uri(std::move(uri)),
@@ -35,7 +38,8 @@
       theme_color(theme_color),
       background_color(background_color),
       last_update_check_time(last_update_check_time),
-      relax_updates(relax_updates) {}
+      relax_updates(relax_updates),
+      update_status(std::move(update_status)) {}
 
 WebApkInfo::~WebApkInfo() {}
 
diff --git a/chrome/browser/android/webapk/webapk_info.h b/chrome/browser/android/webapk/webapk_info.h
index d2d8fb0..d035bb3 100644
--- a/chrome/browser/android/webapk/webapk_info.h
+++ b/chrome/browser/android/webapk/webapk_info.h
@@ -23,6 +23,7 @@
   WebApkInfo(std::string name,
              std::string short_name,
              std::string package_name,
+             std::string id,
              int shell_apk_version,
              int version_code,
              std::string uri,
@@ -34,7 +35,8 @@
              base::Optional<SkColor> theme_color,
              base::Optional<SkColor> background_color,
              base::Time last_update_check_time,
-             bool relax_updates);
+             bool relax_updates,
+             std::string update_status);
   ~WebApkInfo();
 
   WebApkInfo& operator=(WebApkInfo&& other) noexcept;
@@ -49,6 +51,9 @@
   // Package name of the WebAPK.
   std::string package_name;
 
+  // Internal ID of the WebAPK.
+  std::string id;
+
   // Shell APK version of the WebAPK.
   int shell_apk_version;
 
@@ -66,6 +71,9 @@
   base::Time last_update_check_time;
   bool relax_updates;
 
+  // Update Status of the WebAPK.
+  std::string update_status;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WebApkInfo);
 };
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 004c369a..f27f3e5 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -140,6 +140,8 @@
       return webapk::WebApk::DISPLAY_MODE_DIFFERS;
     case WebApkUpdateReason::WEB_SHARE_TARGET_DIFFERS:
       return webapk::WebApk::WEB_SHARE_TARGET_DIFFERS;
+    case WebApkUpdateReason::MANUALLY_TRIGGERED:
+      return webapk::WebApk::MANUALLY_TRIGGERED;
   }
 }
 
diff --git a/chrome/browser/android/webapk/webapk_types.h b/chrome/browser/android/webapk/webapk_types.h
index 5269faf0..73d202f9 100644
--- a/chrome/browser/android/webapk/webapk_types.h
+++ b/chrome/browser/android/webapk/webapk_types.h
@@ -23,6 +23,7 @@
   ORIENTATION_DIFFERS,
   DISPLAY_MODE_DIFFERS,
   WEB_SHARE_TARGET_DIFFERS,
+  MANUALLY_TRIGGERED,
 };
 
 #endif  // CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_TYPES_H_
diff --git a/chrome/browser/autofill/address_accessory_controller_impl.cc b/chrome/browser/autofill/address_accessory_controller_impl.cc
index debb8e0..72aff540 100644
--- a/chrome/browser/autofill/address_accessory_controller_impl.cc
+++ b/chrome/browser/autofill/address_accessory_controller_impl.cc
@@ -27,10 +27,7 @@
 // Defines which types to load from the Personal data manager and add as field
 // to the address sheet. Order matters.
 constexpr ServerFieldType kTypesToInclude[] = {
-    // TODO(crbug.com/965494): Possibly, the names should be in a single chip.
-    ServerFieldType::NAME_FIRST,
-    ServerFieldType::NAME_MIDDLE,
-    ServerFieldType::NAME_LAST,
+    ServerFieldType::NAME_FULL,
     ServerFieldType::COMPANY_NAME,
     ServerFieldType::ADDRESS_HOME_LINE1,
     ServerFieldType::ADDRESS_HOME_LINE2,
diff --git a/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc b/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
index bbf7e26..b13d681 100644
--- a/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
+++ b/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
@@ -114,9 +114,7 @@
       result,
       AddressAccessorySheetDataBuilder(base::string16())
           .AddUserInfo()
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_FIRST))
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_MIDDLE))
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_LAST))
+          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_FULL))
           .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::COMPANY_NAME))
           .AppendSimpleField(
               canadian.GetRawInfo(ServerFieldType::ADDRESS_HOME_LINE1))
@@ -163,11 +161,7 @@
   personal_data_manager()->AddProfile(email);
   ASSERT_EQ(result, AddressAccessorySheetDataBuilder(base::string16())
                         .AddUserInfo()
-                        /*name first:*/
-                        .AppendSimpleField(base::string16())
-                        /*name middle:*/
-                        .AppendSimpleField(base::string16())
-                        /*name last:*/
+                        /*name full:*/
                         .AppendSimpleField(base::string16())
                         /*company name:*/
                         .AppendSimpleField(base::string16())
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
index 4caa854..339addef 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
 #include "chrome/browser/ui/autofill/autofill_popup_layout_model.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
@@ -17,6 +18,22 @@
 
 namespace autofill {
 
+constexpr base::char16 kLabelSeparator = ' ';
+constexpr size_t kMaxBulletCount = 8;
+
+namespace {
+base::string16 CreateLabel(const Suggestion& suggestion) {
+  base::string16 password =
+      suggestion.additional_label.substr(0, kMaxBulletCount);
+  // The label contains the signon_realm or is empty. The additional_label can
+  // never be empty since it must contain a password.
+  if (suggestion.label.empty())
+    return password;
+  return suggestion.label + kLabelSeparator + password;
+}
+
+}  // namespace
+
 AutofillKeyboardAccessoryAdapter::AutofillKeyboardAccessoryAdapter(
     AutofillPopupController* controller,
     unsigned int animation_duration_millis,
@@ -49,12 +66,15 @@
   DCHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
   DCHECK(view_) << "OnSuggestionsChanged called before a View was set!";
 
+  labels_.clear();
   front_element_ = base::nullopt;
   for (int i = 0; i < GetLineCount(); ++i) {
     const Suggestion& suggestion = controller_->GetSuggestionAt(i);
     if (suggestion.frontend_id != POPUP_ITEM_ID_CLEAR_FORM &&
-        suggestion.frontend_id != POPUP_ITEM_ID_CREATE_HINT)
+        suggestion.frontend_id != POPUP_ITEM_ID_CREATE_HINT) {
+      labels_.push_back(CreateLabel(suggestion));
       continue;
+    }
     DCHECK(!front_element_.has_value()) << "Additional front item at: " << i;
     front_element_ = base::Optional<int>(i);
   }
@@ -88,11 +108,7 @@
 const base::string16& AutofillKeyboardAccessoryAdapter::GetElidedLabelAt(
     int row) const {
   DCHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
-  const base::string16& label =
-      controller_->GetElidedLabelAt(OffsetIndexFor(row));
-  if (label.empty())
-    return GetSuggestionAt(row).additional_label;
-  return label;
+  return labels_[row];
 }
 
 bool AutofillKeyboardAccessoryAdapter::GetRemovalConfirmationText(
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
index 8ad5806..c8b597d4 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
@@ -100,6 +100,9 @@
   AutofillPopupController* controller_;  // weak.
   std::unique_ptr<AutofillKeyboardAccessoryAdapter::AccessoryView> view_;
 
+  // The labels to be used for the input chips.
+  std::vector<base::string16> labels_;
+
   // If 0, don't animate suggestion view.
   const unsigned int animation_duration_millis_;
 
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc b/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
index 12ef283..cb78884 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
@@ -5,6 +5,7 @@
 #include <cstddef>
 
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -51,13 +52,20 @@
   DISALLOW_COPY_AND_ASSIGN(MockAccessoryView);
 };
 
+Suggestion createPasswordEntry(std::string password,
+                               std::string username,
+                               std::string psl_origin) {
+  Suggestion s(/*value=*/username, /*label=*/psl_origin, /*icon=*/"",
+               PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY);
+  s.additional_label = ASCIIToUTF16(password);
+  return s;
+}
+
 std::vector<Suggestion> createSuggestions() {
   std::vector<Suggestion> suggestions = {
-      Suggestion("*", "A", "", PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY),
-      Suggestion("**", "", "", PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY),
-      Suggestion("***", "C", "",
-                 PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY)};
-  suggestions[1].additional_label = ASCIIToUTF16("B");
+      createPasswordEntry("****************", "Alf", ""),
+      createPasswordEntry("****************", "Berta", "psl.origin.eg"),
+      createPasswordEntry("***", "Carl", "")};
   return suggestions;
 }
 
@@ -174,14 +182,19 @@
 }
 
 TEST_F(AutofillKeyboardAccessoryAdapterTest, UseAdditionalLabelForElidedLabel) {
-  controller()->set_suggestions(createSuggestions());
+  controller()->set_suggestions(createSuggestions(/*clearItemOffset=*/1));
   NotifyAboutSuggestions();
 
-  // If there is a label, use it.
-  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(0), ASCIIToUTF16("A"));
+  // If there is a label, use it but cap at 8 bullets.
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(0),
+            ASCIIToUTF16("********"));
 
   // If the label is empty, use the additional label:
-  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(1), ASCIIToUTF16("B"));
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(1),
+            ASCIIToUTF16("psl.origin.eg ********"));
+
+  // If the password has less than 8 bullets, show the exact amount.
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(2), ASCIIToUTF16("***"));
 }
 
 TEST_F(AutofillKeyboardAccessoryAdapterTest, ProvideReorderedSuggestions) {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index a8017edc..68ff588 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3515,6 +3515,9 @@
     web_prefs->text_track_text_shadow = style->text_shadow;
     web_prefs->text_track_font_family = style->font_family;
     web_prefs->text_track_font_variant = style->font_variant;
+    web_prefs->text_track_window_color = style->window_color;
+    web_prefs->text_track_window_padding = style->window_padding;
+    web_prefs->text_track_window_radius = style->window_radius;
   }
 
   for (size_t i = 0; i < extra_parts_.size(); ++i)
diff --git a/chrome/browser/chrome_notification_types.h b/chrome/browser/chrome_notification_types.h
index 9176955..763f2fa 100644
--- a/chrome/browser/chrome_notification_types.h
+++ b/chrome/browser/chrome_notification_types.h
@@ -118,14 +118,6 @@
   // the WebContents containing the NavigationController is destroyed.
   NOTIFICATION_TAB_CLOSING,
 
-  // Stuff inside the tabs ---------------------------------------------------
-
-  // This notification is sent when the result of a find-in-page search is
-  // available with the browser process. The source is a Source<WebContents>.
-  // Details encompass a FindNotificationDetail object that tells whether the
-  // match was found or not found.
-  NOTIFICATION_FIND_RESULT_AVAILABLE,
-
   // Authentication ----------------------------------------------------------
 
   // This is sent when a login prompt is shown.  The source is the
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 1ccbe0a7..104e5ff 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -736,6 +736,7 @@
     "crostini/crostini_remover.h",
     "crostini/crostini_reporting_util.cc",
     "crostini/crostini_reporting_util.h",
+    "crostini/crostini_simple_types.h",
     "crostini/crostini_terminal.cc",
     "crostini/crostini_terminal.h",
     "crostini/crostini_util.cc",
@@ -1256,12 +1257,12 @@
     "login/reauth_stats.h",
     "login/saml/in_session_password_change_manager.cc",
     "login/saml/in_session_password_change_manager.h",
+    "login/saml/password_expiry_notification.cc",
+    "login/saml/password_expiry_notification.h",
     "login/saml/saml_offline_signin_limiter.cc",
     "login/saml/saml_offline_signin_limiter.h",
     "login/saml/saml_offline_signin_limiter_factory.cc",
     "login/saml/saml_offline_signin_limiter_factory.h",
-    "login/saml/saml_password_expiry_notification.cc",
-    "login/saml/saml_password_expiry_notification.h",
     "login/saml/saml_profile_prefs.cc",
     "login/saml/saml_profile_prefs.h",
     "login/screen_manager.cc",
@@ -2514,8 +2515,9 @@
     "login/quick_unlock/fingerprint_storage_unittest.cc",
     "login/quick_unlock/pin_storage_prefs_unittest.cc",
     "login/quick_unlock/quick_unlock_storage_unittest.cc",
+    "login/saml/in_session_password_change_manager_unittest.cc",
+    "login/saml/password_expiry_notification_unittest.cc",
     "login/saml/saml_offline_signin_limiter_unittest.cc",
-    "login/saml/saml_password_expiry_notification_unittest.cc",
     "login/screens/multidevice_setup_screen_unittest.cc",
     "login/screens/network_screen_unittest.cc",
     "login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc",
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 81f1716c..18d5739 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
+#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "chrome/browser/ui/browser.h"
@@ -29,137 +30,6 @@
 
 namespace crostini {
 
-// Result types for various callbacks etc.
-
-// WARNING: Do not remove or re-order these values, as they are used in user
-// visible error messages and logs. New entries should only be added to the end.
-// This message was added during development of M74, error codes from prior
-// versions may differ from the numbering here.
-enum class CrostiniResult {
-  SUCCESS = 0,
-  // DBUS_ERROR = 1,
-  // UNPARSEABLE_RESPONSE = 2,
-  // INSUFFICIENT_DISK = 3,
-  CREATE_DISK_IMAGE_FAILED = 4,
-  VM_START_FAILED = 5,
-  VM_STOP_FAILED = 6,
-  DESTROY_DISK_IMAGE_FAILED = 7,
-  LIST_VM_DISKS_FAILED = 8,
-  CLIENT_ERROR = 9,
-  // DISK_TYPE_ERROR = 10,
-  CONTAINER_DOWNLOAD_TIMED_OUT = 11,
-  CONTAINER_CREATE_CANCELLED = 12,
-  CONTAINER_CREATE_FAILED = 13,
-  CONTAINER_START_CANCELLED = 14,
-  CONTAINER_START_FAILED = 15,
-  // LAUNCH_CONTAINER_APPLICATION_FAILED = 16,
-  INSTALL_LINUX_PACKAGE_FAILED = 17,
-  BLOCKING_OPERATION_ALREADY_ACTIVE = 18,
-  UNINSTALL_PACKAGE_FAILED = 19,
-  // SSHFS_MOUNT_ERROR = 20,
-  OFFLINE_WHEN_UPGRADE_REQUIRED = 21,
-  LOAD_COMPONENT_FAILED = 22,
-  // PERMISSION_BROKER_ERROR = 23,
-  // ATTACH_USB_FAILED = 24,
-  // DETACH_USB_FAILED = 25,
-  // LIST_USB_FAILED = 26,
-  CROSTINI_UNINSTALLER_RUNNING = 27,
-  // UNKNOWN_USB_DEVICE = 28,
-  UNKNOWN_ERROR = 29,
-  CONTAINER_EXPORT_IMPORT_FAILED = 30,
-  CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED = 31,
-  CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED = 32,
-  CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE = 33,
-  NOT_ALLOWED = 34,
-  CONTAINER_EXPORT_IMPORT_FAILED_SPACE = 35,
-  GET_CONTAINER_SSH_KEYS_FAILED = 36,
-};
-
-enum class InstallLinuxPackageProgressStatus {
-  SUCCEEDED,
-  FAILED,
-  DOWNLOADING,
-  INSTALLING,
-};
-
-enum class VmState {
-  STARTING,
-  STARTED,
-  STOPPING,
-};
-
-enum class UninstallPackageProgressStatus {
-  SUCCEEDED,
-  FAILED,
-  UNINSTALLING,  // In progress
-};
-
-// TODO(juwa): delete this once the new version of tremplin has shipped.
-enum class ExportContainerProgressStatus {
-  // Deprecated. Has been replaced by STREAMING.
-  PACK,
-  // Deprecated. Has been replaced by STREAMING.
-  DOWNLOAD,
-  STREAMING,
-};
-
-enum class ImportContainerProgressStatus {
-  UPLOAD,
-  UNPACK,
-  FAILURE_ARCHITECTURE,
-  FAILURE_SPACE,
-};
-
-struct VmInfo {
-  VmState state;
-  vm_tools::concierge::VmInfo info;
-};
-
-struct StreamingExportStatus {
-  uint32_t total_files;
-  uint64_t total_bytes;
-  uint32_t exported_files;
-  uint64_t exported_bytes;
-};
-
-struct ContainerInfo {
-  ContainerInfo(std::string name, std::string username, std::string homedir);
-  ~ContainerInfo();
-  ContainerInfo(const ContainerInfo&);
-
-  std::string name;
-  std::string username;
-  base::FilePath homedir;
-  bool sshfs_mounted = false;
-};
-
-// Return type when getting app icons from within a container.
-struct Icon {
-  std::string desktop_file_id;
-
-  // Icon file content in PNG format.
-  std::string content;
-};
-
-struct LinuxPackageInfo {
-  LinuxPackageInfo();
-  LinuxPackageInfo(const LinuxPackageInfo&);
-  ~LinuxPackageInfo();
-
-  bool success;
-
-  // A textual reason for the failure, only set when success is false.
-  std::string failure_reason;
-
-  // The remaining fields are only set when success is true.
-  // package_id is given as "name;version;arch;data".
-  std::string package_id;
-  std::string name;
-  std::string version;
-  std::string summary;
-  std::string description;
-};
-
 class LinuxPackageOperationProgressObserver {
  public:
   // A successfully started package install will continually fire progress
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.cc b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
index e017ce1..3e389a7 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
@@ -18,6 +18,7 @@
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.h b/chrome/browser/chromeos/crostini/crostini_registry_service.h
index 4f4c2e8..6211d8a2 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.h
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.h
@@ -15,7 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/values.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "ui/base/resource/scale_factor.h"
 
diff --git a/chrome/browser/chromeos/crostini/crostini_simple_types.h b/chrome/browser/chromeos/crostini/crostini_simple_types.h
new file mode 100644
index 0000000..b496414f9
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_simple_types.h
@@ -0,0 +1,152 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "chromeos/dbus/concierge/service.pb.h"
+
+// This file contains simple C++ types (enums and Plain-Old-Data structs).
+// Importantly, #include'ing this file will not depend on eventually executing
+// "#include <dbus/dbus.h>",
+
+namespace crostini {
+
+// Result types for various callbacks etc.
+
+// WARNING: Do not remove or re-order these values, as they are used in user
+// visible error messages and logs. New entries should only be added to the end.
+// This message was added during development of M74, error codes from prior
+// versions may differ from the numbering here.
+enum class CrostiniResult {
+  SUCCESS = 0,
+  // DBUS_ERROR = 1,
+  // UNPARSEABLE_RESPONSE = 2,
+  // INSUFFICIENT_DISK = 3,
+  CREATE_DISK_IMAGE_FAILED = 4,
+  VM_START_FAILED = 5,
+  VM_STOP_FAILED = 6,
+  DESTROY_DISK_IMAGE_FAILED = 7,
+  LIST_VM_DISKS_FAILED = 8,
+  CLIENT_ERROR = 9,
+  // DISK_TYPE_ERROR = 10,
+  CONTAINER_DOWNLOAD_TIMED_OUT = 11,
+  CONTAINER_CREATE_CANCELLED = 12,
+  CONTAINER_CREATE_FAILED = 13,
+  CONTAINER_START_CANCELLED = 14,
+  CONTAINER_START_FAILED = 15,
+  // LAUNCH_CONTAINER_APPLICATION_FAILED = 16,
+  INSTALL_LINUX_PACKAGE_FAILED = 17,
+  BLOCKING_OPERATION_ALREADY_ACTIVE = 18,
+  UNINSTALL_PACKAGE_FAILED = 19,
+  // SSHFS_MOUNT_ERROR = 20,
+  OFFLINE_WHEN_UPGRADE_REQUIRED = 21,
+  LOAD_COMPONENT_FAILED = 22,
+  // PERMISSION_BROKER_ERROR = 23,
+  // ATTACH_USB_FAILED = 24,
+  // DETACH_USB_FAILED = 25,
+  // LIST_USB_FAILED = 26,
+  CROSTINI_UNINSTALLER_RUNNING = 27,
+  // UNKNOWN_USB_DEVICE = 28,
+  UNKNOWN_ERROR = 29,
+  CONTAINER_EXPORT_IMPORT_FAILED = 30,
+  CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED = 31,
+  CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED = 32,
+  CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE = 33,
+  NOT_ALLOWED = 34,
+  CONTAINER_EXPORT_IMPORT_FAILED_SPACE = 35,
+  GET_CONTAINER_SSH_KEYS_FAILED = 36,
+};
+
+enum class InstallLinuxPackageProgressStatus {
+  SUCCEEDED,
+  FAILED,
+  DOWNLOADING,
+  INSTALLING,
+};
+
+enum class VmState {
+  STARTING,
+  STARTED,
+  STOPPING,
+};
+
+enum class UninstallPackageProgressStatus {
+  SUCCEEDED,
+  FAILED,
+  UNINSTALLING,  // In progress
+};
+
+// TODO(juwa): delete this once the new version of tremplin has shipped.
+enum class ExportContainerProgressStatus {
+  // Deprecated. Has been replaced by STREAMING.
+  PACK,
+  // Deprecated. Has been replaced by STREAMING.
+  DOWNLOAD,
+  STREAMING,
+};
+
+enum class ImportContainerProgressStatus {
+  UPLOAD,
+  UNPACK,
+  FAILURE_ARCHITECTURE,
+  FAILURE_SPACE,
+};
+
+struct VmInfo {
+  VmState state;
+  vm_tools::concierge::VmInfo info;
+};
+
+struct StreamingExportStatus {
+  uint32_t total_files;
+  uint64_t total_bytes;
+  uint32_t exported_files;
+  uint64_t exported_bytes;
+};
+
+struct ContainerInfo {
+  ContainerInfo(std::string name, std::string username, std::string homedir);
+  ~ContainerInfo();
+  ContainerInfo(const ContainerInfo&);
+
+  std::string name;
+  std::string username;
+  base::FilePath homedir;
+  bool sshfs_mounted = false;
+};
+
+// Return type when getting app icons from within a container.
+struct Icon {
+  std::string desktop_file_id;
+
+  // Icon file content in PNG format.
+  std::string content;
+};
+
+struct LinuxPackageInfo {
+  LinuxPackageInfo();
+  LinuxPackageInfo(const LinuxPackageInfo&);
+  ~LinuxPackageInfo();
+
+  bool success;
+
+  // A textual reason for the failure, only set when success is false.
+  std::string failure_reason;
+
+  // The remaining fields are only set when success is true.
+  // package_id is given as "name;version;arch;data".
+  std::string package_id;
+  std::string name;
+  std::string version;
+  std::string summary;
+  std::string description;
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
diff --git a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
index 9a8ea1f..51a3fb4 100644
--- a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_features.h"
+#include "components/arc/arc_features.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -106,7 +107,10 @@
     dbus::MethodCall* method_call,
     dbus::ExportedObject::ResponseSender response_sender) {
   static const base::Feature constexpr* kFeatureLookup[] = {
-      &features::kUsbbouncer, &features::kUsbguard};
+      &features::kUsbbouncer, &features::kUsbguard,
+      &arc::kNativeBridgeExperimentFeature, &arc::kFilePickerExperimentFeature,
+      &arc::kCustomTabsExperimentFeature, &arc::kPrintSpoolerExperimentFeature,
+  };
 
   dbus::MessageReader reader(method_call);
   std::string feature_name;
diff --git a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
index 23602b9..62bd7de 100644
--- a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
+++ b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
@@ -4,24 +4,85 @@
 
 #include "chrome/browser/chromeos/login/saml/in_session_password_change_manager.h"
 
+#include "ash/public/cpp/session/session_activation_observer.h"
+#include "ash/public/cpp/session/session_controller.h"
+#include "base/feature_list.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part_chromeos.h"
 #include "chrome/browser/chromeos/login/auth/chrome_cryptohome_authenticator.h"
-#include "chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h"
+#include "chrome/browser/chromeos/login/saml/password_expiry_notification.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_ui.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/login/auth/user_context.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 
 namespace chromeos {
 
+namespace {
+
+InSessionPasswordChangeManager* g_test_instance = nullptr;
+
+// Traits for running RecheckPasswordExpiryTask.
+// Runs from the UI thread to show notification.
+const base::TaskTraits kRecheckTaskTraits = {
+    content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT,
+    base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
+
+// A time delta of length one hour.
+const base::TimeDelta kOneHour = base::TimeDelta::FromHours(1);
+
+// A time delta of length one day.
+const base::TimeDelta kOneDay = base::TimeDelta::FromDays(1);
+
+// When the password will expire in |kUrgentWarningDays| or less, the
+// UrgentPasswordExpiryNotification will be used - which is larger and actually
+// a dialog (not a true notification) - instead of the normal notification.
+const int kUrgentWarningDays = 3;
+
+}  // namespace
+
+RecheckPasswordExpiryTask::RecheckPasswordExpiryTask() = default;
+
+RecheckPasswordExpiryTask::~RecheckPasswordExpiryTask() = default;
+
+void RecheckPasswordExpiryTask::Recheck() {
+  CancelPendingRecheck();
+  InSessionPasswordChangeManager::Get()->MaybeShowExpiryNotification();
+}
+
+void RecheckPasswordExpiryTask::RecheckAfter(base::TimeDelta delay) {
+  CancelPendingRecheck();
+  base::PostDelayedTaskWithTraits(
+      FROM_HERE, kRecheckTaskTraits,
+      base::BindOnce(&RecheckPasswordExpiryTask::Recheck,
+                     weak_ptr_factory_.GetWeakPtr()),
+      std::max(delay, kOneHour));
+  // This always waits at least one hour before calling Recheck again - we don't
+  // want some bug to cause this code to run every millisecond.
+}
+
+void RecheckPasswordExpiryTask::CancelPendingRecheck() {
+  weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
 // static
 std::unique_ptr<InSessionPasswordChangeManager>
 InSessionPasswordChangeManager::CreateIfEnabled(Profile* primary_profile) {
-  if (primary_profile->GetPrefs()->GetBoolean(
+  if (base::FeatureList::IsEnabled(features::kInSessionPasswordChange) &&
+      primary_profile->GetPrefs()->GetBoolean(
           prefs::kSamlInSessionPasswordChangeEnabled)) {
-    return std::make_unique<InSessionPasswordChangeManager>(primary_profile);
+    std::unique_ptr<InSessionPasswordChangeManager> manager =
+        std::make_unique<InSessionPasswordChangeManager>(primary_profile);
+    manager->MaybeShowExpiryNotification();
+    return manager;
   } else {
     // If the policy is disabled, clear the SAML password attributes.
     SamlPasswordAttributes::DeleteFromPrefs(primary_profile->GetPrefs());
@@ -29,18 +90,120 @@
   }
 }
 
+// static
+bool InSessionPasswordChangeManager::IsInitialized() {
+  return GetNullable();
+}
+
+// static
+InSessionPasswordChangeManager* InSessionPasswordChangeManager::Get() {
+  InSessionPasswordChangeManager* result = GetNullable();
+  CHECK(result);
+  return result;
+}
+
 InSessionPasswordChangeManager::InSessionPasswordChangeManager(
     Profile* primary_profile)
     : primary_profile_(primary_profile),
       primary_user_(ProfileHelper::Get()->GetUserByProfile(primary_profile)),
-      authenticator_(new ChromeCryptohomeAuthenticator(this)) {
+      authenticator_(new ChromeCryptohomeAuthenticator(this)),
+      urgent_warning_days_(kUrgentWarningDays) {
   DCHECK(primary_user_);
+
+  // Add |this| as a SessionActivationObserver to see when the screen is locked.
+  auto* session_controller = ash::SessionController::Get();
+  if (session_controller) {
+    session_controller->AddSessionActivationObserverForAccountId(
+        primary_user_->GetAccountId(), this);
+  }
 }
 
-InSessionPasswordChangeManager::~InSessionPasswordChangeManager() {}
+InSessionPasswordChangeManager::~InSessionPasswordChangeManager() {
+  // Remove |this| as a SessionActivationObserver.
+  auto* session_controller = ash::SessionController::Get();
+  if (session_controller) {
+    session_controller->AddSessionActivationObserverForAccountId(
+        primary_user_->GetAccountId(), this);
+  }
+}
+
+void InSessionPasswordChangeManager::MaybeShowExpiryNotification() {
+  // We are checking password expiry now, and this function will decide if we
+  // want to check again in the future, so for now, make sure there are no other
+  // pending tasks to check aggain.
+  recheck_task_.CancelPendingRecheck();
+
+  PrefService* prefs = primary_profile_->GetPrefs();
+  if (!prefs->GetBoolean(prefs::kSamlInSessionPasswordChangeEnabled)) {
+    DismissExpiryNotification();
+    return;
+  }
+
+  SamlPasswordAttributes attrs = SamlPasswordAttributes::LoadFromPrefs(prefs);
+  if (!attrs.has_expiration_time()) {
+    DismissExpiryNotification();
+    return;
+  }
+
+  // Calculate how many days until the password will expire.
+  const base::TimeDelta time_until_expiry =
+      attrs.expiration_time() - base::Time::Now();
+  const int less_than_n_days =
+      std::max(0, time_until_expiry.InDaysFloored() + 1);
+  const int advance_warning_days = std::max(
+      0, prefs->GetInteger(prefs::kSamlPasswordExpirationAdvanceWarningDays));
+
+  if (less_than_n_days <= advance_warning_days) {
+    // The password is expired, or expires in less than |advance_warning_days|.
+    // So we show a notification immediately.
+    ShowExpiryNotification(less_than_n_days);
+    // We check again whether to reshow / update the notification after one day:
+    recheck_task_.RecheckAfter(kOneDay);
+
+  } else {
+    // We have not yet reached the advance warning threshold. Check again
+    // once we have arrived at expiry_time minus advance_warning_days...
+    base::TimeDelta recheck_delay =
+        time_until_expiry - base::TimeDelta::FromDays(advance_warning_days);
+    // But, wait an extra hour so that when this code is next run, it is clear
+    // we are now inside advance_warning_days (and not right on the boundary).
+    recheck_delay += kOneHour;
+    recheck_task_.RecheckAfter(recheck_delay);
+  }
+}
+
+void InSessionPasswordChangeManager::ShowExpiryNotification(
+    int less_than_n_days) {
+  // Show a notification, and reshow it each time the screen is unlocked.
+  renotify_on_unlock_ = true;
+
+  less_than_n_days = std::max(0, less_than_n_days);
+  if (less_than_n_days < urgent_warning_days_) {
+    UrgentPasswordExpiryNotificationDialog::Show(less_than_n_days);
+  } else {
+    PasswordExpiryNotification::Show(primary_profile_, less_than_n_days);
+  }
+}
+
+void InSessionPasswordChangeManager::DismissExpiryNotification() {
+  UrgentPasswordExpiryNotificationDialog::Dismiss();
+  PasswordExpiryNotification::Dismiss(primary_profile_);
+}
+
+void InSessionPasswordChangeManager::OnExpiryNotificationDismissedByUser() {
+  // When a notification is dismissed, we then don't pop it up again each time
+  // the user unlocks the screen.
+  renotify_on_unlock_ = false;
+}
+
+void InSessionPasswordChangeManager::OnScreenUnlocked() {
+  if (renotify_on_unlock_) {
+    MaybeShowExpiryNotification();
+  }
+}
 
 void InSessionPasswordChangeManager::StartInSessionPasswordChange() {
-  UrgentPasswordExpiryNotificationDialog::Dismiss();
+  DismissExpiryNotification();
   PasswordChangeDialog::Show(primary_profile_);
 }
 
@@ -86,6 +249,11 @@
   observer_list_.RemoveObserver(observer);
 }
 
+void InSessionPasswordChangeManager::OnAuthFailure(const AuthFailure& error) {
+  VLOG(1) << "Failed to change cryptohome password: " << error.GetErrorString();
+  NotifyObservers(CHANGE_PASSWORD_AUTH_FAILURE);
+}
+
 void InSessionPasswordChangeManager::OnAuthSuccess(
     const UserContext& user_context) {
   VLOG(3) << "Cryptohome password is changed.";
@@ -101,14 +269,38 @@
       loaded.password_change_url())
       .SaveToPrefs(primary_profile_->GetPrefs());
 
-  DismissSamlPasswordExpiryNotification(primary_profile_);
+  DismissExpiryNotification();
   PasswordChangeDialog::Dismiss();
   ConfirmPasswordChangeDialog::Dismiss();
 }
 
-void InSessionPasswordChangeManager::OnAuthFailure(const AuthFailure& error) {
-  VLOG(1) << "Failed to change cryptohome password: " << error.GetErrorString();
-  NotifyObservers(CHANGE_PASSWORD_AUTH_FAILURE);
+void InSessionPasswordChangeManager::OnSessionActivated(bool activated) {
+  // Not needed.
+}
+
+void InSessionPasswordChangeManager::OnLockStateChanged(bool locked) {
+  if (!locked) {
+    OnScreenUnlocked();
+  }
+}
+
+// static
+InSessionPasswordChangeManager* InSessionPasswordChangeManager::GetNullable() {
+  return g_test_instance ? g_test_instance
+                         : g_browser_process->platform_part()
+                               ->in_session_password_change_manager();
+}
+
+// static
+void InSessionPasswordChangeManager::SetForTesting(
+    InSessionPasswordChangeManager* instance) {
+  CHECK(!g_test_instance);
+  g_test_instance = instance;
+}
+
+// static
+void InSessionPasswordChangeManager::ResetForTesting() {
+  g_test_instance = nullptr;
 }
 
 void InSessionPasswordChangeManager::NotifyObservers(Event event) {
diff --git a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.h b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.h
index 041fe69..ba9ba83 100644
--- a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.h
+++ b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/session/session_activation_observer.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/observer_list.h"
 #include "chromeos/login/auth/auth_status_consumer.h"
@@ -22,9 +23,42 @@
 class CryptohomeAuthenticator;
 class UserContext;
 
+// There is at most one instance of this task, which is part of the
+// InSessionPasswordChangeManager singleton. Having a separate class means that
+// pointers to this class can be invalidated without affecting the manager.
+// Calling Recheck or RecheckAfter invalidates the existing pointers before
+// rerunning the task or posting the task to be re-run, which means that there
+// is only ever one of task that is scheduled to be run.
+class RecheckPasswordExpiryTask {
+ private:
+  RecheckPasswordExpiryTask();
+  ~RecheckPasswordExpiryTask();
+
+  // Delegates to InSessionPasswordChangeManager::MaybeShowExpiryNotification.
+  void Recheck();
+
+  // Calls Recheck after the given |delay|.
+  void RecheckAfter(base::TimeDelta delay);
+
+  // Cancels any pending calls to Recheck that are scheduled..
+  void CancelPendingRecheck();
+
+  base::WeakPtrFactory<RecheckPasswordExpiryTask> weak_ptr_factory_{this};
+
+  // Only InSessionPasswordChangeManager can use this class.
+  friend class InSessionPasswordChangeManager;
+
+  DISALLOW_COPY_AND_ASSIGN(RecheckPasswordExpiryTask);
+};
+
 // Manages the flow of changing a password in-session - handles user
 // response from dialogs, and callbacks from subsystems.
-class InSessionPasswordChangeManager : public AuthStatusConsumer {
+// This singleton is scoped to the primary user session - it will exist for as
+// long as the primary user session exists  (but only if the primary user's
+// InSessionPasswordChange policy is enabled and the kInSessionPasswordChange
+// feature is enabled).
+class InSessionPasswordChangeManager : public AuthStatusConsumer,
+                                       public ash::SessionActivationObserver {
  public:
   // Events in the in-session SAML password change flow.
   enum Event {
@@ -42,9 +76,40 @@
   static std::unique_ptr<InSessionPasswordChangeManager> CreateIfEnabled(
       Profile* primary_profile);
 
+  // Returns true if the InSessionPasswordChangeManager is both enabled and
+  // ready, so Get() can safely be called.
+  static bool IsInitialized();
+
+  // Checks that the InSessionPasswordChangeManager is both enabled and ready,
+  // then returns it.
+  static InSessionPasswordChangeManager* Get();
+
   explicit InSessionPasswordChangeManager(Profile* primary_profile);
   ~InSessionPasswordChangeManager() override;
 
+  // Checks if the primary user's password has expired or will soon expire, and
+  // shows a notification if needed. If the password will expire in the distant
+  // future, posts a task to check again in the distant future.
+  void MaybeShowExpiryNotification();
+
+  // Shows a password expiry notification. |less_than_n_days| should be 1 if the
+  // password expires in less than 1 day, 0 if it has already expired, etc.
+  // Negative numbers are treated the same as zero.
+  void ShowExpiryNotification(int less_than_n_days);
+
+  // Dismiss password expiry notification and dismiss urgent password expiry
+  // notification, if either are shown.
+  void DismissExpiryNotification();
+
+  // User dismissed a notification - make sure not to show it again immediately,
+  // even if the password is still scheduled to expire soon.
+  void OnExpiryNotificationDismissedByUser();
+
+  // When the screen is unlocked, password expiry notifications are reshown (if
+  // they are not already dismissed). On each unlock, the notification pops
+  // out of the system tray and is visible on screen again for a few seconds.
+  void OnScreenUnlocked();
+
   // Start the in-session password change flow by showing a dialog that embeds
   // the user's SAML IdP change-password page:
   void StartInSessionPasswordChange();
@@ -70,14 +135,28 @@
   void OnAuthFailure(const AuthFailure& error) override;
   void OnAuthSuccess(const UserContext& user_context) override;
 
+  // ash::SessionActivationObserver:
+  void OnSessionActivated(bool activated) override;
+  void OnLockStateChanged(bool locked) override;
+
  private:
+  static InSessionPasswordChangeManager* GetNullable();
+
+  // Sets the given instance as the singleton for testing.
+  static void SetForTesting(InSessionPasswordChangeManager* instance);
+  static void ResetForTesting();
+
   void NotifyObservers(Event event);
 
   Profile* primary_profile_;
   const user_manager::User* primary_user_;
   base::ObserverList<Observer> observer_list_;
-
+  RecheckPasswordExpiryTask recheck_task_;
   scoped_refptr<CryptohomeAuthenticator> authenticator_;
+  int urgent_warning_days_;
+  bool renotify_on_unlock_ = false;
+
+  friend class InSessionPasswordChangeManagerTest;
 
   DISALLOW_COPY_AND_ASSIGN(InSessionPasswordChangeManager);
 };
diff --git a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc b/chrome/browser/chromeos/login/saml/in_session_password_change_manager_unittest.cc
similarity index 77%
rename from chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc
rename to chrome/browser/chromeos/login/saml/in_session_password_change_manager_unittest.cc
index 077b9df3..9a585bf 100644
--- a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc
+++ b/chrome/browser/chromeos/login/saml/in_session_password_change_manager_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 "chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h"
+#include "chrome/browser/chromeos/login/saml/in_session_password_change_manager.h"
 
 #include "ash/public/cpp/session/session_activation_observer.h"
 #include "ash/public/cpp/session/session_controller.h"
@@ -41,7 +41,9 @@
   return base::ASCIIToUTF16(ascii);
 }
 
-class SamlPasswordExpiryNotificationTest : public testing::Test {
+}  // namespace
+
+class InSessionPasswordChangeManagerTest : public testing::Test {
  public:
   void SetUp() override {
     ASSERT_TRUE(profile_manager_.SetUp());
@@ -62,11 +64,17 @@
 
     display_service_tester_ =
         std::make_unique<NotificationDisplayServiceTester>(profile_);
+    manager_ = std::make_unique<InSessionPasswordChangeManager>(profile_);
+
+    // urgent_warning_days_ = -1: This means we only ever show a standard
+    // notification, instead of an urgent one, because it is simpler to test.
+    // TODO(https://crbug.com/930109): Test both types of notification.
+    manager_->urgent_warning_days_ = -1;
+    InSessionPasswordChangeManager::SetForTesting(manager_.get());
   }
 
   void TearDown() override {
-    display_service_tester_.reset();
-    expiry_notification_test_helper_.ResetForTesting();
+    InSessionPasswordChangeManager::ResetForTesting();
   }
 
  protected:
@@ -82,7 +90,7 @@
 
   void ExpectNotificationAndDismiss() {
     EXPECT_TRUE(Notification().has_value());
-    DismissSamlPasswordExpiryNotification(profile_);
+    manager_->DismissExpiryNotification();
     EXPECT_FALSE(Notification().has_value());
   }
 
@@ -92,49 +100,23 @@
   TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
   TestingProfile* profile_;
 
-  SamlPasswordExpiryNotificationTestHelper expiry_notification_test_helper_;
-
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
+  std::unique_ptr<InSessionPasswordChangeManager> manager_;
 };
 
-}  // namespace
-
-TEST_F(SamlPasswordExpiryNotificationTest, ShowAlreadyExpired) {
-  ShowSamlPasswordExpiryNotification(profile_, 0);
-  ASSERT_TRUE(Notification().has_value());
-
-  EXPECT_EQ(utf16("Password is expired"), Notification()->title());
-  EXPECT_EQ(utf16("Choose a new one immediately"), Notification()->message());
-
-  DismissSamlPasswordExpiryNotification(profile_);
-  EXPECT_FALSE(Notification().has_value());
-}
-
-TEST_F(SamlPasswordExpiryNotificationTest, ShowWillSoonExpire) {
-  ShowSamlPasswordExpiryNotification(profile_, 14);
-  ASSERT_TRUE(Notification().has_value());
-
-  EXPECT_EQ(utf16("Password expires in less than 14 days"),
-            Notification()->title());
-  EXPECT_EQ(utf16("Choose a new one now"), Notification()->message());
-
-  DismissSamlPasswordExpiryNotification(profile_);
-  EXPECT_FALSE(Notification().has_value());
-}
-
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_PolicyDisabled) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_PolicyDisabled) {
   SetExpirationTime(base::Time::Now());
   profile_->GetPrefs()->SetBoolean(prefs::kSamlInSessionPasswordChangeEnabled,
                                    false);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   EXPECT_FALSE(Notification().has_value());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_WillNotExpire) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_WillNotExpire) {
   SamlPasswordAttributes::DeleteFromPrefs(profile_->GetPrefs());
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   EXPECT_FALSE(Notification().has_value());
   // No notification shown now and nothing shown in the next 10 years.
@@ -142,18 +124,18 @@
   EXPECT_FALSE(Notification().has_value());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_AlreadyExpired) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_AlreadyExpired) {
   SetExpirationTime(base::Time::Now() - kOneYear);  // Expired last year.
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is shown immediately since password has expired.
   EXPECT_TRUE(Notification().has_value());
   EXPECT_EQ(utf16("Password is expired"), Notification()->title());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_WillSoonExpire) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_WillSoonExpire) {
   SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2) - kOneHour);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is shown immediately since password will soon expire.
   EXPECT_TRUE(Notification().has_value());
@@ -161,9 +143,9 @@
             Notification()->title());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_WillEventuallyExpire) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_WillEventuallyExpire) {
   SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is not shown when expiration is still over a year away.
   EXPECT_FALSE(Notification().has_value());
@@ -175,9 +157,9 @@
             Notification()->title());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_DeleteExpirationTime) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_DeleteExpirationTime) {
   SetExpirationTime(base::Time::Now() + kOneYear);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is not shown immediately.
   EXPECT_FALSE(Notification().has_value());
@@ -188,9 +170,9 @@
   EXPECT_FALSE(Notification().has_value());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_PasswordChanged) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_PasswordChanged) {
   SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2) - kOneHour);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is shown immediately since password will soon expire.
   EXPECT_TRUE(Notification().has_value());
@@ -199,35 +181,35 @@
 
   // Password is changed and notification is dismissed.
   SamlPasswordAttributes::DeleteFromPrefs(profile_->GetPrefs());
-  DismissSamlPasswordExpiryNotification(profile_);
+  manager_->DismissExpiryNotification();
 
   // From now on, notification will not be reshown.
   test_environment_.FastForwardBy(kTenYears);
   EXPECT_FALSE(Notification().has_value());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_Idempotent) {
+TEST_F(InSessionPasswordChangeManagerTest, MaybeShow_Idempotent) {
   SetExpirationTime(base::Time::Now() + kOneYear);
 
   // Calling MaybeShowSamlPasswordExpiryNotification should only add one task -
   // to maybe show the notification in about a year.
   int baseline_task_count = test_environment_.GetPendingMainThreadTaskCount();
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
   int new_task_count = test_environment_.GetPendingMainThreadTaskCount();
   EXPECT_EQ(1, new_task_count - baseline_task_count);
 
   // Calling it many times shouldn't create more tasks - we only need one task
   // to show the notification in about a year.
   for (int i = 0; i < 10; i++) {
-    MaybeShowSamlPasswordExpiryNotification(profile_);
+    manager_->MaybeShowExpiryNotification();
   }
   new_task_count = test_environment_.GetPendingMainThreadTaskCount();
   EXPECT_EQ(1, new_task_count - baseline_task_count);
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, TimePasses_NoUserActionTaken) {
+TEST_F(InSessionPasswordChangeManagerTest, TimePasses_NoUserActionTaken) {
   SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is not shown immediately.
   EXPECT_FALSE(Notification().has_value());
@@ -253,17 +235,17 @@
   test_environment_.FastForwardBy(kAdvanceWarningTime / 2);
   EXPECT_TRUE(Notification().has_value());
   EXPECT_EQ(utf16("Password is expired"), Notification()->title());
-  EXPECT_EQ(utf16("Choose a new one immediately"), Notification()->message());
+  EXPECT_EQ(utf16("Choose a new one now"), Notification()->message());
 
   test_environment_.FastForwardBy(kOneYear);
   EXPECT_TRUE(Notification().has_value());
   EXPECT_EQ(utf16("Password is expired"), Notification()->title());
-  EXPECT_EQ(utf16("Choose a new one immediately"), Notification()->message());
+  EXPECT_EQ(utf16("Choose a new one now"), Notification()->message());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, TimePasses_NotificationDismissed) {
+TEST_F(InSessionPasswordChangeManagerTest, TimePasses_NotificationDismissed) {
   SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime / 2);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is not shown immediately.
   EXPECT_FALSE(Notification().has_value());
@@ -284,9 +266,9 @@
   ExpectNotificationAndDismiss();
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, ReshowOnUnlock) {
+TEST_F(InSessionPasswordChangeManagerTest, ReshowOnUnlock) {
   SetExpirationTime(base::Time::Now() + kAdvanceWarningTime / 2);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is shown immediately.
   EXPECT_TRUE(Notification().has_value());
@@ -300,26 +282,27 @@
 
   // But when the screen is unlocked, the old notification is replaced with a
   // newer one. The new one is prominently shown on screen for a few seconds.
-  expiry_notification_test_helper_.SimulateUnlockForTesting();
+  manager_->OnScreenUnlocked();
   EXPECT_TRUE(Notification().has_value());
   EXPECT_NE(first_shown_at, Notification()->timestamp());
 }
 
-TEST_F(SamlPasswordExpiryNotificationTest, DontReshowWhenDismissed) {
+TEST_F(InSessionPasswordChangeManagerTest, DontReshowWhenDismissed) {
   SetExpirationTime(base::Time::Now() + kAdvanceWarningTime / 2);
-  MaybeShowSamlPasswordExpiryNotification(profile_);
+  manager_->MaybeShowExpiryNotification();
 
   // Notification is shown immediately.
   EXPECT_TRUE(Notification().has_value());
 
   // If dismissed, the notification won't reappear within the next hour, since
   // we don't want to nag the user continuously.
-  DismissSamlPasswordExpiryNotification(profile_);
+  manager_->DismissExpiryNotification();
+  manager_->OnExpiryNotificationDismissedByUser();
   test_environment_.FastForwardBy(kOneHour);
   EXPECT_FALSE(Notification().has_value());
 
   // Nor will it reappear if the user unlocks the screen.
-  expiry_notification_test_helper_.SimulateUnlockForTesting();
+  manager_->OnScreenUnlocked();
   EXPECT_FALSE(Notification().has_value());
 
   // But it will eventually reappear the next day.
diff --git a/chrome/browser/chromeos/login/saml/password_expiry_notification.cc b/chrome/browser/chromeos/login/saml/password_expiry_notification.cc
new file mode 100644
index 0000000..d0eac107
--- /dev/null
+++ b/chrome/browser/chromeos/login/saml/password_expiry_notification.cc
@@ -0,0 +1,164 @@
+// 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/chromeos/login/saml/password_expiry_notification.h"
+
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/session/session_activation_observer.h"
+#include "ash/public/cpp/session/session_controller.h"
+#include "base/bind.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/login/saml/in_session_password_change_manager.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/notifications/notification_common.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/browser/notifications/notification_display_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_ui.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/login/auth/saml_password_attributes.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "components/prefs/pref_service.h"
+#include "components/vector_icons/vector_icons.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+using message_center::ButtonInfo;
+using message_center::HandleNotificationClickDelegate;
+using message_center::Notification;
+using message_center::NotificationDelegate;
+using message_center::NotificationObserver;
+using message_center::NotificationType;
+using message_center::NotifierId;
+using message_center::NotifierType;
+using message_center::RichNotificationData;
+using message_center::SystemNotificationWarningLevel;
+using message_center::ThunkNotificationDelegate;
+
+namespace chromeos {
+
+namespace {
+
+// Unique ID for this notification.
+const char kNotificationId[] = "saml.password-expiry-notification";
+
+// NotifierId for histogram reporting.
+const base::NoDestructor<NotifierId> kNotifierId(
+    message_center::NotifierType::SYSTEM_COMPONENT,
+    kNotificationId);
+
+// Simplest type of notification UI - no progress bars, images etc.
+const NotificationType kNotificationType =
+    message_center::NOTIFICATION_TYPE_SIMPLE;
+
+// Generic type for notifications that are not from web pages etc.
+const NotificationHandler::Type kNotificationHandlerType =
+    NotificationHandler::Type::TRANSIENT;
+
+// The icon to use for this notification - looks like an office building.
+const gfx::VectorIcon& kIcon = vector_icons::kBusinessIcon;
+
+// Leaving this empty means the notification is attributed to the system -
+// ie "Chromium OS" or similar.
+const base::NoDestructor<base::string16> kEmptyDisplaySource;
+
+// No origin URL is needed since the notification comes from the system.
+const base::NoDestructor<GURL> kEmptyOriginUrl;
+
+// Warning level of WARNING makes the title  orange.
+const SystemNotificationWarningLevel kWarningLevel =
+    SystemNotificationWarningLevel::WARNING;
+
+base::string16 GetTitleText(int less_than_n_days) {
+  return l10n_util::GetPluralStringFUTF16(IDS_PASSWORD_EXPIRY_DAYS_TITLE,
+                                          less_than_n_days);
+}
+
+base::string16 GetBodyText() {
+  return l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CALL_TO_ACTION);
+}
+
+RichNotificationData GetRichNotificationData() {
+  RichNotificationData result;
+  result.buttons = std::vector<ButtonInfo>{ButtonInfo(
+      l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON))};
+  return result;
+}
+
+// Delegate for handling clicks on the notification.
+class PasswordExpiryNotificationDelegate : public NotificationDelegate {
+ public:
+  PasswordExpiryNotificationDelegate();
+
+ protected:
+  ~PasswordExpiryNotificationDelegate() override;
+
+  // message_center::NotificationDelegate:
+  void Close(bool by_user) override;
+  void Click(const base::Optional<int>& button_index,
+             const base::Optional<base::string16>& reply) override;
+};
+
+PasswordExpiryNotificationDelegate::PasswordExpiryNotificationDelegate() =
+    default;
+PasswordExpiryNotificationDelegate::~PasswordExpiryNotificationDelegate() =
+    default;
+
+void PasswordExpiryNotificationDelegate::Close(bool by_user) {
+  if (by_user) {
+    InSessionPasswordChangeManager::Get()
+        ->OnExpiryNotificationDismissedByUser();
+  }
+}
+
+void PasswordExpiryNotificationDelegate::Click(
+    const base::Optional<int>& button_index,
+    const base::Optional<base::string16>& reply) {
+  bool clicked_on_button = button_index.has_value();
+  if (clicked_on_button) {
+    InSessionPasswordChangeManager::Get()->StartInSessionPasswordChange();
+  }
+}
+
+}  // namespace
+
+// static
+void PasswordExpiryNotification::Show(Profile* profile, int less_than_n_days) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  const base::string16 title = GetTitleText(less_than_n_days);
+  const base::string16 body = GetBodyText();
+  const RichNotificationData rich_notification_data = GetRichNotificationData();
+  const scoped_refptr<PasswordExpiryNotificationDelegate> delegate =
+      base::MakeRefCounted<PasswordExpiryNotificationDelegate>();
+
+  std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
+      kNotificationType, kNotificationId, title, body, *kEmptyDisplaySource,
+      *kEmptyOriginUrl, *kNotifierId, rich_notification_data, delegate, kIcon,
+      kWarningLevel);
+
+  NotificationDisplayService* nds =
+      NotificationDisplayServiceFactory::GetForProfile(profile);
+  // Calling close before display ensures that the notification pops up again
+  // even if it is already shown.
+  nds->Close(kNotificationHandlerType, kNotificationId);
+  nds->Display(kNotificationHandlerType, *notification, /*metadata=*/nullptr);
+}
+
+// static
+void PasswordExpiryNotification::Dismiss(Profile* profile) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
+      kNotificationHandlerType, kNotificationId);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/saml/password_expiry_notification.h b/chrome/browser/chromeos/login/saml/password_expiry_notification.h
new file mode 100644
index 0000000..f306604
--- /dev/null
+++ b/chrome/browser/chromeos/login/saml/password_expiry_notification.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
+
+class Profile;
+
+namespace chromeos {
+
+// Utility functions to show or hide a password expiry notification.
+class PasswordExpiryNotification {
+ public:
+  // Shows a password expiry notification. |less_than_n_days| should be 1 if the
+  // password expires in less than 1 day, 0 if it has already expired, etc.
+  // Negative numbers are treated the same as zero.
+  static void Show(Profile* profile, int less_than_n_days);
+
+  // Hides the password expiry notification if it is currently shown.
+  static void Dismiss(Profile* profile);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/login/saml/password_expiry_notification_unittest.cc b/chrome/browser/chromeos/login/saml/password_expiry_notification_unittest.cc
new file mode 100644
index 0000000..bd085d1
--- /dev/null
+++ b/chrome/browser/chromeos/login/saml/password_expiry_notification_unittest.cc
@@ -0,0 +1,75 @@
+// 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/chromeos/login/saml/password_expiry_notification.h"
+
+#include "ash/public/cpp/session/session_activation_observer.h"
+#include "ash/public/cpp/session/session_controller.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/notifications/notification_display_service_impl.h"
+#include "chrome/browser/notifications/notification_display_service_tester.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/login/auth/saml_password_attributes.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "components/user_manager/user_names.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using message_center::Notification;
+
+namespace chromeos {
+
+namespace {
+
+inline base::string16 utf16(const char* ascii) {
+  return base::ASCIIToUTF16(ascii);
+}
+
+class SamlPasswordExpiryNotificationTest : public testing::Test {
+ protected:
+  base::Optional<Notification> Notification() {
+    return NotificationDisplayServiceTester::Get()->GetNotification(
+        "saml.password-expiry-notification");
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+  TestingProfile profile_;
+  NotificationDisplayServiceTester display_service_tester_{&profile_};
+};
+
+}  // namespace
+
+TEST_F(SamlPasswordExpiryNotificationTest, ShowWillSoonExpire) {
+  PasswordExpiryNotification::Show(&profile_, 14);
+  ASSERT_TRUE(Notification().has_value());
+
+  EXPECT_EQ(utf16("Password expires in less than 14 days"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Choose a new one now"), Notification()->message());
+
+  PasswordExpiryNotification::Dismiss(&profile_);
+  EXPECT_FALSE(Notification().has_value());
+}
+
+TEST_F(SamlPasswordExpiryNotificationTest, ShowAlreadyExpired) {
+  PasswordExpiryNotification::Show(&profile_, 0);
+  ASSERT_TRUE(Notification().has_value());
+
+  EXPECT_EQ(utf16("Password is expired"), Notification()->title());
+  EXPECT_EQ(utf16("Choose a new one now"), Notification()->message());
+
+  PasswordExpiryNotification::Dismiss(&profile_);
+  EXPECT_FALSE(Notification().has_value());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc
deleted file mode 100644
index 697e69bc..0000000
--- a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc
+++ /dev/null
@@ -1,385 +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.
-
-#include "chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h"
-
-#include "ash/public/cpp/notification_utils.h"
-#include "ash/public/cpp/session/session_activation_observer.h"
-#include "ash/public/cpp/session/session_controller.h"
-#include "base/bind.h"
-#include "base/strings/string16.h"
-#include "base/strings/string_util.h"
-#include "base/task/post_task.h"
-#include "base/task/task_traits.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/notifications/notification_common.h"
-#include "chrome/browser/notifications/notification_display_service.h"
-#include "chrome/browser/notifications/notification_display_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_ui.h"
-#include "chrome/common/pref_names.h"
-#include "chromeos/login/auth/saml_password_attributes.h"
-#include "chromeos/strings/grit/chromeos_strings.h"
-#include "components/prefs/pref_service.h"
-#include "components/vector_icons/vector_icons.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/message_center/public/cpp/notification.h"
-#include "ui/message_center/public/cpp/notification_delegate.h"
-
-using message_center::ButtonInfo;
-using message_center::HandleNotificationClickDelegate;
-using message_center::Notification;
-using message_center::NotificationObserver;
-using message_center::NotificationType;
-using message_center::NotifierId;
-using message_center::NotifierType;
-using message_center::RichNotificationData;
-using message_center::SystemNotificationWarningLevel;
-using message_center::ThunkNotificationDelegate;
-
-namespace chromeos {
-
-namespace {
-
-// Unique ID for this notification.
-const char kNotificationId[] = "saml.password-expiry-notification";
-
-// NotifierId for histogram reporting.
-const base::NoDestructor<NotifierId> kNotifierId(
-    message_center::NotifierType::SYSTEM_COMPONENT,
-    kNotificationId);
-
-// Simplest type of notification UI - no progress bars, images etc.
-const NotificationType kNotificationType =
-    message_center::NOTIFICATION_TYPE_SIMPLE;
-
-// Generic type for notifications that are not from web pages etc.
-const NotificationHandler::Type kNotificationHandlerType =
-    NotificationHandler::Type::TRANSIENT;
-
-// The icon to use for this notification - looks like an office building.
-const gfx::VectorIcon& kIcon = vector_icons::kBusinessIcon;
-
-// Leaving this empty means the notification is attributed to the system -
-// ie "Chromium OS" or similar.
-const base::NoDestructor<base::string16> kEmptyDisplaySource;
-
-// No origin URL is needed since the notification comes from the system.
-const base::NoDestructor<GURL> kEmptyOriginUrl;
-
-// When the password will expire in |kCriticalWarningDays| or less, the warning
-// will have red text, slightly stronger language, and doesn't automatically
-// time out - it stays on the screen until the user hides or dismisses it.
-const int kCriticalWarningDays = 3;
-
-base::string16 GetTitleText(int less_than_n_days) {
-  return l10n_util::GetPluralStringFUTF16(IDS_PASSWORD_EXPIRY_DAYS_TITLE,
-                                          less_than_n_days);
-}
-
-base::string16 GetBodyText(bool is_critical) {
-  return is_critical
-             ? l10n_util::GetStringUTF16(
-                   IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL)
-             : l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CALL_TO_ACTION);
-}
-
-RichNotificationData GetRichNotificationData(bool is_critical) {
-  RichNotificationData result;
-  result.never_timeout = is_critical;
-  result.buttons = std::vector<ButtonInfo>{ButtonInfo(
-      l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON))};
-  return result;
-}
-
-SystemNotificationWarningLevel GetWarningLevel(bool is_critical) {
-  return is_critical ? SystemNotificationWarningLevel::CRITICAL_WARNING
-                     : SystemNotificationWarningLevel::WARNING;
-}
-
-void ShowNotificationImpl(
-    Profile* profile,
-    int less_than_n_days,
-    scoped_refptr<message_center::NotificationDelegate> delegate) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  const bool is_critical = less_than_n_days <= kCriticalWarningDays;
-  const base::string16 title = GetTitleText(less_than_n_days);
-  const base::string16 body = GetBodyText(is_critical);
-  const RichNotificationData rich_notification_data =
-      GetRichNotificationData(is_critical);
-  const SystemNotificationWarningLevel warning_level =
-      GetWarningLevel(is_critical);
-
-  std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
-      kNotificationType, kNotificationId, title, body, *kEmptyDisplaySource,
-      *kEmptyOriginUrl, *kNotifierId, rich_notification_data, delegate, kIcon,
-      warning_level);
-
-  NotificationDisplayService* nds =
-      NotificationDisplayServiceFactory::GetForProfile(profile);
-  // Calling close before display ensures that the notification pops up again
-  // even if it is already shown.
-  nds->Close(kNotificationHandlerType, kNotificationId);
-  nds->Display(kNotificationHandlerType, *notification, /*metadata=*/nullptr);
-}
-
-// A time delta of length one hour.
-const base::TimeDelta kOneHour = base::TimeDelta::FromHours(1);
-// A time delta of length one day.
-const base::TimeDelta kOneDay = base::TimeDelta::FromDays(1);
-
-// Traits for running RecheckTask. Runs from the UI thread to show notification.
-const base::TaskTraits kRecheckTaskTraits = {
-    content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT,
-    base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
-
-// Returns true if |profile| is still valid.
-bool IsValidProfile(Profile* profile) {
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  return profile_manager && profile_manager->IsValidProfile(profile);
-}
-
-// Returns true if showing the notification is enabled for this profile.
-bool IsEnabledForProfile(Profile* profile) {
-  return chromeos::ProfileHelper::IsPrimaryProfile(profile) &&
-         profile->GetPrefs()->GetBoolean(
-             prefs::kSamlInSessionPasswordChangeEnabled);
-}
-
-// The Rechecker checks periodically if the notification should be shown or
-// updated. When it checks depends on when the password will expire.
-// There is only at most one Rechecker at a time - for the primary user.
-class Rechecker : public ash::SessionActivationObserver,
-                  public NotificationObserver {
- public:
-  // Shows the notification for the primary profile.
-  void ShowNotification(int less_than_n_days);
-
-  // Checks again if the notification should be shown, and maybe shows it.
-  void Recheck();
-
-  // Calls recheck after the given |delay|.
-  void RecheckAfter(base::TimeDelta delay);
-
-  // Cancels any pending tasks to call Recheck again.
-  void CancelPendingRecheck();
-
-  // ash::SessionActivationObserver:
-  void OnSessionActivated(bool activated) override {}  // Not needed.
-  void OnLockStateChanged(bool locked) override;
-
-  // NotificationObserver:
-  void Click(const base::Optional<int>& button_index,
-             const base::Optional<base::string16>& reply) override;
-  void Close(bool by_user) override;
-
-  // Ensures the singleton is initialized and started for the given profile.
-  static void StartForProfile(Profile* profile);
-  // Stops and deletes the Rechecker singleton.
-  static void Stop();
-
- private:
-  // The constructor and destructor also maintain the singleton instance.
-  Rechecker(Profile* profile, const AccountId& account_id);
-  ~Rechecker() override;
-
-  // Singleton, since we only ever need one instance_ for the primary user.
-  static Rechecker* instance_;
-
-  Profile* profile_;
-  const AccountId account_id_;
-  bool reshow_on_unlock_ = false;
-
-  // Weak ptr factory for handling interaction with notifications - these
-  // pointers shouldn't be invalidated until this class is deleted.
-  base::WeakPtrFactory<NotificationObserver> observer_weak_ptr_factory_{this};
-  // Weak ptr factory for posting tasks. Invalidating these pointers will
-  // cancel upcoming tasks.
-  base::WeakPtrFactory<Rechecker> task_weak_ptr_factory_{this};
-
-  // Give test helper access to internals.
-  friend SamlPasswordExpiryNotificationTestHelper;
-
-  DISALLOW_COPY_AND_ASSIGN(Rechecker);
-};
-
-void Rechecker::ShowNotification(int less_than_n_days) {
-  ShowNotificationImpl(profile_, less_than_n_days,
-                       base::MakeRefCounted<ThunkNotificationDelegate>(
-                           observer_weak_ptr_factory_.GetWeakPtr()));
-  // When a notification is currently showing, reshow it on every unlock.
-  reshow_on_unlock_ = true;
-}
-
-void Rechecker::Recheck() {
-  // This cancels any pending call to Recheck - we don't want some bug to cause
-  // us to queue up lots of calls to Recheck in the future, we only want one.
-  CancelPendingRecheck();
-
-  // In case the profile has been deleted since this task was posted.
-  if (!IsValidProfile(profile_)) {
-    delete this;  // No need to keep calling recheck.
-    return;
-  }
-
-  PrefService* prefs = profile_->GetPrefs();
-  SamlPasswordAttributes attrs = SamlPasswordAttributes::LoadFromPrefs(prefs);
-  if (!IsEnabledForProfile(profile_) || !attrs.has_expiration_time()) {
-    // Feature is not enabled for this profile, or profile is not primary, or
-    // there is no expiration time. Dismiss if shown, and stop checking.
-    DismissSamlPasswordExpiryNotification(profile_);
-    delete this;
-    return;
-  }
-
-  // Calculate how many days until the password will expire.
-  const base::TimeDelta time_until_expiry =
-      attrs.expiration_time() - base::Time::Now();
-  const int less_than_n_days =
-      std::max(0, time_until_expiry.InDaysFloored() + 1);
-  const int advance_warning_days = std::max(
-      0, prefs->GetInteger(prefs::kSamlPasswordExpirationAdvanceWarningDays));
-
-  if (less_than_n_days <= advance_warning_days) {
-    // The password is expired, or expires in less than |advance_warning_days|.
-    // So we show a notification immediately.
-    ShowNotification(less_than_n_days);
-    // We check again whether to reshow / update the notification after one day:
-    RecheckAfter(kOneDay);
-
-  } else {
-    // We have not yet reached the advance warning threshold. Check again
-    // once we have arrived at expiry_time minus advance_warning_days...
-    base::TimeDelta recheck_delay =
-        time_until_expiry - base::TimeDelta::FromDays(advance_warning_days);
-    // But, wait an extra hour so that when this code is next run, it is clear
-    // we are now inside advance_warning_days (and not right on the boundary).
-    recheck_delay += kOneHour;
-    RecheckAfter(recheck_delay);
-  }
-}
-
-void Rechecker::RecheckAfter(base::TimeDelta delay) {
-  base::PostDelayedTaskWithTraits(
-      FROM_HERE, kRecheckTaskTraits,
-      base::BindOnce(&Rechecker::Recheck, task_weak_ptr_factory_.GetWeakPtr()),
-      std::max(delay, kOneHour));
-  // This always waits at least one hour before calling Recheck again - we don't
-  // want some bug to cause this code to run every millisecond.
-}
-
-void Rechecker::CancelPendingRecheck() {
-  task_weak_ptr_factory_.InvalidateWeakPtrs();
-}
-
-void Rechecker::OnLockStateChanged(bool locked) {
-  // If a notification is currently showing, we show a new version of it every
-  // time the user unlocks the screen. This makes the notification pop up once
-  // more - just after typing the password is a good time to remind the user.
-  if (!locked && reshow_on_unlock_) {
-    Recheck();
-  }
-}
-
-void Rechecker::Click(const base::Optional<int>& button_index,
-                      const base::Optional<base::string16>& reply) {
-  bool clicked_on_button = button_index.has_value();
-  if (clicked_on_button) {
-    PasswordChangeDialog::Show(profile_);
-  }
-}
-
-void Rechecker::Close(bool by_user) {
-  // When a notification is dismissed, we then don't pop it up again each time
-  // the user unlocks the screen.
-  reshow_on_unlock_ = false;
-}
-
-// static
-void Rechecker::StartForProfile(Profile* profile) {
-  if (!instance_) {
-    const AccountId account_id =
-        ProfileHelper::Get()->GetUserByProfile(profile)->GetAccountId();
-    new Rechecker(profile, account_id);
-  }
-  DCHECK(instance_ && instance_->profile_ == profile);
-  instance_->Recheck();
-}
-
-// static
-void Rechecker::Stop() {
-  delete instance_;
-}
-
-// static
-Rechecker* Rechecker::instance_ = nullptr;
-
-Rechecker::Rechecker(Profile* profile, const AccountId& account_id)
-    : profile_(profile), account_id_(account_id) {
-  // There must not be an existing singleton instance.
-  DCHECK(!instance_);
-  instance_ = this;
-
-  // Add |this| as a SessionActivationObserver to see when the screen is locked.
-  auto* session_controller = ash::SessionController::Get();
-  if (session_controller) {
-    session_controller->AddSessionActivationObserverForAccountId(account_id_,
-                                                                 this);
-  }
-}
-
-Rechecker::~Rechecker() {
-  // Remove this as a SessionActivationObserver.
-  auto* session_controller = ash::SessionController::Get();
-  if (session_controller) {
-    session_controller->RemoveSessionActivationObserverForAccountId(account_id_,
-                                                                    this);
-  }
-
-  // This should still be the singleton instance.
-  DCHECK_EQ(this, instance_);
-  instance_ = nullptr;
-}
-
-}  // namespace
-
-void MaybeShowSamlPasswordExpiryNotification(Profile* profile) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (IsEnabledForProfile(profile)) {
-    Rechecker::StartForProfile(profile);
-  }
-}
-
-void ShowSamlPasswordExpiryNotification(Profile* profile,
-                                        int less_than_n_days) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  ShowNotificationImpl(
-      profile, less_than_n_days,
-      base::MakeRefCounted<HandleNotificationClickDelegate>(
-          base::BindRepeating(&PasswordChangeDialog::Show, profile)));
-}
-
-void DismissSamlPasswordExpiryNotification(Profile* profile) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
-      kNotificationHandlerType, kNotificationId);
-}
-
-void SamlPasswordExpiryNotificationTestHelper::ResetForTesting() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  delete Rechecker::instance_;
-}
-
-void SamlPasswordExpiryNotificationTestHelper::SimulateUnlockForTesting() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  Rechecker::instance_->OnLockStateChanged(/*locked=*/false);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h
deleted file mode 100644
index 7266aa70..0000000
--- a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h
+++ /dev/null
@@ -1,41 +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 CHROME_BROWSER_CHROMEOS_LOGIN_SAML_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SAML_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
-
-class Profile;
-
-namespace chromeos {
-
-// Utility functions to show or hide a password expiry notification.
-
-// Show a password expiry notification if the user's password has expired or
-// soon expires (that is, within pref kSamlPasswordExpirationAdvanceWarningDays
-// time). Otherwise, if the user's password will expire in the more distant
-// future, in that case a notification will be shown in the future. Nothing is
-// shown if the password is not expected to expire.
-void MaybeShowSamlPasswordExpiryNotification(Profile* profile);
-
-// Shows a password expiry notification. |less_than_n_days| should be 1 if the
-// password expires in less than 1 day, 0 if it has already expired, etc.
-// Negative numbers are treated the same as zero.
-void ShowSamlPasswordExpiryNotification(Profile* profile, int less_than_n_days);
-
-// Hides the password expiry notification if it is currently shown.
-void DismissSamlPasswordExpiryNotification(Profile* profile);
-
-// Exposes extra functionality that should only be used during testing.
-class SamlPasswordExpiryNotificationTestHelper {
- public:
-  // Simulate unlocking the screen, which makes the notification pop up again.
-  void SimulateUnlockForTesting();
-
-  // Stop waiting for the password to expire and free up any resources.
-  void ResetForTesting();
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SAML_SAML_PASSWORD_EXPIRY_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index c2e49e2..3244154 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -62,7 +62,6 @@
 #include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
 #include "chrome/browser/chromeos/login/saml/saml_offline_signin_limiter.h"
 #include "chrome/browser/chromeos/login/saml/saml_offline_signin_limiter_factory.h"
-#include "chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h"
 #include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h"
 #include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.h"
@@ -1424,15 +1423,11 @@
       }
     }
 
-    PrefService* prefs = profile->GetPrefs();
-    if (prefs->GetBoolean(prefs::kSamlInSessionPasswordChangeEnabled)) {
-      // Update password expiry data if new data came in during SAML login:
-      if (user_context_.GetSamlPasswordAttributes().has_value()) {
-        user_context_.GetSamlPasswordAttributes()->SaveToPrefs(prefs);
-      }
-      // Show password expiry notification if it is expiring - even if it wasn't
-      // a SAML login (ie, show it even if it was an offline login).
-      MaybeShowSamlPasswordExpiryNotification(profile);
+    // Update password expiry data if new data came in during SAML login:
+    if (base::FeatureList::IsEnabled(::features::kInSessionPasswordChange) &&
+        user_context_.GetSamlPasswordAttributes().has_value()) {
+      user_context_.GetSamlPasswordAttributes()->SaveToPrefs(
+          profile->GetPrefs());
     }
 
     // Transfers authentication-related data from the profile that was used for
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc
index 7b30c7ba..8936fbd0 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc
@@ -59,6 +59,12 @@
   if (!install_attributes_->IsCloudManaged() ||
       !device_settings_service_->policy_data() || !public_key.get() ||
       !public_key->is_loaded()) {
+    LOG(ERROR) << "Policy store failed, is_cloud_managed: "
+               << install_attributes_->IsCloudManaged() << ", policy_data: "
+               << (device_settings_service_->policy_data() != nullptr)
+               << ", public_key: " << (public_key.get() != nullptr)
+               << ", public_key_is_loaded: "
+               << (public_key.get() ? public_key->is_loaded() : false);
     status_ = STATUS_BAD_STATE;
     NotifyStoreError();
     return;
diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_store.cc b/chrome/browser/chromeos/policy/device_local_account_policy_store.cc
index aae7fc9..4562b5c 100644
--- a/chrome/browser/chromeos/policy/device_local_account_policy_store.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_policy_store.cc
@@ -201,6 +201,9 @@
   scoped_refptr<ownership::PublicKey> key =
       device_settings_service_->GetPublicKey();
   if (!key.get() || !key->is_loaded() || !device_policy_data) {
+    LOG(ERROR) << "Failed policy validation, key: " << (key.get() != nullptr)
+               << ", is_loaded: " << (key.get() ? key->is_loaded() : false)
+               << ", device_policy_data: " << (device_policy_data != nullptr);
     status_ = CloudPolicyStore::STATUS_BAD_STATE;
     NotifyStoreLoaded();
     return;
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index fc4a9d6..b688f540 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -257,6 +257,10 @@
             name: 'frameId',
             value: details.frameId.toString()
           });
+          details.requestHeaders.push({
+            name: 'resourceType',
+            value: details.type
+          });
           return {requestHeaders: details.requestHeaders};
         }, {urls: ['*://*/echoheader*']}, ['blocking', 'requestHeaders']);
 
@@ -2525,7 +2529,7 @@
   // response for the navigation preload request, and respond with it to create
   // the page.
   GURL url = embedded_test_server()->GetURL(
-      "/echoheader?foo&frameId&service-worker-navigation-preload");
+      "/echoheader?frameId&resourceType&service-worker-navigation-preload");
   ui_test_utils::NavigateToURL(browser(), url);
 
   content::WebContents* web_contents =
@@ -2534,19 +2538,20 @@
   // Since the request was to "/echoheader", the response describes the request
   // headers.
   //
-  // The expectation is "bar\n0\ntrue" because...
+  // The expectation is "0\nmain_frame\ntrue" because...
   //
-  // 1) The extension is expected to add a "foo: bar" header to the request
-  //    before it goes to network.
-  // 2) The extension is similarly expected to add a "frameId: {id}" header,
-  //    where {id} is details.frameId. This id is 0 for the main frame.
+  // 1) The extension is expected to add a "frameId: {id}" header, where {id} is
+  //    details.frameId. This id is 0 for the main frame.
+  // 2) The extension is similarly expected to add a "resourceType: {type}"
+  //    header, where {type} is details.type.
   // 3) The browser adds a "service-worker-navigation-preload: true" header for
   //    navigation preload requests, so also sanity check that header to prove
   //    that this test is really testing the navigation preload request.
-  EXPECT_EQ("bar\n0\ntrue", EvalJs(web_contents, "document.body.textContent;"));
+  EXPECT_EQ("0\nmain_frame\ntrue",
+            EvalJs(web_contents, "document.body.textContent;"));
 
-  // Repeat the test from an iframe, to test that details.frameId is populated
-  // correctly.
+  // Repeat the test from an iframe, to test that details.frameId and resource
+  // type is populated correctly.
   const char kAddIframe[] = R"(
     (async () => {
       const iframe = document.createElement('iframe');
@@ -2557,10 +2562,13 @@
       });
       const result = iframe.contentWindow.document.body.textContent;
 
-      // Expect "bar\n{frameId}\ntrue" where {frameId} is a positive integer.
+      // Expect "{frameId}\nsub_frame\ntrue" where {frameId} is a positive
+      // integer.
       const split = result.split('\n');
-      if (split[0] == 'bar' && parseInt(split[1]) > 0 && split[2] == 'true')
-        return 'ok';
+      if (parseInt(split[0]) > 0 && split[1] == 'sub_frame' &&
+          split[2] == 'true') {
+          return 'ok';
+      }
       return 'bad result: ' + result;
     })();
   )";
diff --git a/chrome/browser/extensions/forced_extensions/installation_tracker.cc b/chrome/browser/extensions/forced_extensions/installation_tracker.cc
index f50f39d..314fc49 100644
--- a/chrome/browser/extensions/forced_extensions/installation_tracker.cc
+++ b/chrome/browser/extensions/forced_extensions/installation_tracker.cc
@@ -89,6 +89,8 @@
   DCHECK(!reported_);
   // Report only if there was non-empty list of force-installed extensions.
   if (!forced_extensions_.empty()) {
+    UMA_HISTOGRAM_COUNTS_100("Extensions.ForceInstalledTotalCandidateCount",
+                             forced_extensions_.size());
     if (succeeded) {
       UMA_HISTOGRAM_LONG_TIMES("Extensions.ForceInstalledLoadTime",
                                base::Time::Now() - start_time_);
diff --git a/chrome/browser/extensions/forced_extensions/installation_tracker_unittest.cc b/chrome/browser/extensions/forced_extensions/installation_tracker_unittest.cc
index 2fa0326..4cd16dfb 100644
--- a/chrome/browser/extensions/forced_extensions/installation_tracker_unittest.cc
+++ b/chrome/browser/extensions/forced_extensions/installation_tracker_unittest.cc
@@ -37,6 +37,9 @@
     "Extensions.ForceInstalledDownloadingStage";
 constexpr char kFailureCrxInstallErrorStats[] =
     "Extensions.ForceInstalledFailureCrxInstallError";
+constexpr char kTotalCountStats[] =
+    "Extensions.ForceInstalledTotalCandidateCount";
+
 }  // namespace
 
 namespace extensions {
@@ -88,6 +91,9 @@
   histogram_tester_.ExpectTotalCount(kFailureReasons, 0);
   histogram_tester_.ExpectTotalCount(kInstallationStages, 0);
   histogram_tester_.ExpectTotalCount(kFailureCrxInstallErrorStats, 0);
+  histogram_tester_.ExpectUniqueSample(
+      kTotalCountStats,
+      prefs_->GetManagedPref(pref_names::kInstallForceList)->DictSize(), 1);
 }
 
 TEST_F(ForcedExtensionsInstallationTrackerTest,
@@ -105,6 +111,9 @@
       kFailureReasons, InstallationReporter::FailureReason::UNKNOWN, 1);
   histogram_tester_.ExpectTotalCount(kInstallationStages, 0);
   histogram_tester_.ExpectTotalCount(kFailureCrxInstallErrorStats, 0);
+  histogram_tester_.ExpectUniqueSample(
+      kTotalCountStats,
+      prefs_->GetManagedPref(pref_names::kInstallForceList)->DictSize(), 1);
 }
 
 TEST_F(ForcedExtensionsInstallationTrackerTest,
@@ -131,6 +140,9 @@
   histogram_tester_.ExpectTotalCount(kInstallationStages, 0);
   histogram_tester_.ExpectUniqueSample(kFailureCrxInstallErrorStats,
                                        CrxInstallErrorDetail::UNEXPECTED_ID, 1);
+  histogram_tester_.ExpectUniqueSample(
+      kTotalCountStats,
+      prefs_->GetManagedPref(pref_names::kInstallForceList)->DictSize(), 1);
 }
 
 TEST_F(ForcedExtensionsInstallationTrackerTest, ExtensionsStuck) {
@@ -153,6 +165,9 @@
   histogram_tester_.ExpectBucketCount(
       kInstallationStages, InstallationReporter::Stage::DOWNLOADING, 1);
   histogram_tester_.ExpectTotalCount(kFailureCrxInstallErrorStats, 0);
+  histogram_tester_.ExpectUniqueSample(
+      kTotalCountStats,
+      prefs_->GetManagedPref(pref_names::kInstallForceList)->DictSize(), 1);
 }
 
 TEST_F(ForcedExtensionsInstallationTrackerTest, ExtensionsAreDownloading) {
@@ -182,6 +197,9 @@
   histogram_tester_.ExpectBucketCount(
       kInstallationDownloadingStages,
       ExtensionDownloaderDelegate::DOWNLOADING_CRX, 1);
+  histogram_tester_.ExpectUniqueSample(
+      kTotalCountStats,
+      prefs_->GetManagedPref(pref_names::kInstallForceList)->DictSize(), 1);
 }
 
 TEST_F(ForcedExtensionsInstallationTrackerTest, NoExtensionsConfigured) {
@@ -193,6 +211,7 @@
   histogram_tester_.ExpectTotalCount(kFailureReasons, 0);
   histogram_tester_.ExpectTotalCount(kInstallationStages, 0);
   histogram_tester_.ExpectTotalCount(kFailureCrxInstallErrorStats, 0);
+  histogram_tester_.ExpectTotalCount(kTotalCountStats, 0);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 45cba2a7..f5b4b569 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1172,6 +1172,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "enable-winrt-sensor-implementation",
+    "owners": [ "wensh@microsoft.com" ],
+    "expiry_milestone": 80
+  },
+  {
     "name": "enable-generic-sensor",
     "owners": [ "reillyg@chromium.org", "raphael.kubo.da.costa@intel.com" ],
     "expiry_milestone": 72
@@ -1302,6 +1307,11 @@
     "expiry_milestone": 78
   },
   {
+    "name": "enable-defer-all-script",
+    "owners": [ "//components/data_reduction_proxy/OWNERS" ],
+    "expiry_milestone": 82
+  },
+  {
     "name": "enable-layout-ng",
     "owners": [ "layout-dev@chromium.org" ],
     "expiry_milestone": 80
@@ -2525,6 +2535,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "omnibox-search-engine-logo",
+    "owners": [ "wylieb", "chrome-omnibox-team@google.com" ],
+    "expiry_milestone": 80
+  },
+  {
     "name": "omnibox-short-bookmark-suggestions",
     "owners": [ "krb", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 78
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 1a7e2f2..d01461b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -533,6 +533,10 @@
     "If enabled, adds the status of certain experiment variations when making "
     "calls to Google Payments.";
 
+const char kEnableDeferAllScriptName[] = "DeferAllScript previews";
+const char kEnableDeferAllScriptDescription[] =
+    "Enable deferring synchronous script on slow pages.";
+
 const char kEnableSaveDataName[] = "Enables save data feature";
 const char kEnableSaveDataDescription[] =
     "Enables save data feature. May cause user's traffic to be proxied via "
@@ -603,6 +607,11 @@
     "during the handshake when resuming a connection to a compatible TLS 1.3 "
     "server.";
 
+const char kWinrtSensorsImplementationName[] = "WinRT Sensor Implementation";
+const char kWinrtSensorsImplementationDescription[] =
+    "Enables usage of the Windows.Devices.Sensors WinRT APIs on Windows for "
+    "sensors";
+
 const char kEnableGenericSensorName[] = "Generic Sensor";
 const char kEnableGenericSensorDescription[] =
     "Enables motion sensor classes based on Generic Sensor API, i.e. "
@@ -1321,6 +1330,10 @@
     "Display entity suggestions using images and an enhanced layout; showing "
     "more context and descriptive text about the entity.";
 
+const char kOmniboxSearchEngineLogoName[] = "Omnibox search engine logo";
+const char kOmniboxSearchEngineLogoDescription[] =
+    "Display the current default search engine's logo in the omnibox";
+
 const char kOmniboxSpareRendererName[] =
     "Start spare renderer on omnibox focus";
 const char kOmniboxSpareRendererDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 466865b..e81aafd 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -328,6 +328,9 @@
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsName[];
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsDescription[];
 
+extern const char kEnableDeferAllScriptName[];
+extern const char kEnableDeferAllScriptDescription[];
+
 extern const char kEnableSaveDataName[];
 extern const char kEnableSaveDataDescription[];
 
@@ -370,6 +373,9 @@
 extern const char kEnableTLS13EarlyDataName[];
 extern const char kEnableTLS13EarlyDataDescription[];
 
+extern const char kWinrtSensorsImplementationName[];
+extern const char kWinrtSensorsImplementationDescription[];
+
 extern const char kEnableGenericSensorName[];
 extern const char kEnableGenericSensorDescription[];
 
@@ -802,6 +808,9 @@
 extern const char kOmniboxShortBookmarkSuggestionsName[];
 extern const char kOmniboxShortBookmarkSuggestionsDescription[];
 
+extern const char kOmniboxSearchEngineLogoName[];
+extern const char kOmniboxSearchEngineLogoDescription[];
+
 extern const char kOmniboxSpareRendererName[];
 extern const char kOmniboxSpareRendererDescription[];
 
diff --git a/chrome/browser/idle/OWNERS b/chrome/browser/idle/OWNERS
index 1dcec86..971ef1c 100644
--- a/chrome/browser/idle/OWNERS
+++ b/chrome/browser/idle/OWNERS
@@ -1,3 +1,5 @@
 file://content/browser/idle/OWNERS
 
 per-file *permission_context*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
+
+# TEAM: fugu-dev@chromium.org
diff --git a/chrome/browser/native_file_system/OWNERS b/chrome/browser/native_file_system/OWNERS
index 7eb6aed..06981ab 100644
--- a/chrome/browser/native_file_system/OWNERS
+++ b/chrome/browser/native_file_system/OWNERS
@@ -1,6 +1,6 @@
 file://content/browser/native_file_system/OWNERS
 
-per-file *permission_context*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
+per-file *permission*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
 
 # TEAM: storage-dev@chromium.org
 # COMPONENT: Blink>Storage>FileSystem
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
index 070e302..aa530c1 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
@@ -8,6 +8,8 @@
 
 #include "base/bind.h"
 #include "base/task/post_task.h"
+#include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
+#include "chrome/browser/native_file_system/native_file_system_permission_request_manager.h"
 #include "chrome/browser/permissions/permission_util.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -35,15 +37,42 @@
 
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(rfh);
-
   if (!web_contents) {
     // Requested from a worker, or a no longer existing tab.
     std::move(callback).Run(PermissionAction::DISMISSED);
     return;
   }
 
-  ShowNativeFileSystemPermissionDialog(origin, path, is_directory,
-                                       std::move(callback), web_contents);
+  auto* request_manager =
+      NativeFileSystemPermissionRequestManager::FromWebContents(web_contents);
+  if (!request_manager) {
+    std::move(callback).Run(PermissionAction::DISMISSED);
+    return;
+  }
+
+  request_manager->AddRequest({origin, path, is_directory},
+                              std::move(callback));
+}
+
+void ShowDirectoryAccessConfirmationPromptOnUIThread(
+    int process_id,
+    int frame_id,
+    const url::Origin& origin,
+    const base::FilePath& path,
+    base::OnceCallback<void(PermissionAction result)> callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  content::RenderFrameHost* rfh =
+      content::RenderFrameHost::FromID(process_id, frame_id);
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(rfh);
+
+  if (!web_contents) {
+    // Requested from a worker, or a no longer existing tab.
+    std::move(callback).Run(PermissionAction::DISMISSED);
+  }
+
+  ShowNativeFileSystemDirectoryAccessConfirmationDialog(
+      origin, path, std::move(callback), web_contents);
 }
 
 // Returns a callback that calls the passed in |callback| by posting a task to
@@ -63,6 +92,12 @@
 
 }  // namespace
 
+ChromeNativeFileSystemPermissionContext::Grants::Grants() = default;
+ChromeNativeFileSystemPermissionContext::Grants::~Grants() = default;
+ChromeNativeFileSystemPermissionContext::Grants::Grants(Grants&&) = default;
+ChromeNativeFileSystemPermissionContext::Grants&
+ChromeNativeFileSystemPermissionContext::Grants::operator=(Grants&&) = default;
+
 class ChromeNativeFileSystemPermissionContext::PermissionGrantImpl
     : public content::NativeFileSystemPermissionGrant {
  public:
@@ -81,6 +116,11 @@
     return origin_;
   }
 
+  bool is_directory() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return is_directory_;
+  }
+
   const base::FilePath& path() const {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return path_;
@@ -183,6 +223,73 @@
   return result;
 }
 
+void ChromeNativeFileSystemPermissionContext::ConfirmDirectoryReadAccess(
+    const url::Origin& origin,
+    const base::FilePath& path,
+    int process_id,
+    int frame_id,
+    base::OnceCallback<void(PermissionStatus)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(
+          &ShowDirectoryAccessConfirmationPromptOnUIThread, process_id,
+          frame_id, origin, path,
+          base::BindOnce(
+              [](scoped_refptr<base::TaskRunner> task_runner,
+                 base::OnceCallback<void(PermissionStatus result)> callback,
+                 PermissionAction result) {
+                task_runner->PostTask(
+                    FROM_HERE,
+                    base::BindOnce(std::move(callback),
+                                   result == PermissionAction::GRANTED
+                                       ? PermissionStatus::GRANTED
+                                       : PermissionStatus::DENIED));
+              },
+              base::SequencedTaskRunnerHandle::Get(), std::move(callback))));
+}
+
+ChromeNativeFileSystemPermissionContext::Grants
+ChromeNativeFileSystemPermissionContext::GetPermissionGrants(
+    const url::Origin& origin) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  Grants grants;
+  auto it = origins_.find(origin);
+  if (it == origins_.end())
+    return grants;
+  for (const auto& entry : it->second.grants) {
+    if (entry.second->GetStatus() ==
+        PermissionGrantImpl::PermissionStatus::GRANTED) {
+      if (entry.second->is_directory()) {
+        grants.directory_write_grants.push_back(entry.second->path());
+      } else {
+        grants.file_write_grants.push_back(entry.second->path());
+      }
+    }
+  }
+  return grants;
+}
+
+// static
+void ChromeNativeFileSystemPermissionContext::GetPermissionGrantsFromUIThread(
+    content::BrowserContext* browser_context,
+    const url::Origin& origin,
+    base::OnceCallback<void(Grants)> callback) {
+  auto permission_context =
+      NativeFileSystemPermissionContextFactory::GetForProfileIfExists(
+          browser_context);
+  if (!permission_context) {
+    std::move(callback).Run(Grants());
+    return;
+  }
+  base::PostTaskAndReplyWithResult(
+      FROM_HERE, {content::BrowserThread::IO},
+      base::BindOnce(
+          &ChromeNativeFileSystemPermissionContext::GetPermissionGrants,
+          permission_context, origin),
+      std::move(callback));
+}
+
 ChromeNativeFileSystemPermissionContext::
     ~ChromeNativeFileSystemPermissionContext() = default;
 
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
index e4514ff..179c5035 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
@@ -19,8 +19,8 @@
 // Chrome implementation of NativeFileSystemPermissionContext. Currently
 // implements a single per-origin write permission state.
 //
-// All methods (other than the constructor and destructor) should be called on
-// the same sequence.
+// All methods should be called on the same sequence, except for the
+// constructor, destructor, and GetPermissionGrantsFromUIThread method.
 //
 // TODO(mek): Reconsider if this class should just be UI-thread only, avoiding
 // the need to make this ref-counted.
@@ -46,6 +46,31 @@
                           const base::FilePath& path,
                           bool is_directory) override;
 
+  void ConfirmDirectoryReadAccess(
+      const url::Origin& origin,
+      const base::FilePath& path,
+      int process_id,
+      int frame_id,
+      base::OnceCallback<void(PermissionStatus)> callback) override;
+
+  struct Grants {
+    Grants();
+    ~Grants();
+    Grants(Grants&&);
+    Grants& operator=(Grants&&);
+
+    std::vector<base::FilePath> file_write_grants;
+    std::vector<base::FilePath> directory_write_grants;
+  };
+  Grants GetPermissionGrants(const url::Origin& origin);
+
+  // This method must be called on the UI thread, and calls the callback with a
+  // snapshot of the currently granted permissions after looking them up.
+  static void GetPermissionGrantsFromUIThread(
+      content::BrowserContext* browser_context,
+      const url::Origin& origin,
+      base::OnceCallback<void(Grants)> callback);
+
   // RefcountedKeyedService:
   void ShutdownOnUIThread() override;
 
diff --git a/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc b/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
index 4336f58..e5942f8 100644
--- a/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
+++ b/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
@@ -18,6 +18,14 @@
 }
 
 // static
+scoped_refptr<ChromeNativeFileSystemPermissionContext>
+NativeFileSystemPermissionContextFactory::GetForProfileIfExists(
+    content::BrowserContext* profile) {
+  return static_cast<ChromeNativeFileSystemPermissionContext*>(
+      GetInstance()->GetServiceForBrowserContext(profile, false).get());
+}
+
+// static
 NativeFileSystemPermissionContextFactory*
 NativeFileSystemPermissionContextFactory::GetInstance() {
   static base::NoDestructor<NativeFileSystemPermissionContextFactory> instance;
diff --git a/chrome/browser/native_file_system/native_file_system_permission_context_factory.h b/chrome/browser/native_file_system/native_file_system_permission_context_factory.h
index 432ba1f..4bbc2b3 100644
--- a/chrome/browser/native_file_system/native_file_system_permission_context_factory.h
+++ b/chrome/browser/native_file_system/native_file_system_permission_context_factory.h
@@ -18,6 +18,8 @@
  public:
   static scoped_refptr<ChromeNativeFileSystemPermissionContext> GetForProfile(
       content::BrowserContext* profile);
+  static scoped_refptr<ChromeNativeFileSystemPermissionContext>
+  GetForProfileIfExists(content::BrowserContext* profile);
   static NativeFileSystemPermissionContextFactory* GetInstance();
 
  private:
diff --git a/chrome/browser/native_file_system/native_file_system_permission_request_manager.cc b/chrome/browser/native_file_system/native_file_system_permission_request_manager.cc
new file mode 100644
index 0000000..21feff4
--- /dev/null
+++ b/chrome/browser/native_file_system/native_file_system_permission_request_manager.cc
@@ -0,0 +1,122 @@
+// 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/native_file_system/native_file_system_permission_request_manager.h"
+
+#include "base/command_line.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/browser_task_traits.h"
+
+bool operator==(
+    const NativeFileSystemPermissionRequestManager::RequestData& a,
+    const NativeFileSystemPermissionRequestManager::RequestData& b) {
+  return a.origin == b.origin && a.path == b.path &&
+         a.is_directory == b.is_directory;
+}
+
+struct NativeFileSystemPermissionRequestManager::Request {
+  Request(RequestData data,
+          base::OnceCallback<void(PermissionAction result)> callback)
+      : data(std::move(data)) {
+    callbacks.push_back(std::move(callback));
+  }
+
+  const RequestData data;
+  std::vector<base::OnceCallback<void(PermissionAction result)>> callbacks;
+};
+
+NativeFileSystemPermissionRequestManager::
+    ~NativeFileSystemPermissionRequestManager() = default;
+
+void NativeFileSystemPermissionRequestManager::AddRequest(
+    RequestData data,
+    base::OnceCallback<void(PermissionAction result)> callback) {
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDenyPermissionPrompts)) {
+    std::move(callback).Run(PermissionAction::DENIED);
+    return;
+  }
+
+  // Check if any pending requests are identical to the new request.
+  if (current_request_ && current_request_->data == data) {
+    current_request_->callbacks.push_back(std::move(callback));
+    return;
+  }
+  for (const auto& request : queued_requests_) {
+    if (request->data == data) {
+      request->callbacks.push_back(std::move(callback));
+      return;
+    }
+  }
+
+  queued_requests_.push_back(
+      std::make_unique<Request>(std::move(data), std::move(callback)));
+  if (!IsShowingRequest())
+    ScheduleShowRequest();
+}
+
+NativeFileSystemPermissionRequestManager::
+    NativeFileSystemPermissionRequestManager(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+bool NativeFileSystemPermissionRequestManager::CanShowRequest() const {
+  // Deley showing requests until the main frame is fully loaded.
+  // ScheduleShowRequest() will be called again when that happens.
+  return main_frame_has_fully_loaded_ && !queued_requests_.empty() &&
+         !current_request_;
+}
+
+void NativeFileSystemPermissionRequestManager::ScheduleShowRequest() {
+  if (!CanShowRequest())
+    return;
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(
+          &NativeFileSystemPermissionRequestManager::DequeueAndShowRequest,
+          weak_factory_.GetWeakPtr()));
+}
+
+void NativeFileSystemPermissionRequestManager::DequeueAndShowRequest() {
+  if (!CanShowRequest())
+    return;
+
+  current_request_ = std::move(queued_requests_.front());
+  queued_requests_.pop_front();
+
+  ShowNativeFileSystemPermissionDialog(
+      current_request_->data.origin, current_request_->data.path,
+      current_request_->data.is_directory,
+      base::BindOnce(
+          &NativeFileSystemPermissionRequestManager::OnPermissionDialogResult,
+          weak_factory_.GetWeakPtr()),
+      web_contents());
+}
+
+void NativeFileSystemPermissionRequestManager::
+    DocumentOnLoadCompletedInMainFrame() {
+  main_frame_has_fully_loaded_ = true;
+  // This is scheduled because while all calls to the browser have been
+  // issued at DOMContentLoaded, they may be bouncing around in scheduled
+  // callbacks finding the UI thread still. This makes sure we allow those
+  // scheduled calls to AddRequest to complete before we show the page-load
+  // permissions bubble.
+  if (!queued_requests_.empty())
+    ScheduleShowRequest();
+}
+
+void NativeFileSystemPermissionRequestManager::OnPermissionDialogResult(
+    PermissionAction result) {
+  DCHECK(current_request_);
+  for (auto& callback : current_request_->callbacks)
+    std::move(callback).Run(result);
+  current_request_ = nullptr;
+  if (!queued_requests_.empty())
+    ScheduleShowRequest();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(NativeFileSystemPermissionRequestManager)
diff --git a/chrome/browser/native_file_system/native_file_system_permission_request_manager.h b/chrome/browser/native_file_system/native_file_system_permission_request_manager.h
new file mode 100644
index 0000000..a4d9d08b
--- /dev/null
+++ b/chrome/browser/native_file_system/native_file_system_permission_request_manager.h
@@ -0,0 +1,81 @@
+// 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_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_REQUEST_MANAGER_H_
+#define CHROME_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_REQUEST_MANAGER_H_
+
+#include "base/containers/circular_deque.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "url/origin.h"
+
+enum class PermissionAction;
+
+// This class manages native file system permission requests for a particular
+// WebContents. It is very similar to the generic PermissionRequestManager
+// class, and as such deals with throttling, coallescing and/or completely
+// denying permission requests, depending on the situation and policy.
+//
+// Native File System code doesn't just use PermissionRequestManager directly
+// because the permission requests use different UI, and as such can't easily
+// be supported by PermissionRequestManager.
+//
+// The NativeFileSystemPermissionRequestManager should be used on the UI thread.
+class NativeFileSystemPermissionRequestManager
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<
+          NativeFileSystemPermissionRequestManager> {
+ public:
+  ~NativeFileSystemPermissionRequestManager() override;
+
+  struct RequestData {
+    RequestData(const url::Origin& origin,
+                const base::FilePath& path,
+                bool is_directory)
+        : origin(origin), path(path), is_directory(is_directory) {}
+    RequestData(RequestData&&) = default;
+    RequestData& operator=(RequestData&&) = default;
+
+    url::Origin origin;
+    base::FilePath path;
+    bool is_directory;
+  };
+
+  void AddRequest(RequestData request,
+                  base::OnceCallback<void(PermissionAction result)> callback);
+
+ private:
+  friend class content::WebContentsUserData<
+      NativeFileSystemPermissionRequestManager>;
+
+  explicit NativeFileSystemPermissionRequestManager(
+      content::WebContents* web_contents);
+
+  bool IsShowingRequest() const { return current_request_ != nullptr; }
+  bool CanShowRequest() const;
+  void ScheduleShowRequest();
+  void DequeueAndShowRequest();
+
+  // WebContentsObserver
+  void DocumentOnLoadCompletedInMainFrame() override;
+
+  void OnPermissionDialogResult(PermissionAction result);
+
+  struct Request;
+  // Request currently being shown in prompt.
+  std::unique_ptr<Request> current_request_;
+  // Queued up requests.
+  base::circular_deque<std::unique_ptr<Request>> queued_requests_;
+
+  // We only show new prompts when this is true.
+  bool main_frame_has_fully_loaded_ = false;
+
+  base::WeakPtrFactory<NativeFileSystemPermissionRequestManager> weak_factory_{
+      this};
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+#endif  // CHROME_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_REQUEST_MANAGER_H_
diff --git a/chrome/browser/notifications/proto/impression.proto b/chrome/browser/notifications/proto/impression.proto
index c16865e..d753c33 100644
--- a/chrome/browser/notifications/proto/impression.proto
+++ b/chrome/browser/notifications/proto/impression.proto
@@ -11,7 +11,7 @@
 // Contains data to determine when a notification should be shown to the user
 // and the user impression towards this notification. Should match Impression in
 // impression_types.h.
-// Next tag: 7
+// Next tag: 8
 message Impression {
   // The type of user feedback from a displayed notification. Should match
   // UserFeedback in notification_scheduler_types.h.
@@ -41,6 +41,13 @@
     EVENING = 2;
   }
 
+  // Used to override default user action and impression mapping.
+  // Next tag: 3
+  message ImpressionMapping {
+    optional UserFeedback user_feedback = 1;
+    optional ImpressionResult impression_result = 2;
+  }
+
   // Creation time stamp in milliseconds since epoch.
   optional int64 create_time = 1;
 
@@ -59,4 +66,6 @@
 
   // The unique identifier of the notification.
   optional string guid = 6;
+
+  repeated ImpressionMapping impression_mapping = 7;
 }
diff --git a/chrome/browser/notifications/proto/notification_entry.proto b/chrome/browser/notifications/proto/notification_entry.proto
index f8dabfed..c9b0ce3 100644
--- a/chrome/browser/notifications/proto/notification_entry.proto
+++ b/chrome/browser/notifications/proto/notification_entry.proto
@@ -9,16 +9,20 @@
 package notifications.proto;
 
 import "client_state.proto";
+import "impression.proto";
 import "notification_data.proto";
 
 // Defines scheduling and throttling details.
+// Next tag: 3
 message ScheduleParams {
   enum Priority {
     LOW = 0;
     HIGH = 1;
     NO_THROTTLE = 2;
   }
+
   optional Priority priority = 1;
+  repeated Impression.ImpressionMapping impression_mapping = 2;
 }
 
 // The notification entry that contains all data for a scheduled notification.
diff --git a/chrome/browser/notifications/scheduler/internal/impression_types.cc b/chrome/browser/notifications/scheduler/internal/impression_types.cc
index 148e385a..681c017 100644
--- a/chrome/browser/notifications/scheduler/internal/impression_types.cc
+++ b/chrome/browser/notifications/scheduler/internal/impression_types.cc
@@ -13,11 +13,15 @@
                        const base::Time& create_time)
     : create_time(create_time), guid(guid), type(type) {}
 
+Impression::Impression(const Impression& other) = default;
+
+Impression::~Impression() = default;
+
 bool Impression::operator==(const Impression& other) const {
   return create_time == other.create_time && feedback == other.feedback &&
          impression == other.impression && integrated == other.integrated &&
          task_start_time == other.task_start_time && guid == other.guid &&
-         type == other.type;
+         type == other.type && impression_mapping == other.impression_mapping;
 }
 
 SuppressionInfo::SuppressionInfo(const base::Time& last_trigger,
diff --git a/chrome/browser/notifications/scheduler/internal/impression_types.h b/chrome/browser/notifications/scheduler/internal/impression_types.h
index 876ada57..f01f05083 100644
--- a/chrome/browser/notifications/scheduler/internal/impression_types.h
+++ b/chrome/browser/notifications/scheduler/internal/impression_types.h
@@ -29,6 +29,8 @@
   Impression(SchedulerClientType type,
              const std::string& guid,
              const base::Time& create_time);
+  Impression(const Impression& other);
+  ~Impression();
 
   bool operator==(const Impression& other) const;
 
@@ -58,6 +60,9 @@
   // initialized.
   // TODO(xingliu): Consider to persist this as well.
   SchedulerClientType type = SchedulerClientType::kUnknown;
+
+  // Used to override default impression result.
+  std::map<UserFeedback, ImpressionResult> impression_mapping;
 };
 
 // Contains details about supression and recovery after suppression expired.
diff --git a/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc b/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
index 02c5aa2..f64de7f 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
+++ b/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/notifications/scheduler/internal/background_task_coordinator.h"
 #include "chrome/browser/notifications/scheduler/internal/display_decider.h"
 #include "chrome/browser/notifications/scheduler/internal/distribution_policy.h"
-#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
 #include "chrome/browser/notifications/scheduler/internal/impression_history_tracker.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_scheduler_context.h"
@@ -62,23 +61,13 @@
     impression_tracker_delegate_ = impression_tracker_delegate;
     callback_ = std::move(callback);
 
-    context->icon_store()->Init(base::BindOnce(
-        &InitHelper::OnIconStoreInitialized, weak_ptr_factory_.GetWeakPtr()));
-  }
-
- private:
-  void OnIconStoreInitialized(bool success) {
-    if (!success) {
-      std::move(callback_).Run(false /*success*/);
-      return;
-    }
-
     context_->impression_tracker()->Init(
         impression_tracker_delegate_,
         base::BindOnce(&InitHelper::OnImpressionTrackerInitialized,
                        weak_ptr_factory_.GetWeakPtr()));
   }
 
+ private:
   void OnImpressionTrackerInitialized(bool success) {
     if (!success) {
       std::move(callback_).Run(false /*success*/);
diff --git a/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.cc b/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.cc
index 2694a781..196d864 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.cc
+++ b/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.cc
@@ -10,7 +10,6 @@
 #include "base/time/default_clock.h"
 #include "chrome/browser/notifications/scheduler/internal/background_task_coordinator.h"
 #include "chrome/browser/notifications/scheduler/internal/display_decider.h"
-#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
 #include "chrome/browser/notifications/scheduler/internal/impression_history_tracker.h"
 #include "chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h"
 #include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
@@ -24,14 +23,12 @@
 NotificationSchedulerContext::NotificationSchedulerContext(
     std::unique_ptr<NotificationSchedulerClientRegistrar> client_registrar,
     std::unique_ptr<NotificationBackgroundTaskScheduler> background_task,
-    std::unique_ptr<IconStore> icon_store,
     std::unique_ptr<ImpressionHistoryTracker> impression_tracker,
     std::unique_ptr<ScheduledNotificationManager> notification_manager,
     std::unique_ptr<DisplayAgent> display_agent,
     std::unique_ptr<DisplayDecider> display_decider,
     std::unique_ptr<SchedulerConfig> config)
     : client_registrar_(std::move(client_registrar)),
-      icon_store_(std::move(icon_store)),
       impression_tracker_(std::move(impression_tracker)),
       notification_manager_(std::move(notification_manager)),
       display_agent_(std::move(display_agent)),
diff --git a/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.h b/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.h
index e01299e6..6de0494 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.h
+++ b/chrome/browser/notifications/scheduler/internal/notification_scheduler_context.h
@@ -16,7 +16,6 @@
 class BackgroundTaskCoordinator;
 class DisplayAgent;
 class DisplayDecider;
-class IconStore;
 class ImpressionHistoryTracker;
 class NotificationBackgroundTaskScheduler;
 class NotificationSchedulerClientRegistrar;
@@ -30,7 +29,6 @@
   NotificationSchedulerContext(
       std::unique_ptr<NotificationSchedulerClientRegistrar> client_registrar,
       std::unique_ptr<NotificationBackgroundTaskScheduler> background_task,
-      std::unique_ptr<IconStore> icon_store,
       std::unique_ptr<ImpressionHistoryTracker> impression_tracker,
       std::unique_ptr<ScheduledNotificationManager> notification_manager,
       std::unique_ptr<DisplayAgent> display_agent,
@@ -46,8 +44,6 @@
     return background_task_coordinator_.get();
   }
 
-  IconStore* icon_store() { return icon_store_.get(); }
-
   ImpressionHistoryTracker* impression_tracker() {
     return impression_tracker_.get();
   }
@@ -66,9 +62,6 @@
   // Holds a list of clients using the notification scheduler system.
   std::unique_ptr<NotificationSchedulerClientRegistrar> client_registrar_;
 
-  // Stores notification icons.
-  std::unique_ptr<IconStore> icon_store_;
-
   // Tracks user impressions towards specific notification type.
   std::unique_ptr<ImpressionHistoryTracker> impression_tracker_;
 
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
index 2167858..e716adc 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
@@ -267,12 +267,28 @@
 void ScheduleParamsToProto(ScheduleParams* params,
                            proto::ScheduleParams* proto) {
   proto->set_priority(ScheduleParamsPriorityToProto(params->priority));
+
+  for (const auto& mapping : params->impression_mapping) {
+    auto* proto_impression_mapping = proto->add_impression_mapping();
+    proto_impression_mapping->set_user_feedback(ToUserFeedback(mapping.first));
+    proto_impression_mapping->set_impression_result(
+        ToImpressionResult(mapping.second));
+  }
 }
 
 // Converts ScheduleParams from proto buffer type.
 void ScheduleParamsFromProto(proto::ScheduleParams* proto,
                              ScheduleParams* params) {
   params->priority = ScheduleParamsPriorityFromProto(proto->priority());
+
+  for (int i = 0; i < proto->impression_mapping_size(); ++i) {
+    const auto& proto_impression_mapping = proto->impression_mapping(i);
+    auto user_feedback =
+        FromUserFeedback(proto_impression_mapping.user_feedback());
+    auto impression_result =
+        FromImpressionResult(proto_impression_mapping.impression_result());
+    params->impression_mapping[user_feedback] = impression_result;
+  }
 }
 
 }  // namespace
@@ -303,6 +319,14 @@
     impression_ptr->set_task_start_time(
         ToSchedulerTaskTime(impression.task_start_time));
     impression_ptr->set_guid(impression.guid);
+
+    for (const auto& mapping : impression.impression_mapping) {
+      auto* proto_impression_mapping = impression_ptr->add_impression_mapping();
+      proto_impression_mapping->set_user_feedback(
+          ToUserFeedback(mapping.first));
+      proto_impression_mapping->set_impression_result(
+          ToImpressionResult(mapping.second));
+    }
   }
 
   if (client_state->suppression_info.has_value()) {
@@ -334,6 +358,17 @@
         FromSchedulerTaskTime(proto_impression.task_start_time());
     impression.guid = proto_impression.guid();
     impression.type = client_state->type;
+
+    for (int i = 0; i < proto_impression.impression_mapping_size(); ++i) {
+      const auto& proto_impression_mapping =
+          proto_impression.impression_mapping(i);
+      auto user_feedback =
+          FromUserFeedback(proto_impression_mapping.user_feedback());
+      auto impression_result =
+          FromImpressionResult(proto_impression_mapping.impression_result());
+      impression.impression_mapping[user_feedback] = impression_result;
+    }
+
     client_state->impressions.emplace_back(std::move(impression));
   }
 
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
index 20ccd82..0cccc36 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
@@ -149,6 +149,11 @@
     first_impression.task_start_time = task_start_time;
     TestClientStateConversion(&client_state);
   }
+
+  // Verify impression mapping.
+  first_impression.impression_mapping[UserFeedback::kClick] =
+      ImpressionResult::kNeutral;
+  TestClientStateConversion(&client_state);
 }
 
 // Verifies multiple impressions are serialized correctly.
@@ -195,6 +200,11 @@
     entry.schedule_params.priority = priority;
     TestNotificationEntryConversion(&entry);
   }
+  entry.schedule_params.impression_mapping[UserFeedback::kDismiss] =
+      ImpressionResult::kPositive;
+  entry.schedule_params.impression_mapping[UserFeedback::kClick] =
+      ImpressionResult::kNeutral;
+  TestNotificationEntryConversion(&entry);
 }
 
 // Verifies buttons are converted correctly to proto buffers.
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
index 539d6ff6..23acc2f 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/guid.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
 #include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
 #include "chrome/browser/notifications/scheduler/public/notification_params.h"
@@ -27,13 +28,15 @@
 
 class ScheduledNotificationManagerImpl : public ScheduledNotificationManager {
  public:
-  using Store = std::unique_ptr<CollectionStore<NotificationEntry>>;
+  using NotificationStore = std::unique_ptr<CollectionStore<NotificationEntry>>;
 
   ScheduledNotificationManagerImpl(
-      Store store,
+      NotificationStore notification_store,
+      std::unique_ptr<IconStore> icon_store,
       const std::vector<SchedulerClientType>& clients,
       const SchedulerConfig& config)
-      : store_(std::move(store)),
+      : notification_store_(std::move(notification_store)),
+        icon_store_(std::move(icon_store)),
         clients_(clients.begin(), clients.end()),
         delegate_(nullptr),
         config_(config),
@@ -43,9 +46,10 @@
   void Init(Delegate* delegate, InitCallback callback) override {
     DCHECK(!delegate_);
     delegate_ = delegate;
-    store_->InitAndLoad(
-        base::BindOnce(&ScheduledNotificationManagerImpl::OnStoreInitialized,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+    notification_store_->InitAndLoad(base::BindOnce(
+        &ScheduledNotificationManagerImpl::OnNotificationStoreInitialized,
+        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
 
   // NotificationManager implementation.
@@ -69,7 +73,7 @@
     entry->schedule_params = std::move(notification_params->schedule_params);
     auto* entry_ptr = entry.get();
     notifications_[type][guid] = std::move(entry);
-    store_->Add(
+    notification_store_->Add(
         guid, *entry_ptr,
         base::BindOnce(&ScheduledNotificationManagerImpl::OnNotificationAdded,
                        weak_ptr_factory_.GetWeakPtr()));
@@ -88,7 +92,7 @@
     if (notifications_[entry->type].empty())
       notifications_.erase(entry->type);
 
-    store_->Delete(
+    notification_store_->Delete(
         guid,
         base::BindOnce(&ScheduledNotificationManagerImpl::OnNotificationDeleted,
                        weak_ptr_factory_.GetWeakPtr()));
@@ -133,7 +137,7 @@
     while (it != notifications_[type].end()) {
       const auto& entry = *it->second;
       ++it;
-      store_->Delete(
+      notification_store_->Delete(
           entry.guid,
           base::BindOnce(
               &ScheduledNotificationManagerImpl::OnNotificationDeleted,
@@ -155,9 +159,10 @@
     }
   }
 
-  void OnStoreInitialized(InitCallback callback,
-                          bool success,
-                          CollectionStore<NotificationEntry>::Entries entries) {
+  void OnNotificationStoreInitialized(
+      InitCallback callback,
+      bool success,
+      CollectionStore<NotificationEntry>::Entries entries) {
     if (!success) {
       std::move(callback).Run(false);
       return;
@@ -169,7 +174,7 @@
       bool expired = entry->create_time + config_.notification_expiration <=
                      base::Time::Now();
       if (expired) {
-        store_->Delete(
+        notification_store_->Delete(
             entry->guid,
             base::BindOnce(
                 &ScheduledNotificationManagerImpl::OnNotificationDeleted,
@@ -179,14 +184,26 @@
       }
     }
     SyncRegisteredClients();
-    std::move(callback).Run(true);
+
+    icon_store_->Init(base::BindOnce(
+        &ScheduledNotificationManagerImpl::OnIconStoreInitialized,
+        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  }
+
+  void OnIconStoreInitialized(InitCallback callback, bool success) {
+    std::move(callback).Run(success);
   }
 
   void OnNotificationAdded(bool success) { NOTIMPLEMENTED(); }
 
   void OnNotificationDeleted(bool success) { NOTIMPLEMENTED(); }
 
-  Store store_;
+  void OnIconAdded(bool success) { NOTIMPLEMENTED(); }
+
+  void OnIconDeleted(bool success) { NOTIMPLEMENTED(); }
+
+  NotificationStore notification_store_;
+  std::unique_ptr<IconStore> icon_store_;
   const std::unordered_set<SchedulerClientType> clients_;
   Delegate* delegate_;
   std::map<SchedulerClientType,
@@ -201,11 +218,12 @@
 // static
 std::unique_ptr<ScheduledNotificationManager>
 ScheduledNotificationManager::Create(
-    std::unique_ptr<CollectionStore<NotificationEntry>> store,
+    std::unique_ptr<CollectionStore<NotificationEntry>> notification_store,
+    std::unique_ptr<IconStore> icon_store,
     const std::vector<SchedulerClientType>& clients,
     const SchedulerConfig& config) {
-  return std::make_unique<ScheduledNotificationManagerImpl>(std::move(store),
-                                                            clients, config);
+  return std::make_unique<ScheduledNotificationManagerImpl>(
+      std::move(notification_store), std::move(icon_store), clients, config);
 }
 
 ScheduledNotificationManager::ScheduledNotificationManager() = default;
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
index 1d100882f..f228dad 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
@@ -19,6 +19,7 @@
 struct NotificationEntry;
 struct NotificationParams;
 struct SchedulerConfig;
+class IconStore;
 
 // Class to manage in-memory scheduled notifications loaded from the storage.
 class ScheduledNotificationManager {
@@ -43,7 +44,8 @@
 
   // Creates the instance.
   static std::unique_ptr<ScheduledNotificationManager> Create(
-      std::unique_ptr<CollectionStore<NotificationEntry>> store,
+      std::unique_ptr<CollectionStore<NotificationEntry>> notification_store,
+      std::unique_ptr<IconStore> icon_store,
       const std::vector<SchedulerClientType>& clients,
       const SchedulerConfig& config);
 
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
index c4d91e2..21d492c 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_task_environment.h"
 #include "chrome/browser/notifications/scheduler/internal/collection_store.h"
+#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
 #include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
 #include "chrome/browser/notifications/scheduler/public/notification_params.h"
@@ -60,18 +61,37 @@
   DISALLOW_COPY_AND_ASSIGN(MockNotificationStore);
 };
 
+class MockIconStore : public IconStore {
+ public:
+  MockIconStore() {}
+
+  MOCK_METHOD1(Init, void(IconStore::InitCallback));
+  MOCK_METHOD2(Load, void(const std::string&, IconStore::LoadCallback));
+  MOCK_METHOD3(Add,
+               void(const std::string&,
+                    std::unique_ptr<IconEntry>,
+                    IconStore::UpdateCallback));
+  MOCK_METHOD2(Delete, void(const std::string&, IconStore::UpdateCallback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockIconStore);
+};
+
 class ScheduledNotificationManagerTest : public testing::Test {
  public:
-  ScheduledNotificationManagerTest() : store_(nullptr) {}
+  ScheduledNotificationManagerTest()
+      : notification_store_(nullptr), icon_store_(nullptr) {}
   ~ScheduledNotificationManagerTest() override = default;
 
   void SetUp() override {
     delegate_ = std::make_unique<MockDelegate>();
-    auto store = std::make_unique<MockNotificationStore>();
-    store_ = store.get();
+    auto notification_store = std::make_unique<MockNotificationStore>();
+    auto icon_store = std::make_unique<MockIconStore>();
+    notification_store_ = notification_store.get();
+    icon_store_ = icon_store.get();
     config_.notification_expiration = base::TimeDelta::FromDays(1);
     manager_ = ScheduledNotificationManager::Create(
-        std::move(store),
+        std::move(notification_store), std::move(icon_store),
         {SchedulerClientType::kTest1, SchedulerClientType::kTest2,
          SchedulerClientType::kTest3},
         config_);
@@ -79,7 +99,8 @@
 
  protected:
   ScheduledNotificationManager* manager() { return manager_.get(); }
-  MockNotificationStore* store() { return store_; }
+  MockNotificationStore* notification_store() { return notification_store_; }
+  MockIconStore* icon_store() { return icon_store_; }
   MockDelegate* delegate() { return delegate_.get(); }
   const SchedulerConfig& config() const { return config_; }
   // Initializes the manager with predefined data in the store.
@@ -92,11 +113,16 @@
     }
 
     // Initialize the store and call the callback.
-    EXPECT_CALL(*store(), InitAndLoad(_))
+    EXPECT_CALL(*notification_store(), InitAndLoad(_))
         .WillOnce(
             Invoke([&entries](base::OnceCallback<void(bool, Entries)> cb) {
               std::move(cb).Run(true, std::move(entries));
             }));
+    EXPECT_CALL(*icon_store(), Init(_))
+        .WillOnce(Invoke([](base::OnceCallback<void(bool)> cb) {
+          std::move(cb).Run(true);
+        }));
+
     base::RunLoop loop;
     manager()->Init(delegate(),
                     base::BindOnce(
@@ -111,7 +137,8 @@
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<MockDelegate> delegate_;
-  MockNotificationStore* store_;
+  MockNotificationStore* notification_store_;
+  MockIconStore* icon_store_;
   std::vector<SchedulerClientType> clients_;
   std::unique_ptr<ScheduledNotificationManager> manager_;
   SchedulerConfig config_;
@@ -121,7 +148,7 @@
 
 // Verify that error is received when initialization failed.
 TEST_F(ScheduledNotificationManagerTest, InitFailed) {
-  EXPECT_CALL(*store(), InitAndLoad(_))
+  EXPECT_CALL(*notification_store(), InitAndLoad(_))
       .WillOnce(Invoke([](base::OnceCallback<void(bool, Entries)> cb) {
         std::move(cb).Run(false, Entries());
       }));
@@ -151,7 +178,7 @@
   EXPECT_FALSE(guid.empty());
 
   // Verify call contract.
-  EXPECT_CALL(*store(), Add(guid, _, _));
+  EXPECT_CALL(*notification_store(), Add(guid, _, _));
   manager()->ScheduleNotification(std::move(params));
 
   // Verify in-memory data.
@@ -174,7 +201,7 @@
       SchedulerClientType::kTest1, NotificationData(), ScheduleParams());
 
   // Verify call contract.
-  EXPECT_CALL(*store(), Add(_, _, _));
+  EXPECT_CALL(*notification_store(), Add(_, _, _));
   manager()->ScheduleNotification(std::move(params));
 
   // Verify in-memory data.
@@ -198,7 +225,7 @@
   InitWithData(std::vector<NotificationEntry>({entry}));
 
   // Verify delegate and dependency call contract.
-  EXPECT_CALL(*store(), Delete(kGuid, _));
+  EXPECT_CALL(*notification_store(), Delete(kGuid, _));
   EXPECT_CALL(*delegate(), DisplayNotification(NotificationEntryIs(entry)));
   manager()->DisplayNotification(kGuid);
 
@@ -266,24 +293,28 @@
   manager()->GetAllNotifications(&notifications);
   EXPECT_EQ(notifications.size(), 3u);
 
-  EXPECT_CALL(*store(), Delete(_, _)).Times(2).RetiresOnSaturation();
+  EXPECT_CALL(*notification_store(), Delete(_, _))
+      .Times(2)
+      .RetiresOnSaturation();
   manager()->DeleteNotifications(SchedulerClientType::kTest2);
   manager()->GetAllNotifications(&notifications);
   EXPECT_EQ(notifications.size(), 2u);
 
   // Ensure deleting non-existing key will not crash, and store will not call
   // Delete.
-  EXPECT_CALL(*store(), Delete(_, _)).Times(0).RetiresOnSaturation();
+  EXPECT_CALL(*notification_store(), Delete(_, _))
+      .Times(0)
+      .RetiresOnSaturation();
   manager()->DeleteNotifications(SchedulerClientType::kTest2);
   manager()->GetAllNotifications(&notifications);
   EXPECT_EQ(notifications.size(), 2u);
 
-  EXPECT_CALL(*store(), Delete(_, _)).RetiresOnSaturation();
+  EXPECT_CALL(*notification_store(), Delete(_, _)).RetiresOnSaturation();
   manager()->DeleteNotifications(SchedulerClientType::kTest1);
   manager()->GetAllNotifications(&notifications);
   EXPECT_EQ(notifications.size(), 1u);
 
-  EXPECT_CALL(*store(), Delete(_, _)).RetiresOnSaturation();
+  EXPECT_CALL(*notification_store(), Delete(_, _)).RetiresOnSaturation();
   manager()->DeleteNotifications(SchedulerClientType::kTest3);
   manager()->GetAllNotifications(&notifications);
   EXPECT_EQ(notifications.size(), 0u);
@@ -308,7 +339,9 @@
   entry5.create_time =
       now - base::TimeDelta::FromDays(1) - base::TimeDelta::FromMicroseconds(1);
 
-  EXPECT_CALL(*store(), Delete(_, _)).Times(3).RetiresOnSaturation();
+  EXPECT_CALL(*notification_store(), Delete(_, _))
+      .Times(3)
+      .RetiresOnSaturation();
   InitWithData(std::vector<NotificationEntry>(
       {entry0, entry1, entry2, entry3, entry4, entry5}));
   ScheduledNotificationManager::Notifications notifications;
diff --git a/chrome/browser/notifications/scheduler/public/schedule_params.cc b/chrome/browser/notifications/scheduler/public/schedule_params.cc
index b926dd1..c01fda3 100644
--- a/chrome/browser/notifications/scheduler/public/schedule_params.cc
+++ b/chrome/browser/notifications/scheduler/public/schedule_params.cc
@@ -8,8 +8,11 @@
 
 ScheduleParams::ScheduleParams() : priority(Priority::kLow) {}
 
+ScheduleParams::ScheduleParams(const ScheduleParams& other) = default;
+
 bool ScheduleParams::operator==(const ScheduleParams& other) const {
-  return priority == other.priority;
+  return priority == other.priority &&
+         impression_mapping == other.impression_mapping;
 }
 
 ScheduleParams::~ScheduleParams() = default;
diff --git a/chrome/browser/notifications/scheduler/public/schedule_params.h b/chrome/browser/notifications/scheduler/public/schedule_params.h
index f4ce176..31b9655 100644
--- a/chrome/browser/notifications/scheduler/public/schedule_params.h
+++ b/chrome/browser/notifications/scheduler/public/schedule_params.h
@@ -5,6 +5,10 @@
 #ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_SCHEDULE_PARAMS_H_
 #define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_SCHEDULE_PARAMS_H_
 
+#include <map>
+
+#include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h"
+
 namespace notifications {
 
 // Specifies when to show the scheduled notification, and throttling details.
@@ -23,10 +27,19 @@
   };
 
   ScheduleParams();
+  ScheduleParams(const ScheduleParams& other);
   bool operator==(const ScheduleParams& other) const;
   ~ScheduleParams();
 
   Priority priority;
+
+  // Override the default mapping from an user action to impression result. By
+  // default, click on the notification and helpful button click are positive
+  // impression and may increase feature exposure. Unhelp button click is
+  // negative impression and may reduce feature exposure. Dimiss/close
+  // notification is neutural. Only put value when need to change the default
+  // mapping.
+  std::map<UserFeedback, ImpressionResult> impression_mapping;
 };
 
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc
index d9b9b1fd..c46c331 100644
--- a/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc
+++ b/chrome/browser/notifications/scheduler/schedule_service_factory_helper.cc
@@ -79,13 +79,13 @@
   auto notification_store =
       std::make_unique<NotificationStore>(std::move(notification_db));
   auto notification_manager = ScheduledNotificationManager::Create(
-      std::move(notification_store), registered_clients, *config.get());
+      std::move(notification_store), std::move(icon_store), registered_clients,
+      *config.get());
 
   auto context = std::make_unique<NotificationSchedulerContext>(
       std::move(client_registrar), std::move(background_task_scheduler),
-      std::move(icon_store), std::move(impression_tracker),
-      std::move(notification_manager), std::move(display_agent),
-      DisplayDecider::Create(), std::move(config));
+      std::move(impression_tracker), std::move(notification_manager),
+      std::move(display_agent), DisplayDecider::Create(), std::move(config));
 
   auto scheduler = NotificationScheduler::Create(std::move(context));
   auto init_aware_scheduler =
diff --git a/chrome/browser/notifications/scheduler/test/test_utils.cc b/chrome/browser/notifications/scheduler/test/test_utils.cc
index c64a3b4..721dd1f 100644
--- a/chrome/browser/notifications/scheduler/test/test_utils.cc
+++ b/chrome/browser/notifications/scheduler/test/test_utils.cc
@@ -100,6 +100,11 @@
          << " \n schedule params: priority:"
          << static_cast<int>(entry->schedule_params.priority);
 
+  for (const auto& mapping : entry->schedule_params.impression_mapping) {
+    stream << " \n impression mapping: " << static_cast<int>(mapping.first)
+           << " : " << static_cast<int>(mapping.second);
+  }
+
   stream << " \n icons_id:";
   for (const auto& icon_id : entry->icons_uuid)
     stream << icon_id << "  ";
@@ -130,6 +135,12 @@
            << static_cast<int>(impression.task_start_time) << "\n"
            << "guid: " << impression.guid << "\n"
            << "type: " << static_cast<int>(impression.type);
+
+    for (const auto& mapping : impression.impression_mapping) {
+      stream << " \n impression mapping: " << static_cast<int>(mapping.first)
+             << " : " << static_cast<int>(mapping.second);
+    }
+
     log += stream.str();
   }
 
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
index aa3f58ab..793ea4bc 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
@@ -39,9 +39,9 @@
 const char kHistogramAMPSubframeLargestContentPaintFullNavigation[] =
     "PaintTiming.InputToLargestContentPaint.Subframe.FullNavigation";
 const char kHistogramAMPSubframeFirstInputDelay[] =
-    "InteractiveTiming.FirstInputDelay3.Subframe";
+    "InteractiveTiming.FirstInputDelay4.Subframe";
 const char kHistogramAMPSubframeFirstInputDelayFullNavigation[] =
-    "InteractiveTiming.FirstInputDelay3.Subframe.FullNavigation";
+    "InteractiveTiming.FirstInputDelay4.Subframe.FullNavigation";
 const char kHistogramAMPSubframeLayoutStabilityJankScore[] =
     "Experimental.LayoutStability.JankScore.Subframe";
 const char kHistogramAMPSubframeLayoutStabilityJankScoreFullNavigation[] =
@@ -393,7 +393,7 @@
 
     if (subframe_info.timing->interactive_timing->first_input_delay
             .has_value()) {
-      builder.SetSubFrame_InteractiveTiming_FirstInputDelay3(
+      builder.SetSubFrame_InteractiveTiming_FirstInputDelay4(
           subframe_info.timing->interactive_timing->first_input_delay.value()
               .InMilliseconds());
 
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc
index 2826809..9d01f65 100644
--- a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc
@@ -337,13 +337,13 @@
       "PageLoad.Clients.AMP.PaintTiming.InputToLargestContentPaint.Subframe",
       1);
   histogram_tester().ExpectTotalCount(
-      "PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay3.Subframe", 1);
+      "PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay4.Subframe", 1);
 
   ukm::mojom::UkmEntryPtr entry = GetAmpPageLoadUkmEntry(amp_url);
   ASSERT_NE(nullptr, entry.get());
   test_ukm_recorder().ExpectEntrySourceHasUrl(entry.get(), amp_url);
   test_ukm_recorder().ExpectEntryMetric(
-      entry.get(), "SubFrame.InteractiveTiming.FirstInputDelay3", 3);
+      entry.get(), "SubFrame.InteractiveTiming.FirstInputDelay4", 3);
   test_ukm_recorder().ExpectEntryMetric(
       entry.get(), "SubFrame.PaintTiming.NavigationToFirstContentfulPaint", 5);
   test_ukm_recorder().ExpectEntryMetric(
@@ -438,14 +438,14 @@
       "FullNavigation",
       1);
   histogram_tester().ExpectTotalCount(
-      "PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay3.Subframe."
+      "PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay4.Subframe."
       "FullNavigation",
       1);
 
   ukm::mojom::UkmEntryPtr entry = GetAmpPageLoadUkmEntry(amp_url);
   test_ukm_recorder().ExpectEntrySourceHasUrl(entry.get(), amp_url);
   test_ukm_recorder().ExpectEntryMetric(
-      entry.get(), "SubFrame.InteractiveTiming.FirstInputDelay3", 3);
+      entry.get(), "SubFrame.InteractiveTiming.FirstInputDelay4", 3);
   test_ukm_recorder().ExpectEntryMetric(
       entry.get(), "SubFrame.PaintTiming.NavigationToFirstContentfulPaint", 5);
   test_ukm_recorder().ExpectEntryMetric(
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
index c55d71a..7edb0ea 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc
@@ -1316,11 +1316,11 @@
   NavigateAndCommit(GURL(kDefaultTestUrl2));
 
   EXPECT_THAT(
-      histogram_tester().GetAllSamples(internal::kHistogramFirstInputDelay),
+      histogram_tester().GetAllSamples(internal::kHistogramFirstInputDelay4),
       testing::ElementsAre(base::Bucket(5, 1)));
-  EXPECT_THAT(
-      histogram_tester().GetAllSamples(internal::kHistogramFirstInputTimestamp),
-      testing::ElementsAre(base::Bucket(4780, 1)));
+  EXPECT_THAT(histogram_tester().GetAllSamples(
+                  internal::kHistogramFirstInputTimestamp4),
+              testing::ElementsAre(base::Bucket(4780, 1)));
 }
 
 TEST_F(CorePageLoadMetricsObserverTest, LongestInputDelayAndTimestamp) {
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index 01fccd6..f6b5f8b 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -833,9 +833,9 @@
     test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
                                                 GURL(kTestUrl1));
     test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(), PageLoad::kInteractiveTiming_FirstInputDelay3Name, 50);
+        kv.second.get(), PageLoad::kInteractiveTiming_FirstInputDelay4Name, 50);
     test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(), PageLoad::kInteractiveTiming_FirstInputTimestamp3Name,
+        kv.second.get(), PageLoad::kInteractiveTiming_FirstInputTimestamp4Name,
         712);
   }
 }
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 970f8ea..80185b6 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -489,8 +489,8 @@
       GetPasswordProtectionService();
   if (pps) {
     pps->MaybeStartPasswordFieldOnFocusRequest(
-        web_contents(), GetMainFrameURL(), form_action, frame_url,
-        pps->GetAccountInfo().hosted_domain);
+        web_contents(), web_contents()->GetLastCommittedURL(), form_action,
+        frame_url, pps->GetAccountInfo().hosted_domain);
   }
 }
 
@@ -516,9 +516,9 @@
       (password_type == PasswordType::PRIMARY_ACCOUNT_PASSWORD) &&
       (sync && sync->IsSyncFeatureActive() && !sync->IsLocalSyncEnabled());
   pps->MaybeStartProtectedPasswordEntryRequest(
-      web_contents(), GetMainFrameURL(), username, password_type,
-      pps->GetAccountInfo().hosted_domain, is_account_syncing, matching_domains,
-      password_field_exists);
+      web_contents(), web_contents()->GetLastCommittedURL(), username,
+      password_type, pps->GetAccountInfo().hosted_domain, is_account_syncing,
+      matching_domains, password_field_exists);
 }
 
 void ChromePasswordManagerClient::LogPasswordReuseDetectedEvent() {
diff --git a/chrome/browser/payments/manifest_verifier_browsertest.cc b/chrome/browser/payments/manifest_verifier_browsertest.cc
index 4f23aa1..c0a89c69 100644
--- a/chrome/browser/payments/manifest_verifier_browsertest.cc
+++ b/chrome/browser/payments/manifest_verifier_browsertest.cc
@@ -22,6 +22,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
 
 namespace payments {
 namespace {
@@ -75,6 +76,8 @@
     return verified_apps_;
   }
 
+  const std::string& error_message() const { return error_message_; }
+
   // Expects that the verified payment app with |id| has the |expected_scope|
   // and the |expected_methods| and the
   // |expect_has_explicitly_verified_methods|.
@@ -90,13 +93,16 @@
     EXPECT_EQ(expected_methods, actual_methods);
     EXPECT_EQ(expect_has_explicitly_verified_methods,
               it->second->has_explicitly_verified_methods);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
  private:
   // Called by the verifier upon completed verification. These |apps| have only
   // valid payment methods.
-  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps) {
+  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
+                             const std::string& error_message) {
     verified_apps_ = std::move(apps);
+    error_message_ = error_message;
   }
 
   // Serves the payment method manifest files.
@@ -105,6 +111,8 @@
   // The apps that have been verified by the Verify() method.
   content::PaymentAppProvider::PaymentApps verified_apps_;
 
+  std::string error_message_;
+
   DISALLOW_COPY_AND_ASSIGN(ManifestVerifierBrowserTest);
 };
 
@@ -115,6 +123,7 @@
     Verify(content::PaymentAppProvider::PaymentApps());
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -122,6 +131,7 @@
     Verify(content::PaymentAppProvider::PaymentApps());
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -135,6 +145,7 @@
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -146,6 +157,7 @@
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -161,6 +173,7 @@
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -173,6 +186,7 @@
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -188,6 +202,7 @@
 
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card"}, false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -201,6 +216,7 @@
 
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card"}, false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -220,6 +236,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card", "interledger"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -235,6 +252,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card", "interledger"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -255,6 +273,7 @@
     EXPECT_EQ(2U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card"}, false);
     ExpectApp(1, "https://alicepay.com/webpay", {"basic-card"}, false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -271,6 +290,7 @@
     EXPECT_EQ(2U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"basic-card"}, false);
     ExpectApp(1, "https://alicepay.com/webpay", {"basic-card"}, false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -290,6 +310,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"https://frankpay.com/webpay"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -303,6 +324,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/webpay", {"https://frankpay.com/webpay"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -322,6 +344,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://404.com/webpay", {"https://frankpay.com/webpay"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -335,6 +358,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://404.com/webpay", {"https://frankpay.com/webpay"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -355,6 +379,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/anything/here",
               {"https://bobpay.com/does/not/matter/whats/here"}, true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -369,6 +394,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://bobpay.com/anything/here",
               {"https://bobpay.com/does/not/matter/whats/here"}, true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -389,6 +415,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://404.com/anything/here",
               {"https://404.com/does/not/matter/whats/here"}, true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -403,6 +430,7 @@
     EXPECT_EQ(1U, verified_apps().size());
     ExpectApp(0, "https://404.com/anything/here",
               {"https://404.com/does/not/matter/whats/here"}, true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -430,6 +458,7 @@
     ExpectApp(0, "https://alicepay.com/webpay",
               {"https://georgepay.com/webpay", "https://ikepay.com/webpay"},
               true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -450,6 +479,7 @@
     ExpectApp(0, "https://alicepay.com/webpay",
               {"https://georgepay.com/webpay", "https://ikepay.com/webpay"},
               true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -472,6 +502,7 @@
               {"basic-card", "https://alicepay.com/webpay2",
                "https://ikepay.com/webpay"},
               true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -490,36 +521,87 @@
               {"basic-card", "https://alicepay.com/webpay2",
                "https://ikepay.com/webpay"},
               true);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
 // Verify that a payment handler from https://bobpay.com/webpay cannot use
 // payment method names that are unreachable websites, the origin of which does
 // not match that of the payment handler.
-IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, PaymentMethodName404) {
+IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
+                       SinglePaymentMethodName404) {
+  std::string expected_pattern =
+      "Unable to make a HEAD request to "
+      "\"https://127.0.0.1:\\d+/404.test/webpay\" for payment method manifest.";
   {
     content::PaymentAppProvider::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
-    apps[0]->scope = GURL("https://bobpay.com/webpay");
-    apps[0]->enabled_methods.push_back("https://404.com/webpay");
-    apps[0]->enabled_methods.push_back("https://404aswell.com/webpay");
+    apps[0]->scope = GURL("https://bobpay.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404.test/webpay");
 
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 
   // Repeat verifications should have identical results.
   {
     content::PaymentAppProvider::PaymentApps apps;
     apps[0] = std::make_unique<content::StoredPaymentApp>();
-    apps[0]->scope = GURL("https://bobpay.com/webpay");
-    apps[0]->enabled_methods.push_back("https://404.com/webpay");
-    apps[0]->enabled_methods.push_back("https://404aswell.com/webpay");
+    apps[0]->scope = GURL("https://bobpay.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404.test/webpay");
 
     Verify(std::move(apps));
 
     EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
+  }
+}
+
+// Verify that a payment handler from https://bobpay.com/webpay cannot use
+// payment method names that are unreachable websites, the origin of which does
+// not match that of the payment handler. Since multiple downloads fail, the
+// error message will describe the first failure.
+IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
+                       MultiplePaymentMethodName404) {
+  std::string expected_pattern =
+      "Unable to make a HEAD request to "
+      "\"https://127.0.0.1:\\d+/404(aswell)?.test/webpay\" for payment method "
+      "manifest.";
+  {
+    content::PaymentAppProvider::PaymentApps apps;
+    apps[0] = std::make_unique<content::StoredPaymentApp>();
+    apps[0]->scope = GURL("https://bobpay.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404aswell.test/webpay");
+
+    Verify(std::move(apps));
+
+    EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
+  }
+
+  // Repeat verifications should have identical results.
+  {
+    content::PaymentAppProvider::PaymentApps apps;
+    apps[0] = std::make_unique<content::StoredPaymentApp>();
+    apps[0]->scope = GURL("https://bobpay.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404.test/webpay");
+    apps[0]->enabled_methods.push_back("https://404aswell.test/webpay");
+
+    Verify(std::move(apps));
+
+    EXPECT_TRUE(verified_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 }
 
@@ -544,6 +626,7 @@
               {"basic-card", "interledger", "payee-credit-transfer",
                "payer-credit-transfer", "tokenized-card"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat verifications should have identical results.
@@ -565,6 +648,7 @@
               {"basic-card", "interledger", "payee-credit-transfer",
                "payer-credit-transfer", "tokenized-card"},
               false);
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
diff --git a/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc b/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
index cd6be44..8cf2cc7d 100644
--- a/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
+++ b/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
@@ -30,6 +30,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
 
 namespace payments {
 namespace {
@@ -209,6 +210,9 @@
     return installable_apps_;
   }
 
+  // Returns the error message from the service worker payment app factory.
+  const std::string& error_message() const { return error_message_; }
+
   // Expects that the first app has the |expected_method|.
   void ExpectPaymentAppWithMethod(const std::string& expected_method) {
     ExpectPaymentAppFromScopeWithMethod(kDefaultScope, expected_method);
@@ -258,9 +262,11 @@
   // valid payment methods.
   void OnGotAllPaymentApps(
       content::PaymentAppProvider::PaymentApps apps,
-      ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps) {
+      ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps,
+      const std::string& error_message) {
     apps_ = std::move(apps);
     installable_apps_ = std::move(installable_apps);
+    error_message_ = error_message;
   }
 
   // Starts the |test_server| for |hostname|. Returns true on success.
@@ -341,6 +347,9 @@
   // GetAllPaymentAppsForMethods() method.
   ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps_;
 
+  // The error message returned by the service worker factory.
+  std::string error_message_;
+
   base::test::ScopedFeatureList scoped_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerPaymentAppFactoryBrowserTest);
@@ -354,6 +363,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -363,6 +373,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -378,6 +389,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -388,6 +400,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -402,6 +415,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("basic-card");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -412,6 +426,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("basic-card");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -426,6 +441,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://alicepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -436,6 +452,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://alicepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -452,6 +469,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -461,6 +479,7 @@
 
     EXPECT_TRUE(installable_apps().empty());
     EXPECT_TRUE(apps().empty());
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -477,6 +496,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://frankpay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -486,6 +506,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://frankpay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -503,6 +524,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://georgepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -512,6 +534,7 @@
     EXPECT_TRUE(installable_apps().empty());
     ASSERT_EQ(1U, apps().size());
     ExpectPaymentAppWithMethod("https://georgepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -533,6 +556,7 @@
                                         "https://georgepay.com/webpay");
     ExpectPaymentAppFromScopeWithMethod("/app2/",
                                         "https://georgepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -545,6 +569,7 @@
                                         "https://georgepay.com/webpay");
     ExpectPaymentAppFromScopeWithMethod("/app2/",
                                         "https://georgepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -569,6 +594,7 @@
                                         "https://georgepay.com/webpay");
     ExpectPaymentAppFromScopeWithMethod("/app2/",
                                         "https://frankpay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -582,6 +608,7 @@
                                         "https://georgepay.com/webpay");
     ExpectPaymentAppFromScopeWithMethod("/app2/",
                                         "https://frankpay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -597,6 +624,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://kylepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -606,6 +634,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://kylepay.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -613,11 +642,18 @@
 // redirects to a different site (https://kylepay.com/webpay).
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        InvalidDifferentSiteRedirect) {
+  std::string expected_pattern =
+      "Cross-site redirect from \"https://larrypay.com:\\d+/webpay\" to "
+      "\"https://kylepay.com/webpay\" not allowed for payment manifests.";
+
   {
     GetAllPaymentAppsForMethods({"https://larrypay.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 
   // Repeat lookups should have identical results.
@@ -626,6 +662,9 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 }
 
@@ -633,11 +672,15 @@
 // it redirects 4 times (charlie -> david -> frank -> george -> harry).
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        FourRedirectsIsNotValid) {
+  std::string expected_error_message =
+      "Unable to download the payment manifest because reached the maximum "
+      "number of redirects.";
   {
     GetAllPaymentAppsForMethods({"https://charlie.example.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 
   // Repeat lookups should have identical results.
@@ -646,6 +689,7 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 }
 
@@ -659,6 +703,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://harry.example.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -668,6 +713,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://harry.example.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -681,6 +727,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://harry.example.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 
   // Repeat lookups should have identical results.
@@ -690,6 +737,7 @@
     EXPECT_TRUE(apps().empty());
     ASSERT_EQ(1U, installable_apps().size());
     ExpectInstallablePaymentAppInScope("https://harry.example.com/webpay");
+    EXPECT_TRUE(error_message().empty()) << error_message();
   }
 }
 
@@ -698,11 +746,18 @@
 // https://harry.example.com/payment-manifest.json.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        CrossOriginHttpLinkHeaderIsInvalid) {
+  std::string expected_pattern =
+      "Cross-origin payment method manifest "
+      "\"https://harry.example.com/payment-manifest.json\" not allowed for the "
+      "payment method \"https://ike.example.com:\\d+/webpay\".";
   {
     GetAllPaymentAppsForMethods({"https://ike.example.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 
   // Repeat lookups should have identical results.
@@ -711,6 +766,9 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 }
 
@@ -718,11 +776,18 @@
 // its cross-origin default application https://harry.example.com/app.json.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        CrossOriginDefaultApplicationIsInvalid) {
+  std::string expected_pattern =
+      "Cross-origin default application https://harry.example.com/app.json not "
+      "allowed in payment method manifest "
+      "https://john.example.com:\\d+/payment-manifest.json.";
   {
     GetAllPaymentAppsForMethods({"https://john.example.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 
   // Repeat lookups should have identical results.
@@ -731,6 +796,9 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_TRUE(RE2::FullMatch(error_message(), expected_pattern))
+        << "Actual error message \"" << error_message()
+        << "\" did not match expected pattern \"" << expected_pattern << "\".";
   }
 }
 
@@ -738,11 +806,15 @@
 // its cross-origin service worker location https://harry.example.com/app.js.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        CrossOriginServiceWorkerIsInvalid) {
+  std::string expected_error_message =
+      "Cross-origin \"serviceworker\".\"src\" https://harry.example.com/app.js "
+      "not allowed in web app manifest https://kyle.example.com/app.json.";
   {
     GetAllPaymentAppsForMethods({"https://kyle.example.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 
   // Repeat lookups should have identical results.
@@ -751,6 +823,7 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 }
 
@@ -758,11 +831,16 @@
 // its cross-origin service worker scope https://harry.example.com/webpay/".
 IN_PROC_BROWSER_TEST_F(ServiceWorkerPaymentAppFactoryBrowserTest,
                        CrossOriginServiceWorkerScopeIsInvalid) {
+  std::string expected_error_message =
+      "Cross-origin \"serviceworker\".\"scope\" "
+      "https://harry.example.com/webpay not allowed in web app manifest "
+      "https://larry.example.com/app.json.";
   {
     GetAllPaymentAppsForMethods({"https://larry.example.com/webpay"});
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 
   // Repeat lookups should have identical results.
@@ -771,6 +849,7 @@
 
     EXPECT_TRUE(apps().empty());
     EXPECT_TRUE(installable_apps().empty());
+    EXPECT_EQ(expected_error_message, error_message());
   }
 }
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc b/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc
index 34f76a5d..5132720 100644
--- a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.cc
@@ -7,7 +7,9 @@
 #include <limits>
 #include <string>
 
+#include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/callback.h"
 #include "base/files/file_util.h"
 #include "base/hash/md5.h"
 #include "base/logging.h"
@@ -20,12 +22,15 @@
 #include "build/build_config.h"
 #include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
+#include "third_party/leveldatabase/src/include/leveldb/env.h"
 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
 
 namespace performance_manager {
 
 namespace {
 
+bool g_use_in_memory_db_for_testing = false;
+
 // The name of the following histograms is the same as the one used in the
 // //c/b/resource_coordinator version of this file. It's fine to keep the same
 // name as these 2 codepath will never be enabled at the same time. These
@@ -159,6 +164,12 @@
     return db_.get();
   }
 
+  void SetInitializationCallbackForTesting(base::OnceClosure callback) {
+    init_callback_for_testing_ = std::move(callback);
+    if (DBIsInitialized())
+      std::move(init_callback_for_testing_).Run();
+  }
+
  private:
   enum class OpeningType {
     // A new database has been created.
@@ -170,6 +181,10 @@
   // Implementation for the OpenOrCreateDatabase function.
   OpeningType OpenOrCreateDatabaseImpl();
 
+  // A levelDB environment that gets used for testing. This allows using an
+  // in-memory database when needed.
+  std::unique_ptr<leveldb::Env> env_for_testing_;
+
   // The on disk location of the database.
   const base::FilePath db_path_;
   // The connection to the LevelDB database.
@@ -179,12 +194,18 @@
   // The options to be used for all database write operations.
   leveldb::WriteOptions write_options_;
 
+  base::OnceClosure init_callback_for_testing_;
+
   SEQUENCE_CHECKER(sequence_checker_);
   DISALLOW_COPY_AND_ASSIGN(AsyncHelper);
 };
 
 void LevelDBSiteDataStore::AsyncHelper::OpenOrCreateDatabase() {
   OpeningType opening_type = OpenOrCreateDatabaseImpl();
+
+  if (init_callback_for_testing_)
+    std::move(init_callback_for_testing_).Run();
+
   if (!db_)
     return;
   std::string db_metadata;
@@ -362,6 +383,12 @@
 
   leveldb_env::Options options;
   options.create_if_missing = true;
+
+  if (g_use_in_memory_db_for_testing) {
+    env_for_testing_ = leveldb_chrome::NewMemEnv("LevelDBSiteDataStore");
+    options.env = env_for_testing_.get();
+  }
+
   leveldb::Status status =
       leveldb_env::OpenDB(options, db_path_.AsUTF8Unsafe(), &db_);
 
@@ -468,6 +495,16 @@
       std::move(reply_callback));
 }
 
+void LevelDBSiteDataStore::SetInitializationCallbackForTesting(
+    base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  blocking_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&LevelDBSiteDataStore::AsyncHelper::
+                                    SetInitializationCallbackForTesting,
+                                base::Unretained(async_helper_.get()),
+                                std::move(callback)));
+}
+
 bool LevelDBSiteDataStore::DatabaseIsInitializedForTesting() {
   return async_helper_->DBIsInitialized();
 }
@@ -476,4 +513,11 @@
   return async_helper_->GetDBForTesting();
 }
 
+// static
+std::unique_ptr<base::AutoReset<bool>>
+LevelDBSiteDataStore::UseInMemoryDBForTesting() {
+  return std::make_unique<base::AutoReset<bool>>(
+      &g_use_in_memory_db_for_testing, true);
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h b/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h
index f3c54c0da..0e00bfa 100644
--- a/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h
+++ b/chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_LEVELDB_SITE_DATA_STORE_H_
 
+#include "base/auto_reset.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/sequence_checker.h"
@@ -41,6 +42,7 @@
       const std::vector<url::Origin>& site_origins) override;
   void ClearStore() override;
   void GetStoreSize(GetStoreSizeCallback callback) override;
+  void SetInitializationCallbackForTesting(base::OnceClosure callback) override;
 
   bool DatabaseIsInitializedForTesting();
 
@@ -51,6 +53,10 @@
   // thread safe.
   leveldb::DB* GetDBForTesting();
 
+  // Make the new instances of this class use an in memory database rather than
+  // creating it on disk.
+  static std::unique_ptr<base::AutoReset<bool>> UseInMemoryDBForTesting();
+
   static const size_t kDbVersion;
   static const char kDbMetadataKey[];
 
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
new file mode 100644
index 0000000..e9e0ec5
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
@@ -0,0 +1,85 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_reader.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_writer.h"
+
+namespace performance_manager {
+
+NonRecordingSiteDataCache::NonRecordingSiteDataCache(
+    const std::string& browser_context_id,
+    SiteDataCacheInspector* data_cache_inspector,
+    SiteDataCache* data_cache_for_readers)
+    : data_cache_for_readers_(data_cache_for_readers),
+      data_cache_inspector_(data_cache_inspector),
+      browser_context_id_(browser_context_id) {
+  DCHECK(data_cache_for_readers_);
+  // Register the debug interface against the browser context.
+  SiteDataCacheFactory::GetInstance()->SetDataCacheInspectorForBrowserContext(
+      this, browser_context_id_);
+}
+
+NonRecordingSiteDataCache::~NonRecordingSiteDataCache() {
+  SiteDataCacheFactory::GetInstance()->SetDataCacheInspectorForBrowserContext(
+      nullptr, browser_context_id_);
+}
+
+std::unique_ptr<SiteDataReader> NonRecordingSiteDataCache::GetReaderForOrigin(
+    const url::Origin& origin) {
+  return data_cache_for_readers_->GetReaderForOrigin(origin);
+}
+
+std::unique_ptr<SiteDataWriter> NonRecordingSiteDataCache::GetWriterForOrigin(
+    const url::Origin& origin,
+    performance_manager::TabVisibility tab_visibility) {
+  // Return a fake data writer.
+  SiteDataWriter* writer = new NoopSiteDataWriter();
+  return base::WrapUnique(writer);
+}
+
+bool NonRecordingSiteDataCache::IsRecordingForTesting() {
+  return false;
+}
+
+const char* NonRecordingSiteDataCache::GetDataCacheName() {
+  return "NonRecordingSiteDataCache";
+}
+
+std::vector<url::Origin> NonRecordingSiteDataCache::GetAllInMemoryOrigins() {
+  if (!data_cache_inspector_)
+    return std::vector<url::Origin>();
+
+  return data_cache_inspector_->GetAllInMemoryOrigins();
+}
+
+void NonRecordingSiteDataCache::GetDataStoreSize(
+    DataStoreSizeCallback on_have_data) {
+  if (!data_cache_inspector_) {
+    std::move(on_have_data).Run(base::nullopt, base::nullopt);
+    return;
+  }
+
+  data_cache_inspector_->GetDataStoreSize(std::move(on_have_data));
+}
+
+bool NonRecordingSiteDataCache::GetDataForOrigin(
+    const url::Origin& origin,
+    bool* is_dirty,
+    std::unique_ptr<SiteDataProto>* data) {
+  if (!data_cache_inspector_)
+    return false;
+
+  return data_cache_inspector_->GetDataForOrigin(origin, is_dirty, data);
+}
+
+NonRecordingSiteDataCache* NonRecordingSiteDataCache::GetDataCache() {
+  return this;
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h
new file mode 100644
index 0000000..b7d7ddd
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+
+namespace performance_manager {
+
+// Implementation of a SiteDataCache that ensures that no data gets persisted.
+//
+// This class should be used for off the record profiles.
+class NonRecordingSiteDataCache : public SiteDataCache,
+                                  public SiteDataCacheInspector {
+ public:
+  NonRecordingSiteDataCache(const std::string& browser_context_id,
+                            SiteDataCacheInspector* data_cache_inspector,
+                            SiteDataCache* data_cache_for_readers);
+  ~NonRecordingSiteDataCache() override;
+
+  // SiteDataCache:
+  std::unique_ptr<SiteDataReader> GetReaderForOrigin(
+      const url::Origin& origin) override;
+  std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
+      const url::Origin& origin,
+      performance_manager::TabVisibility tab_visibility) override;
+  bool IsRecordingForTesting() override;
+
+  // SiteDataCacheInspector:
+  const char* GetDataCacheName() override;
+  std::vector<url::Origin> GetAllInMemoryOrigins() override;
+  void GetDataStoreSize(DataStoreSizeCallback on_have_data) override;
+  bool GetDataForOrigin(const url::Origin& origin,
+                        bool* is_dirty,
+                        std::unique_ptr<SiteDataProto>* data) override;
+  NonRecordingSiteDataCache* GetDataCache() override;
+
+ private:
+  // The data cache to use to create the readers served by this data store. E.g.
+  // during an incognito session it should point to the data cache used by the
+  // parent session.
+  SiteDataCache* data_cache_for_readers_;
+
+  // The inspector implementation this instance delegates to.
+  SiteDataCacheInspector* data_cache_inspector_;
+
+  // The ID of the browser context this data store is associated with.
+  const std::string browser_context_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(NonRecordingSiteDataCache);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_PERSISTENCE_SITE_DATA_NON_RECORDING_SITE_DATA_CACHE_H_
diff --git a/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
new file mode 100644
index 0000000..bc694a3
--- /dev/null
+++ b/chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc
@@ -0,0 +1,144 @@
+// 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/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
+
+#include "chrome/browser/performance_manager/persistence/site_data/leveldb_site_data_store.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
+#include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+
+namespace {
+
+class NonRecordingSiteDataCacheTest : public testing::Test {
+ public:
+  NonRecordingSiteDataCacheTest()
+      : use_in_memory_db_for_testing_(
+            LevelDBSiteDataStore::UseInMemoryDBForTesting()),
+        factory_(SiteDataCacheFactory::CreateForTesting(
+            test_browser_thread_bundle_.GetMainThreadTaskRunner())),
+        off_the_record_profile_(parent_profile_.GetOffTheRecordProfile()) {}
+
+  ~NonRecordingSiteDataCacheTest() override { factory_.reset(); }
+
+  void SetUp() override {
+    recording_data_cache_ = base::WrapUnique(new SiteDataCacheImpl(
+        parent_profile_.UniqueId(), parent_profile_.GetPath()));
+
+    // Wait for the database to be initialized.
+    base::RunLoop run_loop;
+    recording_data_cache_->SetInitializationCallbackForTesting(
+        run_loop.QuitClosure());
+    run_loop.Run();
+
+    non_recording_data_cache_ = std::make_unique<NonRecordingSiteDataCache>(
+        off_the_record_profile_->UniqueId(), recording_data_cache_.get(),
+        recording_data_cache_.get());
+  }
+
+ protected:
+  const url::Origin kTestOrigin =
+      url::Origin::Create(GURL("http://www.foo.com"));
+
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+
+  // Ensure that the database used by the data store owned by
+  // |recording_data_cache_| gets created in memory. This avoid having to wait
+  // for it to be fully closed before destroying |parent_profile_|.
+  std::unique_ptr<base::AutoReset<bool>> use_in_memory_db_for_testing_;
+
+  // The data cache factory that will be used by the caches tested here.
+  std::unique_ptr<SiteDataCacheFactory, base::OnTaskRunnerDeleter> factory_;
+
+  // The on the record profile.
+  TestingProfile parent_profile_;
+  // An off the record profile owned by |parent_profile|.
+  Profile* off_the_record_profile_;
+
+  std::unique_ptr<SiteDataCacheImpl> recording_data_cache_;
+  std::unique_ptr<NonRecordingSiteDataCache> non_recording_data_cache_;
+};
+
+}  // namespace
+
+TEST_F(NonRecordingSiteDataCacheTest, EndToEnd) {
+  // Ensures that the observation made via a writer created by the non
+  // recording data cache aren't recorded.
+  auto reader = non_recording_data_cache_->GetReaderForOrigin(kTestOrigin);
+  EXPECT_TRUE(reader);
+  auto fake_writer = non_recording_data_cache_->GetWriterForOrigin(
+      kTestOrigin, performance_manager::TabVisibility::kBackground);
+  EXPECT_TRUE(fake_writer);
+  auto real_writer = recording_data_cache_->GetWriterForOrigin(
+      kTestOrigin, performance_manager::TabVisibility::kBackground);
+  EXPECT_TRUE(real_writer);
+
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader->UpdatesTitleInBackground());
+  fake_writer->NotifySiteLoaded();
+  fake_writer->NotifyUpdatesTitleInBackground();
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader->UpdatesTitleInBackground());
+
+  real_writer->NotifySiteLoaded();
+  real_writer->NotifyUpdatesTitleInBackground();
+  EXPECT_EQ(performance_manager::SiteFeatureUsage::kSiteFeatureInUse,
+            reader->UpdatesTitleInBackground());
+
+  // These unload events shouldn't be registered, make sure that they aren't by
+  // unloading the site more time than it has been loaded.
+  fake_writer->NotifySiteUnloaded();
+  fake_writer->NotifySiteUnloaded();
+
+  real_writer->NotifySiteUnloaded();
+}
+
+TEST_F(NonRecordingSiteDataCacheTest, InspectorWorks) {
+  // Make sure the inspector interface was registered at construction.
+  SiteDataCacheInspector* inspector = factory_->GetInspectorForBrowserContext(
+      off_the_record_profile_->UniqueId());
+  EXPECT_NE(nullptr, inspector);
+  EXPECT_EQ(non_recording_data_cache_.get(), inspector);
+
+  EXPECT_STREQ("NonRecordingSiteDataCache", inspector->GetDataCacheName());
+
+  // We expect an empty data cache at the outset.
+  EXPECT_EQ(0U, inspector->GetAllInMemoryOrigins().size());
+  std::unique_ptr<SiteDataProto> data;
+  bool is_dirty = false;
+  EXPECT_FALSE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+  EXPECT_FALSE(is_dirty);
+  EXPECT_EQ(nullptr, data.get());
+
+  {
+    // Add an entry through the writing data cache, see that it's reflected in
+    // the inspector interface.
+    auto writer = recording_data_cache_->GetWriterForOrigin(
+        kTestOrigin, performance_manager::TabVisibility::kBackground);
+
+    EXPECT_EQ(1U, inspector->GetAllInMemoryOrigins().size());
+    EXPECT_TRUE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+    EXPECT_FALSE(is_dirty);
+    ASSERT_NE(nullptr, data.get());
+
+    // Touch the underlying data, see that the dirty bit updates.
+    writer->NotifySiteLoaded();
+    EXPECT_TRUE(inspector->GetDataForOrigin(kTestOrigin, &is_dirty, &data));
+  }
+
+  // Make sure the interface is unregistered from the browser context on
+  // destruction.
+  non_recording_data_cache_.reset();
+  EXPECT_EQ(nullptr, factory_->GetInspectorForBrowserContext(
+                         off_the_record_profile_->UniqueId()));
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h b/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h
index 0542377..3284ef7d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h
+++ b/chrome/browser/performance_manager/persistence/site_data/noop_site_data_writer.h
@@ -30,8 +30,9 @@
       uint64_t private_footprint_kb_estimate) override;
 
  private:
-  // Private constructor, these objects are meant to be created by a noop site
-  // data store.
+  friend class NonRecordingSiteDataCache;
+  // Private constructor, these objects are meant to be created by a
+  // NonRecordingSiteDataCache.
   NoopSiteDataWriter();
 
   DISALLOW_COPY_AND_ASSIGN(NoopSiteDataWriter);
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc
index 3ca86dc..2ae7465 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.cc
@@ -9,8 +9,8 @@
 #include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/task_runner_util.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/performance_manager/performance_manager.h"
+#include "chrome/browser/performance_manager/persistence/site_data/non_recording_site_data_cache.h"
 #include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h"
 #include "chrome/browser/performance_manager/persistence/site_data/site_data_cache_inspector.h"
 #include "content/public/browser/browser_context.h"
@@ -30,7 +30,6 @@
     const scoped_refptr<base::SequencedTaskRunner> task_runner)
     : task_runner_(task_runner) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
 SiteDataCacheFactory::~SiteDataCacheFactory() {
@@ -68,19 +67,24 @@
 // static
 void SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(
     SiteDataCacheFactory* factory,
-    content::BrowserContext* browser_context) {
+    content::BrowserContext* browser_context,
+    content::BrowserContext* parent_context) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(factory);
 
   // As |factory| will be deleted on its task runner it's safe to pass the raw
   // pointer to BindOnce, it's guaranteed that this task will run before the
   // factory.
+  base::Optional<std::string> parent_context_id;
+  if (parent_context) {
+    DCHECK(browser_context->IsOffTheRecord());
+    parent_context_id = parent_context->UniqueId();
+  }
   factory->task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&SiteDataCacheFactory::OnBrowserContextCreated,
                      base::Unretained(factory), browser_context->UniqueId(),
-                     browser_context->GetPath(),
-                     browser_context->IsOffTheRecord()));
+                     browser_context->GetPath(), parent_context_id));
 }
 
 // static
@@ -97,28 +101,28 @@
                      base::Unretained(factory), browser_context->UniqueId()));
 }
 
-// static
 SiteDataCache* SiteDataCacheFactory::GetDataCacheForBrowserContext(
     const std::string& browser_context_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = data_cache_map_.find(browser_context_id);
   if (it != data_cache_map_.end())
     return it->second.get();
   return nullptr;
 }
 
-// static
 SiteDataCacheInspector* SiteDataCacheFactory::GetInspectorForBrowserContext(
     const std::string& browser_context_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = data_cache_inspector_map_.find(browser_context_id);
   if (it != data_cache_inspector_map_.end())
     return it->second;
   return nullptr;
 }
 
-// static
 void SiteDataCacheFactory::SetDataCacheInspectorForBrowserContext(
     SiteDataCacheInspector* inspector,
     const std::string& browser_context_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (inspector) {
     DCHECK_EQ(nullptr, GetInspectorForBrowserContext(browser_context_id));
     data_cache_inspector_map_.emplace(
@@ -132,14 +136,23 @@
 void SiteDataCacheFactory::OnBrowserContextCreated(
     const std::string& browser_context_id,
     const base::FilePath& context_path,
-    bool context_is_off_the_record) {
+    base::Optional<std::string> parent_context_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DCHECK(!base::Contains(data_cache_map_, browser_context_id));
 
-  if (context_is_off_the_record) {
-    // TODO(sebmarchand): Add support for off-the-record contexts.
-    NOTREACHED();
+  if (parent_context_id) {
+    SiteDataCacheInspector* parent_debug =
+        GetInspectorForBrowserContext(parent_context_id.value());
+    DCHECK(parent_debug);
+    DCHECK(base::Contains(data_cache_map_, parent_context_id.value()));
+    SiteDataCache* data_cache_for_readers =
+        data_cache_map_[parent_context_id.value()].get();
+    DCHECK(data_cache_for_readers);
+    data_cache_map_.emplace(std::make_pair(
+        std::move(browser_context_id),
+        std::make_unique<NonRecordingSiteDataCache>(
+            browser_context_id, parent_debug, data_cache_for_readers)));
   } else {
     data_cache_map_.emplace(std::make_pair(
         std::move(browser_context_id),
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h
index 88bdf8cc..f27b4006 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory.h
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "base/location.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "chrome/browser/performance_manager/persistence/site_data/site_data_cache.h"
@@ -57,9 +58,14 @@
   // destroyed. They should be called from the UI thread, a task will then be
   // posted to the task_runner owned by |factory| to create the data store
   // associated with this browser context.
+  //
+  // If this browser context is inheriting from a parent context (e.g. if it's
+  // off the record) then this parent context should be specified via
+  // |parent_context|.
   static void OnBrowserContextCreatedOnUIThread(
       SiteDataCacheFactory* factory,
-      content::BrowserContext* browser_context);
+      content::BrowserContext* browser_context,
+      content::BrowserContext* parent_context);
   static void OnBrowserContextDestroyedOnUIThread(
       SiteDataCacheFactory* factory,
       content::BrowserContext* browser_context);
@@ -98,7 +104,7 @@
   // that runs on this object's task runner.
   void OnBrowserContextCreated(const std::string& browser_context_id,
                                const base::FilePath& context_path,
-                               bool context_is_off_the_record);
+                               base::Optional<std::string> parent_context_id);
   void OnBrowserContextDestroyed(const std::string& browser_context_id);
 
   // The task runner on which this object lives, this is expected to be the
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
index 3a4c6a22..3c49d47 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
@@ -41,7 +41,7 @@
 
 TEST_F(SiteDataCacheFactoryTest, EndToEnd) {
   SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(factory_.get(),
-                                                          &profile_);
+                                                          &profile_, nullptr);
 
   base::RunLoop run_loop;
   task_runner_->PostTask(
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
index 06bb6895..4afd3c10 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.cc
@@ -6,6 +6,7 @@
 
 #include <set>
 
+#include "base/callback.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/stl_util.h"
@@ -158,4 +159,9 @@
   data_store_->ClearStore();
 }
 
+void SiteDataCacheImpl::SetInitializationCallbackForTesting(
+    base::OnceClosure callback) {
+  data_store_->SetInitializationCallbackForTesting(std::move(callback));
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
index 1469bf5..b7d84145 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_cache_impl.h
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/callback_forward.h"
 #include "base/containers/flat_map.h"
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
@@ -36,7 +37,7 @@
                     const base::FilePath& browser_context_path);
   ~SiteDataCacheImpl() override;
 
-  // SiteCharacteristicDataCache:
+  // SiteDataCache:
   std::unique_ptr<SiteDataReader> GetReaderForOrigin(
       const url::Origin& origin) override;
   std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
@@ -70,6 +71,10 @@
   // Clear the data cache and the on-disk store.
   void ClearAllSiteData();
 
+  // Set a callback that will be called once the data store backing this cache
+  // has been fully initialized.
+  void SetInitializationCallbackForTesting(base::OnceClosure callback);
+
  private:
   // Returns a pointer to the SiteDataImpl object associated with |origin|,
   // create one and add it to |origin_data_map_| if it doesn't exist.
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
index b98f94c..72a4f69b 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/test/bind_test_util.h"
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
index 9ed6c74..801988d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback.h"
 #include "chrome/browser/performance_manager/persistence/site_data/site_data_impl.h"
 
 namespace performance_manager {
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc b/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
index 81354759..1cee22d 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_reader_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/callback.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
diff --git a/chrome/browser/performance_manager/persistence/site_data/site_data_store.h b/chrome/browser/performance_manager/persistence/site_data/site_data_store.h
index b91a0909..54d64d7 100644
--- a/chrome/browser/performance_manager/persistence/site_data/site_data_store.h
+++ b/chrome/browser/performance_manager/persistence/site_data/site_data_store.h
@@ -7,7 +7,7 @@
 
 #include <vector>
 
-#include "base/callback.h"
+#include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/optional.h"
 #include "chrome/browser/performance_manager/persistence/site_data/site_data.pb.h"
@@ -52,6 +52,11 @@
 
   // Retrieve the size of the store.
   virtual void GetStoreSize(GetStoreSizeCallback callback) = 0;
+
+  // Set a callback that will be called once the data store has been fully
+  // initialized.
+  virtual void SetInitializationCallbackForTesting(
+      base::OnceClosure callback) = 0;
 };
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
index 5b782dd..d39094f6 100644
--- a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/performance_manager/persistence/site_data/unittest_utils.h"
+#include "base/callback.h"
 
 #include <utility>
 
@@ -36,5 +37,10 @@
   std::move(callback).Run(base::nullopt, base::nullopt);
 }
 
+void NoopSiteDataStore::SetInitializationCallbackForTesting(
+    base::OnceClosure callback) {
+  std::move(callback).Run();
+}
+
 }  // namespace testing
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
index f7ea028..12a20dc 100644
--- a/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
+++ b/chrome/browser/performance_manager/persistence/site_data/unittest_utils.h
@@ -44,6 +44,7 @@
       const std::vector<url::Origin>& site_origins) override;
   void ClearStore() override;
   void GetStoreSize(GetStoreSizeCallback callback) override;
+  void SetInitializationCallbackForTesting(base::OnceClosure callback) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NoopSiteDataStore);
diff --git a/chrome/browser/policy/e2e_test/.vpython b/chrome/browser/policy/e2e_test/.vpython
index 262de132..7d94b3b 100644
--- a/chrome/browser/policy/e2e_test/.vpython
+++ b/chrome/browser/policy/e2e_test/.vpython
@@ -11,7 +11,7 @@
 
 wheel: <
   name: "infra/celab/celab/windows-amd64"
-  version: "ewVkoqiGDczXj334qRCQ8uSvGl6dd6XQGAcdbmgwpbsC"
+  version: "t5ee9dgnv7arG5o74SeesxNLMN-f5Z-RLd0IX9YQvrcC"
 >
 
 # googleapiclient
diff --git a/chrome/browser/policy/e2e_test/tests/__init__.py b/chrome/browser/policy/e2e_test/tests/__init__.py
index 26eadb6..90bedaae 100644
--- a/chrome/browser/policy/e2e_test/tests/__init__.py
+++ b/chrome/browser/policy/e2e_test/tests/__init__.py
@@ -4,8 +4,7 @@
 
 from force_google_safe_search.force_google_safe_search import *
 from homepage.homepage import *
-# TODO(feiling): Fix RestoreOnStartupTest on LUCI bots.
-# from restore_on_startup.restore_on_startup import *
+from restore_on_startup.restore_on_startup import *
 from popups_allowed.popups_allowed import *
 from url_blacklist.url_blacklist import *
 from url_whitelist.url_whitelist import *
diff --git a/chrome/browser/policy/e2e_test/tests/restore_on_startup/restore_on_startup.py b/chrome/browser/policy/e2e_test/tests/restore_on_startup/restore_on_startup.py
index a696649..36c36def 100644
--- a/chrome/browser/policy/e2e_test/tests/restore_on_startup/restore_on_startup.py
+++ b/chrome/browser/policy/e2e_test/tests/restore_on_startup/restore_on_startup.py
@@ -92,7 +92,13 @@
         ])
     output_urls = json.loads(output)
     self.assertEqual(len(output_urls), 1)
-    self.assertTrue('/_/chrome/newtab' in output_urls[0])
+
+    # The URL of the new tab can be one of the following:
+    # - https://www.google.com/_/chrome/newtab?ie=UTF-8
+    # - chrome://newtab
+    # - chrome-search://local-ntp/local-ntp.html
+    self.assertTrue('/newtab' in output_urls[0] or
+                    'local-ntp.html' in output_urls[0])
 
   @test
   def test_OpenListOfUrls(self):
diff --git a/chrome/browser/predictors/loading_predictor_tab_helper.cc b/chrome/browser/predictors/loading_predictor_tab_helper.cc
index 9a01fbc..a2ff39f 100644
--- a/chrome/browser/predictors/loading_predictor_tab_helper.cc
+++ b/chrome/browser/predictors/loading_predictor_tab_helper.cc
@@ -42,7 +42,8 @@
     case content::ResourceType::kServiceWorker:
     case content::ResourceType::kCspReport:
     case content::ResourceType::kPluginResource:
-    case content::ResourceType::kNavigationPreload:
+    case content::ResourceType::kNavigationPreloadMainFrame:
+    case content::ResourceType::kNavigationPreloadSubFrame:
       return net::LOWEST;
   }
 }
diff --git a/chrome/browser/resources/chromeos/login/saml_password_attributes.js b/chrome/browser/resources/chromeos/login/saml_password_attributes.js
index 0067ea2..880ceec 100644
--- a/chrome/browser/resources/chromeos/login/saml_password_attributes.js
+++ b/chrome/browser/resources/chromeos/login/saml_password_attributes.js
@@ -64,19 +64,36 @@
    * be empty if some or all of the attributes could not be extracted.
    */
   function readPasswordAttributes(xmlStr) {
-    if (xmlStr.length < MIN_SANE_XML_LENGTH ||
-        xmlStr.length > MAX_SANE_XML_LENGTH) {
-      return PasswordAttributes.EMPTY;
-    }
-    const xmlDom = new DOMParser().parseFromString(xmlStr, 'text/xml');
-    if (!xmlDom) {
-      return PasswordAttributes.EMPTY;
-    }
+    // Don't throw any exception that could cause login to fail - extracting
+    // these attributes can fail, but login should not be interrupted.
+    try {
+      if (!xmlStr || typeof xmlStr != 'string') {
+        return PasswordAttributes.EMPTY;
+      }
+      if (xmlStr.length < MIN_SANE_XML_LENGTH ||
+          xmlStr.length > MAX_SANE_XML_LENGTH) {
+        return PasswordAttributes.EMPTY;
+      }
+      if (!xmlStr.includes(SCHEMA_NAME_PREFIX)) {
+        // No need to bother parsing the XML if it doesn't contain this string.
+        return PasswordAttributes.EMPTY;
+      }
 
-    return new PasswordAttributes(
-        extractTimestampFromXml(xmlDom, PASSWORD_MODIFIED_TIMESTAMP_SELECTOR),
-        extractTimestampFromXml(xmlDom, PASSWORD_EXPIRATION_TIMESTAMP_SELECTOR),
-        extractStringFromXml(xmlDom, PASSWORD_CHANGE_URL_SELECTOR));
+      const xmlDom = new DOMParser().parseFromString(xmlStr, 'text/xml');
+      if (!xmlDom) {
+        return PasswordAttributes.EMPTY;
+      }
+
+      return new PasswordAttributes(
+          extractTimestampFromXml(xmlDom, PASSWORD_MODIFIED_TIMESTAMP_SELECTOR),
+          extractTimestampFromXml(
+              xmlDom, PASSWORD_EXPIRATION_TIMESTAMP_SELECTOR),
+          extractStringFromXml(xmlDom, PASSWORD_CHANGE_URL_SELECTOR));
+
+    } catch (error) {
+      console.error('Error reading password attributes: ' + error);
+      return PasswordAttributes.EMPTY;
+    }
   }
 
   /**
diff --git a/chrome/browser/resources/omnibox/omnibox_output.js b/chrome/browser/resources/omnibox/omnibox_output.js
index 4fa0ac2..d128558 100644
--- a/chrome/browser/resources/omnibox/omnibox_output.js
+++ b/chrome/browser/resources/omnibox/omnibox_output.js
@@ -824,25 +824,47 @@
      * @return {string|undefined}
      */
     static classifyJsonWord(word) {
-      if (/^\d+$/.test(word)) {
+      // Statically creating the regexes only once.
+      OutputJsonProperty.classifications =
+          OutputJsonProperty.classifications || [
+            {re: /^"[^]*":$/, clazz: 'key'},
+            {re: /^"[^]*"$/, clazz: 'string'},
+            {re: /true|false/, clazz: 'boolean'},
+            {re: /null/, clazz: 'null'},
+          ];
+      OutputJsonProperty.spaceRegex = OutputJsonProperty.spaceRegex || /^\s*$/;
+
+      // Using isNaN, because Number.isNaN checks explicitly for NaN whereas
+      // isNaN coerces the param to a Number. I.e. isNaN('3') === false, while
+      // Number.isNaN('3') === true.
+      if (isNaN(word)) {
+        const classification =
+            OutputJsonProperty.classifications.find(({re}) => re.test(word));
+        return classification && classification.clazz;
+      } else if (!OutputJsonProperty.spaceRegex.test(word)) {
         return 'number';
       }
-      if (/^"[^]*":$/.test(word)) {
-        return 'key';
-      }
-      if (/^"[^]*"$/.test(word)) {
-        return 'string';
-      }
-      if (/true|false/.test(word)) {
-        return 'boolean';
-      }
-      if (/null/.test(word)) {
-        return 'null';
-      }
     }
   }
 
-  class OutputAdditionalInfoProperty extends OutputJsonProperty {
+  class OutputAdditionalInfoProperty extends OutputProperty {
+    constructor() {
+      super();
+      const container = document.createElement('div');
+
+      /** @private {!Element} */
+      this.pre_ = document.createElement('pre');
+      this.pre_.classList.add('json');
+      container.appendChild(this.pre_);
+
+      /** @private {!Element} */
+      this.link_ = document.createElement('a');
+      this.link_.download = 'AdditionalInfo.json';
+
+      container.appendChild(this.link_);
+      this.appendChild(container);
+    }
+
     /** @private @override */
     render_() {
       clearChildren(this.pre_);
@@ -852,6 +874,7 @@
         this.pre_.appendChild(
             OutputJsonProperty.renderJsonWord(value + '\n', ['number']));
       });
+      this.link_.href = this.createDownloadLink_();
     }
 
     /** @override @return {string} */
@@ -867,6 +890,16 @@
         {key: 'document_type', value: this.values_[1]}
       ];
     }
+
+    /** @private @return {string} */
+    createDownloadLink_() {
+      const obj = this.tuples_.reduce((obj, {key, value}) => {
+        obj[key] = value;
+        return obj;
+      }, {});
+      const obj64 = btoa(unescape(encodeURIComponent(JSON.stringify(obj))));
+      return `data:application/json;base64,${obj64}`;
+    }
   }
 
   class OutputUrlProperty extends FlexWrappingOutputProperty {
diff --git a/chrome/browser/resources/omnibox/output_results_group.css b/chrome/browser/resources/omnibox/output_results_group.css
index 06b14f98..56f5d51 100644
--- a/chrome/browser/resources/omnibox/output_results_group.css
+++ b/chrome/browser/resources/omnibox/output_results_group.css
@@ -232,6 +232,20 @@
   color: red;
 }
 
+.cell-additional-info a {
+  background-image: url(../../../../third_party/blink/renderer/modules/media_controls/resources/ic_download.svg);
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: contain;
+  display: block;
+  height: 16px;
+  width: 16px;
+}
+
+.cell-additional-info:not(:hover) a {
+  visibility: hidden;
+}
+
 /* boolean cells */
 
 .check-mark,
diff --git a/chrome/browser/resources/print_preview/ui/destination_list.js b/chrome/browser/resources/print_preview/ui/destination_list.js
index 3b4d1cc..c513269 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list.js
+++ b/chrome/browser/resources/print_preview/ui/destination_list.js
@@ -68,8 +68,7 @@
     }
 
     this.updateList(
-        'matchingDestinations_',
-        destination => destination.key + '/' + destination.connectionStatusText,
+        'matchingDestinations_', destination => destination.key,
         this.searchQuery ?
             this.destinations.filter(
                 d => d.matches(/** @type {!RegExp} */ (this.searchQuery))) :
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chrome/browser/resources/settings/a11y_page/a11y_page.html
index 0b546ed..a4acf13 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.html
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.html
@@ -43,14 +43,14 @@
         </div>
       </if>
       <if expr="chromeos">
-        <div route-path="default">
-          <template is="dom-if" if="[[showCaptionSettings_]]">
-            <cr-link-row class="hr" id="captions"
-                label="$i18n{captionsTitle}"
-                on-click="onTapCaptions_">
-            </cr-link-row>
-          </template>
-          <template is="dom-if" if="[[pageVisibility.webstoreLink]]">
+        <template is="dom-if" if="[[pageVisibility.webstoreLink]]">
+          <div route-path="default">
+            <template is="dom-if" if="[[showCaptionSettings_]]">
+              <cr-link-row class="hr" id="captions"
+                  label="$i18n{captionsTitle}"
+                  on-click="onTapCaptions_">
+              </cr-link-row>
+            </template>
             <settings-toggle-button id="a11yImageLabels"
                 hidden$="[[!showAccessibilityLabelsSetting_]]"
                 pref="{{prefs.settings.a11y.enable_accessibility_image_labels}}"
@@ -67,42 +67,38 @@
                 on-click="onManageAccessibilityFeaturesTap_"
                 sub-label="$i18n{moreFeaturesLinkDescription}">
             </cr-link-row>
-            <template is="dom-if" route-path="/manageAccessibility">
-              <settings-subpage
-                  associated-control="[[$$('#subpage-trigger')]]"
-                  page-title="$i18n{manageAccessibilityFeatures}">
-                <settings-manage-a11y-page prefs="{{prefs}}">
-                </settings-manage-a11y-page>
+          </div>
+          <template is="dom-if" route-path="/manageAccessibility">
+            <settings-subpage associated-control="[[$$('#subpage-trigger')]]"
+                page-title="$i18n{manageAccessibilityFeatures}">
+              <settings-manage-a11y-page prefs="{{prefs}}">
+              </settings-manage-a11y-page>
+            </settings-subpage>
+          </template>
+          <template is="dom-if" route-path="/manageAccessibility/tts">
+            <settings-subpage
+                associated-control="[[$$('#subpage-trigger')]]"
+                page-title="$i18n{manageTtsSettings}">
+              <settings-tts-subpage prefs="{{prefs}}">
+              </settings-tts-subpage>
+            </settings-subpage>
+          </template>
+          <template is="dom-if" if="[[showExperimentalSwitchAccess_]]">
+            <template is="dom-if"
+                route-path="/manageAccessibility/switchAccess">
+              <settings-subpage associated-control="[[$$('#subpage-trigger')]]"
+                  page-title="$i18n{manageSwitchAccessSettings}">
+                <settings-switch-access-subpage prefs="{{prefs.settings.a11y}}">
+                </settings-switch-access-subpage>
               </settings-subpage>
             </template>
-            <template is="dom-if" route-path="/manageAccessibility/tts">
-              <settings-subpage
-                  associated-control="[[$$('#subpage-trigger')]]"
-                  page-title="$i18n{manageTtsSettings}">
-                <settings-tts-subpage prefs="{{prefs}}">
-                </settings-tts-subpage>
-              </settings-subpage>
-            </template>
-            <template is="dom-if" if="[[showExperimentalSwitchAccess_]]">
-              <template is="dom-if"
-                  route-path="/manageAccessibility/switchAccess">
-                <settings-subpage
-                    associated-control="[[$$('#subpage-trigger')]]"
-                    page-title="$i18n{manageSwitchAccessSettings}">
-                  <settings-switch-access-subpage
-                      prefs="{{prefs.settings.a11y}}">
-                  </settings-switch-access-subpage>
-                </settings-subpage>
-              </template>
-            </template>
           </template>
-          <cr-link-row class="hr"
-              label="$i18n{moreFeaturesLink}"
-              on-click="onMoreFeaturesLinkClick_"
-              sub-label="$i18n{a11yWebStore}"
-              hidden="[[pageVisibility.webstoreLink]]" external>
-          </cr-link-row>
-        </div>
+        </template>
+        <cr-link-row class="hr"
+            label="$i18n{moreFeaturesLink}"
+            on-click="onMoreFeaturesLinkClick_"
+            sub-label="$i18n{a11yWebStore}"
+            hidden="[[pageVisibility.webstoreLink]]" external></cr-link-row>
       </if>
 
       <if expr="chromeos or is_linux or is_win">
diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chrome/browser/resources/settings/basic_page/basic_page.html
index 4dbac5e..57ea8e7 100644
--- a/chrome/browser/resources/settings/basic_page/basic_page.html
+++ b/chrome/browser/resources/settings/basic_page/basic_page.html
@@ -26,7 +26,6 @@
 <link rel="import" href="../device_page/device_page.html">
 <link rel="import" href="../internet_page/internet_page.html">
 <link rel="import" href="../multidevice_page/multidevice_page.html">
-<link rel="import" href="../parental_controls_page/parental_controls_page.html">
 </if>
 
 <if expr="not chromeos">
@@ -154,15 +153,6 @@
             </settings-people-page>
           </settings-section>
         </template>
-<if expr="chromeos">
-        <template is="dom-if" if="[[showParentalControls]]" restamp>
-          <settings-section page-title="$i18n{parentalControlsPageTitle}"
-              section="parentalControls">
-            <settings-parental-controls-page>
-            </settings-parental-controls-page>
-          </settings-section>
-        </template>
-</if>
         <template is="dom-if" if="[[showPage_(pageVisibility.autofill)]]"
             restamp>
           <settings-section page-title="$i18n{autofillPageTitle}"
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index cfd6b3f..e2642b2 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -63,7 +63,7 @@
 group("closure_compile") {
   deps = [
     "os_a11y_page:closure_compile",
-    "os_downloads_page:closure_compile",
+    "os_files_page:closure_compile",
     "os_languages_page:closure_compile",
     "os_people_page:closure_compile",
     "os_printing_page:closure_compile",
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.html b/chrome/browser/resources/settings/chromeos/lazy_load.html
index 5ef6846..91f958bc 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.html
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.html
@@ -3,7 +3,7 @@
 <body>
   <link rel="import" href="../date_time_page/date_time_page.html">
   <link rel="import" href="os_a11y_page/os_a11y_page.html">
-  <link rel="import" href="os_downloads_page/os_downloads_page.html">
+  <link rel="import" href="os_files_page/os_files_page.html">
   <link rel="import" href="os_languages_page/os_languages_page.html">
   <link rel="import" href="os_printing_page/os_printing_page.html">
   <link rel="import" href="os_privacy_page/os_privacy_page.html">
diff --git a/chrome/browser/resources/settings/chromeos/os_downloads_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
similarity index 89%
rename from chrome/browser/resources/settings/chromeos/os_downloads_page/BUILD.gn
rename to chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
index 38799493..a1b5d3d6 100644
--- a/chrome/browser/resources/settings/chromeos/os_downloads_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/BUILD.gn
@@ -6,7 +6,7 @@
 
 js_type_check("closure_compile") {
   deps = [
-    ":os_downloads_page",
+    ":os_files_page",
     ":smb_shares_page",
   ]
 }
@@ -18,7 +18,7 @@
   ]
 }
 
-js_library("os_downloads_page") {
+js_library("os_files_page") {
   deps = [
     "../..:page_visibility",
     "../..:route",
diff --git a/chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.html b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.html
similarity index 90%
rename from chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.html
rename to chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.html
index 5168a9d..77ead4e 100644
--- a/chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.html
@@ -8,10 +8,10 @@
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="smb_shares_page.html">
 
-<dom-module id="os-settings-downloads-page">
+<dom-module id="os-settings-files-page">
   <template>
   <style include="settings-shared"></style>
-  <settings-animated-pages id="pages" section="downloads"
+  <settings-animated-pages id="pages" section="files"
       focus-config="[[focusConfig_]]">
     <div route-path="default">
       <settings-toggle-button
@@ -34,5 +34,5 @@
     </template>
   </settings-animated-pages>
   </template>
-  <script src="os_downloads_page.js"></script>
+  <script src="os_files_page.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.js b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.js
similarity index 87%
rename from chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.js
rename to chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.js
index f8f7f48..ff8f34e 100644
--- a/chrome/browser/resources/settings/chromeos/os_downloads_page/os_downloads_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.js
@@ -4,12 +4,11 @@
 
 /**
  * @fileoverview
- * 'os-settings-downloads-page' is the settings page containing downloads
- * settings.
+ * 'os-settings-files-page' is the settings page containing files settings.
  *
  */
 Polymer({
-  is: 'os-settings-downloads-page',
+  is: 'os-settings-files-page',
 
   properties: {
     /**
diff --git a/chrome/browser/resources/settings/chromeos/os_downloads_page/smb_shares_page.html b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_downloads_page/smb_shares_page.html
rename to chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.html
diff --git a/chrome/browser/resources/settings/chromeos/os_downloads_page/smb_shares_page.js b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_downloads_page/smb_shares_page.js
rename to chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.js
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
index 14d235a..72f767c 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
@@ -122,12 +122,6 @@
         <iron-icon icon="cr:person"></iron-icon>
         $i18n{peoplePageTitle}
       </a>
-      <a id="parentalControls" href="/parentalControls"
-          hidden="[[!showParentalControls]]">
-        <iron-icon icon="cr20:kite">
-        </iron-icon>
-        $i18n{parentalControlsPageTitle}
-      </a>
       <a id="personalization" href="/personalization">
         <iron-icon icon="settings:palette"></iron-icon>
         $i18n{personalizationPageTitle}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
index 8aa1493..86d4122b 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
@@ -247,13 +247,15 @@
                 </os-settings-languages-page>
               </settings-section>
             </template>
+            <!-- TODO(jordynass): Change from downloads to files once
+              pageVisibility is forked for OS Settings.-->
             <template is="dom-if" if="[[showPage_(pageVisibility.downloads)]]"
                 restamp>
-              <settings-section page-title="$i18n{downloadsPageTitle}"
-                  section="downloads">
-                <os-settings-downloads-page prefs="{{prefs}}"
+              <settings-section page-title="$i18n{filesPageTitle}"
+                  section="files">
+                <os-settings-files-page prefs="{{prefs}}"
                     page-visibility="[[pageVisibility.downloads]]">
-                </os-settings-downloads-page>
+                </os-settings-files-page>
               </settings-section>
             </template>
             <template is="dom-if" if="[[showPage_(pageVisibility.printing)]]"
diff --git a/chrome/browser/resources/settings/os_settings_resources.grd b/chrome/browser/resources/settings/os_settings_resources.grd
index 216ef26e..b50af0d 100644
--- a/chrome/browser/resources/settings/os_settings_resources.grd
+++ b/chrome/browser/resources/settings/os_settings_resources.grd
@@ -447,20 +447,20 @@
       <structure name="IDR_OS_SETTINGS_DEVICE_NIGHT_LIGHT_SLIDER_JS"
                  file="device_page/night_light_slider.js"
                  type="chrome_html" />
-      <structure name="IDR_OS_SETTINGS_DOWNLOADS_PAGE_HTML"
-                 file="chromeos/os_downloads_page/os_downloads_page.html"
+      <structure name="IDR_OS_SETTINGS_FILES_PAGE_HTML"
+                 file="chromeos/os_files_page/os_files_page.html"
                  type="chrome_html"
                  preprocess="true"
                  allowexternalscript="true" />
-      <structure name="IDR_OS_SETTINGS_DOWNLOADS_PAGE_JS"
-                 file="chromeos/os_downloads_page/os_downloads_page.js"
+      <structure name="IDR_OS_SETTINGS_FILES_PAGE_JS"
+                 file="chromeos/os_files_page/os_files_page.js"
                  type="chrome_html"
                  preprocess="true" />
       <structure name="IDR_OS_SETTINGS_SMB_SHARES_PAGE_HTML"
-                 file="chromeos/os_downloads_page/smb_shares_page.html"
+                 file="chromeos/os_files_page/smb_shares_page.html"
                  type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_SMB_SHARES_PAGE_JS"
-                 file="chromeos/os_downloads_page/smb_shares_page.js"
+                 file="chromeos/os_files_page/smb_shares_page.js"
                  type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_I18n_SETUP_HTML"
                  file="i18n_setup.html"
diff --git a/chrome/browser/resources/settings/people_page/users_page.html b/chrome/browser/resources/settings/people_page/users_page.html
index 7bfa99c..3f17fcf 100644
--- a/chrome/browser/resources/settings/people_page/users_page.html
+++ b/chrome/browser/resources/settings/people_page/users_page.html
@@ -10,6 +10,10 @@
 <link rel="import" href="user_list.html">
 <link rel="import" href="users_add_user_dialog.html">
 
+<if expr="chromeos">
+<link rel="import" href="../parental_controls_page/parental_controls_page.html">
+</if>
+
 <dom-module id="settings-users-page">
   <template>
     <style include="settings-shared action-link">
@@ -25,6 +29,10 @@
       .block {
         display: block;
       }
+
+      #header {
+        padding-inline-start: 20px;
+      }
     </style>
     <div class="settings-box" hidden$="[[!isWhitelistManaged_]]">
       $i18n{settingsManagedLabel}
@@ -64,6 +72,13 @@
         </a>
       </div>
     </div>
+<if expr="chromeos">
+    <template is="dom-if" if="[[showParentalControls_]]">
+      <h2 id="header" class="title">$i18n{parentalControlsPageTitle}</h2>
+      <settings-parental-controls-page>
+      </settings-parental-controls-page>
+    </template>
+</if>
     <settings-users-add-user-dialog id="addUserDialog"
         on-close="onAddUserDialogClose_">
     </settings-users-add-user-dialog>
diff --git a/chrome/browser/resources/settings/people_page/users_page.js b/chrome/browser/resources/settings/people_page/users_page.js
index f8f19f3..11bfa00 100644
--- a/chrome/browser/resources/settings/people_page/users_page.js
+++ b/chrome/browser/resources/settings/people_page/users_page.js
@@ -30,6 +30,19 @@
       type: Boolean,
       value: false,
     },
+
+    /** @private */
+    showParentalControls_: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  /** @override */
+  ready: function() {
+    this.showParentalControls_ =
+        loadTimeData.valueExists('showParentalControls') &&
+        loadTimeData.getBoolean('showParentalControls');
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 8511441..afa7a072 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -64,7 +64,6 @@
  *   NETWORK_DETAIL: (undefined|!settings.Route),
  *   ON_STARTUP: (undefined|!settings.Route),
  *   PASSWORDS: (undefined|!settings.Route),
- *   PARENTAL_CONTROLS: (undefined|!settings.Route),
  *   PAYMENTS: (undefined|!settings.Route),
  *   PEOPLE: (undefined|!settings.Route),
  *   PERSONALIZATION: (undefined|!settings.Route),
@@ -257,12 +256,6 @@
     r.SMART_LOCK =
         r.MULTIDEVICE_FEATURES.createChild('/multidevice/features/smartLock');
 
-    if (loadTimeData.valueExists('showParentalControls') &&
-        loadTimeData.getBoolean('showParentalControls')) {
-      r.PARENTAL_CONTROLS =
-          r.BASIC.createSection('/parentalControls', 'parentalControls');
-    }
-
     // TODO(hsuregan): Remove once this file is forked.
     if (loadTimeData.getBoolean('showOSSettings')) {
       r.PERSONALIZATION =
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
index 305aa50..9369f5f5 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -125,13 +125,6 @@
         <iron-icon icon="cr:person"></iron-icon>
         $i18n{peoplePageTitle}
       </a>
-<if expr="chromeos">
-      <a id="parentalControls" href="/parentalControls"
-          hidden="[[!showParentalControls]]">
-        <iron-icon icon="cr20:kite"></iron-icon>
-        $i18n{parentalControlsPageTitle}
-      </a>
-</if>
       <a id="autofill" href="/autofill"
           hidden="[[!pageVisibility.autofill]]">
         <iron-icon icon="settings:assignment"></iron-icon>
diff --git a/chrome/browser/resources/settings/site_settings/chooser_exception_list.js b/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
index 7e37cde..79c7539 100644
--- a/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
+++ b/chrome/browser/resources/settings/site_settings/chooser_exception_list.js
@@ -176,7 +176,9 @@
       return Object.assign(exception, {sites});
     });
 
-    if (!this.updateList('chooserExceptions', x => x.displayName, exceptions)) {
+    if (!this.updateList(
+            'chooserExceptions', x => x.displayName, exceptions,
+            true /* uidBasedUpdate */)) {
       // The chooser objects have not been changed, so check if their site
       // permissions have changed. The |exceptions| and |this.chooserExceptions|
       // arrays should be the same length.
diff --git a/chrome/browser/resources/settings/site_settings/site_data.js b/chrome/browser/resources/settings/site_settings/site_data.js
index f15cb13..06c8e74 100644
--- a/chrome/browser/resources/settings/site_settings/site_data.js
+++ b/chrome/browser/resources/settings/site_settings/site_data.js
@@ -176,8 +176,7 @@
   updateSiteList_: function() {
     this.isLoading_ = true;
     this.browserProxy_.getDisplayList(this.filter).then(listInfo => {
-      this.updateList(
-          'sites', item => `${item.site}_${item.localData}`, listInfo.items);
+      this.updateList('sites', item => item.site, listInfo.items);
       this.isLoading_ = false;
       this.fire('site-data-list-complete');
     });
diff --git a/chrome/browser/resources/settings/site_settings/site_list.js b/chrome/browser/resources/settings/site_settings/site_list.js
index c1bcb458..5e9cbba 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.js
+++ b/chrome/browser/resources/settings/site_settings/site_list.js
@@ -339,14 +339,10 @@
                     site.setting == this.categorySubtype)
             .map(site => this.expandSiteException(site));
 
-    // <if expr="not chromeos">
-    this.updateList('sites', (x) => x.origin, sites);
-    // </if>
-
     // <if expr="chromeos">
     sites = this.processExceptionsForAndroidSmsInfo_(sites);
-    this.updateList('sites', (x) => x.origin + x.showAndroidSmsNote, sites);
     // </if>
+    this.updateList('sites', x => x.origin, sites);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/site_settings/zoom_levels.js b/chrome/browser/resources/settings/site_settings/zoom_levels.js
index 1614243c..e600e08f 100644
--- a/chrome/browser/resources/settings/site_settings/zoom_levels.js
+++ b/chrome/browser/resources/settings/site_settings/zoom_levels.js
@@ -47,7 +47,7 @@
    *     their zoom levels.
    */
   onZoomLevelsChanged_: function(sites) {
-    this.updateList('sites_', item => `${item.origin}_${item.zoom}`, sites);
+    this.updateList('sites_', item => item.origin, sites);
     this.showNoSites_ = this.sites_.length == 0;
   },
 
diff --git a/chrome/browser/resources/webapks/about_webapks.js b/chrome/browser/resources/webapks/about_webapks.js
index 2be6576..4c65246 100644
--- a/chrome/browser/resources/webapks/about_webapks.js
+++ b/chrome/browser/resources/webapks/about_webapks.js
@@ -7,6 +7,7 @@
  *   name: string,
  *   shortName: string,
  *   packageName: string,
+ *   id: string,
  *   shellApkVersion: number,
  *   versionCode: number,
  *   uri: string,
@@ -19,11 +20,20 @@
  *   backgroundColor: string,
  *   lastUpdateCheckTimeMs: number,
  *   relaxUpdates: boolean,
+ *   updateStatus: string,
  * }}
  */
 let WebApkInfo;
 
 /**
+ * @typedef {{
+ *   id: string,
+ *   status: string,
+ * }}
+ */
+let UpdateStatus;
+
+/**
  * Creates and returns an element (with |text| as content) assigning it the
  * |className| class.
  *
@@ -40,8 +50,8 @@
 
 /**
  * Callback from the backend with the information of a WebAPK to display.
- * This will be called once for each WebAPK available on the device and each
- * one will be appended at the end of the other.
+ * This will be called once. All WebAPKs available on the device will be
+ * returned.
  *
  * @param {!Array<WebApkInfo>} webApkList List of objects with information about
  * WebAPKs installed.
@@ -67,6 +77,19 @@
 }
 
 /**
+ * @param {HTMLElement} webApkList List of elements which contain WebAPK
+ * attributes.
+ * @param {string} text For the button.
+ * @param {function()} callback Invoked on click.
+ */
+function addWebApkButton(webApkList, text, callback) {
+  const divElement =
+      createElementWithTextAndClass(text, 'button', 'update-button');
+  divElement.onclick = callback;
+  webApkList.appendChild(divElement);
+}
+
+/**
  * Adds a new entry to the page with the information of a WebAPK.
  *
  * @param {WebApkInfo} webApkInfo Information about an installed WebAPK.
@@ -100,6 +123,16 @@
   addWebApkField(
       webApkList, 'Check for Updates Less Frequently: ',
       webApkInfo.relaxUpdates.toString());
+  addWebApkField(webApkList, 'Update Status: ', webApkInfo.updateStatus);
+
+  addWebApkButton(webApkList, 'Update ' + webApkInfo.name, () => {
+    alert(
+        'The WebAPK will check for an update the next time it launches. ' +
+        'If an update is available, the "Update Status" on this page ' +
+        'will switch to "Scheduled". The update will be installed once ' +
+        'the WebAPK is closed (this may take a few minutes).');
+    chrome.send('requestWebApkUpdate', [webApkInfo.id]);
+  });
 }
 
 document.addEventListener('DOMContentLoaded', function() {
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 97d6998a..0457b1ff 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -190,7 +190,6 @@
       pref_service_(profile_->GetPrefs()),
       theme_observer_(this),
       native_theme_(ui::NativeTheme::GetInstanceForNativeUi()),
-      background_updated_timestamp_(base::TimeTicks::Now()),
       weak_ptr_factory_(this) {
   // The initialization below depends on a typical set of browser threads. Skip
   // it if we are running in a unit test without the full suite.
@@ -442,14 +441,12 @@
   pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
   RemoveLocalBackgroundImageCopy();
 
-  background_updated_timestamp_ = base::TimeTicks::Now();
-
   if (background_url.is_valid() && is_backdrop_url) {
     const GURL& thumbnail_url =
         background_service_->GetThumbnailUrl(background_url);
-    FetchCustomBackground(
-        background_updated_timestamp_,
-        thumbnail_url.is_valid() ? thumbnail_url : background_url);
+    FetchCustomBackground(background_url, thumbnail_url.is_valid()
+                                              ? thumbnail_url
+                                              : background_url);
 
     base::DictionaryValue background_info = GetBackgroundInfoAsDict(
         background_url, attribution_line_1, attribution_line_2, action_url);
@@ -467,7 +464,6 @@
 }
 
 void InstantService::SetBackgroundToLocalResource() {
-  background_updated_timestamp_ = base::TimeTicks::Now();
   pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, true);
   UpdateThemeInfo();
 }
@@ -800,7 +796,7 @@
 }
 
 void InstantService::UpdateCustomBackgroundColorAsync(
-    base::TimeTicks timestamp,
+    const GURL& image_url,
     const gfx::Image& fetched_image,
     const image_fetcher::RequestMetadata& metadata) {
   // Calculate the bitmap color asynchronously as it is slow (1-2 seconds for
@@ -810,11 +806,11 @@
         FROM_HERE, {base::TaskPriority::BEST_EFFORT},
         base::BindOnce(&GetBitmapMainColor, *fetched_image.ToSkBitmap()),
         base::BindOnce(&InstantService::UpdateCustomBackgroundPrefsWithColor,
-                       weak_ptr_factory_.GetWeakPtr(), timestamp));
+                       weak_ptr_factory_.GetWeakPtr(), image_url));
   }
 }
 
-void InstantService::FetchCustomBackground(base::TimeTicks timestamp,
+void InstantService::FetchCustomBackground(const GURL& image_url,
                                            const GURL& fetch_url) {
   DCHECK(!fetch_url.is_empty());
 
@@ -841,9 +837,9 @@
   image_fetcher::ImageFetcherParams params(traffic_annotation,
                                            kCustomBackgroundsUmaClientName);
   image_fetcher_->FetchImage(
-      fetch_url,
+      image_url,
       base::BindOnce(&InstantService::UpdateCustomBackgroundColorAsync,
-                     weak_ptr_factory_.GetWeakPtr(), timestamp),
+                     weak_ptr_factory_.GetWeakPtr(), image_url),
       std::move(params));
 }
 
@@ -885,9 +881,8 @@
   registry->RegisterBooleanPref(prefs::kNtpShortcutsVisible, true);
 }
 
-void InstantService::UpdateCustomBackgroundPrefsWithColor(
-    base::TimeTicks timestamp,
-    SkColor color) {
+void InstantService::UpdateCustomBackgroundPrefsWithColor(const GURL& image_url,
+                                                          SkColor color) {
   // Update background color only if the selected background is still the same.
   const base::DictionaryValue* background_info =
       pref_service_->GetDictionary(prefs::kNtpCustomBackgroundDict);
@@ -896,7 +891,7 @@
 
   GURL current_bg_url(
       background_info->FindKey(kNtpCustomBackgroundURL)->GetString());
-  if (timestamp == background_updated_timestamp_) {
+  if (current_bg_url == image_url) {
     pref_service_->Set(prefs::kNtpCustomBackgroundDict,
                        GetBackgroundInfoWithColor(background_info, color));
   }
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index 35e6132..2a73342 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -162,12 +162,12 @@
 
   // Calculates the most frequent color of the image and stores it in prefs.
   void UpdateCustomBackgroundColorAsync(
-      base::TimeTicks timestamp,
+      const GURL& image_url,
       const gfx::Image& fetched_image,
       const image_fetcher::RequestMetadata& metadata);
 
   // Fetches the image for the given |fetch_url|.
-  void FetchCustomBackground(base::TimeTicks timestamp, const GURL& fetch_url);
+  void FetchCustomBackground(const GURL& image_url, const GURL& fetch_url);
 
  private:
   class SearchProviderObserver;
@@ -184,9 +184,6 @@
   FRIEND_TEST_ALL_PREFIXES(InstantServiceTest, DoesToggleShortcutsVisibility);
   FRIEND_TEST_ALL_PREFIXES(InstantServiceTest, IsCustomLinksEnabled);
   FRIEND_TEST_ALL_PREFIXES(InstantServiceTest, TestNoThemeInfo);
-  FRIEND_TEST_ALL_PREFIXES(InstantServiceTest, TestUpdateCustomBackgroundColor);
-  FRIEND_TEST_ALL_PREFIXES(InstantServiceTest,
-                           LocalImageDoesNotUpdateCustomBackgroundColor);
 
   // KeyedService:
   void Shutdown() override;
@@ -240,17 +237,12 @@
   // chrome-search://local-ntp/background.jpg
   void SetBackgroundToLocalResource();
 
-  // Updates custom background prefs with color if the background hasn't changed
-  // since the calculation started.
-  void UpdateCustomBackgroundPrefsWithColor(base::TimeTicks timestamp,
+  // Updates custom background prefs with color for the given |image_url|.
+  void UpdateCustomBackgroundPrefsWithColor(const GURL& image_url,
                                             SkColor color);
 
   void SetImageFetcherForTesting(image_fetcher::ImageFetcher* image_fetcher);
 
-  base::TimeTicks GetBackgroundUpdatedTimestampForTesting() {
-    return background_updated_timestamp_;
-  }
-
   Profile* const profile_;
 
   // The process ids associated with Instant processes.
@@ -287,8 +279,6 @@
 
   std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
 
-  base::TimeTicks background_updated_timestamp_;
-
   base::WeakPtrFactory<InstantService> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(InstantService);
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index fc86ac3..610fad2 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -581,7 +581,7 @@
 
   // Background color will not update if no background is set.
   instant_service_->UpdateCustomBackgroundColorAsync(
-      base::TimeTicks::Now(), image, image_fetcher::RequestMetadata());
+      GURL(), image, image_fetcher::RequestMetadata());
   thread_bundle()->RunUntilIdle();
   EXPECT_FALSE(CheckBackgroundColor(
       SK_ColorRED,
@@ -597,9 +597,9 @@
   instant_service_->SetCustomBackgroundURLWithAttributions(
       kUrl, kAttributionLine1, kAttributionLine2, kActionUrl);
 
-  // Background color will not update if background timestamp has changed.
+  // Background color will not update if current background url changed.
   instant_service_->UpdateCustomBackgroundColorAsync(
-      base::TimeTicks::Now(), image, image_fetcher::RequestMetadata());
+      GURL("different_url"), image, image_fetcher::RequestMetadata());
   thread_bundle()->RunUntilIdle();
   EXPECT_FALSE(CheckBackgroundColor(
       SK_ColorRED,
@@ -607,52 +607,9 @@
 
   // Background color should update.
   instant_service_->UpdateCustomBackgroundColorAsync(
-      instant_service_->GetBackgroundUpdatedTimestampForTesting(), image,
-      image_fetcher::RequestMetadata());
+      kUrl, image, image_fetcher::RequestMetadata());
   thread_bundle()->RunUntilIdle();
   EXPECT_TRUE(CheckBackgroundColor(
       SK_ColorRED,
       pref_service->GetDictionary(prefs::kNtpCustomBackgroundDict)));
 }
-
-TEST_F(InstantServiceTest, LocalImageDoesNotUpdateCustomBackgroundColor) {
-  SkBitmap bitmap;
-  bitmap.allocN32Pixels(32, 32);
-  bitmap.eraseColor(SK_ColorRED);
-  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap);
-  sync_preferences::TestingPrefServiceSyncable* pref_service =
-      profile()->GetTestingPrefService();
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII("test_file"));
-  base::FilePath copy_path(profile_path.AppendASCII(
-      chrome::kChromeSearchLocalNtpBackgroundFilename));
-  base::WriteFile(path, "background_image", 16);
-
-  instant_service_->SelectLocalBackgroundImage(path);
-
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-
-  const GURL kUrl("https://www.foo.com");
-  const std::string kAttributionLine1 = "foo";
-  const std::string kAttributionLine2 = "bar";
-  const GURL kActionUrl("https://www.bar.com");
-
-  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->SetCustomBackgroundURLWithAttributions(
-      kUrl, kAttributionLine1, kAttributionLine2, kActionUrl);
-  base::TimeTicks time_set =
-      instant_service_->GetBackgroundUpdatedTimestampForTesting();
-
-  instant_service_->SelectLocalBackgroundImage(path);
-
-  // Background color will not update if a local image was uploaded in the
-  // meantime.
-  instant_service_->UpdateCustomBackgroundColorAsync(
-      time_set, image, image_fetcher::RequestMetadata());
-  thread_bundle()->RunUntilIdle();
-  EXPECT_FALSE(CheckBackgroundColor(
-      SK_ColorRED,
-      pref_service->GetDictionary(prefs::kNtpCustomBackgroundDict)));
-}
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index b858066..04dbee0 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -27,7 +27,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/search.h"
@@ -38,6 +37,8 @@
 #include "chrome/browser/sessions/tab_loader.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_list_observer.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_tabrestore.h"
@@ -53,8 +54,6 @@
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/browser/dom_storage_context.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -95,7 +94,7 @@
 // SessionRestoreImpl is responsible for fetching the set of tabs to create
 // from SessionService. SessionRestoreImpl deletes itself when done.
 
-class SessionRestoreImpl : public content::NotificationObserver {
+class SessionRestoreImpl : public BrowserListObserver {
  public:
   SessionRestoreImpl(Profile* profile,
                      Browser* browser,
@@ -155,10 +154,8 @@
       return browser;
     }
 
-    if (browser_) {
-      registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
-                     content::Source<Browser>(browser_));
-    }
+    if (browser_)
+      BrowserList::AddObserver(this);
 
     return browser_;
   }
@@ -252,6 +249,7 @@
   }
 
   ~SessionRestoreImpl() override {
+    BrowserList::RemoveObserver(this);
     active_session_restorers->erase(this);
     if (active_session_restorers->empty()) {
       delete active_session_restorers;
@@ -259,18 +257,10 @@
     }
   }
 
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    switch (type) {
-      case chrome::NOTIFICATION_BROWSER_CLOSED:
-        delete this;
-        return;
-
-      default:
-        NOTREACHED();
-        break;
-    }
+  // BrowserListObserver:
+  void OnBrowserRemoved(Browser* browser) override {
+    if (browser == browser_)
+      delete this;
   }
 
   Profile* profile() { return profile_; }
@@ -318,7 +308,7 @@
       // if the browser is deleted. Don't listen to anything. This avoid a
       // possible double delete too (if browser is closed before DeleteSoon() is
       // processed).
-      registrar_.RemoveAll();
+      BrowserList::RemoveObserver(this);
     }
 
 #if defined(OS_CHROMEOS)
@@ -706,8 +696,6 @@
   std::vector<std::unique_ptr<sessions::SessionWindow>> windows_;
   SessionID active_window_id_;
 
-  content::NotificationRegistrar registrar_;
-
   // When asynchronous it's possible for there to be no windows. To make sure
   // Chrome doesn't prematurely exit we register a KeepAlive for the lifetime
   // of this object.
diff --git a/chrome/browser/signin/signin_manager_android_wrapper.cc b/chrome/browser/signin/signin_manager_android_wrapper.cc
index fc64845a..6d200f94 100644
--- a/chrome/browser/signin/signin_manager_android_wrapper.cc
+++ b/chrome/browser/signin/signin_manager_android_wrapper.cc
@@ -5,10 +5,12 @@
 #include "chrome/browser/signin/signin_manager_android_wrapper.h"
 
 SigninManagerAndroidWrapper::SigninManagerAndroidWrapper(
-    Profile* profile,
+    SigninClient* signin_client,
+    PrefService* local_state_prefs_service,
     identity::IdentityManager* identity_manager,
     std::unique_ptr<SigninManagerDelegate> signin_manager_delegate)
-    : signin_manager_android_(profile,
+    : signin_manager_android_(signin_client,
+                              local_state_prefs_service,
                               identity_manager,
                               std::move(signin_manager_delegate)) {}
 
diff --git a/chrome/browser/signin/signin_manager_android_wrapper.h b/chrome/browser/signin/signin_manager_android_wrapper.h
index 8589a759..c482505 100644
--- a/chrome/browser/signin/signin_manager_android_wrapper.h
+++ b/chrome/browser/signin/signin_manager_android_wrapper.h
@@ -10,13 +10,12 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
-class Profile;
-
 class SigninManagerAndroidWrapper : public KeyedService {
  public:
   // initializes the member signin_manager_android_ and keeps ownership.
   SigninManagerAndroidWrapper(
-      Profile* profile,
+      SigninClient* signin_client,
+      PrefService* local_state_prefs_service,
       identity::IdentityManager* identity_manager,
       std::unique_ptr<SigninManagerDelegate> signin_manager_delegate);
 
diff --git a/chrome/browser/signin/signin_manager_android_wrapper_factory.cc b/chrome/browser/signin/signin_manager_android_wrapper_factory.cc
index 895bee2..a7a90339 100644
--- a/chrome/browser/signin/signin_manager_android_wrapper_factory.cc
+++ b/chrome/browser/signin/signin_manager_android_wrapper_factory.cc
@@ -6,14 +6,18 @@
 
 #include "chrome/browser/android/signin/chrome_signin_manager_delegate.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/chrome_signin_client_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
+#include "chrome/browser/browser_process.h"
+
 SigninManagerAndroidWrapperFactory::SigninManagerAndroidWrapperFactory()
     : BrowserContextKeyedServiceFactory(
           "SigninManagerAndroidWrapper",
           BrowserContextDependencyManager::GetInstance()) {
   DependsOn(IdentityManagerFactory::GetInstance());
+  DependsOn(ChromeSigninClientFactory::GetInstance());
 }
 
 SigninManagerAndroidWrapperFactory::~SigninManagerAndroidWrapperFactory() {}
@@ -36,10 +40,12 @@
 KeyedService* SigninManagerAndroidWrapperFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
+  auto* signin_client = ChromeSigninClientFactory::GetForProfile(profile);
   auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
   auto signin_manager_delegate =
       std::make_unique<ChromeSigninManagerDelegate>();
 
-  return new SigninManagerAndroidWrapper(profile, identity_manager,
-                                         std::move(signin_manager_delegate));
+  return new SigninManagerAndroidWrapper(
+      signin_client, g_browser_process->local_state(), identity_manager,
+      std::move(signin_manager_delegate));
 }
diff --git a/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc b/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
index 28f3fa3b..bba50ba 100644
--- a/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
+++ b/chrome/browser/ssl/chrome_ssl_host_state_delegate.cc
@@ -51,23 +51,18 @@
 // This parameter controls whether the count of recurrent errors is
 // per-browsing-session or persisted to a pref, accumulating across browsing
 // sessions. Default is "in-memory".
-constexpr char kRecurrentInterstitialModeParam[] = "mode";
-constexpr char kRecurrentInterstitialModeInMemory[] = "in-memory";
-constexpr char kRecurrentInterstitialModePref[] = "pref";
-
 #if defined(OS_ANDROID)
-const base::FeatureParam<std::string> kRecurrentInterstitialMode{
-    &kRecurrentInterstitialFeature, kRecurrentInterstitialModeParam,
-    kRecurrentInterstitialModePref};
+ChromeSSLHostStateDelegate::RecurrentInterstitialMode
+    kRecurrentInterstitialDefaultMode =
+        ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF;
 #else
-const base::FeatureParam<std::string> kRecurrentInterstitialMode{
-    &kRecurrentInterstitialFeature, kRecurrentInterstitialModeParam,
-    kRecurrentInterstitialModeInMemory};
+ChromeSSLHostStateDelegate::RecurrentInterstitialMode
+    kRecurrentInterstitialDefaultMode =
+        ChromeSSLHostStateDelegate::RecurrentInterstitialMode::IN_MEMORY;
 #endif
 
 // The number of times an error must recur before the recurrent error message is
 // shown.
-constexpr char kRecurrentInterstitialThresholdParam[] = "threshold";
 constexpr int kRecurrentInterstitialDefaultThreshold = 3;
 
 // If "mode" is "pref", a pref stores the time at which each error most recently
@@ -75,7 +70,6 @@
 // more than the threshold number of times with the most recent instance being
 // less than |kRecurrentInterstitialResetTimeParam| seconds in the past. The
 // default is 3 days.
-constexpr char kRecurrentInterstitialResetTimeParam[] = "reset-time";
 constexpr int kRecurrentInterstitialDefaultResetTime =
     259200;  // 3 days in seconds
 
@@ -147,18 +141,17 @@
 bool DoesRecurrentInterstitialPrefMeetThreshold(Profile* profile,
                                                 base::Clock* clock,
                                                 int error,
-                                                int threshold) {
+                                                int threshold,
+                                                int error_reset_time) {
   const base::DictionaryValue* pref =
       profile->GetPrefs()->GetDictionary(prefs::kRecurrentSSLInterstitial);
   const base::Value* list_value = pref->FindKey(net::ErrorToShortString(error));
   if (!list_value)
     return false;
 
-  base::Time cutoff_time =
-      clock->Now() -
-      base::TimeDelta::FromSeconds(base::GetFieldTrialParamByFeatureAsInt(
-          kRecurrentInterstitialFeature, kRecurrentInterstitialResetTimeParam,
-          kRecurrentInterstitialDefaultResetTime));
+  base::Time cutoff_time;
+  cutoff_time = clock->Now() - base::TimeDelta::FromSeconds(error_reset_time);
+
   // Assume that the values in the list are in increasing order;
   // UpdateRecurrentInterstitialPref() maintains this ordering. Check if there
   // are more than |threshold| values after the cutoff time.
@@ -248,18 +241,16 @@
 
 }  // namespace
 
-// TODO(https://crbug.com/953972): Remove this and all dependent code paths.
-const base::Feature kRecurrentInterstitialFeature{
-    "RecurrentInterstitialFeature", base::FEATURE_ENABLED_BY_DEFAULT};
-
 ChromeSSLHostStateDelegate::ChromeSSLHostStateDelegate(Profile* profile)
     : clock_(new base::DefaultClock()),
-      profile_(profile) {
+      profile_(profile),
+      recurrent_interstitial_threshold_for_testing(-1),
+      recurrent_interstitial_mode_for_testing(NOT_SET),
+      recurrent_interstitial_reset_time_for_testing(-1) {
   MigrateOldSettings(HostContentSettingsMapFactory::GetForProfile(profile));
 }
 
-ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {
-}
+ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {}
 
 void ChromeSSLHostStateDelegate::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
@@ -469,17 +460,10 @@
       error != net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED) {
     return;
   }
-
-  if (!base::FeatureList::IsEnabled(kRecurrentInterstitialFeature)) {
-    return;
-  }
-
-  const std::string mode_param = kRecurrentInterstitialMode.Get();
-  const int threshold = base::GetFieldTrialParamByFeatureAsInt(
-      kRecurrentInterstitialFeature, kRecurrentInterstitialThresholdParam,
-      kRecurrentInterstitialDefaultThreshold);
-
-  if (mode_param.empty() || mode_param == kRecurrentInterstitialModeInMemory) {
+  RecurrentInterstitialMode mode_param = GetRecurrentInterstitialMode();
+  const int threshold = GetRecurrentInterstitialThreshold();
+  if (mode_param ==
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::IN_MEMORY) {
     const auto count_it = recurrent_errors_.find(error);
     if (count_it == recurrent_errors_.end()) {
       recurrent_errors_[error] = 1;
@@ -489,29 +473,26 @@
       return;
     }
     recurrent_errors_[error] = count_it->second + 1;
-  } else if (mode_param == kRecurrentInterstitialModePref) {
+  } else if (mode_param ==
+             ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF) {
     UpdateRecurrentInterstitialPref(profile_, clock_.get(), error, threshold);
   }
 }
 
 bool ChromeSSLHostStateDelegate::HasSeenRecurrentErrors(int error) const {
-  if (!base::FeatureList::IsEnabled(kRecurrentInterstitialFeature)) {
-    return false;
-  }
-
-  const std::string mode_param = kRecurrentInterstitialMode.Get();
-  const int threshold = base::GetFieldTrialParamByFeatureAsInt(
-      kRecurrentInterstitialFeature, kRecurrentInterstitialThresholdParam,
-      kRecurrentInterstitialDefaultThreshold);
-
-  if (mode_param.empty() || mode_param == kRecurrentInterstitialModeInMemory) {
+  RecurrentInterstitialMode mode_param = GetRecurrentInterstitialMode();
+  const int threshold = GetRecurrentInterstitialThreshold();
+  if (mode_param ==
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::IN_MEMORY) {
     const auto count_it = recurrent_errors_.find(error);
     if (count_it == recurrent_errors_.end())
       return false;
     return count_it->second >= threshold;
-  } else if (mode_param == kRecurrentInterstitialModePref) {
-    return DoesRecurrentInterstitialPrefMeetThreshold(profile_, clock_.get(),
-                                                      error, threshold);
+  } else if (mode_param ==
+             ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF) {
+    return DoesRecurrentInterstitialPrefMeetThreshold(
+        profile_, clock_.get(), error, threshold,
+        GetRecurrentInterstitialResetTime());
   }
 
   return false;
@@ -529,6 +510,46 @@
   clock_ = std::move(clock);
 }
 
+void ChromeSSLHostStateDelegate::SetRecurrentInterstitialThresholdForTesting(
+    int threshold) {
+  recurrent_interstitial_threshold_for_testing = threshold;
+}
+
+void ChromeSSLHostStateDelegate::SetRecurrentInterstitialModeForTesting(
+    ChromeSSLHostStateDelegate::RecurrentInterstitialMode mode) {
+  recurrent_interstitial_mode_for_testing = mode;
+}
+
+void ChromeSSLHostStateDelegate::SetRecurrentInterstitialResetTimeForTesting(
+    int reset) {
+  recurrent_interstitial_reset_time_for_testing = reset;
+}
+
+int ChromeSSLHostStateDelegate::GetRecurrentInterstitialThreshold() const {
+  if (recurrent_interstitial_threshold_for_testing == -1) {
+    return kRecurrentInterstitialDefaultThreshold;
+  } else {
+    return recurrent_interstitial_threshold_for_testing;
+  }
+}
+
+int ChromeSSLHostStateDelegate::GetRecurrentInterstitialResetTime() const {
+  if (recurrent_interstitial_reset_time_for_testing == -1) {
+    return kRecurrentInterstitialDefaultResetTime;
+  } else {
+    return recurrent_interstitial_reset_time_for_testing;
+  }
+}
+
+ChromeSSLHostStateDelegate::RecurrentInterstitialMode
+ChromeSSLHostStateDelegate::GetRecurrentInterstitialMode() const {
+  if (recurrent_interstitial_mode_for_testing == NOT_SET) {
+    return kRecurrentInterstitialDefaultMode;
+  } else {
+    return recurrent_interstitial_mode_for_testing;
+  }
+}
+
 // This helper function gets the dictionary of certificate fingerprints to
 // errors of certificates that have been accepted by the user from the content
 // dictionary that has been passed in. The returned pointer is owned by the the
diff --git a/chrome/browser/ssl/chrome_ssl_host_state_delegate.h b/chrome/browser/ssl/chrome_ssl_host_state_delegate.h
index 870949b8..176ebf8 100644
--- a/chrome/browser/ssl/chrome_ssl_host_state_delegate.h
+++ b/chrome/browser/ssl/chrome_ssl_host_state_delegate.h
@@ -35,6 +35,8 @@
 // - when errors have recurred multiple times
 class ChromeSSLHostStateDelegate : public content::SSLHostStateDelegate {
  public:
+  enum RecurrentInterstitialMode { PREF, IN_MEMORY, NOT_SET };
+
   explicit ChromeSSLHostStateDelegate(Profile* profile);
   ~ChromeSSLHostStateDelegate() override;
 
@@ -83,6 +85,15 @@
   // SetClockForTesting takes ownership of the passed in clock.
   void SetClockForTesting(std::unique_ptr<base::Clock> clock);
 
+  void SetRecurrentInterstitialThresholdForTesting(int threshold);
+  void SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode mode);
+  void SetRecurrentInterstitialResetTimeForTesting(int reset);
+
+  RecurrentInterstitialMode GetRecurrentInterstitialMode() const;
+  int GetRecurrentInterstitialThreshold() const;
+  int GetRecurrentInterstitialResetTime() const;
+
  private:
   // Used to specify whether new content setting entries should be created if
   // they don't already exist when querying the user's settings.
@@ -131,6 +142,10 @@
   std::map<int /* error code */, int /* count */> recurrent_errors_;
 
   DISALLOW_COPY_AND_ASSIGN(ChromeSSLHostStateDelegate);
+
+  int recurrent_interstitial_threshold_for_testing;
+  enum RecurrentInterstitialMode recurrent_interstitial_mode_for_testing;
+  int recurrent_interstitial_reset_time_for_testing;
 };
 
 #endif  // CHROME_BROWSER_SSL_CHROME_SSL_HOST_STATE_DELEGATE_H_
diff --git a/chrome/browser/ssl/chrome_ssl_host_state_delegate_test.cc b/chrome/browser/ssl/chrome_ssl_host_state_delegate_test.cc
index 0f8c052e..01211eb0 100644
--- a/chrome/browser/ssl/chrome_ssl_host_state_delegate_test.cc
+++ b/chrome/browser/ssl/chrome_ssl_host_state_delegate_test.cc
@@ -344,15 +344,15 @@
 // after seeing an error of interest multiple times, in the default mode in
 // which error occurrences are stored in-memory.
 IN_PROC_BROWSER_TEST_F(ChromeSSLHostStateDelegateTest, HasSeenRecurrentErrors) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(kRecurrentInterstitialFeature,
-                                                  {{"threshold", "2"}});
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
   content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
   ChromeSSLHostStateDelegate* chrome_state =
       static_cast<ChromeSSLHostStateDelegate*>(state);
+  chrome_state->SetRecurrentInterstitialThresholdForTesting(2);
+  chrome_state->SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF);
 
   chrome_state->DidDisplayErrorPage(net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED);
   EXPECT_FALSE(chrome_state->HasSeenRecurrentErrors(
@@ -370,15 +370,15 @@
 // count of each error is persisted across browsing sessions).
 IN_PROC_BROWSER_TEST_F(ChromeSSLHostStateDelegateTest,
                        HasSeenRecurrentErrorsPref) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      kRecurrentInterstitialFeature, {{"threshold", "2"}, {"mode", "pref"}});
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
   content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
   ChromeSSLHostStateDelegate* chrome_state =
       static_cast<ChromeSSLHostStateDelegate*>(state);
+  chrome_state->SetRecurrentInterstitialThresholdForTesting(2);
+  chrome_state->SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF);
 
   chrome_state->DidDisplayErrorPage(net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED);
   EXPECT_FALSE(chrome_state->HasSeenRecurrentErrors(
@@ -396,6 +396,10 @@
   // Create a new ChromeSSLHostStateDelegate to check that the state has been
   // saved to the pref and that the new ChromeSSLHostStateDelegate reads it.
   ChromeSSLHostStateDelegate new_state(profile);
+  new_state.SetRecurrentInterstitialThresholdForTesting(2);
+  new_state.SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF);
+
   EXPECT_TRUE(new_state.HasSeenRecurrentErrors(
       net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED));
   EXPECT_TRUE(new_state.HasSeenRecurrentErrors(net::ERR_CERT_SYMANTEC_LEGACY));
@@ -410,15 +414,15 @@
 // going backwards in pref mode.
 IN_PROC_BROWSER_TEST_F(ChromeSSLHostStateDelegateTest,
                        HasSeenRecurrentErrorsPrefClockGoesBackwards) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      kRecurrentInterstitialFeature, {{"threshold", "2"}, {"mode", "pref"}});
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
   content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
   ChromeSSLHostStateDelegate* chrome_state =
       static_cast<ChromeSSLHostStateDelegate*>(state);
+  chrome_state->SetRecurrentInterstitialThresholdForTesting(2);
+  chrome_state->SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF);
 
   base::SimpleTestClock* clock = new base::SimpleTestClock();
   clock->SetNow(base::Time::Now());
@@ -448,16 +452,15 @@
 // threshold of 3 errors, unlike previous tests which use a threshold of 2.
 IN_PROC_BROWSER_TEST_F(ChromeSSLHostStateDelegateTest,
                        HasSeenRecurrentErrorsPrefErrorsInPast) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeatureWithParameters(
-      kRecurrentInterstitialFeature,
-      {{"threshold", "3"}, {"mode", "pref"}, {"reset-time", "10"}});
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
   content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
   ChromeSSLHostStateDelegate* chrome_state =
       static_cast<ChromeSSLHostStateDelegate*>(state);
+  chrome_state->SetRecurrentInterstitialResetTimeForTesting(10);
+  chrome_state->SetRecurrentInterstitialModeForTesting(
+      ChromeSSLHostStateDelegate::RecurrentInterstitialMode::PREF);
 
   base::SimpleTestClock* clock = new base::SimpleTestClock();
   clock->SetNow(base::Time::Now());
@@ -774,4 +777,4 @@
       content::SSLHostStateDelegate::ALLOWED,
       state->QueryPolicy("127.0.0.1", *cert, net::ERR_CERT_COMMON_NAME_INVALID,
                          &unused_value));
-}
+}
\ No newline at end of file
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index ab29d39e..67eaedf 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -7760,56 +7760,42 @@
   mock_cert_verifier()->set_default_result(
       net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED);
 
-  // When |show_error_message| is false, the test checks the field trial
-  // configuration in which recurrent errors are tracked and histograms are
-  // recorded but the interstitial UI isn't actually modified. This
-  // configuration allows comparison of clickthrough rates for the exact same
-  // error conditions with and without the modified error UI.
-  for (const auto& show_error_message : {false, true}) {
-    ChromeSSLHostStateDelegate* state =
-        reinterpret_cast<ChromeSSLHostStateDelegate*>(
-            browser()->profile()->GetSSLHostStateDelegate());
-    state->ResetRecurrentErrorCountForTesting();
+  ChromeSSLHostStateDelegate* state =
+      reinterpret_cast<ChromeSSLHostStateDelegate*>(
+          browser()->profile()->GetSSLHostStateDelegate());
+  state->ResetRecurrentErrorCountForTesting();
 
-    base::HistogramTester histograms;
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitAndEnableFeatureWithParameters(
-        kRecurrentInterstitialFeature,
-        {{"threshold", "2"},
-         {"show_error_ui", show_error_message ? "true" : "false"}});
+  base::HistogramTester histograms;
+  state->SetRecurrentInterstitialThresholdForTesting(2);
 
-    // Use different hostnames for the two test cases to avoid the clickthrough
-    // from one interfering with the other.
-    GURL url =
-        https_server.GetURL(show_error_message ? "show_error_message.test"
-                                               : "no_error_message.test",
-                            "/");
-    ui_test_utils::NavigateToURL(browser(), url);
-    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
-    WaitForInterstitial(tab);
-    ExpectInterstitialElementHidden(tab, "recurrent-error-message",
-                                    true /* expect_hidden */);
-    histograms.ExpectUniqueSample(kRecurrentInterstitialHistogram, false, 1);
+  // Use different hostnames for the two test cases to avoid the clickthrough
+  // from one interfering with the other.
+  GURL url = https_server.GetURL("show_error_message.test", "/");
+  ui_test_utils::NavigateToURL(browser(), url);
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  WaitForInterstitial(tab);
+  ExpectInterstitialElementHidden(tab, "recurrent-error-message",
+                                  true /* expect_hidden */);
+  histograms.ExpectUniqueSample(kRecurrentInterstitialHistogram, false, 1);
 
-    ui_test_utils::NavigateToURL(browser(), url);
-    WaitForInterstitial(tab);
-    ExpectInterstitialElementHidden(tab, "recurrent-error-message",
-                                    !show_error_message /* expect_hidden */);
-    histograms.ExpectBucketCount(kRecurrentInterstitialHistogram, true, 1);
-    histograms.ExpectUniqueSample(
-        kRecurrentInterstitialActionHistogram,
-        SSLErrorControllerClient::RecurrentErrorAction::kShow, 1);
+  ui_test_utils::NavigateToURL(browser(), url);
+  WaitForInterstitial(tab);
+  ExpectInterstitialElementHidden(tab, "recurrent-error-message",
+                                  false /* expect_hidden */);
+  histograms.ExpectBucketCount(kRecurrentInterstitialHistogram, true, 1);
+  histograms.ExpectUniqueSample(
+      kRecurrentInterstitialActionHistogram,
+      SSLErrorControllerClient::RecurrentErrorAction::kShow, 1);
 
-    // Proceed through the interstitial and observe that the histogram is
-    // recorded correctly.
-    content::TestNavigationObserver nav_observer(tab, 1);
-    ASSERT_TRUE(content::ExecuteScript(
-        tab, "window.certificateErrorPageController.proceed();"));
-    nav_observer.Wait();
-    histograms.ExpectBucketCount(
-        kRecurrentInterstitialActionHistogram,
-        SSLErrorControllerClient::RecurrentErrorAction::kProceed, 1);
-  }
+  // Proceed through the interstitial and observe that the histogram is
+  // recorded correctly.
+  content::TestNavigationObserver nav_observer(tab, 1);
+  ASSERT_TRUE(content::ExecuteScript(
+      tab, "window.certificateErrorPageController.proceed();"));
+  nav_observer.Wait();
+  histograms.ExpectBucketCount(
+      kRecurrentInterstitialActionHistogram,
+      SSLErrorControllerClient::RecurrentErrorAction::kProceed, 1);
 }
 
 // TODO(jcampan): more tests to do below.
diff --git a/chrome/browser/ssl/ssl_error_controller_client.cc b/chrome/browser/ssl/ssl_error_controller_client.cc
index 60966d13..abfeb18a 100644
--- a/chrome/browser/ssl/ssl_error_controller_client.cc
+++ b/chrome/browser/ssl/ssl_error_controller_client.cc
@@ -226,7 +226,5 @@
 }
 
 bool SSLErrorControllerClient::HasSeenRecurrentError() {
-  return HasSeenRecurrentErrorInternal(web_contents_, cert_error_) &&
-         base::GetFieldTrialParamByFeatureAsBool(kRecurrentInterstitialFeature,
-                                                 "show_error_ui", true);
+  return HasSeenRecurrentErrorInternal(web_contents_, cert_error_);
 }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c7d46ac..f25c3847 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -112,8 +112,10 @@
     "find_bar/find_bar_state_factory.cc",
     "find_bar/find_bar_state_factory.h",
     "find_bar/find_notification_details.h",
+    "find_bar/find_result_observer.h",
     "find_bar/find_tab_helper.cc",
     "find_bar/find_tab_helper.h",
+    "find_bar/find_types.h",
     "interventions/framebust_block_message_delegate.cc",
     "interventions/framebust_block_message_delegate.h",
     "interventions/intervention_delegate.h",
@@ -635,10 +637,6 @@
       "android/autofill/card_unmask_prompt_view_android.h",
       "android/autofill/credit_card_scanner_view_android.cc",
       "android/autofill/credit_card_scanner_view_android.h",
-      "android/bluetooth_chooser_android.cc",
-      "android/bluetooth_chooser_android.h",
-      "android/bluetooth_scanning_prompt_android.cc",
-      "android/bluetooth_scanning_prompt_android.h",
       "android/chrome_http_auth_handler.cc",
       "android/chrome_http_auth_handler.h",
       "android/color_chooser_dialog_android.cc",
@@ -648,6 +646,12 @@
       "android/content_settings/popup_blocked_infobar_delegate.h",
       "android/context_menu_helper.cc",
       "android/context_menu_helper.h",
+      "android/device_dialog/bluetooth_chooser_android.cc",
+      "android/device_dialog/bluetooth_chooser_android.h",
+      "android/device_dialog/bluetooth_scanning_prompt_android.cc",
+      "android/device_dialog/bluetooth_scanning_prompt_android.h",
+      "android/device_dialog/usb_chooser_dialog_android.cc",
+      "android/device_dialog/usb_chooser_dialog_android.h",
       "android/external_protocol_dialog_android.cc",
       "android/infobars/ads_blocked_infobar.cc",
       "android/infobars/ads_blocked_infobar.h",
@@ -743,8 +747,6 @@
       "android/tab_model/tab_model_observer_jni_bridge.h",
       "android/toolbar/location_bar_model_android.cc",
       "android/toolbar/location_bar_model_android.h",
-      "android/usb_chooser_dialog_android.cc",
-      "android/usb_chooser_dialog_android.h",
       "android/view_android_helper.cc",
       "android/view_android_helper.h",
       "browser_otr_state_android.cc",
diff --git a/chrome/browser/ui/android/device_dialog/OWNERS b/chrome/browser/ui/android/device_dialog/OWNERS
new file mode 100644
index 0000000..298cd1c
--- /dev/null
+++ b/chrome/browser/ui/android/device_dialog/OWNERS
@@ -0,0 +1,2 @@
+juncai@chromium.org
+reillyg@chromium.org
diff --git a/chrome/browser/ui/android/bluetooth_chooser_android.cc b/chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.cc
similarity index 98%
rename from chrome/browser/ui/android/bluetooth_chooser_android.cc
rename to chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.cc
index ed15f40..9d77bea 100644
--- a/chrome/browser/ui/android/bluetooth_chooser_android.cc
+++ b/chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/android/bluetooth_chooser_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h"
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
diff --git a/chrome/browser/ui/android/bluetooth_chooser_android.h b/chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h
similarity index 90%
rename from chrome/browser/ui/android/bluetooth_chooser_android.h
rename to chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h
index af41b0e..05a7452 100644
--- a/chrome/browser/ui/android/bluetooth_chooser_android.h
+++ b/chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_ANDROID_BLUETOOTH_CHOOSER_ANDROID_H_
-#define CHROME_BROWSER_UI_ANDROID_BLUETOOTH_CHOOSER_ANDROID_H_
+#ifndef CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_CHOOSER_ANDROID_H_
+#define CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_CHOOSER_ANDROID_H_
 
 #include "base/android/scoped_java_ref.h"
 #include "content/public/browser/bluetooth_chooser.h"
@@ -58,4 +58,4 @@
   BluetoothChooser::EventHandler event_handler_;
 };
 
-#endif  // CHROME_BROWSER_UI_ANDROID_BLUETOOTH_CHOOSER_ANDROID_H_
+#endif  // CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_CHOOSER_ANDROID_H_
diff --git a/chrome/browser/ui/android/bluetooth_scanning_prompt_android.cc b/chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.cc
similarity index 97%
rename from chrome/browser/ui/android/bluetooth_scanning_prompt_android.cc
rename to chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.cc
index e941cac..fe2a1bc 100644
--- a/chrome/browser/ui/android/bluetooth_scanning_prompt_android.cc
+++ b/chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/android/bluetooth_scanning_prompt_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h"
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
diff --git a/chrome/browser/ui/android/bluetooth_scanning_prompt_android.h b/chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h
similarity index 82%
rename from chrome/browser/ui/android/bluetooth_scanning_prompt_android.h
rename to chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h
index 9ec2a32..89fe02f 100644
--- a/chrome/browser/ui/android/bluetooth_scanning_prompt_android.h
+++ b/chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_ANDROID_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
-#define CHROME_BROWSER_UI_ANDROID_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
+#ifndef CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
+#define CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
 
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
@@ -16,7 +16,7 @@
 class BluetoothScanningPromptAndroid : public content::BluetoothScanningPrompt {
  public:
   // A Java counterpart will be generated for this enum.
-  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser
+  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.device_dialog
   enum BluetoothScanningPermissionEvent {
     ALLOW = 0,
     BLOCK = 1,
@@ -47,4 +47,4 @@
   DISALLOW_COPY_AND_ASSIGN(BluetoothScanningPromptAndroid);
 };
 
-#endif  // CHROME_BROWSER_UI_ANDROID_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
+#endif  // CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_BLUETOOTH_SCANNING_PROMPT_ANDROID_H_
diff --git a/chrome/browser/ui/android/usb_chooser_dialog_android.cc b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
similarity index 98%
rename from chrome/browser/ui/android/usb_chooser_dialog_android.cc
rename to chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
index 78423c1..2160e48 100644
--- a/chrome/browser/ui/android/usb_chooser_dialog_android.cc
+++ b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/android/usb_chooser_dialog_android.h"
+#include "chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h"
 
 #include <stddef.h>
 
diff --git a/chrome/browser/ui/android/usb_chooser_dialog_android.h b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h
similarity index 90%
rename from chrome/browser/ui/android/usb_chooser_dialog_android.h
rename to chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h
index 97f5498..00ce6b7 100644
--- a/chrome/browser/ui/android/usb_chooser_dialog_android.h
+++ b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_ANDROID_USB_CHOOSER_DIALOG_ANDROID_H_
-#define CHROME_BROWSER_UI_ANDROID_USB_CHOOSER_DIALOG_ANDROID_H_
+#ifndef CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_USB_CHOOSER_DIALOG_ANDROID_H_
+#define CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_USB_CHOOSER_DIALOG_ANDROID_H_
 
 #include <memory>
 #include <string>
@@ -69,4 +69,4 @@
   DISALLOW_COPY_AND_ASSIGN(UsbChooserDialogAndroid);
 };
 
-#endif  // CHROME_BROWSER_UI_ANDROID_USB_CHOOSER_DIALOG_ANDROID_H_
+#endif  // CHROME_BROWSER_UI_ANDROID_DEVICE_DIALOG_USB_CHOOSER_DIALOG_ANDROID_H_
diff --git a/chrome/browser/ui/app_list/search/omnibox_result.cc b/chrome/browser/ui/app_list/search/omnibox_result.cc
index 018dcaa5..801e9c1 100644
--- a/chrome/browser/ui/app_list/search/omnibox_result.cc
+++ b/chrome/browser/ui/app_list/search/omnibox_result.cc
@@ -31,8 +31,7 @@
 
 namespace {
 
-// #000 at 87% opacity.
-constexpr SkColor kListIconColor = SkColorSetARGB(0xDE, 0x00, 0x00, 0x00);
+constexpr SkColor kListIconColor = gfx::kGoogleGrey700;
 
 int ACMatchStyleToTagStyle(int styles) {
   int tag_styles = 0;
diff --git a/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
index 6f39ae3b..92aec05 100644
--- a/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/app_menu_constants.h"
 #include "ash/public/cpp/shelf_item.h"
 #include "base/bind_helpers.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
diff --git a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
index 9c1b3b1..28335b4 100644
--- a/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_context_menu_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/bind_test_util.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/chromeos/arc/icon_decode_request.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index a9da630..53fe269 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -182,6 +182,7 @@
 #include "components/sessions/core/tab_restore_service.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
 #include "components/translate/core/browser/language_state.h"
+#include "components/user_manager/user_manager.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "components/zoom/zoom_controller.h"
@@ -237,6 +238,8 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/user_manager/user_manager.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -314,6 +317,26 @@
          (!profile->IsGuestSession() || profile->IsOffTheRecord()) &&
          profile->AllowsBrowserWindows();
 }
+bool IsOnKioskSplashScreen() {
+#if defined(OS_CHROMEOS)
+  session_manager::SessionManager* session_manager =
+      session_manager::SessionManager::Get();
+  if (!session_manager)
+    return false;
+  // We have to check this way because of CHECK() in UserManager::Get().
+  if (!user_manager::UserManager::IsInitialized())
+    return false;
+  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+  if (!user_manager->IsLoggedInAsKioskApp() &&
+      !user_manager->IsLoggedInAsArcKioskApp())
+    return false;
+  if (session_manager->session_state() != session_manager::SessionState::LOGIN_PRIMARY)
+    return false;
+  return true;
+#else
+  return false;
+#endif
+}
 
 }  // namespace
 
@@ -394,6 +417,10 @@
 Browser* Browser::Create(const CreateParams& params) {
   if (!CanCreateBrowserForProfile(params.profile))
     return nullptr;
+  // Disable browser creation on kiosk mode while loading.
+  if (IsOnKioskSplashScreen())
+    return nullptr;
+
   return new Browser(params);
 }
 
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index dee922d..e96de7b 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -47,6 +47,7 @@
 #include "chrome/browser/ui/find_bar/find_bar.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/in_product_help/reopen_tab_in_product_help.h"
 #include "chrome/browser/ui/in_product_help/reopen_tab_in_product_help_factory.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
@@ -1122,8 +1123,7 @@
 
 void CloseFind(Browser* browser) {
   browser->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 }
 
 void Zoom(Browser* browser, content::PageZoom zoom) {
diff --git a/chrome/browser/ui/browser_unittest.cc b/chrome/browser/ui/browser_unittest.cc
index 7e60b65..1df794b 100644
--- a/chrome/browser/ui/browser_unittest.cc
+++ b/chrome/browser/ui/browser_unittest.cc
@@ -12,6 +12,11 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/user_manager/user_names.h"
+#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
 #include "components/zoom/zoom_controller.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/site_instance.h"
@@ -22,6 +27,7 @@
 using content::SiteInstance;
 using content::WebContents;
 using content::WebContentsTester;
+using session_manager::SessionState;
 
 class BrowserUnitTest : public BrowserWithTestWindowTest {
  public:
@@ -268,6 +274,39 @@
   EXPECT_TRUE(otr_browser);
 }
 
+#if defined(OS_CHROMEOS)
+TEST_F(BrowserUnitTest, CreateBrowserDuringKioskSplashScreen) {
+  session_manager::SessionManager session_manager;
+
+  // Setting up user manager state to be in kiosk mode:
+  // Creating a new user manager.
+  chromeos::FakeChromeUserManager* user_manager =
+      new chromeos::FakeChromeUserManager();
+  user_manager::ScopedUserManager manager{
+      std::unique_ptr<user_manager::UserManager>(user_manager)};
+  const user_manager::User* user =
+      user_manager->AddKioskAppUser(AccountId::FromUserEmail("fake_user@test"));
+  user_manager->LoginUser(user->GetAccountId());
+
+  TestingProfile profile;
+  Browser::CreateParams create_params(&profile, false);
+
+  std::unique_ptr<BrowserWindow> window1(CreateBrowserWindow());
+  create_params.window = window1.get();
+  session_manager.SetSessionState(SessionState::LOGIN_PRIMARY);
+  std::unique_ptr<Browser> test_browser(Browser::Create(create_params));
+  // Browser should not be created during login session state.
+  EXPECT_FALSE(test_browser);
+
+  std::unique_ptr<BrowserWindow> window2(CreateBrowserWindow());
+  create_params.window = window2.get();
+  session_manager.SetSessionState(SessionState::ACTIVE);
+  std::unique_ptr<Browser> test_browser2(Browser::Create(create_params));
+  // Normal flow, creation succeeds.
+  EXPECT_TRUE(test_browser2);
+}
+#endif
+
 class BrowserBookmarkBarTest : public BrowserWithTestWindowTest {
  public:
   BrowserBookmarkBarTest() {}
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.cc b/chrome/browser/ui/find_bar/find_bar_controller.cc
index 8d453fd..1ec625d 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.cc
+++ b/chrome/browser/ui/find_bar/find_bar_controller.cc
@@ -11,7 +11,6 @@
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -22,10 +21,12 @@
 #include "chrome/browser/ui/find_bar/find_bar_state.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
+#include "content/public/browser/notification_types.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/range/range.h"
@@ -118,8 +119,9 @@
   find_bar_->SetFocusAndSelection();
 }
 
-void FindBarController::EndFindSession(SelectionAction selection_action,
-                                       ResultAction result_action) {
+void FindBarController::EndFindSession(
+    FindOnPageSelectionAction selection_action,
+    FindBoxResultAction result_action) {
   find_bar_->Hide(true);
 
   // |web_contents_| can be NULL for a number of reasons, for example when the
@@ -133,7 +135,7 @@
     // tickmarks and highlighting.
     find_tab_helper->StopFinding(selection_action);
 
-    if (result_action == kClearResultsInFindBox)
+    if (result_action == FindBoxResultAction::kClear)
       find_bar_->ClearResults(find_tab_helper->find_result());
 
     // When we get dismissed we restore the focus to where it belongs.
@@ -152,13 +154,17 @@
 
     FindTabHelper* find_tab_helper =
         FindTabHelper::FromWebContents(web_contents_);
-    if (find_tab_helper)
+    if (find_tab_helper) {
       find_tab_helper->set_selected_range(find_bar_->GetSelectedRange());
+      find_tab_observer_.Remove(find_tab_helper);
+    }
   }
 
   web_contents_ = contents;
   FindTabHelper* find_tab_helper =
       web_contents_ ? FindTabHelper::FromWebContents(web_contents_) : nullptr;
+  if (find_tab_helper)
+    find_tab_observer_.Add(find_tab_helper);
 
   // Hide any visible find window from the previous tab if a NULL tab contents
   // is passed in or if the find UI is not active in the new tab.
@@ -170,9 +176,6 @@
   if (!web_contents_)
     return;
 
-  registrar_.Add(this,
-                 chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-                 content::Source<WebContents>(web_contents_));
   registrar_.Add(
       this,
       content::NOTIFICATION_NAV_ENTRY_COMMITTED,
@@ -208,24 +211,21 @@
 void FindBarController::Observe(int type,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
-  if (type == chrome::NOTIFICATION_FIND_RESULT_AVAILABLE) {
-    DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
-    OnFindResultAvailable();
-  } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) {
-    NavigationController* source_controller =
-        content::Source<NavigationController>(source).ptr();
-    if (source_controller == &web_contents_->GetController()) {
-      content::LoadCommittedDetails* commit_details =
-          content::Details<content::LoadCommittedDetails>(details).ptr();
-      ui::PageTransition transition_type =
-          commit_details->entry->GetTransitionType();
-      // Hide the find bar on reload or navigation.
-      if (find_bar_->IsFindBarVisible() && commit_details->is_main_frame &&
-          (ui::PageTransitionCoreTypeIs(transition_type,
-                                        ui::PAGE_TRANSITION_RELOAD) ||
-           commit_details->is_navigation_to_different_page()))
-        EndFindSession(kKeepSelectionOnPage, kClearResultsInFindBox);
-    }
+  DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type);
+  NavigationController* source_controller =
+      content::Source<NavigationController>(source).ptr();
+  if (source_controller == &web_contents_->GetController()) {
+    content::LoadCommittedDetails* commit_details =
+        content::Details<content::LoadCommittedDetails>(details).ptr();
+    ui::PageTransition transition_type =
+        commit_details->entry->GetTransitionType();
+    // Hide the find bar on reload or navigation.
+    if (find_bar_->IsFindBarVisible() && commit_details->is_main_frame &&
+        (ui::PageTransitionCoreTypeIs(transition_type,
+                                      ui::PAGE_TRANSITION_RELOAD) ||
+         commit_details->is_navigation_to_different_page()))
+      EndFindSession(FindOnPageSelectionAction::kKeep,
+                     FindBoxResultAction::kClear);
   }
 }
 
@@ -269,7 +269,9 @@
   return new_pos;
 }
 
-void FindBarController::OnFindResultAvailable() {
+void FindBarController::OnFindResultAvailable(
+    content::WebContents* web_contents) {
+  DCHECK_EQ(web_contents, web_contents_);
   UpdateFindBarForCurrentResult();
 
   FindTabHelper* find_tab_helper =
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.h b/chrome/browser/ui/find_bar/find_bar_controller.h
index f6c5692c..7d5809d 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.h
+++ b/chrome/browser/ui/find_bar/find_bar_controller.h
@@ -8,8 +8,11 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string16.h"
 #include "chrome/browser/ui/find_bar/find_bar_platform_helper.h"
+#include "chrome/browser/ui/find_bar/find_result_observer.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 
@@ -24,24 +27,12 @@
 class Rect;
 }
 
-class FindBarController : public content::NotificationObserver {
+enum class FindOnPageSelectionAction;
+enum class FindBoxResultAction;
+
+class FindBarController : public content::NotificationObserver,
+                          public FindResultObserver {
  public:
-  // An enum listing the possible actions to take on a find-in-page selection
-  // in the page when ending the find session.
-  enum SelectionAction {
-    kKeepSelectionOnPage,     // Translate the find selection into a normal
-                              // selection.
-    kClearSelectionOnPage,    // Clear the find selection.
-    kActivateSelectionOnPage  // Focus and click the selected node (for links).
-  };
-
-  // An enum listing the possible actions to take on a find-in-page results in
-  // the Find box when ending the find session.
-  enum ResultAction {
-    kClearResultsInFindBox,  // Clear search string, ordinal and match count.
-    kKeepResultsInFindBox,   // Leave the results untouched.
-  };
-
   // FindBar takes ownership of |find_bar_view|.
   FindBarController(FindBar* find_bar, Browser* browser);
 
@@ -51,10 +42,10 @@
   void Show();
 
   // Ends the current session. |selection_action| specifies what to do with the
-  // selection on the page created by the find operation. |results_action|
+  // selection on the page created by the find operation. |result_action|
   // specifies what to do with the contents of the Find box (after ending).
-  void EndFindSession(SelectionAction selection_action,
-                      ResultAction results_action);
+  void EndFindSession(FindOnPageSelectionAction selection_action,
+                      FindBoxResultAction result_action);
 
   // The visibility of the find bar view changed.
   void FindBarVisibilityChanged();
@@ -72,6 +63,9 @@
                const content::NotificationSource& source,
                const content::NotificationDetails& details) override;
 
+  // FindResultObserver:
+  void OnFindResultAvailable(content::WebContents* web_contents) override;
+
   void SetText(base::string16 text);
 
   // Called when the find text is updated in response to a user action.
@@ -88,10 +82,6 @@
       const gfx::Rect& avoid_overlapping_rect);
 
  private:
-  // Called when we've received notice of a find result for the associated
-  // WebContents.
-  void OnFindResultAvailable();
-
   // Sends an update to the find bar with the tab contents' current result. The
   // web_contents_ must be non-NULL before this call. This handles
   // de-flickering in addition to just calling the update function.
@@ -121,6 +111,8 @@
   int last_reported_matchcount_ = 0;
   int last_reported_ordinal_ = 0;
 
+  ScopedObserver<FindTabHelper, FindResultObserver> find_tab_observer_{this};
+
   DISALLOW_COPY_AND_ASSIGN(FindBarController);
 };
 
diff --git a/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc b/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
index b6f8904..0e3e0d6 100644
--- a/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
+++ b/chrome/browser/ui/find_bar/find_bar_host_browsertest.cc
@@ -25,10 +25,11 @@
 #include "chrome/browser/ui/find_bar/find_bar_host_unittest_util.h"
 #include "chrome/browser/ui/find_bar/find_notification_details.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views_mode_controller.h"
 #include "chrome/common/url_constants.h"
-#include "chrome/test/base/find_in_page_observer.h"
+#include "chrome/test/base/find_result_waiter.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/prefs/pref_service.h"
@@ -563,7 +564,7 @@
   EXPECT_EQ(3, ordinal);
 
   // End the find session.
-  find_tab_helper->StopFinding(FindBarController::kKeepSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kKeep);
 }
 
 // This tests that we start searching after selected text.
@@ -1044,7 +1045,7 @@
       browser()->tab_strip_model()->GetActiveWebContents());
   // Stop the (non-existing) find operation, and clear the selection (which
   // signals the UI is still active).
-  find_tab_helper->StopFinding(FindBarController::kClearSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kClear);
   // Make sure the Find UI flag hasn't been cleared, it must be so that the UI
   // still responds to browser window resizing.
   ASSERT_TRUE(find_tab_helper->find_ui_active());
@@ -1073,8 +1074,7 @@
 
   // End the Find session, thereby making the next F3 start afresh.
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Simulate F3 while Find box is closed. Should have 1 match.
   EXPECT_EQ(1, FindInPageASCII(web_contents, "", kFwd, kIgnoreCase, &ordinal));
@@ -1122,8 +1122,7 @@
   // Switch back to first tab.
   browser()->tab_strip_model()->ActivateTabAt(0);
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
   // Simulate F3.
   ui_test_utils::FindInPage(web_contents_1, base::string16(),
                             kFwd, kIgnoreCase, &ordinal, NULL);
@@ -1159,8 +1158,7 @@
 
   // Close the Find box.
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Open the Find box again.
   EnsureFindBoxOpen();
@@ -1229,8 +1227,7 @@
 
   // Close the Find box.
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Now create a second tab and load the same page.
   chrome::AddTabAt(browser(), GURL(), -1, true);
@@ -1255,8 +1252,7 @@
 
   // Close the Find box.
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Re-open the Find box.
   // This is a special case: previous search in WebContents used to get cleared
@@ -1293,8 +1289,7 @@
 
   // Close the Find box.
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Open a new incognito window and navigate to the same page.
   Profile* incognito_profile = browser()->profile()->GetOffTheRecordProfile();
@@ -1321,8 +1316,7 @@
 
   // Close the Find box.
   incognito_browser->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
 
   // Now open a new tab in the original (non-incognito) browser.
   chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
@@ -1355,7 +1349,7 @@
   content::WindowedNotificationObserver observer(
       content::NOTIFICATION_LOAD_STOP,
       content::Source<NavigationController>(&web_contents->GetController()));
-  find_tab_helper->StopFinding(FindBarController::kActivateSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kActivate);
   observer.Wait();
 }
 
@@ -1509,14 +1503,12 @@
   // Close the find bar.
   FindTabHelper* find_tab_helper =
       FindTabHelper::FromWebContents(web_contents_incognito);
-  find_tab_helper->StopFinding(FindBarController::kActivateSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kActivate);
 
   // Cmd + G triggers IDC_FIND_NEXT command. Thus we test FindInPage()
   // method from browser_commands.cc. FindInPage16() bypasses it.
   EXPECT_TRUE(chrome::ExecuteCommand(browser_incognito, IDC_FIND_NEXT));
-  ui_test_utils::FindInPageNotificationObserver observer(
-      web_contents_incognito);
-  observer.Wait();
+  ui_test_utils::FindResultWaiter(web_contents_incognito).Wait();
   EXPECT_EQ(ASCIIToUTF16("foo"),
             GetFindBarTextForBrowser(browser_incognito));
   EXPECT_EQ(ASCIIToUTF16("2/2"),
@@ -1547,9 +1539,7 @@
   EXPECT_TRUE(chrome::ExecuteCommand(browser_incognito, IDC_FIND_NEXT));
   WebContents* web_contents_incognito =
       browser_incognito->tab_strip_model()->GetActiveWebContents();
-  ui_test_utils::FindInPageNotificationObserver observer(
-      web_contents_incognito);
-  observer.Wait();
+  ui_test_utils::FindResultWaiter(web_contents_incognito).Wait();
   EXPECT_EQ(ASCIIToUTF16("bar"),
             GetFindBarTextForBrowser(browser_incognito));
 }
diff --git a/chrome/browser/ui/find_bar/find_bar_host_interactive_uitest.cc b/chrome/browser/ui/find_bar/find_bar_host_interactive_uitest.cc
index 404669c1..0a89974 100644
--- a/chrome/browser/ui/find_bar/find_bar_host_interactive_uitest.cc
+++ b/chrome/browser/ui/find_bar/find_bar_host_interactive_uitest.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/find_bar/find_bar.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
@@ -89,7 +90,7 @@
   EXPECT_EQ(1, ordinal);
 
   // End the find session, which should set focus to the link.
-  find_tab_helper->StopFinding(FindBarController::kKeepSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kKeep);
 
   // Verify that the link is focused.
   ASSERT_TRUE(FocusedOnPage(web_contents, &result));
@@ -107,7 +108,7 @@
       &result));
 
   // End the find session.
-  find_tab_helper->StopFinding(FindBarController::kKeepSelectionOnPage);
+  find_tab_helper->StopFinding(FindOnPageSelectionAction::kKeep);
 
   // Verify that link2 is not focused.
   ASSERT_TRUE(FocusedOnPage(web_contents, &result));
diff --git a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac.mm b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac.mm
index d332d96..8476edc 100644
--- a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac.mm
+++ b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac.mm
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #import "chrome/browser/ui/find_bar/find_bar_platform_helper.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "content/public/browser/web_contents.h"
 #import "ui/base/cocoa/find_pasteboard.h"
@@ -70,7 +71,7 @@
           continue;
         FindTabHelper* find_tab_helper =
             FindTabHelper::FromWebContents(web_contents);
-        find_tab_helper->StopFinding(FindBarController::kClearSelectionOnPage);
+        find_tab_helper->StopFinding(FindOnPageSelectionAction::kClear);
       }
     }
 
diff --git a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm
index 494dd9f2..6034ff196 100644
--- a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm
+++ b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm
@@ -15,8 +15,9 @@
 #include "chrome/browser/ui/find_bar/find_bar.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/view_ids.h"
-#include "chrome/test/base/find_in_page_observer.h"
+#include "chrome/test/base/find_result_waiter.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -196,9 +197,7 @@
   ASSERT_TRUE(chrome::ExecuteCommand(browser_incognito, IDC_FIND_NEXT));
   content::WebContents* web_contents_incognito =
       browser_incognito->tab_strip_model()->GetActiveWebContents();
-  ui_test_utils::FindInPageNotificationObserver observer(
-      web_contents_incognito);
-  observer.Wait();
+  ui_test_utils::FindResultWaiter(web_contents_incognito).Wait();
 
   FindBarController* find_bar_controller =
       browser_incognito->GetFindBarController();
@@ -256,8 +255,8 @@
 
   // Go back to the first tab and end the search.
   browser()->tab_strip_model()->ActivateTabAt(0);
-  find_bar_controller->EndFindSession(FindBarController::kKeepSelectionOnPage,
-                                      FindBarController::kKeepResultsInFindBox);
+  find_bar_controller->EndFindSession(FindOnPageSelectionAction::kKeep,
+                                      FindBoxResultAction::kKeep);
   // Simulate F3.
   ui_test_utils::FindInPage(first_active_web_contents, base::string16(), true,
                             false, nullptr, nullptr);
diff --git a/chrome/browser/ui/find_bar/find_result_observer.h b/chrome/browser/ui/find_bar/find_result_observer.h
new file mode 100644
index 0000000..44947818
--- /dev/null
+++ b/chrome/browser/ui/find_bar/find_result_observer.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 CHROME_BROWSER_UI_FIND_BAR_FIND_RESULT_OBSERVER_H_
+#define CHROME_BROWSER_UI_FIND_BAR_FIND_RESULT_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+
+namespace content {
+class WebContents;
+}
+
+class FindResultObserver : public base::CheckedObserver {
+ public:
+  virtual void OnFindResultAvailable(content::WebContents* web_contents) = 0;
+};
+
+#endif  // CHROME_BROWSER_UI_FIND_BAR_FIND_RESULT_OBSERVER_H_
diff --git a/chrome/browser/ui/find_bar/find_tab_helper.cc b/chrome/browser/ui/find_bar/find_tab_helper.cc
index e72ea07..fb47d5356 100644
--- a/chrome/browser/ui/find_bar/find_tab_helper.cc
+++ b/chrome/browser/ui/find_bar/find_tab_helper.cc
@@ -13,6 +13,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/find_bar/find_bar_state.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
+#include "chrome/browser/ui/find_bar/find_result_observer.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -36,7 +38,14 @@
       last_search_result_() {
 }
 
-FindTabHelper::~FindTabHelper() {
+FindTabHelper::~FindTabHelper() = default;
+
+void FindTabHelper::AddObserver(FindResultObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void FindTabHelper::RemoveObserver(FindResultObserver* observer) {
+  observers_.RemoveObserver(observer);
 }
 
 void FindTabHelper::StartFinding(base::string16 search_string,
@@ -103,9 +112,8 @@
                        std::move(options));
 }
 
-void FindTabHelper::StopFinding(
-    FindBarController::SelectionAction selection_action) {
-  if (selection_action == FindBarController::kClearSelectionOnPage) {
+void FindTabHelper::StopFinding(FindOnPageSelectionAction selection_action) {
+  if (selection_action == FindOnPageSelectionAction::kClear) {
     // kClearSelection means the find string has been cleared by the user, but
     // the UI has not been dismissed. In that case we want to clear the
     // previously remembered search (http://crbug.com/42639).
@@ -122,13 +130,13 @@
 
   content::StopFindAction action;
   switch (selection_action) {
-    case FindBarController::kClearSelectionOnPage:
+    case FindOnPageSelectionAction::kClear:
       action = content::STOP_FIND_ACTION_CLEAR_SELECTION;
       break;
-    case FindBarController::kKeepSelectionOnPage:
+    case FindOnPageSelectionAction::kKeep:
       action = content::STOP_FIND_ACTION_KEEP_SELECTION;
       break;
-    case FindBarController::kActivateSelectionOnPage:
+    case FindOnPageSelectionAction::kActivate:
       action = content::STOP_FIND_ACTION_ACTIVATE_SELECTION;
       break;
     default:
@@ -181,10 +189,8 @@
     last_search_result_ = FindNotificationDetails(
         request_id, number_of_matches, selection, active_match_ordinal,
         final_update);
-    content::NotificationService::current()->Notify(
-        chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-        content::Source<WebContents>(web_contents()),
-        content::Details<FindNotificationDetails>(&last_search_result_));
+    for (auto& observer : observers_)
+      observer.OnFindResultAvailable(web_contents());
   }
 }
 
diff --git a/chrome/browser/ui/find_bar/find_tab_helper.h b/chrome/browser/ui/find_bar/find_tab_helper.h
index 4b47546..055fc5d 100644
--- a/chrome/browser/ui/find_bar/find_tab_helper.h
+++ b/chrome/browser/ui/find_bar/find_tab_helper.h
@@ -7,18 +7,23 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
-#include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_notification_details.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "ui/gfx/range/range.h"
 
+class FindResultObserver;
+enum class FindOnPageSelectionAction;
+
 // Per-tab find manager. Handles dealing with the life cycle of find sessions.
 class FindTabHelper : public content::WebContentsObserver,
                       public content::WebContentsUserData<FindTabHelper> {
  public:
   ~FindTabHelper() override;
 
+  void AddObserver(FindResultObserver* observer);
+  void RemoveObserver(FindResultObserver* observer);
+
   // Starts the Find operation by calling StartFinding on the Tab. This function
   // can be called from the outside as a result of hot-keys, so it uses the
   // last remembered search string as specified with set_find_string(). This
@@ -31,7 +36,7 @@
                     bool run_synchronously_for_testing = false);
 
   // Stops the current Find operation.
-  void StopFinding(FindBarController::SelectionAction selection_action);
+  void StopFinding(FindOnPageSelectionAction selection_action);
 
   // When the user commits to a search query or jumps from one result
   // to the next, move accessibility focus to the next find result.
@@ -150,6 +155,8 @@
   // information to build its presentation.
   FindNotificationDetails last_search_result_;
 
+  base::ObserverList<FindResultObserver> observers_;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
   DISALLOW_COPY_AND_ASSIGN(FindTabHelper);
diff --git a/chrome/browser/ui/find_bar/find_types.h b/chrome/browser/ui/find_bar/find_types.h
new file mode 100644
index 0000000..7da40514
--- /dev/null
+++ b/chrome/browser/ui/find_bar/find_types.h
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_FIND_BAR_FIND_TYPES_H_
+#define CHROME_BROWSER_UI_FIND_BAR_FIND_TYPES_H_
+
+// An enum listing the possible actions to take on a find-in-page selection
+// in the page when ending the find session.
+enum class FindOnPageSelectionAction {
+  kKeep,     // Translate the find selection into a normal selection.
+  kClear,    // Clear the find selection.
+  kActivate  // Focus and click the selected node (for links).
+};
+
+// An enum listing the possible actions to take on a find-in-page results in
+// the Find box when ending the find session.
+enum class FindBoxResultAction {
+  kClear,  // Clear search string, ordinal and match count.
+  kKeep,   // Leave the results untouched.
+};
+
+#endif  // CHROME_BROWSER_UI_FIND_BAR_FIND_TYPES_H_
diff --git a/chrome/browser/ui/hats/OWNERS b/chrome/browser/ui/hats/OWNERS
new file mode 100644
index 0000000..1d527a2
--- /dev/null
+++ b/chrome/browser/ui/hats/OWNERS
@@ -0,0 +1,3 @@
+kylixrd@chromium.org
+robliao@chromium.org
+weili@chromium.org
diff --git a/chrome/browser/ui/hats/hats_service.cc b/chrome/browser/ui/hats/hats_service.cc
index 398b2e1..16d6965a 100644
--- a/chrome/browser/ui/hats/hats_service.cc
+++ b/chrome/browser/ui/hats/hats_service.cc
@@ -29,34 +29,25 @@
 
 constexpr char kHatsSurveyTriggerSatisfaction[] = "satisfaction";
 
-HatsFinchConfig CreateHatsFinchConfig() {
-  HatsFinchConfig config;
-  config.trigger = base::FeatureParam<std::string>(
-                       &features::kHappinessTrackingSurveysForDesktop,
-                       kHatsSurveyTrigger, kHatsSurveyTriggerDefault)
-                       .Get();
-
-  config.probability =
-      base::FeatureParam<double>(&features::kHappinessTrackingSurveysForDesktop,
-                                 kHatsSurveyProbability,
-                                 kHatsSurveyProbabilityDefault)
-          .Get();
-
-  config.site_ids.insert(
-      std::make_pair("en", base::FeatureParam<std::string>(
-                               &features::kHappinessTrackingSurveysForDesktop,
-                               kHatsSurveyEnSiteID, kHatsSurveyEnSiteIDDefault)
-                               .Get()));
-  return config;
-}
 }  // namespace
 
-HatsFinchConfig::HatsFinchConfig() = default;
-HatsFinchConfig::~HatsFinchConfig() = default;
-HatsFinchConfig::HatsFinchConfig(const HatsFinchConfig& other) = default;
-
 HatsService::HatsService(Profile* profile)
-    : profile_(profile), hats_finch_config_(CreateHatsFinchConfig()) {}
+    : profile_(profile),
+      trigger_(base::FeatureParam<std::string>(
+                   &features::kHappinessTrackingSurveysForDesktop,
+                   kHatsSurveyTrigger,
+                   kHatsSurveyTriggerDefault)
+                   .Get()),
+      probability_(base::FeatureParam<double>(
+                       &features::kHappinessTrackingSurveysForDesktop,
+                       kHatsSurveyProbability,
+                       kHatsSurveyProbabilityDefault)
+                       .Get()),
+      en_site_id_(base::FeatureParam<std::string>(
+                      &features::kHappinessTrackingSurveysForDesktop,
+                      kHatsSurveyEnSiteID,
+                      kHatsSurveyEnSiteIDDefault)
+                      .Get()) {}
 
 void HatsService::LaunchSatisfactionSurvey() {
   if (ShouldShowSurvey(kHatsSurveyTriggerSatisfaction)) {
@@ -67,10 +58,9 @@
 }
 
 bool HatsService::ShouldShowSurvey(const std::string& trigger) const {
-  if ((hats_finch_config_.trigger == trigger ||
-       hats_finch_config_.trigger == kHatsSurveyTriggerDefault) &&
+  if ((trigger_ == trigger || trigger_ == kHatsSurveyTriggerDefault) &&
       !launch_hats_) {
-    if (base::RandDouble() < hats_finch_config_.probability) {
+    if (base::RandDouble() < probability_) {
       // we only want to ever show hats once per profile.
       launch_hats_ = true;
       return true;
diff --git a/chrome/browser/ui/hats/hats_service.h b/chrome/browser/ui/hats/hats_service.h
index c7fc370..34d088d 100644
--- a/chrome/browser/ui/hats/hats_service.h
+++ b/chrome/browser/ui/hats/hats_service.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 #define CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 
-#include <map>
 #include <string>
 
 #include "base/gtest_prod_util.h"
@@ -14,20 +13,6 @@
 
 class Profile;
 
-struct HatsFinchConfig {
-  HatsFinchConfig();
-  ~HatsFinchConfig();
-  HatsFinchConfig(const HatsFinchConfig& other);
-
-  double probability;   // This is the percent of users [0,1] that will see the
-                        // survey
-  std::string trigger;  // This is the name of the survey in question.
-
-  // This is a map between the locale being presented and the site ID used to
-  // fetch the survey.
-  std::map<std::string, std::string> site_ids;
-};
-
 // This class provides the client side logic for determining if a
 // survey should be shown for any trigger based on input from a finch
 // configuration. It is created on a per profile basis.
@@ -39,17 +24,28 @@
   // it's appropriate.
   void LaunchSatisfactionSurvey();
 
+  // Returns the en-us site ID for the HaTS survey.
+  const std::string& en_site_id() const { return en_site_id_; }
+
  private:
+  FRIEND_TEST_ALL_PREFIXES(HatsForceEnabledTest, ParamsWithAForcedFlagTest);
+
   // This returns true is the survey trigger specified should be shown.
   bool ShouldShowSurvey(const std::string& trigger) const;
 
   // a temporary flag to ensure that hats is not launched multiple times
   // TODO: replace with pref lookup
   static bool launch_hats_;
-  Profile* profile_;
-  const HatsFinchConfig hats_finch_config_;
+  Profile* const profile_;
 
-  FRIEND_TEST_ALL_PREFIXES(HatsForceEnabledTest, ParamsWithAForcedFlagTest);
+  // Trigger string identifier.
+  const std::string trigger_;
+
+  // Percent of users [0,1] that will see the survey.
+  const double probability_;
+
+  // Site ID for the survey.
+  const std::string en_site_id_;
 
   DISALLOW_COPY_AND_ASSIGN(HatsService);
 };
diff --git a/chrome/browser/ui/hats/hats_unittest.cc b/chrome/browser/ui/hats/hats_unittest.cc
index ed56f9a42..b2e6cf2b 100644
--- a/chrome/browser/ui/hats/hats_unittest.cc
+++ b/chrome/browser/ui/hats/hats_unittest.cc
@@ -30,5 +30,5 @@
 };
 
 TEST_F(HatsForceEnabledTest, ParamsWithAForcedFlagTest) {
-  ASSERT_EQ(1, hats_service_->hats_finch_config_.probability);
+  ASSERT_EQ(1, hats_service_->probability_);
 }
diff --git a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
index 90f77c7..fc6d56c 100644
--- a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -47,6 +48,7 @@
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/omnibox/browser/omnibox_view.h"
 #include "components/omnibox/browser/test_location_bar_model.h"
+#include "components/omnibox/common/omnibox_features.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/mock_configuration_policy_provider.h"
 #include "components/search_engines/template_url.h"
@@ -1331,11 +1333,14 @@
 #if !defined(OS_MACOSX)
 // Mac intentionally does not support this behavior.
 IN_PROC_BROWSER_TEST_F(OmniboxViewTest, TabTraverseResultsTest) {
-  OmniboxView* omnibox_view = NULL;
+  OmniboxView* omnibox_view = nullptr;
   ASSERT_NO_FATAL_FAILURE(GetOmniboxView(&omnibox_view));
   OmniboxPopupModel* popup_model = omnibox_view->model()->popup_model();
   ASSERT_TRUE(popup_model);
 
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(omnibox::kOmniboxWrapPopupPosition);
+
   // Input something to trigger results.
   const ui::KeyboardCode kKeys[] = {
     ui::VKEY_B, ui::VKEY_A, ui::VKEY_R, ui::VKEY_UNKNOWN
@@ -1352,6 +1357,8 @@
        popup_model->selected_line() < size - 1;
        old_selected_line = popup_model->selected_line()) {
     ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, 0));
+    ASSERT_FALSE(omnibox_view->model()->is_keyword_hint());
+    ASSERT_EQ(base::string16(), omnibox_view->model()->keyword());
     ASSERT_LT(old_selected_line, popup_model->selected_line());
   }
 
@@ -1391,6 +1398,90 @@
   ASSERT_FALSE(omnibox_view->model()->is_keyword_hint());
   ASSERT_EQ(text, omnibox_view->model()->keyword());
   ASSERT_TRUE(omnibox_view->GetText().empty());
+  ASSERT_EQ(0U, omnibox_view->model()->popup_model()->selected_line());
+
+  // The location bar should still have focus.
+  ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
+
+  // Pressing tab again should move to the next result and clear keyword
+  // mode.
+  ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, 0));
+  ASSERT_FALSE(omnibox_view->model()->is_keyword_hint());
+  ASSERT_NE(text, omnibox_view->model()->keyword());
+  ASSERT_EQ(1U, omnibox_view->model()->popup_model()->selected_line());
+
+  // The location bar should still have focus.
+  ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
+
+  // Moving back up should not show keyword mode.
+  ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN));
+  ASSERT_TRUE(omnibox_view->model()->is_keyword_hint());
+  ASSERT_EQ(text, omnibox_view->model()->keyword());
+
+  ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
+}
+
+IN_PROC_BROWSER_TEST_F(OmniboxViewTest, WrappingTabTraverseResultsTest) {
+  OmniboxView* omnibox_view = nullptr;
+  ASSERT_NO_FATAL_FAILURE(GetOmniboxView(&omnibox_view));
+  OmniboxPopupModel* popup_model = omnibox_view->model()->popup_model();
+  ASSERT_TRUE(popup_model);
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(omnibox::kOmniboxWrapPopupPosition);
+
+  // Input something to trigger results.
+  const ui::KeyboardCode kKeys[] = {ui::VKEY_B, ui::VKEY_A, ui::VKEY_R,
+                                    ui::VKEY_UNKNOWN};
+  ASSERT_NO_FATAL_FAILURE(SendKeySequence(kKeys));
+  ASSERT_NO_FATAL_FAILURE(WaitForAutocompleteControllerDone());
+  ASSERT_TRUE(popup_model->IsOpen());
+
+  size_t old_selected_line = popup_model->selected_line();
+  EXPECT_EQ(0U, old_selected_line);
+
+  // Move down the results.
+  for (size_t size = popup_model->result().size();
+       popup_model->selected_line() < size - 1;
+       old_selected_line = popup_model->selected_line()) {
+    ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, 0));
+    ASSERT_LT(old_selected_line, popup_model->selected_line());
+  }
+
+  // Wrap to top.
+  ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, 0));
+  ASSERT_EQ(0U, popup_model->selected_line());
+  ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
+
+  // Wrap to bottom.
+  ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN));
+  ASSERT_EQ(old_selected_line, popup_model->selected_line());
+  ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
+
+  // Move back up the results.
+  for (; popup_model->selected_line() > 0U;
+       old_selected_line = popup_model->selected_line()) {
+    ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN));
+    ASSERT_GT(old_selected_line, popup_model->selected_line());
+  }
+
+  const TestHistoryEntry kHistoryFoo = {"http://foo/", "Page foo", 1, 1, false};
+
+  // Add a history entry so "foo" gets multiple matches.
+  ASSERT_NO_FATAL_FAILURE(AddHistoryEntry(
+      kHistoryFoo, base::Time::Now() - base::TimeDelta::FromHours(1)));
+
+  // Load results.
+  ASSERT_NO_FATAL_FAILURE(omnibox_view->SelectAll(false));
+  ASSERT_NO_FATAL_FAILURE(SendKeySequence(kSearchKeywordKeys));
+  ASSERT_NO_FATAL_FAILURE(WaitForAutocompleteControllerDone());
+
+  // Trigger keyword mode by tab.
+  base::string16 text = ASCIIToUTF16(kSearchKeyword);
+  ASSERT_NO_FATAL_FAILURE(SendKey(ui::VKEY_TAB, 0));
+  ASSERT_FALSE(omnibox_view->model()->is_keyword_hint());
+  ASSERT_EQ(text, omnibox_view->model()->keyword());
+  ASSERT_TRUE(omnibox_view->GetText().empty());
 
   // The location bar should still have focus.
   ASSERT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 8530e121..b500be9 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_observer.h"
 #include "chrome/browser/metrics/oom/out_of_memory_reporter.h"
 #include "chrome/browser/metrics/renderer_uptime_web_contents_observer.h"
+#include "chrome/browser/native_file_system/native_file_system_permission_request_manager.h"
 #include "chrome/browser/net/net_error_tab_helper.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
@@ -225,6 +226,7 @@
   metrics::RendererUptimeWebContentsObserver::CreateForWebContents(
       web_contents);
   MixedContentSettingsTabHelper::CreateForWebContents(web_contents);
+  NativeFileSystemPermissionRequestManager::CreateForWebContents(web_contents);
   NavigationCorrectionTabObserver::CreateForWebContents(web_contents);
   NavigationMetricsRecorder::CreateForWebContents(web_contents);
   OutOfMemoryReporter::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 664ce5ce..7f202a2 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -88,7 +88,7 @@
 constexpr int kMinimumVerticalPadding = 2 + kTopBottomPadding;
 
 // The normal height of the item which may be exceeded if text is large.
-constexpr int kDefaultHeight = 48;
+constexpr int kDefaultDownloadItemHeight = 48;
 
 // Amount of time between accessible alert events.
 constexpr base::TimeDelta kAccessibleAlertInterval =
@@ -444,7 +444,7 @@
   if (mode_ != DANGEROUS_MODE)
     width += dropdown_button_->GetPreferredSize().width();
 
-  return gfx::Size(width, std::max(kDefaultHeight,
+  return gfx::Size(width, std::max(kDefaultDownloadItemHeight,
                                    2 * kMinimumVerticalPadding + child_height));
 }
 
diff --git a/chrome/browser/ui/views/find_bar_host.cc b/chrome/browser/ui/views/find_bar_host.cc
index b850e99..49d2bbf 100644
--- a/chrome/browser/ui/views/find_bar_host.cc
+++ b/chrome/browser/ui/views/find_bar_host.cc
@@ -9,6 +9,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/find_bar_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -192,17 +193,15 @@
   ui::KeyboardCode key = accelerator.key_code();
   if (key == ui::VKEY_RETURN && accelerator.IsCtrlDown()) {
     // Ctrl+Enter closes the Find session and navigates any link that is active.
-    find_bar_controller_->EndFindSession(
-        FindBarController::kActivateSelectionOnPage,
-        FindBarController::kClearResultsInFindBox);
+    find_bar_controller_->EndFindSession(FindOnPageSelectionAction::kActivate,
+                                         FindBoxResultAction::kClear);
     return true;
   } else if (key == ui::VKEY_ESCAPE) {
     // This will end the Find session and hide the window, causing it to loose
     // focus and in the process unregister us as the handler for the Escape
     // accelerator through the OnWillChangeFocus event.
-    find_bar_controller_->EndFindSession(
-        FindBarController::kKeepSelectionOnPage,
-        FindBarController::kKeepResultsInFindBox);
+    find_bar_controller_->EndFindSession(FindOnPageSelectionAction::kKeep,
+                                         FindBoxResultAction::kKeep);
     return true;
   } else {
     NOTREACHED() << "Unknown accelerator";
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index 2accdcc..cd5d170b 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
 #include "chrome/browser/ui/find_bar/find_notification_details.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/find_bar_host.h"
@@ -395,8 +396,7 @@
       break;
     case VIEW_ID_FIND_IN_PAGE_CLOSE_BUTTON:
       find_bar_host_->GetFindBarController()->EndFindSession(
-          FindBarController::kKeepSelectionOnPage,
-          FindBarController::kKeepResultsInFindBox);
+          FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
       break;
     default:
       NOTREACHED() << "Unknown button";
@@ -476,7 +476,7 @@
     // The last two params here are forward (true) and case sensitive (false).
     find_tab_helper->StartFinding(search_text, true, false);
   } else {
-    find_tab_helper->StopFinding(FindBarController::kClearSelectionOnPage);
+    find_tab_helper->StopFinding(FindOnPageSelectionAction::kClear);
     UpdateForResult(find_tab_helper->find_result(), base::string16());
     find_bar_host_->MoveWindowIfNecessary(gfx::Rect());
 
diff --git a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
index d7c7299..71bce1a 100644
--- a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
@@ -13,10 +13,13 @@
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/find_bar/find_notification_details.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/find_bar_host.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/test/base/find_result_waiter.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -78,15 +81,10 @@
   }
 
   FindNotificationDetails WaitForFindResult() {
-    content::Source<WebContents> source(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    ui_test_utils::WindowedNotificationObserverWithDetails
-        <FindNotificationDetails> observer(
-            chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, source);
-    observer.Wait();
-    FindNotificationDetails details;
-    EXPECT_TRUE(observer.GetDetailsFor(source.map_key(), &details));
-    return details;
+    WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    ui_test_utils::FindResultWaiter(web_contents).Wait();
+    return FindTabHelper::FromWebContents(web_contents)->find_result();
   }
 
   FindNotificationDetails WaitForFinalFindResult() {
@@ -263,8 +261,7 @@
   browser()->GetFindBarController()->Show();
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_OMNIBOX));
 
   // Focus the location bar, find something on the page, close the find box,
@@ -276,8 +273,7 @@
       browser()->tab_strip_model()->GetActiveWebContents(),
       ASCIIToUTF16("a"), true, false, NULL, NULL);
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
 
   // Focus the location bar, open and close the find box, focus should return to
@@ -288,8 +284,7 @@
   browser()->GetFindBarController()->Show();
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   browser()->GetFindBarController()->EndFindSession(
-      FindBarController::kKeepSelectionOnPage,
-      FindBarController::kKeepResultsInFindBox);
+      FindOnPageSelectionAction::kKeep, FindBoxResultAction::kKeep);
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_OMNIBOX));
 }
 
diff --git a/chrome/browser/ui/views/hats/hats_web_dialog.cc b/chrome/browser/ui/views/hats/hats_web_dialog.cc
index ac5e20a..dd98b7c 100644
--- a/chrome/browser/ui/views/hats/hats_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_web_dialog.cc
@@ -11,6 +11,8 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/browser_resources.h"
@@ -29,13 +31,8 @@
 namespace {
 
 // Default width/height of the dialog in screen size.
-const int kDefaultWidth = 400;
-const int kDefaultHeight = 420;
-
-// Site ID of HaTS survey for Chrome Desktop.
-// TODO(weili): Replace this with the pilot survey site ID retrieved from finch
-// config.
-constexpr char kSiteID[] = "z4cctguzopq5x2ftal6vdgjrui";
+const int kDefaultHatsDialogWidth = 400;
+const int kDefaultHatsDialogHeight = 420;
 
 // Placeholder strings in html file to be replaced when the file is loaded.
 constexpr char kScriptSrcReplacementToken[] = "$SCRIPT_SRC";
@@ -80,16 +77,19 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(browser);
 
+  Profile* profile = browser->profile();
+
   // Self deleting upon close.
-  auto* hats_dialog = new HatsWebDialog();
+  auto* hats_dialog = new HatsWebDialog(
+      HatsServiceFactory::GetForProfile(profile, true)->en_site_id());
 
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
 
-  chrome::ShowWebDialog(browser_view->GetWidget()->GetNativeView(),
-                        browser->profile(), hats_dialog);
+  chrome::ShowWebDialog(browser_view->GetWidget()->GetNativeView(), profile,
+                        hats_dialog);
 }
 
-HatsWebDialog::HatsWebDialog() {
+HatsWebDialog::HatsWebDialog(const std::string& site_id) : site_id_(site_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
@@ -108,7 +108,7 @@
 GURL HatsWebDialog::GetDialogContentURL() const {
   // Load the html data and use it in a data URL to be displayed in the dialog.
   std::string url_str =
-      LoadLocalHtmlAsString(kSiteID, version_info::GetVersionNumber());
+      LoadLocalHtmlAsString(site_id_, version_info::GetVersionNumber());
   url::RawCanonOutputT<char> url;
   url::EncodeURIComponent(url_str.c_str(), url_str.length(), &url);
   return GURL("data:text/html;charset=utf-8," +
@@ -119,7 +119,7 @@
     std::vector<content::WebUIMessageHandler*>* handlers) const {}
 
 void HatsWebDialog::GetDialogSize(gfx::Size* size) const {
-  size->SetSize(kDefaultWidth, kDefaultHeight);
+  size->SetSize(kDefaultHatsDialogWidth, kDefaultHatsDialogHeight);
 }
 
 bool HatsWebDialog::CanResizeDialog() const {
diff --git a/chrome/browser/ui/views/hats/hats_web_dialog.h b/chrome/browser/ui/views/hats/hats_web_dialog.h
index 223ff4b5..93615c1c 100644
--- a/chrome/browser/ui/views/hats/hats_web_dialog.h
+++ b/chrome/browser/ui/views/hats/hats_web_dialog.h
@@ -22,8 +22,8 @@
   static void Show(const Browser* browser);
 
  private:
-  // Use Show() above.
-  HatsWebDialog();
+  // Use Show() above. |site_id| is used to select the survey.
+  explicit HatsWebDialog(const std::string& site_id);
   ~HatsWebDialog() override;
 
   // ui::WebDialogDelegate implementation.
@@ -44,6 +44,7 @@
                          const content::ContextMenuParams& params) override;
 
   const std::string html_data_;
+  const std::string site_id_;
 
   DISALLOW_COPY_AND_ASSIGN(HatsWebDialog);
 };
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
index b3b4b86f..6d945140 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 #include "chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
@@ -51,15 +52,33 @@
   url::Origin origin =
       url::Origin::Create(GetWebContents()->GetLastCommittedURL());
 
-  // TODO(https://crbug.com/979684): Display actual files and directories,
-  // rather than these random hard coded values.
-  NativeFileSystemUsageBubbleView::Usage usage;
-  usage.readable_directories.emplace_back(FILE_PATH_LITERAL("My Fonts"));
-  usage.writable_files.emplace_back(FILE_PATH_LITERAL("/foo/bar/demo.txt"));
-  usage.writable_files.emplace_back(FILE_PATH_LITERAL("README.md"));
-  usage.writable_directories.emplace_back(FILE_PATH_LITERAL("My Project"));
-  NativeFileSystemUsageBubbleView::ShowBubble(GetWebContents(), origin,
-                                              std::move(usage));
+  ChromeNativeFileSystemPermissionContext::GetPermissionGrantsFromUIThread(
+      GetWebContents()->GetBrowserContext(), origin,
+      base::BindOnce(
+          [](int frame_tree_node_id, const url::Origin& origin,
+             ChromeNativeFileSystemPermissionContext::Grants grants) {
+            auto* web_contents =
+                content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
+            if (!web_contents ||
+                url::Origin::Create(web_contents->GetLastCommittedURL()) !=
+                    origin) {
+              // If web-contents has navigated to a different origin while we
+              // were looking up current usage, don't show the dialog to avoid
+              // showing the dialog for the wrong origin.
+              return;
+            }
+
+            NativeFileSystemUsageBubbleView::Usage usage;
+            usage.readable_directories =
+                web_contents->GetNativeFileSystemDirectoryHandles();
+            usage.writable_files = std::move(grants.file_write_grants);
+            usage.writable_directories =
+                std::move(grants.directory_write_grants);
+
+            NativeFileSystemUsageBubbleView::ShowBubble(web_contents, origin,
+                                                        std::move(usage));
+          },
+          GetWebContents()->GetMainFrame()->GetFrameTreeNodeId(), origin));
 }
 
 const gfx::VectorIcon& NativeFileSystemAccessIconView::GetVectorIcon() const {
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view.cc b/chrome/browser/ui/views/translate/translate_bubble_view.cc
index 24af817..ceb6b3f5 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view.cc
@@ -140,6 +140,17 @@
     view->SetHighlightedButton(highlighted_button);
   views::Widget* bubble_widget =
       views::BubbleDialogDelegateView::CreateBubble(view);
+
+  // TAB UI has the same view throughout. Select the right tab based on |step|
+  // upon initialization.
+  if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
+    TranslateBubbleModel::ViewState state =
+        TranslateBubbleModelImpl::TranslateStepToViewState(step);
+    translate_bubble_view_->SwitchView(state);
+  } else {
+    translate_bubble_view_->SwitchToErrorView(error_type);
+  }
+
   // |allow_refocus_alert| is set to false because translate bubble does not
   // have an additional screen reader alert instructing the user to use a
   // hotkey combination to focus the bubble.
@@ -202,9 +213,9 @@
 
 void TranslateBubbleView::TabSelectedAt(int index) {
   // Tabbed pane is indexed from left to right starting at 0.
-  if (index == 1) {
+  if (!model_->IsPageTranslatedInCurrentLanguages() && index == 1) {
     Translate();
-  } else {
+  } else if (index == 0) {
     ShowOriginal();
   }
 }
@@ -755,8 +766,9 @@
   for (views::View* view : children())
     view->SetVisible(view == GetCurrentView());
 
-  // Not required for TAB ui because the title is not shown.
-  if (bubble_ui_model_ != language::TranslateUIBubbleModel::TAB &&
+  // Not required for TAB UI or Button_GM2 UI because the title is not shown.
+  if (bubble_ui_model_ != language::TranslateUIBubbleModel::BUTTON_GM2 &&
+      bubble_ui_model_ != language::TranslateUIBubbleModel::TAB &&
       GetWidget()) {
     GetWidget()->UpdateWindowTitle();
   }
@@ -888,6 +900,7 @@
   tab_translate_options_button->set_ink_drop_base_color(gfx::kChromeIconGrey);
   tab_translate_options_button->SetInkDropMode(views::Button::InkDropMode::ON);
   tab_translate_options_button->SetID(BUTTON_ID_OPTIONS_MENU_TAB);
+  tab_translate_options_button->SetFocusForPlatform();
   tab_translate_options_button->set_request_focus_on_press(true);
 
   // Close button
@@ -1659,10 +1672,11 @@
 void TranslateBubbleView::SwitchView(
     TranslateBubbleModel::ViewState view_state) {
   TranslateBubbleModel::ViewState current_state = model_->GetViewState();
-  if ((bubble_ui_model_ == language::TranslateUIBubbleModel::TAB &&
-       TabUiIsEquivalentState(view_state) &&
-       TabUiIsEquivalentState(current_state)) ||
-      current_state == view_state) {
+  if (bubble_ui_model_ == language::TranslateUIBubbleModel::TAB) {
+    SwitchTabForViewState(view_state);
+  }
+
+  if (current_state == view_state) {
     return;
   }
 
@@ -1673,6 +1687,17 @@
   SizeToContents();
 }
 
+void TranslateBubbleView::SwitchTabForViewState(
+    TranslateBubbleModel::ViewState view_state) {
+  if ((view_state == TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE ||
+       view_state == TranslateBubbleModel::VIEW_STATE_TRANSLATING) &&
+      tabbed_pane_->GetSelectedTabIndex() != 1) {
+    tabbed_pane_->SelectTabAt(1);
+  } else if (view_state == TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE &&
+             tabbed_pane_->GetSelectedTabIndex() != 0) {
+    tabbed_pane_->SelectTabAt(0);
+  }
+}
 void TranslateBubbleView::SwitchToErrorView(
     translate::TranslateErrors::Type error_type) {
   SwitchView(TranslateBubbleModel::VIEW_STATE_ERROR);
@@ -1680,13 +1705,6 @@
   model_->ShowError(error_type);
 }
 
-bool TranslateBubbleView::TabUiIsEquivalentState(
-    TranslateBubbleModel::ViewState view_state) {
-  return view_state == TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE ||
-         view_state == TranslateBubbleModel::VIEW_STATE_TRANSLATING ||
-         view_state == TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE;
-}
-
 void TranslateBubbleView::UpdateAdvancedView() {
   DCHECK(advanced_done_button_);
   advanced_done_button_->SetText(
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view.h b/chrome/browser/ui/views/translate/translate_bubble_view.h
index 0cc9092..91f45c1 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view.h
+++ b/chrome/browser/ui/views/translate/translate_bubble_view.h
@@ -200,6 +200,8 @@
                            AlwaysTranslateLanguageMenuItem);
   FRIEND_TEST_ALL_PREFIXES(TranslateBubbleViewTest,
                            TabUiAlwaysTranslateLanguageMenuItem);
+  FRIEND_TEST_ALL_PREFIXES(TranslateBubbleViewTest,
+                           TabUiTabSelectedAfterTranslation);
   FRIEND_TEST_ALL_PREFIXES(TranslateLanguageBrowserTest, TranslateAndRevert);
   FRIEND_TEST_ALL_PREFIXES(TranslateBubbleViewBrowserTest,
                            CheckNeverTranslateThisSiteBlacklist);
@@ -304,6 +306,9 @@
   // Switches the view type.
   void SwitchView(TranslateBubbleModel::ViewState view_state);
 
+  // SwitchView handler for TAB UI since TAB UI has the same view throughout.
+  void SwitchTabForViewState(TranslateBubbleModel::ViewState view_state);
+
   // Switches to the error view.
   void SwitchToErrorView(translate::TranslateErrors::Type error_type);
 
@@ -315,9 +320,6 @@
   void ShowOriginal();
   void ConfirmAdvancedOptions();
 
-  // Return true if the current state is in advanced state for TAB UI.
-  bool TabUiIsEquivalentState(TranslateBubbleModel::ViewState view_state);
-
   // Handles the reset button in advanced view under Tab UI.
   void ResetLanguage();
 
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc b/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc
index 87a190cc..018f5bd 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc
@@ -707,4 +707,20 @@
 
   TriggerOptionsMenuTab();
   EXPECT_FALSE(bubble_->tab_options_menu_model_->IsItemCheckedAt(index));
-}
\ No newline at end of file
+}
+
+TEST_F(TranslateBubbleViewTest, TabUiTabSelectedAfterTranslation) {
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      language::kUseButtonTranslateBubbleUi,
+      {{language::kTranslateUIBubbleKey,
+        language::kTranslateUIBubbleTabValue}});
+
+  CreateAndShowBubble();
+  EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
+            static_cast<size_t>(0));
+  mock_model_->Translate();
+  EXPECT_TRUE(mock_model_->translate_called_);
+  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE);
+  EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
+            static_cast<size_t>(1));
+}
diff --git a/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc
index 4dcc6ff..ae19e2b9 100644
--- a/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc
@@ -14,6 +14,7 @@
 #include "chrome/common/webui_url_constants.h"
 #include "components/prefs/pref_service.h"
 #include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/shadow_types.h"
 #include "url/gurl.h"
 
@@ -60,12 +61,12 @@
   // Will be deleted by |SystemWebDialogDelegate::OnDialogClosed|.
   g_dialog = new AccountManagerWelcomeDialog();
   g_dialog->ShowSystemDialog();
-
   return true;
 }
 
 void AccountManagerWelcomeDialog::AdjustWidgetInitParams(
     views::Widget::InitParams* params) {
+  params->keep_on_top = false;
   params->type = views::Widget::InitParams::Type::TYPE_WINDOW_FRAMELESS;
   params->shadow_type = views::Widget::InitParams::ShadowType::SHADOW_TYPE_DROP;
   params->shadow_elevation = wm::kShadowElevationActiveWindow;
diff --git a/chrome/browser/ui/webui/chromeos/account_migration_welcome_dialog.cc b/chrome/browser/ui/webui/chromeos/account_migration_welcome_dialog.cc
index a165ac6..51ff02f 100644
--- a/chrome/browser/ui/webui/chromeos/account_migration_welcome_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/account_migration_welcome_dialog.cc
@@ -17,6 +17,7 @@
 #include "components/prefs/pref_service.h"
 #include "net/base/url_util.h"
 #include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/shadow_types.h"
 
 namespace chromeos {
@@ -62,6 +63,7 @@
 
 void AccountMigrationWelcomeDialog::AdjustWidgetInitParams(
     views::Widget::InitParams* params) {
+  params->keep_on_top = false;
   params->type = views::Widget::InitParams::Type::TYPE_WINDOW_FRAMELESS;
   params->shadow_type = views::Widget::InitParams::ShadowType::SHADOW_TYPE_DROP;
   params->shadow_elevation = wm::kShadowElevationActiveWindow;
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
index 7a246fc..d1acb913 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
@@ -31,10 +31,10 @@
     add_supervision::mojom::AddSupervisionHandlerRequest request,
     content::WebUI* web_ui,
     Delegate* delegate)
-    : binding_(this, std::move(request)),
-      web_ui_(web_ui),
+    : web_ui_(web_ui),
       identity_manager_(
           IdentityManagerFactory::GetForProfile(Profile::FromWebUI(web_ui))),
+      binding_(this, std::move(request)),
       delegate_(delegate) {}
 
 AddSupervisionHandler::~AddSupervisionHandler() = default;
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.h b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.h
index cd7c3dec..b085b43 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.h
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.h
@@ -56,8 +56,6 @@
                                   GoogleServiceAuthError error,
                                   identity::AccessTokenInfo access_token_info);
 
-  mojo::Binding<add_supervision::mojom::AddSupervisionHandler> binding_;
-
   // The AddSupervisionUI that this AddSupervisionHandler belongs to.
   content::WebUI* web_ui_;
 
@@ -65,6 +63,8 @@
   identity::IdentityManager* identity_manager_;
   std::unique_ptr<identity::AccessTokenFetcher> oauth2_access_token_fetcher_;
 
+  mojo::Binding<add_supervision::mojom::AddSupervisionHandler> binding_;
+
   Delegate* delegate_;
 
   base::WeakPtrFactory<AddSupervisionHandler> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 67ce027..3fa20360 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -244,8 +244,7 @@
 }
 
 bool ExtractSamlPasswordAttributesEnabled() {
-  return ProfileHelper::Get()->GetSigninProfile()->GetPrefs()->GetBoolean(
-      prefs::kSamlInSessionPasswordChangeEnabled);
+  return base::FeatureList::IsEnabled(features::kInSessionPasswordChange);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc
index 5c60c661..2becb485 100644
--- a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler.cc
@@ -32,6 +32,7 @@
 const char kNoScriptPreviewsHtmlId[] = "noscript-preview-status";
 const char kResourceLoadingHintsHtmlId[] = "resource-loading-hints-status";
 const char kOfflinePreviewsHtmlId[] = "offline-preview-status";
+const char kDeferAllScriptPreviewsHtmlId[] = "defer-all-script-preview-status";
 
 // Descriptions for previews.
 const char kPreviewsAllowedDescription[] = "Previews Allowed";
@@ -39,6 +40,7 @@
     "Lite Page Redirect / Server Previews";
 const char kNoScriptDescription[] = "NoScript Previews";
 const char kResourceLoadingHintsDescription[] = "ResourceLoadingHints Previews";
+const char kDeferAllScriptPreviewsDescription[] = "DeferAllScript Previews";
 const char kOfflineDesciption[] = "Offline Previews";
 
 // Flag feature name.
@@ -46,6 +48,7 @@
 const char kLitePageRedirectFeatureName[] = "LitePageServerPreviews";
 const char kNoScriptFeatureName[] = "NoScriptPreviews";
 const char kResourceLoadingHintsFeatureName[] = "ResourceLoadingHints";
+const char kDeferAllScriptFeatureName[] = "DeferAllScript";
 #if defined(OS_ANDROID)
 const char kOfflinePageFeatureName[] = "OfflinePreviews";
 #endif  // OS_ANDROID
@@ -56,6 +59,7 @@
 const char kOfflinePageFlagHtmlId[] = "offline-page-flag";
 const char kLitePageRedirectFlagHtmlId[] = "lite-page-redirect-flag";
 const char kResourceLoadingHintsFlagHtmlId[] = "resource-loading-hints-flag";
+const char kDeferAllScriptFlagHtmlId[] = "defer-all-script-flag";
 const char kNoScriptFlagHtmlId[] = "noscript-flag";
 const char kEctFlagHtmlId[] = "ect-flag";
 const char kIgnorePreviewsBlacklistFlagHtmlId[] = "ignore-previews-blacklist";
@@ -70,6 +74,8 @@
     "chrome://flags/#enable-lite-page-server-previews";
 const char kResourceLoadingHintsFlagLink[] =
     "chrome://flags/#enable-resource-loading-hints";
+const char kDeferAllScriptFlagLink[] =
+    "chrome://flags/#enable-defer-all-script";
 const char kNoScriptFlagLink[] = "chrome://flags/#enable-noscript-previews";
 const char kEctFlagLink[] = "chrome://flags/#force-effective-connection-type";
 const char kIgnorePreviewsBlacklistLink[] =
@@ -241,6 +247,14 @@
   resource_loading_hints_status->htmlId = kResourceLoadingHintsHtmlId;
   statuses.push_back(std::move(resource_loading_hints_status));
 
+  auto defer_all_script_preview_status = mojom::PreviewsStatus::New();
+  defer_all_script_preview_status->description =
+      kDeferAllScriptPreviewsDescription;
+  defer_all_script_preview_status->enabled =
+      previews::params::IsDeferAllScriptPreviewsEnabled();
+  defer_all_script_preview_status->htmlId = kDeferAllScriptPreviewsHtmlId;
+  statuses.push_back(std::move(defer_all_script_preview_status));
+
   auto noscript_status = mojom::PreviewsStatus::New();
   noscript_status->description = kNoScriptDescription;
   noscript_status->enabled = previews::params::IsNoScriptPreviewsEnabled();
@@ -294,6 +308,15 @@
   resource_loading_hints_status->htmlId = kResourceLoadingHintsFlagHtmlId;
   flags.push_back(std::move(resource_loading_hints_status));
 
+  auto defer_all_script_status = mojom::PreviewsFlag::New();
+  defer_all_script_status->description =
+      flag_descriptions::kEnableDeferAllScriptName;
+  defer_all_script_status->link = kDeferAllScriptFlagLink;
+  defer_all_script_status->value =
+      GetFeatureFlagStatus(kDeferAllScriptFeatureName);
+  defer_all_script_status->htmlId = kDeferAllScriptFlagHtmlId;
+  flags.push_back(std::move(defer_all_script_status));
+
   auto noscript_status = mojom::PreviewsFlag::New();
   noscript_status->description = flag_descriptions::kEnableNoScriptPreviewsName;
   noscript_status->link = kNoScriptFlagLink;
diff --git a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
index b594b454..3f8998a 100644
--- a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
@@ -54,6 +54,8 @@
 constexpr char kOfflinePreviewsHtmlId[] = "offline-preview-status";
 constexpr char kLitePageRedirectHtmlId[] = "lite-page-redirect-status";
 constexpr char kResourceLoadingHintsHtmlId[] = "resource-loading-hints-status";
+constexpr char kDeferAllScriptPreviewsHtmlId[] =
+    "defer-all-script-preview-status";
 constexpr char kNoScriptPreviewsHtmlId[] = "noscript-preview-status";
 
 // Descriptions for previews.
@@ -63,6 +65,7 @@
     "Lite Page Redirect / Server Previews";
 constexpr char kResourceLoadingHintsDescription[] =
     "ResourceLoadingHints Previews";
+constexpr char kDeferAllScriptPreviewsDescription[] = "DeferAllScript Previews";
 constexpr char kNoScriptDescription[] = "NoScript Previews";
 
 // The HTML DOM ID used in Javascript.
@@ -70,6 +73,7 @@
 constexpr char kLitePageRedirectFlagHtmlId[] = "lite-page-redirect-flag";
 constexpr char kResourceLoadingHintsFlagHtmlId[] =
     "resource-loading-hints-flag";
+constexpr char kDeferAllScriptFlagHtmlId[] = "defer-all-script-flag";
 constexpr char kNoScriptFlagHtmlId[] = "noscript-flag";
 constexpr char kEctFlagHtmlId[] = "ect-flag";
 constexpr char kIgnorePreviewsBlacklistFlagHtmlId[] =
@@ -84,6 +88,8 @@
     "chrome://flags/#enable-lite-page-server-previews";
 constexpr char kResourceLoadingHintsFlagLink[] =
     "chrome://flags/#enable-resource-loading-hints";
+constexpr char kDeferAllScriptFlagLink[] =
+    "chrome://flags/#enable-defer-all-script";
 constexpr char kNoScriptFlagLink[] = "chrome://flags/#enable-noscript-previews";
 constexpr char kEctFlagLink[] =
     "chrome://flags/#force-effective-connection-type";
@@ -96,6 +102,7 @@
 constexpr char kOfflinePageFeatureName[] = "OfflinePreviews";
 constexpr char kLitePageRedirectFeatureName[] = "LitePageServerPreviews";
 constexpr char kResourceLoadingHintsFeatureName[] = "ResourceLoadingHints";
+constexpr char kDeferAllScriptFeatureName[] = "DeferAllScriptPreviews";
 constexpr char kNoScriptFeatureName[] = "NoScriptPreviews";
 
 constexpr char kDefaultFlagValue[] = "Default";
@@ -325,7 +332,7 @@
   page_handler_->GetPreviewsEnabled(
       base::BindOnce(&MockGetPreviewsEnabledCallback));
 
-  constexpr size_t expected = 5;
+  constexpr size_t expected = 6;
   EXPECT_EQ(expected, passed_in_modes.size());
 }
 
@@ -464,11 +471,39 @@
   EXPECT_TRUE(resource_loading_hints->second->enabled);
 }
 
+TEST_F(InterventionsInternalsPageHandlerTest, DeferAllScriptPreviewsDisabled) {
+  // Init with kDeferAllScriptPreviews disabled.
+  scoped_feature_list_->InitWithFeatures(
+      {}, {previews::features::kDeferAllScriptPreviews});
+
+  page_handler_->GetPreviewsEnabled(
+      base::BindOnce(&MockGetPreviewsEnabledCallback));
+  auto defer_all_script = passed_in_modes.find(kDeferAllScriptPreviewsHtmlId);
+  ASSERT_NE(passed_in_modes.end(), defer_all_script);
+  EXPECT_EQ(kDeferAllScriptPreviewsDescription,
+            defer_all_script->second->description);
+  EXPECT_FALSE(defer_all_script->second->enabled);
+}
+
+TEST_F(InterventionsInternalsPageHandlerTest, DeferAllScriptPreviewsEnabled) {
+  // Init with kDeferAllScriptPreviews enabled.
+  scoped_feature_list_->InitWithFeatures(
+      {previews::features::kDeferAllScriptPreviews}, {});
+
+  page_handler_->GetPreviewsEnabled(
+      base::BindOnce(&MockGetPreviewsEnabledCallback));
+  auto defer_all_script = passed_in_modes.find(kDeferAllScriptPreviewsHtmlId);
+  ASSERT_NE(passed_in_modes.end(), defer_all_script);
+  EXPECT_EQ(kDeferAllScriptPreviewsDescription,
+            defer_all_script->second->description);
+  EXPECT_TRUE(defer_all_script->second->enabled);
+}
+
 TEST_F(InterventionsInternalsPageHandlerTest, GetFlagsCount) {
   page_handler_->GetPreviewsFlagsDetails(
       base::BindOnce(&MockGetPreviewsFlagsCallback));
 
-  constexpr size_t expected = 8;
+  constexpr size_t expected = 9;
   EXPECT_EQ(expected, passed_in_flags.size());
 }
 
@@ -663,6 +698,53 @@
 }
 
 TEST_F(InterventionsInternalsPageHandlerTest,
+       GetFlagsDeferAllScriptDefaultValue) {
+  page_handler_->GetPreviewsFlagsDetails(
+      base::BindOnce(&MockGetPreviewsFlagsCallback));
+  auto defer_all_script_flag = passed_in_flags.find(kDeferAllScriptFlagHtmlId);
+
+  ASSERT_NE(passed_in_flags.end(), defer_all_script_flag);
+  EXPECT_EQ(flag_descriptions::kEnableDeferAllScriptName,
+            defer_all_script_flag->second->description);
+  EXPECT_EQ(kDefaultFlagValue, defer_all_script_flag->second->value);
+  EXPECT_EQ(kDeferAllScriptFlagLink, defer_all_script_flag->second->link);
+}
+
+TEST_F(InterventionsInternalsPageHandlerTest, GetFlagsDeferAllScriptEnabled) {
+  base::test::ScopedCommandLine scoped_command_line;
+  base::CommandLine* command_line = scoped_command_line.GetProcessCommandLine();
+  command_line->AppendSwitchASCII(switches::kEnableFeatures,
+                                  kDeferAllScriptFeatureName);
+
+  page_handler_->GetPreviewsFlagsDetails(
+      base::BindOnce(&MockGetPreviewsFlagsCallback));
+  auto defer_all_script_flag = passed_in_flags.find(kDeferAllScriptFlagHtmlId);
+
+  ASSERT_NE(passed_in_flags.end(), defer_all_script_flag);
+  EXPECT_EQ(flag_descriptions::kEnableDeferAllScriptName,
+            defer_all_script_flag->second->description);
+  EXPECT_EQ(kEnabledFlagValue, defer_all_script_flag->second->value);
+  EXPECT_EQ(kDeferAllScriptFlagLink, defer_all_script_flag->second->link);
+}
+
+TEST_F(InterventionsInternalsPageHandlerTest, GetFlagsDeferAllScriptDisabled) {
+  base::test::ScopedCommandLine scoped_command_line;
+  base::CommandLine* command_line = scoped_command_line.GetProcessCommandLine();
+  command_line->AppendSwitchASCII(switches::kDisableFeatures,
+                                  kDeferAllScriptFeatureName);
+
+  page_handler_->GetPreviewsFlagsDetails(
+      base::BindOnce(&MockGetPreviewsFlagsCallback));
+  auto defer_all_script_flag = passed_in_flags.find(kDeferAllScriptFlagHtmlId);
+
+  ASSERT_NE(passed_in_flags.end(), defer_all_script_flag);
+  EXPECT_EQ(flag_descriptions::kEnableDeferAllScriptName,
+            defer_all_script_flag->second->description);
+  EXPECT_EQ(kDisabledFlagValue, defer_all_script_flag->second->value);
+  EXPECT_EQ(kDeferAllScriptFlagLink, defer_all_script_flag->second->link);
+}
+
+TEST_F(InterventionsInternalsPageHandlerTest,
        GetFlagsLitePageRedirectDefaultValue) {
   page_handler_->GetPreviewsFlagsDetails(
       base::BindOnce(&MockGetPreviewsFlagsCallback));
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 41ceadb..7f2e1ff 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1037,48 +1037,53 @@
   html_source->AddString("naturalScrollLearnMoreLink",
                          GetHelpUrlWithBoard(chrome::kNaturalScrollHelpURL));
 }
-#endif
 
-void AddDownloadsStrings(content::WebUIDataSource* html_source) {
+void AddFilesStrings(content::WebUIDataSource* html_source) {
   static constexpr LocalizedString kLocalizedStrings[] = {
-    {"downloadsPageTitle", IDS_SETTINGS_DOWNLOADS},
-    {"downloadLocation", IDS_SETTINGS_DOWNLOAD_LOCATION},
-    {"changeDownloadLocation", IDS_SETTINGS_CHANGE_DOWNLOAD_LOCATION},
-    {"promptForDownload", IDS_SETTINGS_PROMPT_FOR_DOWNLOAD},
-    {"disconnectGoogleDriveAccount", IDS_SETTINGS_DISCONNECT_GOOGLE_DRIVE},
-    {"openFileTypesAutomatically", IDS_SETTINGS_OPEN_FILE_TYPES_AUTOMATICALLY},
-#if defined(OS_CHROMEOS)
-    {"smbSharesTitle", IDS_SETTINGS_DOWNLOADS_SMB_SHARES},
-    {"smbSharesLearnMoreLabel",
-     IDS_SETTINGS_DOWNLOADS_SMB_SHARES_LEARN_MORE_LABEL},
-    {"addSmbShare", IDS_SETTINGS_DOWNLOADS_SMB_SHARES_ADD_SHARE},
-    {"smbShareAddedSuccessfulMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_SUCCESS_MESSAGE},
-    {"smbShareAddedErrorMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_ERROR_MESSAGE},
-    {"smbShareAddedAuthFailedMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_AUTH_FAILED_MESSAGE},
-    {"smbShareAddedNotFoundMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_NOT_FOUND_MESSAGE},
-    {"smbShareAddedUnsupportedDeviceMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_UNSUPPORTED_DEVICE_MESSAGE},
-    {"smbShareAddedMountExistsMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_EXISTS_MESSAGE},
-    {"smbShareAddedInvalidURLMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_INVALID_URL_MESSAGE},
-    {"smbShareAddedInvalidSSOURLMessage",
-     IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_INVALID_SSO_URL_MESSAGE},
-#endif
+      {"filesPageTitle", IDS_OS_SETTINGS_FILES},
+      {"smbSharesTitle", IDS_SETTINGS_DOWNLOADS_SMB_SHARES},
+      {"smbSharesLearnMoreLabel",
+       IDS_SETTINGS_DOWNLOADS_SMB_SHARES_LEARN_MORE_LABEL},
+      {"addSmbShare", IDS_SETTINGS_DOWNLOADS_SMB_SHARES_ADD_SHARE},
+      {"smbShareAddedSuccessfulMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_SUCCESS_MESSAGE},
+      {"smbShareAddedErrorMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_ERROR_MESSAGE},
+      {"smbShareAddedAuthFailedMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_AUTH_FAILED_MESSAGE},
+      {"smbShareAddedNotFoundMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_NOT_FOUND_MESSAGE},
+      {"smbShareAddedUnsupportedDeviceMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_UNSUPPORTED_DEVICE_MESSAGE},
+      {"smbShareAddedMountExistsMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_EXISTS_MESSAGE},
+      {"smbShareAddedInvalidURLMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_INVALID_URL_MESSAGE},
+      {"smbShareAddedInvalidSSOURLMessage",
+       IDS_SETTINGS_DOWNLOADS_SHARE_ADDED_MOUNT_INVALID_SSO_URL_MESSAGE},
   };
   AddLocalizedStringsBulk(html_source, kLocalizedStrings,
                           base::size(kLocalizedStrings));
 
-#if defined(OS_CHROMEOS)
   chromeos::smb_dialog::AddLocalizedStrings(html_source);
 
   html_source->AddString("smbSharesLearnMoreURL",
                          GetHelpUrlWithBoard(chrome::kSmbSharesLearnMoreURL));
-#endif
+}
+#endif  // defined(OS_CHROMEOS)
+
+void AddDownloadsStrings(content::WebUIDataSource* html_source) {
+  static constexpr LocalizedString kLocalizedStrings[] = {
+      {"downloadsPageTitle", IDS_SETTINGS_DOWNLOADS},
+      {"downloadLocation", IDS_SETTINGS_DOWNLOAD_LOCATION},
+      {"changeDownloadLocation", IDS_SETTINGS_CHANGE_DOWNLOAD_LOCATION},
+      {"promptForDownload", IDS_SETTINGS_PROMPT_FOR_DOWNLOAD},
+      {"disconnectGoogleDriveAccount", IDS_SETTINGS_DISCONNECT_GOOGLE_DRIVE},
+      {"openFileTypesAutomatically",
+       IDS_SETTINGS_OPEN_FILE_TYPES_AUTOMATICALLY},
+  };
+  AddLocalizedStringsBulk(html_source, kLocalizedStrings,
+                          base::size(kLocalizedStrings));
 }
 
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
@@ -3221,6 +3226,7 @@
   AddDateTimeStrings(html_source);
   AddDeviceStrings(html_source);
   AddEasyUnlockStrings(html_source);
+  AddFilesStrings(html_source);
   AddInternetStrings(html_source);
   AddMultideviceStrings(html_source);
   AddParentalControlStrings(html_source);
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.cc b/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.cc
index aaab6f0..856ad1a2 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.cc
@@ -16,6 +16,7 @@
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/views/widget/widget.h"
 #include "url/gurl.h"
 
 namespace chromeos {
@@ -46,6 +47,11 @@
   dialog->ShowSystemDialog();
 }
 
+void InlineLoginHandlerDialogChromeOS::AdjustWidgetInitParams(
+    views::Widget::InitParams* params) {
+  params->keep_on_top = false;
+}
+
 gfx::Size InlineLoginHandlerDialogChromeOS::GetMaximumDialogSize() {
   gfx::Size size;
   GetDialogSize(&size);
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.h b/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.h
index 703e4a2..5a5e94f 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.h
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_dialog_chromeos.h
@@ -28,6 +28,9 @@
   // account re-authentication.
   static void Show(const std::string& email = std::string());
 
+  // ui::SystemWebDialogDelegate overrides.
+  void AdjustWidgetInitParams(views::Widget::InitParams* params) override;
+
   // web_modal::WebContentsModalDialogHost overrides.
   gfx::Size GetMaximumDialogSize() override;
   gfx::NativeView GetHostView() const override;
diff --git a/chrome/browser/ui/webui/webapks_handler.cc b/chrome/browser/ui/webui/webapks_handler.cc
index 73e48ff..bd24b8b 100644
--- a/chrome/browser/ui/webui/webapks_handler.cc
+++ b/chrome/browser/ui/webui/webapks_handler.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/ui/webui/webapks_handler.h"
 
+#include <memory>
 #include <string>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/callback_forward.h"
@@ -25,6 +27,10 @@
       "requestWebApksInfo",
       base::BindRepeating(&WebApksHandler::HandleRequestWebApksInfo,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "requestWebApkUpdate",
+      base::BindRepeating(&WebApksHandler::HandleRequestWebApkUpdate,
+                          base::Unretained(this)));
 }
 
 void WebApksHandler::HandleRequestWebApksInfo(const base::ListValue* args) {
@@ -33,13 +39,21 @@
       &WebApksHandler::OnWebApkInfoRetrieved, weak_ptr_factory_.GetWeakPtr()));
 }
 
+void WebApksHandler::HandleRequestWebApkUpdate(const base::ListValue* args) {
+  AllowJavascript();
+  for (const auto& val : args->GetList()) {
+    if (val.is_string())
+      ShortcutHelper::SetForceWebApkUpdate(val.GetString());
+  }
+}
+
 void WebApksHandler::OnWebApkInfoRetrieved(
     const std::vector<WebApkInfo>& webapks_list) {
   if (!IsJavascriptAllowed())
     return;
   base::ListValue list;
   for (const auto& webapk_info : webapks_list) {
-    std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
+    auto result = std::make_unique<base::DictionaryValue>();
     result->SetString("name", webapk_info.name);
     result->SetString("shortName", webapk_info.short_name);
     result->SetString("packageName", webapk_info.package_name);
@@ -61,6 +75,7 @@
     result->SetDouble("lastUpdateCheckTimeMs",
                       webapk_info.last_update_check_time.ToJsTime());
     result->SetBoolean("relaxUpdates", webapk_info.relax_updates);
+    result->SetString("updateStatus", webapk_info.update_status);
     list.Append(std::move(result));
   }
 
diff --git a/chrome/browser/ui/webui/webapks_handler.h b/chrome/browser/ui/webui/webapks_handler.h
index 7d051ee8..1ecda6f 100644
--- a/chrome/browser/ui/webui/webapks_handler.h
+++ b/chrome/browser/ui/webui/webapks_handler.h
@@ -28,7 +28,12 @@
   // Handler for the "requestWebApksInfo" message. This requests
   // information for the installed WebAPKs and returns it to JS using
   // OnWebApkInfoReceived().
-  virtual void HandleRequestWebApksInfo(const base::ListValue* args);
+  void HandleRequestWebApksInfo(const base::ListValue* args);
+
+  // Handler for the "requestWebApkUpdate" message. This sets the
+  // update flag for a set of WebAPKs. |args| should contain the
+  // webapp IDs of the WebAPKs to update.
+  void HandleRequestWebApkUpdate(const base::ListValue* args);
 
  private:
   // Sends information for the installed WebAPKs to JS.
diff --git a/chrome/browser/vr/service/arcore_consent_prompt_interface.cc b/chrome/browser/vr/service/arcore_consent_prompt_interface.cc
index b7ecf750..80ece6d9 100644
--- a/chrome/browser/vr/service/arcore_consent_prompt_interface.cc
+++ b/chrome/browser/vr/service/arcore_consent_prompt_interface.cc
@@ -7,17 +7,17 @@
 namespace vr {
 
 namespace {
-ArcoreConsentPromptInterface* g_arcore_consent_prompt = nullptr;
+ArCoreConsentPromptInterface* g_arcore_consent_prompt = nullptr;
 }
 
 // static
-void ArcoreConsentPromptInterface::SetInstance(
-    ArcoreConsentPromptInterface* instance) {
+void ArCoreConsentPromptInterface::SetInstance(
+    ArCoreConsentPromptInterface* instance) {
   g_arcore_consent_prompt = instance;
 }
 
 // static
-ArcoreConsentPromptInterface* ArcoreConsentPromptInterface::GetInstance() {
+ArCoreConsentPromptInterface* ArCoreConsentPromptInterface::GetInstance() {
   return g_arcore_consent_prompt;
 }
 
diff --git a/chrome/browser/vr/service/arcore_consent_prompt_interface.h b/chrome/browser/vr/service/arcore_consent_prompt_interface.h
index 442c908..44249ecf 100644
--- a/chrome/browser/vr/service/arcore_consent_prompt_interface.h
+++ b/chrome/browser/vr/service/arcore_consent_prompt_interface.h
@@ -12,10 +12,10 @@
 
 // TODO(crbug.com/968233): Unify consent flow.
 // This class solves layering problem until the above bug gets fixed.
-class VR_EXPORT ArcoreConsentPromptInterface {
+class VR_EXPORT ArCoreConsentPromptInterface {
  public:
-  static void SetInstance(ArcoreConsentPromptInterface*);
-  static ArcoreConsentPromptInterface* GetInstance();
+  static void SetInstance(ArCoreConsentPromptInterface*);
+  static ArCoreConsentPromptInterface* GetInstance();
 
   virtual void ShowConsentPrompt(
       int render_process_id,
diff --git a/chrome/browser/vr/service/xr_device_impl.cc b/chrome/browser/vr/service/xr_device_impl.cc
index db3b47b..097f1ed 100644
--- a/chrome/browser/vr/service/xr_device_impl.cc
+++ b/chrome/browser/vr/service/xr_device_impl.cc
@@ -211,7 +211,7 @@
       if (IsXrDeviceConsentPromptDisabledForTesting()) {
         DoRequestSession(std::move(options), std::move(callback));
       } else {
-        ArcoreConsentPromptInterface::GetInstance()->ShowConsentPrompt(
+        ArCoreConsentPromptInterface::GetInstance()->ShowConsentPrompt(
             render_frame_host_->GetProcess()->GetID(),
             render_frame_host_->GetRoutingID(),
             base::BindOnce(&XRDeviceImpl::OnConsentResult,
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index bde87c7..a33daf3 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -368,6 +368,19 @@
     "IncompatibleApplicationsWarning", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
+#if defined(OS_CHROMEOS)
+// Enables scraping of password-expiry information during SAML login flow, which
+// can lead to an in-session flow for changing SAML password if it has expired.
+// This is safe to enable by default since it does not cause the password-expiry
+// information to be stored, or any user-visible change - in order for anything
+// to happen, the domain administrator has to intentionally send this extra
+// info in the SAML response, and enable the InSessionPasswordChange policy.
+// So, this feature is just for disabling the scraping code if it causes
+// any unforeseen issues.
+const base::Feature kInSessionPasswordChange{"InSessionPasswordChange",
+                                             base::FEATURE_ENABLED_BY_DEFAULT};
+#endif  // defined(OS_CHROMEOS)
+
 #if defined(OS_ANDROID)
 // Enables or disables the installable ambient badge infobar.
 const base::Feature kInstallableAmbientBadgeInfoBar{
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 97f35037..0e618cb 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -240,6 +240,11 @@
 extern const base::Feature kIncompatibleApplicationsWarning;
 #endif
 
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kInSessionPasswordChange;
+#endif
+
 #if defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kInstallableAmbientBadgeInfoBar;
diff --git a/chrome/installer/mac/signing/commands.py b/chrome/installer/mac/signing/commands.py
index fa23202..c6ec720 100644
--- a/chrome/installer/mac/signing/commands.py
+++ b/chrome/installer/mac/signing/commands.py
@@ -18,7 +18,34 @@
 
 def copy_files(source, dest):
     assert source[-1] != '/'
-    subprocess.check_call(['rsync', '-a', '--delete', source, dest])
+    subprocess.check_call(
+        ['rsync', '--archive', '--checksum', '--delete', source, dest])
+
+
+def copy_dir_overwrite_and_count_changes(source, dest, dry_run=False):
+    assert source[-1] != '/'
+    command = [
+        'rsync', '--archive', '--checksum', '--itemize-changes', '--delete',
+        source + '/', dest
+    ]
+    if dry_run:
+        command.append('--dry-run')
+    output = subprocess.check_output(command)
+
+    # --itemize-changes will print a '.' in the first column if the item is not
+    # being updated, created, or deleted. This happens if only attributes
+    # change, such as a timestamp or permissions. Timestamp changes are
+    # uninteresting for the purposes of determining changed content, but
+    # permissions changes are not. Columns 6-8 are also checked so that files
+    # that have potentially interesting attributes (permissions, owner, or
+    # group) changing are counted, but column 5 for the timestamp is not
+    # considered.
+    changes = 0
+    for line in output.split('\n'):
+        if line == '' or (line[0] == '.' and line[5:8] == '...'):
+            continue
+        changes += 1
+    return changes
 
 
 def move_file(source, dest):
diff --git a/chrome/installer/mac/signing/commands_test.py b/chrome/installer/mac/signing/commands_test.py
index ad57f02..1fe17396 100644
--- a/chrome/installer/mac/signing/commands_test.py
+++ b/chrome/installer/mac/signing/commands_test.py
@@ -29,6 +29,110 @@
         os.unlink(path)
         self.assertFalse(commands.file_exists(path))
 
+    def test_copy_dir_overwrite_and_count_changes(self):
+        source_dir = os.path.join(self.tempdir, 'source')
+        os.mkdir(source_dir)
+
+        os.mkdir(os.path.join(source_dir, 'dir'))
+        open(os.path.join(source_dir, 'dir', 'file'), 'w').close()
+        with open(os.path.join(source_dir, 'file'), 'w') as file:
+            file.write('contents')
+
+        dest_dir = os.path.join(self.tempdir, 'dest')
+
+        # Make sure that dry_run doesn't actually change anything by testing it
+        # a couple of times before doing any real work.
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=True), 4)
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=True), 4)
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 4)
+
+        # Now test that a subsequent copy of the same thing doesn't report any
+        # changes.
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=True), 0)
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 0)
+
+        self.assertTrue(os.path.isdir(dest_dir))
+        self.assertTrue(os.path.isfile(os.path.join(dest_dir, 'file')))
+        self.assertTrue(os.path.isdir(os.path.join(dest_dir, 'dir')))
+        self.assertTrue(os.path.isfile(os.path.join(dest_dir, 'dir', 'file')))
+
+        self.assertEqual(
+            os.path.getsize(os.path.join(dest_dir, 'dir', 'file')), 0)
+        with open(os.path.join(dest_dir, 'file')) as file:
+            self.assertEqual(file.read(), 'contents')
+
+        # No changes to source should result in no changes reported.
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 0)
+
+        # Changing a timestamp isn't reported, but the timestamp does get
+        # updated.
+        os.utime(os.path.join(source_dir, 'dir', 'file'), (0, 0))
+
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 0)
+
+        self.assertEqual(
+            os.path.getmtime(os.path.join(dest_dir, 'dir', 'file')), 0)
+
+        # Changing a file is reported.
+        with open(os.path.join(source_dir, 'file'), 'w') as file:
+            file.write('new contents')
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 1)
+
+        with open(os.path.join(dest_dir, 'file')) as file:
+            self.assertEqual(file.read(), 'new contents')
+
+        # Changing a file whose length doesn't change is reported.
+        with open(os.path.join(source_dir, 'file'), 'w') as file:
+            file.write('new_contents')
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 1)
+
+        with open(os.path.join(dest_dir, 'file')) as file:
+            self.assertEqual(file.read(), 'new_contents')
+
+        # Creating directories and files are reported.
+        os.mkdir(os.path.join(source_dir, 'new_dir'))
+        open(os.path.join(source_dir, 'new_file'), 'w').close()
+
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 2)
+
+        self.assertTrue(os.path.isfile(os.path.join(dest_dir, 'new_file')))
+        self.assertTrue(os.path.isdir(os.path.join(dest_dir, 'new_dir')))
+
+        # Removing files and directories is also reported.
+        os.rmdir(os.path.join(source_dir, 'new_dir'))
+        os.unlink(os.path.join(source_dir, 'new_file'))
+        os.mkdir(os.path.join(source_dir, 'newer_dir'))
+        open(os.path.join(source_dir, 'newer_file'), 'w').close()
+
+        self.assertEqual(
+            commands.copy_dir_overwrite_and_count_changes(
+                source_dir, dest_dir, dry_run=False), 4)
+
+        self.assertFalse(os.path.exists(os.path.join(dest_dir, 'new_file')))
+        self.assertFalse(os.path.exists(os.path.join(dest_dir, 'new_dir')))
+        self.assertTrue(os.path.isfile(os.path.join(dest_dir, 'newer_file')))
+        self.assertTrue(os.path.isdir(os.path.join(dest_dir, 'newer_dir')))
+
     def test_move_file(self):
         orig_path = os.path.join(self.tempdir, 'file.txt')
         new_path = os.path.join(self.tempdir, 'renamed.txt')
diff --git a/chrome/installer/mac/signing/modification_test.py b/chrome/installer/mac/signing/modification_test.py
index 32f3b9b2..6e546d93b 100644
--- a/chrome/installer/mac/signing/modification_test.py
+++ b/chrome/installer/mac/signing/modification_test.py
@@ -51,6 +51,34 @@
         self.paths = model.Paths('$I', '$O', '$W')
         self.config = test_config.TestConfig()
 
+    def _is_framework_unchanged(self, plistlib, mocks):
+        # Determines whether any modifications were made within the framework
+        # according to calls to plistlib.writePlist or any of the mocked calls
+        # in |mocks|. This is done by examining the calls' arguments for a
+        # substring pointing into the framework.
+
+        def _do_mock_calls_mention_framework(mock_calls):
+            for call in mock_calls:
+                for tup in call:
+                    for arg in tup:
+                        # Don't anchor this substring in a particular directory
+                        # because it may appear in any of $I, $O, or $W. Don't
+                        # anchor it with App Product.app either, because it may
+                        # be renamed (to App Product Canary.app).
+                        if 'Contents/Frameworks/Product Framework.framework' in arg:
+                            return True
+
+            return False
+
+        if _do_mock_calls_mention_framework(plistlib.writePlist.mock_calls):
+            return False
+
+        for mocked in mocks.values():
+            if _do_mock_calls_mention_framework(mocked):
+                return False
+
+        return True
+
     def test_base_distribution(self, plistlib, **kwargs):
         dist = model.Distribution()
         config = dist.to_config(self.config)
@@ -78,6 +106,8 @@
         self.assertEqual(0, kwargs['move_file'].call_count)
         self.assertEqual(0, kwargs['write_file'].call_count)
 
+        self.assertTrue(self._is_framework_unchanged(plistlib, kwargs))
+
     def test_distribution_with_brand(self, plistlib, **kwargs):
         dist = model.Distribution(branding_code='MOO')
         config = dist.to_config(self.config)
@@ -105,6 +135,8 @@
         ])
         self.assertEqual(0, kwargs['move_file'].call_count)
 
+        self.assertTrue(self._is_framework_unchanged(plistlib, kwargs))
+
     def test_distribution_with_channel(self, plistlib, **kwargs):
         dist = model.Distribution(channel='dev')
         config = dist.to_config(self.config)
@@ -134,6 +166,8 @@
         self.assertEqual(0, kwargs['move_file'].call_count)
         self.assertEqual(0, kwargs['write_file'].call_count)
 
+        self.assertTrue(self._is_framework_unchanged(plistlib, kwargs))
+
     def test_distribution_with_product_dirname(self, plistlib, **kwargs):
         dist = model.Distribution(product_dirname='Farmland/Cows')
         config = dist.to_config(self.config)
@@ -162,6 +196,8 @@
         self.assertEqual(0, kwargs['move_file'].call_count)
         self.assertEqual(0, kwargs['write_file'].call_count)
 
+        self.assertTrue(self._is_framework_unchanged(plistlib, kwargs))
+
     def test_distribution_with_creator_code(self, plistlib, **kwargs):
         dist = model.Distribution(creator_code='Mooo')
         config = dist.to_config(self.config)
@@ -292,3 +328,5 @@
             }, '$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.canary.manifest/Contents/Resources/test.signing.bundle_id.canary.manifest'
                      )
         ])
+
+        self.assertFalse(self._is_framework_unchanged(plistlib, kwargs))
diff --git a/chrome/installer/mac/signing/pipeline.py b/chrome/installer/mac/signing/pipeline.py
index 8f8185b..f8318c3b 100644
--- a/chrome/installer/mac/signing/pipeline.py
+++ b/chrome/installer/mac/signing/pipeline.py
@@ -14,7 +14,7 @@
 from . import commands, model, modification, notarize, signing
 
 
-def _customize_and_sign_chrome(paths, dist_config, dest_dir):
+def _customize_and_sign_chrome(paths, dist_config, dest_dir, signed_frameworks):
     """Does channel customization and signing of a Chrome distribution. The
     resulting app bundle is moved into |dest_dir|.
 
@@ -23,6 +23,14 @@
         dist_config: A |config.CodeSignConfig| for the |model.Distribution|.
         dest_dir: The directory into which the product will be placed when
             the operations are completed.
+        signed_frameworks: A dict that will store paths and change counts of
+            already-signed inner frameworks keyed by bundle ID. Paths are used
+            to recycle already-signed frameworks instead of re-signing them.
+            Change counts are used to verify equivalence of frameworks when
+            recycling them. Callers can pass an empty dict on the first call,
+            and reuse the same dict for subsequent calls. This function will
+            produce and consume entries in the dict. If this sharing is
+            undesired, pass None instead of a dict.
     """
     # Copy the app to sign into the work dir.
     commands.copy_files(
@@ -32,7 +40,53 @@
     modification.customize_distribution(paths, dist_config.distribution,
                                         dist_config)
 
-    signing.sign_chrome(paths, dist_config)
+    work_dir_framework_path = os.path.join(paths.work,
+                                           dist_config.framework_dir)
+    if signed_frameworks is not None and dist_config.base_bundle_id in signed_frameworks:
+        # If the inner framework has already been modified and signed for this
+        # bundle ID, recycle the existing signed copy without signing a new
+        # copy. This ensures that bit-for-bit identical input will result in
+        # bit-for-bit identical signatures not affected by differences in, for
+        # example, the signature's timestamp. All variants of a product sharing
+        # the same bundle ID are assumed to have bit-for-bit identical
+        # frameworks.
+        #
+        # This is significant because of how binary diff updates work. Binary
+        # diffs are built between two successive versions on the basis of their
+        # inner frameworks being bit-for-bit identical without regard to any
+        # customizations applied only to the outer app. In order for these to
+        # apply to all installations regardless of the presence or specific
+        # values of any app-level customizations, all inner frameworks for a
+        # single version and base bundle ID must always remain bit-for-bit
+        # identical, including their signatures.
+        (signed_framework_path, signed_framework_change_count
+        ) = signed_frameworks[dist_config.base_bundle_id]
+        actual_framework_change_count = commands.copy_dir_overwrite_and_count_changes(
+            os.path.join(dest_dir, signed_framework_path),
+            work_dir_framework_path,
+            dry_run=False)
+
+        if actual_framework_change_count != signed_framework_change_count:
+            raise ValueError(
+                'While customizing and signing {} ({}), actual_framework_change_count {} != signed_framework_change_count {}'
+                .format(dist_config.base_bundle_id, dist_config.dmg_basename,
+                        actual_framework_change_count,
+                        signed_framework_change_count))
+
+        signing.sign_chrome(paths, dist_config, sign_framework=False)
+    else:
+        unsigned_framework_path = os.path.join(paths.work,
+                                               'modified_unsigned_framework')
+        commands.copy_dir_overwrite_and_count_changes(
+            work_dir_framework_path, unsigned_framework_path, dry_run=False)
+        signing.sign_chrome(paths, dist_config, sign_framework=True)
+        actual_framework_change_count = commands.copy_dir_overwrite_and_count_changes(
+            work_dir_framework_path, unsigned_framework_path, dry_run=True)
+        if signed_frameworks is not None:
+            dest_dir_framework_path = os.path.join(dest_dir,
+                                                   dist_config.framework_dir)
+            signed_frameworks[dist_config.base_bundle_id] = (
+                dest_dir_framework_path, actual_framework_change_count)
 
     app_path = os.path.join(paths.work, dist_config.app_dir)
     commands.make_dir(dest_dir)
@@ -212,6 +266,7 @@
         # First, sign all the distributions and optionally submit the
         # notarization requests.
         uuids_to_config = {}
+        signed_frameworks = {}
         for dist in config.distributions:
             with commands.WorkDirectory(orig_paths) as paths:
                 dist_config = dist.to_config(config)
@@ -224,7 +279,8 @@
                     dest_dir = notary_paths.work
 
                 dest_dir = os.path.join(dest_dir, dist_config.dmg_basename)
-                _customize_and_sign_chrome(paths, dist_config, dest_dir)
+                _customize_and_sign_chrome(paths, dist_config, dest_dir,
+                                           signed_frameworks)
 
                 # If the build products are to be notarized, ZIP the app bundle
                 # and submit it for notarization.
diff --git a/chrome/installer/mac/signing/pipeline_test.py b/chrome/installer/mac/signing/pipeline_test.py
index e4c831f..a0d65d8 100644
--- a/chrome/installer/mac/signing/pipeline_test.py
+++ b/chrome/installer/mac/signing/pipeline_test.py
@@ -28,8 +28,9 @@
 
 @mock.patch.multiple(
     'signing.commands', **{
-        m: mock.DEFAULT for m in ('move_file', 'copy_files', 'run_command',
-                                  'make_dir', 'shutil')
+        m: mock.DEFAULT for m in ('move_file', 'copy_files',
+                                  'copy_dir_overwrite_and_count_changes',
+                                  'run_command', 'make_dir', 'shutil')
     })
 @mock.patch.multiple(
     'signing.signing',
@@ -53,35 +54,138 @@
         dist_config = dist.to_config(config)
         paths = self.paths.replace_work('$W')
 
-        pipeline._customize_and_sign_chrome(paths, dist_config, '$D')
+        pipeline._customize_and_sign_chrome(paths, dist_config, '$D', None)
 
         manager.assert_has_calls([
             mock.call.copy_files('$I/App Product.app', '$W'),
             mock.call.customize_distribution(paths, dist, dist_config),
-            mock.call.sign_chrome(paths, dist_config),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=False),
+            mock.call.sign_chrome(paths, dist_config, sign_framework=True),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=True),
             mock.call.make_dir('$D'),
             mock.call.move_file('$W/App Product.app', '$D/App Product.app')
         ])
 
     @mock.patch('signing.modification.customize_distribution')
-    def test_customize_and_sign_chrome_customize(self, customize, **kwargs):
+    def test_customize_and_sign_chrome_customize_different_product(
+            self, customize, **kwargs):
         manager = mock.Mock()
         for attr in kwargs:
             manager.attach_mock(kwargs[attr], attr)
         manager.attach_mock(customize, 'customize_distribution')
 
         dist = model.Distribution(
-            channel_customize=True, app_name_fragment='Canary')
+            channel_customize=True,
+            channel='canary',
+            app_name_fragment='Canary')
         config = test_config.TestConfig()
         dist_config = dist.to_config(config)
         paths = self.paths.replace_work('$W')
 
-        pipeline._customize_and_sign_chrome(paths, dist_config, '$D')
+        pipeline._customize_and_sign_chrome(paths, dist_config, '$D', None)
 
         manager.assert_has_calls([
             mock.call.copy_files('$I/App Product.app', '$W'),
             mock.call.customize_distribution(paths, dist, dist_config),
-            mock.call.sign_chrome(paths, dist_config),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=False),
+            mock.call.sign_chrome(paths, dist_config, sign_framework=True),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=True),
+            mock.call.make_dir('$D'),
+            mock.call.move_file('$W/App Product Canary.app',
+                                '$D/App Product Canary.app')
+        ])
+
+    @mock.patch('signing.modification.customize_distribution')
+    def test_customize_and_sign_chrome_customize_multiple_times(
+            self, customize, **kwargs):
+        kwargs['copy_dir_overwrite_and_count_changes'].return_value = 0
+
+        manager = mock.Mock()
+        for attr in kwargs:
+            manager.attach_mock(kwargs[attr], attr)
+        manager.attach_mock(customize, 'customize_distribution')
+
+        config = test_config.TestConfig()
+
+        base_dist = model.Distribution()
+        base_dist_config = base_dist.to_config(config)
+        paths = self.paths.replace_work('$W')
+
+        signed_frameworks = {}
+        pipeline._customize_and_sign_chrome(paths, base_dist_config, '$D',
+                                            signed_frameworks)
+
+        branded_dist = model.Distribution(
+            branding_code='c0de', dmg_name_fragment='Branded')
+        branded_dist_config = branded_dist.to_config(config)
+        paths = self.paths.replace_work('$W')
+
+        pipeline._customize_and_sign_chrome(paths, branded_dist_config, '$D',
+                                            signed_frameworks)
+
+        channel_dist = model.Distribution(
+            channel_customize=True,
+            channel='canary',
+            app_name_fragment='Canary')
+        channel_dist_config = channel_dist.to_config(config)
+        paths = self.paths.replace_work('$W')
+
+        pipeline._customize_and_sign_chrome(paths, channel_dist_config, '$D',
+                                            signed_frameworks)
+
+        manager.assert_has_calls([
+            mock.call.copy_files('$I/App Product.app', '$W'),
+            mock.call.customize_distribution(paths, base_dist,
+                                             base_dist_config),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=False),
+            mock.call.sign_chrome(paths, base_dist_config, sign_framework=True),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=True),
+            mock.call.make_dir('$D'),
+            mock.call.move_file('$W/App Product.app', '$D/App Product.app'),
+            mock.call.copy_files('$I/App Product.app', '$W'),
+            mock.call.customize_distribution(paths, branded_dist,
+                                             branded_dist_config),
+            # TODO(https://crbug.com/964608): $D/$D is a relative path treatment
+            # bug.
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$D/$D/App Product.app/Contents/Frameworks/Product Framework.framework',
+                '$W/App Product.app/Contents/Frameworks/Product Framework.framework',
+                dry_run=False),
+            mock.call.sign_chrome(
+                paths, branded_dist_config, sign_framework=False),
+            mock.call.make_dir('$D'),
+            mock.call.move_file('$W/App Product.app', '$D/App Product.app'),
+            mock.call.copy_files('$I/App Product.app', '$W'),
+            mock.call.customize_distribution(paths, channel_dist,
+                                             channel_dist_config),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=False),
+            mock.call.sign_chrome(
+                paths, channel_dist_config, sign_framework=True),
+            mock.call.copy_dir_overwrite_and_count_changes(
+                '$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework',
+                '$W/modified_unsigned_framework',
+                dry_run=True),
             mock.call.make_dir('$D'),
             mock.call.move_file('$W/App Product Canary.app',
                                 '$D/App Product Canary.app')
@@ -374,7 +478,7 @@
         manager.assert_has_calls([
             # First customize the distribution and sign it.
             mock.call._customize_and_sign_chrome(
-                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99'),
+                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99', mock.ANY),
 
             # Prepare the app for notarization.
             mock.call.run_command([
@@ -422,7 +526,7 @@
         manager.assert_has_calls([
             # First customize the distribution and sign it.
             mock.call._customize_and_sign_chrome(
-                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99'),
+                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99', mock.ANY),
 
             # Prepare the app for notarization.
             mock.call.run_command([
@@ -461,7 +565,7 @@
         manager.assert_has_calls([
             # First customize the distribution and sign it.
             mock.call._customize_and_sign_chrome(
-                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99'),
+                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99', mock.ANY),
             mock.call.shutil.rmtree('$W_2'),
 
             # Make the DMG.
@@ -483,8 +587,8 @@
 
         manager.assert_has_calls([
             # First customize the distribution and sign it.
-            mock.call._customize_and_sign_chrome(mock.ANY, mock.ANY,
-                                                 '$O/AppProduct-99.0.9999.99'),
+            mock.call._customize_and_sign_chrome(
+                mock.ANY, mock.ANY, '$O/AppProduct-99.0.9999.99', mock.ANY),
             mock.call.shutil.rmtree('$W_2'),
             mock.call.shutil.rmtree('$W_1'),
 
@@ -522,10 +626,11 @@
 
         manager.assert_has_calls([
             mock.call._customize_and_sign_chrome(
-                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99'),
+                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99', mock.ANY),
             mock.call.shutil.rmtree('$W_2'),
             mock.call._customize_and_sign_chrome(
-                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99-ForCows'),
+                mock.ANY, mock.ANY, '$W_1/AppProduct-99.0.9999.99-ForCows',
+                mock.ANY),
             mock.call.shutil.rmtree('$W_3'),
             mock.call._package_and_sign_dmg(
                 self.paths.replace_work('$W_1/AppProduct-99.0.9999.99'),
diff --git a/chrome/installer/mac/signing/signing.py b/chrome/installer/mac/signing/signing.py
index b1673a02..de69f7ba 100644
--- a/chrome/installer/mac/signing/signing.py
+++ b/chrome/installer/mac/signing/signing.py
@@ -85,8 +85,7 @@
                 '{}.helper.renderer'.format(uncustomized_bundle_id),
                 # Do not use |full_hardened_runtime_options| because library
                 # validation is incompatible with the JIT entitlement.
-                options=CodeSignOptions.RESTRICT +
-                CodeSignOptions.KILL +
+                options=CodeSignOptions.RESTRICT + CodeSignOptions.KILL +
                 CodeSignOptions.HARDENED_RUNTIME,
                 entitlements='helper-renderer-entitlements.plist',
                 verify_options=VerifyOptions.DEEP),
@@ -98,8 +97,7 @@
                 # Do not use |full_hardened_runtime_options| because library
                 # validation is incompatible with the disable-library-validation
                 # entitlement.
-                options=CodeSignOptions.RESTRICT +
-                CodeSignOptions.KILL +
+                options=CodeSignOptions.RESTRICT + CodeSignOptions.KILL +
                 CodeSignOptions.HARDENED_RUNTIME,
                 entitlements='helper-plugin-entitlements.plist',
                 verify_options=VerifyOptions.DEEP),
@@ -226,7 +224,7 @@
         commands.run_command(['spctl', '--assess', '-vv', app_path])
 
 
-def sign_chrome(paths, config):
+def sign_chrome(paths, config, sign_framework=False):
     """Code signs the Chrome application bundle and all of its internal nested
     code parts.
 
@@ -234,6 +232,9 @@
         paths: A |model.Paths| object.
         config: The |model.CodeSignConfig| object. The |app_product| binary and
             nested binaries must exist in |paths.work|.
+        sign_framework: True if the inner framework is to be signed in addition
+            to the outer application. False if only the outer application is to
+            be signed.
     """
     parts = get_parts(config)
 
@@ -247,18 +248,20 @@
 
     _sanity_check_version_keys(paths, parts)
 
-    # To sign an .app bundle that contains nested code, the nested components
-    # themselves must be signed. Each of these components is signed below. Note
-    # that unless a framework has multiple versions (which is discouraged),
-    # signing the entire framework is equivalent to signing the Current version.
-    # https://developer.apple.com/library/content/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG13
-    for name, part in parts.items():
-        if name in ('app', 'framework'):
-            continue
-        sign_part(paths, config, part)
+    if sign_framework:
+        # To sign an .app bundle that contains nested code, the nested
+        # components themselves must be signed. Each of these components is
+        # signed below. Note that unless a framework has multiple versions
+        # (which is discouraged), signing the entire framework is equivalent to
+        # signing the Current version.
+        # https://developer.apple.com/library/content/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG13
+        for name, part in parts.items():
+            if name in ('app', 'framework'):
+                continue
+            sign_part(paths, config, part)
 
-    # Sign the framework bundle.
-    sign_part(paths, config, parts['framework'])
+        # Sign the framework bundle.
+        sign_part(paths, config, parts['framework'])
 
     provisioning_profile_basename = config.provisioning_profile_basename
     if provisioning_profile_basename:
diff --git a/chrome/installer/mac/signing/signing_test.py b/chrome/installer/mac/signing/signing_test.py
index 5163b68..8555d3d 100644
--- a/chrome/installer/mac/signing/signing_test.py
+++ b/chrome/installer/mac/signing/signing_test.py
@@ -57,7 +57,6 @@
         self.assertEqual('test.signing.bundle_id.helper',
                          all_parts['helper-app'].identifier)
 
-
     def test_part_options(self):
         parts = signing.get_parts(test_config.TestConfig())
         self.assertEqual(
@@ -73,13 +72,11 @@
                 model.CodeSignOptions.HARDENED_RUNTIME),
             set(parts['helper-app'].options))
         self.assertEqual(
-            set(model.CodeSignOptions.RESTRICT +
-                model.CodeSignOptions.KILL +
+            set(model.CodeSignOptions.RESTRICT + model.CodeSignOptions.KILL +
                 model.CodeSignOptions.HARDENED_RUNTIME),
             set(parts['helper-renderer-app'].options))
         self.assertEqual(
-            set(model.CodeSignOptions.RESTRICT +
-                model.CodeSignOptions.KILL +
+            set(model.CodeSignOptions.RESTRICT + model.CodeSignOptions.KILL +
                 model.CodeSignOptions.HARDENED_RUNTIME),
             set(parts['helper-plugin-app'].options))
         self.assertEqual(
@@ -266,7 +263,7 @@
         dist = model.Distribution()
         config = dist.to_config(test_config.TestConfig())
 
-        signing.sign_chrome(self.paths, config)
+        signing.sign_chrome(self.paths, config, sign_framework=True)
 
         # No files should be moved.
         self.assertEqual(0, kwargs['move_file'].call_count)
@@ -314,7 +311,7 @@
 
         config = dist.to_config(Config())
 
-        signing.sign_chrome(self.paths, config)
+        signing.sign_chrome(self.paths, config, sign_framework=True)
 
         self.assertEqual(kwargs['run_command'].mock_calls, [
             mock.call.run_command([
@@ -334,7 +331,7 @@
                 return None
 
         config = dist.to_config(Config())
-        signing.sign_chrome(self.paths, config)
+        signing.sign_chrome(self.paths, config, sign_framework=True)
 
         self.assertEqual(0, kwargs['copy_files'].call_count)
 
@@ -355,8 +352,9 @@
             # If the file is missing, signing should fail since TestConfig has
             # no optional parts.
             config = model.Distribution().to_config(test_config.TestConfig())
-            self.assertRaises(FileNotFoundError,
-                              lambda: signing.sign_chrome(self.paths, config))
+            self.assertRaises(
+                FileNotFoundError,
+                lambda: signing.sign_chrome(self.paths, config, sign_framework=True))
 
             class Config(test_config.TestConfig):
 
@@ -366,14 +364,50 @@
 
             # With the part marked as optional, it should succeed.
             config = model.Distribution().to_config(Config())
-            signing.sign_chrome(self.paths, config)
+            signing.sign_chrome(self.paths, config, sign_framework=True)
+
+    @mock.patch('signing.signing._sanity_check_version_keys')
+    def test_sign_chrome_no_framework(self, *args, **kwargs):
+        manager = mock.Mock()
+        for kwarg in kwargs:
+            manager.attach_mock(kwargs[kwarg], kwarg)
+
+        dist = model.Distribution()
+        config = dist.to_config(test_config.TestConfig())
+
+        signing.sign_chrome(self.paths, config, sign_framework=False)
+
+        # No files should be moved.
+        self.assertEqual(0, kwargs['move_file'].call_count)
+
+        # Test that the provisioning profile is copied.
+        self.assertEqual(kwargs['copy_files'].mock_calls, [
+            mock.call.copy_files(
+                '$I/Product Packaging/provisiontest.provisionprofile',
+                '$W/App Product.app/Contents/embedded.provisionprofile')
+        ])
+
+        # Ensure that only the app is signed.
+        signed_paths = [
+            call[1][2].path for call in kwargs['sign_part'].mock_calls
+        ]
+        self.assertEqual(signed_paths, ['App Product.app'])
+
+        self.assertEqual(kwargs['run_command'].mock_calls, [
+            mock.call.run_command([
+                'codesign', '--display', '--requirements', '-', '--verbose=5',
+                '$W/App Product.app'
+            ]),
+            mock.call.run_command(
+                ['spctl', '--assess', '-vv', '$W/App Product.app']),
+        ])
 
     @mock.patch(
         'signing.commands.plistlib.readPlist',
         side_effect=_get_plist_read('99.0.9999.99'))
     def test_sanity_check_ok(self, read_plist, **kwargs):
         config = model.Distribution().to_config(test_config.TestConfig())
-        signing.sign_chrome(self.paths, config)
+        signing.sign_chrome(self.paths, config, sign_framework=True)
 
     @mock.patch(
         'signing.commands.plistlib.readPlist',
@@ -381,4 +415,4 @@
     def test_sanity_check_bad(self, read_plist, **kwargs):
         config = model.Distribution().to_config(test_config.TestConfig())
         self.assertRaises(ValueError,
-                          lambda: signing.sign_chrome(self.paths, config))
+                          lambda: signing.sign_chrome(self.paths, config, sign_framework=True))
diff --git a/chrome/lib/util/public/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java b/chrome/lib/util/public/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
index be18b30..5fb8057 100644
--- a/chrome/lib/util/public/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
+++ b/chrome/lib/util/public/android/java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java
@@ -52,6 +52,18 @@
     }
 
     /**
+     * Checks whether the given event is any of DPAD left or NUMPAD left.
+     * @param event Event to be checked.
+     * @return Whether the event should be processed as a navigation left.
+     */
+    public static boolean isGoLeft(KeyEvent event) {
+        return isActionDown(event)
+                && (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT
+                        || (!event.isNumLockOn()
+                                && event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_4));
+    }
+
+    /**
      * Checks whether the given event is any of DPAD down, DPAD up, NUMPAD down or NUMPAD up.
      * @param event Event to be checked.
      * @return Whether the event should be processed as any of navigation up or navigation down.
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index ddf4f1d..5a78397 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -539,9 +539,8 @@
     "if (window.chrome &&"
     "    window.chrome.embeddedSearch &&"
     "    window.chrome.embeddedSearch.newTabPage &&"
-    "    window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected &&"
-    "    typeof "
-    "window.chrome.embeddedSearch.newTabPage.onlocalbackgroundselected =="
+    "    window.chrome.embeddedSearch.newTabPage.onthemechange &&"
+    "    typeof window.chrome.embeddedSearch.newTabPage.onthemechange =="
     "        'function') {"
     "  "
     "window.chrome.embeddedSearch.newTabPage."
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c50a31d..1f4a5551 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -108,8 +108,8 @@
     "base/chrome_test_utils.h",
     "base/chrome_unit_test_suite.cc",
     "base/chrome_unit_test_suite.h",
-    "base/find_in_page_observer.cc",
-    "base/find_in_page_observer.h",
+    "base/find_result_waiter.cc",
+    "base/find_result_waiter.h",
     "base/process_inspector_win.cc",
     "base/process_inspector_win.h",
     "base/process_lineage_win.cc",
@@ -2981,6 +2981,7 @@
     "../browser/performance_manager/performance_manager_unittest.cc",
     "../browser/performance_manager/persistence/site_data/exponential_moving_average_unittest.cc",
     "../browser/performance_manager/persistence/site_data/leveldb_site_data_store_unittest.cc",
+    "../browser/performance_manager/persistence/site_data/non_recording_site_data_cache_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_cache_impl_unittest.cc",
     "../browser/performance_manager/persistence/site_data/site_data_impl_unittest.cc",
diff --git a/chrome/test/base/find_in_page_observer.cc b/chrome/test/base/find_in_page_observer.cc
deleted file mode 100644
index 4cfce7c5..0000000
--- a/chrome/test/base/find_in_page_observer.cc
+++ /dev/null
@@ -1,66 +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 "chrome/test/base/find_in_page_observer.h"
-
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/ui/find_bar/find_tab_helper.h"
-#include "content/public/test/test_utils.h"
-
-namespace ui_test_utils {
-
-FindInPageNotificationObserver::FindInPageNotificationObserver(
-    content::WebContents* parent_tab)
-    : active_match_ordinal_(-1),
-      number_of_matches_(0),
-      current_find_request_id_(0),
-      seen_(false),
-      running_(false) {
-  FindTabHelper* find_tab_helper =
-      FindTabHelper::FromWebContents(parent_tab);
-  current_find_request_id_ = find_tab_helper->current_find_request_id();
-  registrar_.Add(this, chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-                 content::Source<content::WebContents>(parent_tab));
-}
-
-FindInPageNotificationObserver::~FindInPageNotificationObserver() {}
-
-void FindInPageNotificationObserver::Wait() {
-  if (seen_)
-    return;
-  running_ = true;
-  message_loop_runner_ = new content::MessageLoopRunner;
-  message_loop_runner_->Run();
-}
-
-void FindInPageNotificationObserver::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, type);
-
-  content::Details<FindNotificationDetails> find_details(details);
-  if (find_details->request_id() != current_find_request_id_)
-    return;
-
-  // We get multiple responses and one of those will contain the ordinal.
-  // This message comes to us before the final update is sent.
-  if (find_details->active_match_ordinal() > -1) {
-    active_match_ordinal_ = find_details->active_match_ordinal();
-    selection_rect_ = find_details->selection_rect();
-  }
-  if (find_details->final_update()) {
-    number_of_matches_ = find_details->number_of_matches();
-    seen_ = true;
-    if (running_) {
-      running_ = false;
-      message_loop_runner_->Quit();
-    }
-  } else {
-    DVLOG(1) << "Ignoring, since we only care about the final message";
-  }
-}
-
-}  // namespace ui_test_utils
-
diff --git a/chrome/test/base/find_in_page_observer.h b/chrome/test/base/find_in_page_observer.h
deleted file mode 100644
index 5bff63a..0000000
--- a/chrome/test/base/find_in_page_observer.h
+++ /dev/null
@@ -1,64 +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.
-
-#ifndef CHROME_TEST_BASE_FIND_IN_PAGE_OBSERVER_H_
-#define CHROME_TEST_BASE_FIND_IN_PAGE_OBSERVER_H_
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-#include "ui/gfx/geometry/rect.h"
-
-namespace content {
-class MessageLoopRunner;
-class WebContents;
-}
-
-namespace ui_test_utils {
-
-// FindInPageNotificationObserver allows blocking UI thread until find results
-// are available. Typical usage:
-// FindInPageWchar();
-// FindInPageNotificationObserver observer(tab);
-// observer.Wait();
-
-// Always construct FindInPageNotificationObserver AFTER initiating the search.
-// It captures the current search ID in constructor and waits for it only.
-class FindInPageNotificationObserver : public content::NotificationObserver {
- public:
-  explicit FindInPageNotificationObserver(content::WebContents* parent_tab);
-  ~FindInPageNotificationObserver() override;
-
-  void Wait();
-
-  int active_match_ordinal() const { return active_match_ordinal_; }
-  int number_of_matches() const { return number_of_matches_; }
-  gfx::Rect selection_rect() const { return selection_rect_; }
-
- private:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
-  content::NotificationRegistrar registrar_;
-  // We will at some point (before final update) be notified of the ordinal and
-  // we need to preserve it so we can send it later.
-  int active_match_ordinal_;
-  int number_of_matches_;
-  gfx::Rect selection_rect_;
-  // The id of the current find request, obtained from WebContents. Allows us
-  // to monitor when the search completes.
-  int current_find_request_id_;
-  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
-
-  bool seen_; // true after transition to expected state has been seen
-  bool running_; // indicates whether message loop is running
-
-  DISALLOW_COPY_AND_ASSIGN(FindInPageNotificationObserver);
-};
-
-}  // namespace ui_test_utils
-
-#endif  // CHROME_TEST_BASE_FIND_IN_PAGE_OBSERVER_H_
diff --git a/chrome/test/base/find_result_waiter.cc b/chrome/test/base/find_result_waiter.cc
new file mode 100644
index 0000000..50ebaa0
--- /dev/null
+++ b/chrome/test/base/find_result_waiter.cc
@@ -0,0 +1,52 @@
+// 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 "chrome/test/base/find_result_waiter.h"
+
+#include "base/run_loop.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "content/public/test/test_utils.h"
+
+namespace ui_test_utils {
+
+FindResultWaiter::FindResultWaiter(content::WebContents* parent_tab) {
+  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(parent_tab);
+  current_find_request_id_ = find_tab_helper->current_find_request_id();
+  observer_.Add(find_tab_helper);
+}
+
+FindResultWaiter::~FindResultWaiter() = default;
+
+void FindResultWaiter::Wait() {
+  if (seen_)
+    return;
+  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_->Run();
+}
+
+void FindResultWaiter::OnFindResultAvailable(
+    content::WebContents* web_contents) {
+  const FindNotificationDetails& find_details =
+      FindTabHelper::FromWebContents(web_contents)->find_result();
+
+  if (find_details.request_id() != current_find_request_id_)
+    return;
+
+  // We get multiple responses and one of those will contain the ordinal.
+  // This message comes to us before the final update is sent.
+  if (find_details.active_match_ordinal() > -1) {
+    active_match_ordinal_ = find_details.active_match_ordinal();
+    selection_rect_ = find_details.selection_rect();
+  }
+  if (find_details.final_update()) {
+    number_of_matches_ = find_details.number_of_matches();
+    seen_ = true;
+    if (run_loop_ && run_loop_->running())
+      run_loop_->Quit();
+  } else {
+    DVLOG(1) << "Ignoring, since we only care about the final message";
+  }
+}
+
+}  // namespace ui_test_utils
diff --git a/chrome/test/base/find_result_waiter.h b/chrome/test/base/find_result_waiter.h
new file mode 100644
index 0000000..b4d1231
--- /dev/null
+++ b/chrome/test/base/find_result_waiter.h
@@ -0,0 +1,69 @@
+// 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.
+
+#ifndef CHROME_TEST_BASE_FIND_RESULT_WAITER_H_
+#define CHROME_TEST_BASE_FIND_RESULT_WAITER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/ui/find_bar/find_result_observer.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace base {
+class RunLoop;
+}
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace ui_test_utils {
+
+// FindResultWaiter allows blocking UI thread until find results are available.
+// Typical usage:
+//
+//   FindInPageWchar();
+//   FindResultWaiter observer(tab);
+//   observer.Wait();
+//
+// Always construct FindResultWaiter AFTER initiating the search. It captures
+// the current search ID in the constructor and waits for it only.
+class FindResultWaiter : public FindResultObserver {
+ public:
+  explicit FindResultWaiter(content::WebContents* parent_tab);
+  ~FindResultWaiter() override;
+
+  void Wait();
+
+  int active_match_ordinal() const { return active_match_ordinal_; }
+  int number_of_matches() const { return number_of_matches_; }
+  gfx::Rect selection_rect() const { return selection_rect_; }
+
+ private:
+  // FindResultObserver:
+  void OnFindResultAvailable(content::WebContents* web_contents) override;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+  ScopedObserver<FindTabHelper, FindResultObserver> observer_{this};
+
+  // We will at some point (before final update) be notified of the ordinal and
+  // we need to preserve it so we can send it later.
+  int active_match_ordinal_ = -1;
+  int number_of_matches_ = 0;
+  gfx::Rect selection_rect_;
+  // The id of the current find request, obtained from WebContents. Allows us
+  // to monitor when the search completes.
+  int current_find_request_id_ = 0;
+
+  bool seen_ = false;  // true after transition to expected state has been seen
+
+  DISALLOW_COPY_AND_ASSIGN(FindResultWaiter);
+};
+
+}  // namespace ui_test_utils
+
+#endif  // CHROME_TEST_BASE_FIND_RESULT_WAITER_H_
diff --git a/chrome/test/base/ui_test_utils.cc b/chrome/test/base/ui_test_utils.cc
index b6b8ee0..210c3ce 100644
--- a/chrome/test/base/ui_test_utils.cc
+++ b/chrome/test/base/ui_test_utils.cc
@@ -43,7 +43,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
-#include "chrome/test/base/find_in_page_observer.h"
+#include "chrome/test/base/find_result_waiter.h"
 #include "components/app_modal/app_modal_dialog_queue.h"
 #include "components/app_modal/javascript_app_modal_dialog.h"
 #include "components/bookmarks/browser/bookmark_model.h"
@@ -442,7 +442,7 @@
   FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(tab);
   find_tab_helper->StartFinding(search_string, forward, match_case,
                                 true /* run_synchronously_for_testing */);
-  FindInPageNotificationObserver observer(tab);
+  FindResultWaiter observer(tab);
   observer.Wait();
   if (ordinal)
     *ordinal = observer.active_match_ordinal();
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 71d46c42..67b8fe6 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -182,19 +182,10 @@
   if (status.IsError())
     return status;
 
-  if (window.state != "normal") {
-    // restore window to normal first to allow position change.
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   auto bounds = std::make_unique<base::DictionaryValue>();
   bounds->SetInteger("left", x);
   bounds->SetInteger("top", y);
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::MaximizeWindow(const std::string& target_id) {
@@ -206,19 +197,9 @@
   if (window.state == "maximized")
     return Status(kOk);
 
-  if (window.state != "normal") {
-    // always restore window to normal first, since chrome ui doesn't allow
-    // maximizing a minimized or fullscreen window.
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   auto bounds = std::make_unique<base::DictionaryValue>();
   bounds->SetString("windowState", "maximized");
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::MinimizeWindow(const std::string& target_id) {
@@ -230,18 +211,9 @@
   if (window.state == "minimized")
     return Status(kOk);
 
-  if (window.state != "normal") {
-    // restore window to normal first
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   auto bounds = std::make_unique<base::DictionaryValue>();
   bounds->SetString("windowState", "minimized");
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::FullScreenWindow(const std::string& target_id) {
@@ -253,17 +225,9 @@
   if (window.state == "fullscreen")
     return Status(kOk);
 
-  if (window.state != "normal") {
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   auto bounds = std::make_unique<base::DictionaryValue>();
   bounds->SetString("windowState", "fullscreen");
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::SetWindowRect(const std::string& target_id,
@@ -275,15 +239,6 @@
 
   auto bounds = std::make_unique<base::DictionaryValue>();
 
-  // fully exit fullscreen
-  if (window.state != "normal") {
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   // window position
   int x = 0;
   int y = 0;
@@ -300,7 +255,7 @@
     bounds->SetInteger("height", height);
   }
 
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::GetWindowSize(const std::string& target_id,
@@ -333,14 +288,23 @@
 }
 
 Status ChromeImpl::SetWindowBounds(
-    int window_id,
+    Window* window,
     std::unique_ptr<base::DictionaryValue> bounds) {
   Status status = devtools_websocket_client_->ConnectIfNecessary();
   if (status.IsError())
     return status;
 
   base::DictionaryValue params;
-  params.SetInteger("windowId", window_id);
+  params.SetInteger("windowId", window->id);
+  if (window->state != "normal") {
+    params.SetString("bounds.windowState", "normal");
+    status = devtools_websocket_client_->SendCommand("Browser.setWindowBounds",
+                                                     params);
+    if (status.IsError())
+      return status;
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+  }
+
   params.Set("bounds", bounds->CreateDeepCopy());
   status = devtools_websocket_client_->SendCommand("Browser.setWindowBounds",
                                                    params);
@@ -352,14 +316,13 @@
   if (!bounds->GetString("windowState", &state))
     return Status(kOk);
 
-  Window window;
-  status = GetWindowBounds(window_id, &window);
+  status = GetWindowBounds(window->id, window);
   if (status.IsError())
     return status;
-  if (window.state == state)
+  if (window->state == state)
     return Status(kOk);
 
-  if (state == "maximized" && window.state == "normal") {
+  if (state == "maximized" && window->state == "normal") {
     // Maximize window is not supported in some environment, such as Mac Chrome
     // version 70 and above, or Linux without a window manager.
     // In these cases, we simulate window maximization by setting window size
@@ -391,7 +354,7 @@
                                                    params);
   } else {
     return Status(kUnknownError, "failed to change window state to " + state +
-                                     ", current state is " + window.state);
+                                     ", current state is " + window->state);
   }
 }
 
@@ -404,19 +367,10 @@
   if (status.IsError())
     return status;
 
-  if (window.state != "normal") {
-    // restore window to normal first to allow size change.
-    auto bounds = std::make_unique<base::DictionaryValue>();
-    bounds->SetString("windowState", "normal");
-    status = SetWindowBounds(window.id, std::move(bounds));
-    if (status.IsError())
-      return status;
-  }
-
   auto bounds = std::make_unique<base::DictionaryValue>();
   bounds->SetInteger("width", width);
   bounds->SetInteger("height", height);
-  return SetWindowBounds(window.id, std::move(bounds));
+  return SetWindowBounds(&window, std::move(bounds));
 }
 
 Status ChromeImpl::ParseWindow(std::unique_ptr<base::DictionaryValue> params,
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 3204a85..652e8e2 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -79,7 +79,7 @@
   Status ParseWindowBounds(std::unique_ptr<base::DictionaryValue> params,
                            Window* window);
   Status GetWindowBounds(int window_id, Window* window);
-  Status SetWindowBounds(int window_id,
+  Status SetWindowBounds(Window* window,
                          std::unique_ptr<base::DictionaryValue> bounds);
 
   bool quit_;
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.cc b/chrome/test/chromedriver/chrome/stub_web_view.cc
index bb3a0b4b..4c86e253 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.cc
+++ b/chrome/test/chromedriver/chrome/stub_web_view.cc
@@ -222,14 +222,6 @@
   return Status(kOk);
 }
 
-Status StubWebView::GetScreenOrientation(std::string* orientation) {
-  return Status(kOk);
-}
-
-Status StubWebView::SetScreenOrientation(std::string orientation) {
-  return Status(kOk);
-}
-
 bool StubWebView::IsOOPIF(const std::string& frame_id) {
   return false;
 }
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.h b/chrome/test/chromedriver/chrome/stub_web_view.h
index 5bad1cd2..6710df9 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.h
+++ b/chrome/test/chromedriver/chrome/stub_web_view.h
@@ -109,8 +109,6 @@
                                  int xoffset,
                                  int yoffset) override;
   Status SynthesizePinchGesture(int x, int y, double scale_factor) override;
-  Status GetScreenOrientation(std::string* orientation) override;
-  Status SetScreenOrientation(std::string orientation) override;
   bool IsOOPIF(const std::string& frame_id) override;
   FrameTracker* GetFrameTracker() const override;
   std::unique_ptr<base::Value> GetCastSinks() override;
diff --git a/chrome/test/chromedriver/chrome/web_view.h b/chrome/test/chromedriver/chrome/web_view.h
index 3fd9f3ce..36e7329 100644
--- a/chrome/test/chromedriver/chrome/web_view.h
+++ b/chrome/test/chromedriver/chrome/web_view.h
@@ -235,10 +235,6 @@
 
   virtual Status SynthesizePinchGesture(int x, int y, double scale_factor) = 0;
 
-  virtual Status GetScreenOrientation(std::string* orientation) = 0;
-
-  virtual Status SetScreenOrientation(std::string orientation) = 0;
-
   virtual bool IsOOPIF(const std::string& frame_id) = 0;
 
   virtual FrameTracker* GetFrameTracker() const = 0;
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index 75847a56..f02fb577 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -970,28 +970,6 @@
   return client_->SendCommand("Input.synthesizePinchGesture", params);
 }
 
-Status WebViewImpl::GetScreenOrientation(std::string* orientation) {
-  base::DictionaryValue empty_params;
-  std::unique_ptr<base::DictionaryValue> result;
-  Status status =
-    client_->SendCommandAndGetResult("Emulation.getScreenOrientation",
-                                      empty_params,
-                                      &result);
-  if (status.IsError() || !result->GetString("orientation", orientation))
-    return status;
-  return Status(kOk);
-}
-
-Status WebViewImpl::SetScreenOrientation(std::string orientation) {
-  base::DictionaryValue params;
-  params.SetString("screenOrientation", orientation);
-  Status status =
-    client_->SendCommand("Emulation.lockScreenOrientation", params);
-  if (status.IsError())
-    return status;
-  return Status(kOk);
-}
-
 Status WebViewImpl::CallAsyncFunctionInternal(
     const std::string& frame,
     const std::string& function,
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h
index ac52367..bb00be6 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.h
+++ b/chrome/test/chromedriver/chrome/web_view_impl.h
@@ -149,8 +149,6 @@
                                  int xoffset,
                                  int yoffset) override;
   Status SynthesizePinchGesture(int x, int y, double scale_factor) override;
-  Status GetScreenOrientation(std::string* orientation) override;
-  Status SetScreenOrientation(std::string orientation) override;
   bool IsOOPIF(const std::string& frame_id) override;
   FrameTracker* GetFrameTracker() const override;
   std::unique_ptr<base::Value> GetCastSinks() override;
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index f2712fe..a29f59e0 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -611,16 +611,6 @@
     params = {'cmd': cmd, 'params': cmd_params};
     return self.ExecuteCommand(Command.SEND_COMMAND_AND_GET_RESULT, params)
 
-  def GetScreenOrientation(self):
-    screen_orientation = self.ExecuteCommand(Command.GET_SCREEN_ORIENTATION)
-    return {
-       'orientation': screen_orientation['orientation']
-    }
-
-  def SetScreenOrientation(self, orientation_type):
-    params = {'parameters': {'orientation': orientation_type}}
-    self.ExecuteCommand(Command.SET_SCREEN_ORIENTATION, params)
-
   def SendKeys(self, *values):
     typing = []
     for value in values:
diff --git a/chrome/test/chromedriver/client/command_executor.py b/chrome/test/chromedriver/client/command_executor.py
index ba4390a..a608deb 100644
--- a/chrome/test/chromedriver/client/command_executor.py
+++ b/chrome/test/chromedriver/client/command_executor.py
@@ -143,8 +143,6 @@
       _Method.DELETE, '/session/:sessionId/session_storage')
   GET_SESSION_STORAGE_SIZE = (
       _Method.GET, '/session/:sessionId/session_storage/size')
-  GET_SCREEN_ORIENTATION = (_Method.GET, '/session/:sessionId/orientation')
-  SET_SCREEN_ORIENTATION = (_Method.POST, '/session/:sessionId/orientation')
   MOUSE_CLICK = (_Method.POST, '/session/:sessionId/click')
   MOUSE_DOUBLE_CLICK = (_Method.POST, '/session/:sessionId/doubleclick')
   MOUSE_BUTTON_DOWN = (_Method.POST, '/session/:sessionId/buttondown')
diff --git a/chrome/test/chromedriver/log_replay/client_replay.py b/chrome/test/chromedriver/log_replay/client_replay.py
index 6418470..1d94f20 100755
--- a/chrome/test/chromedriver/log_replay/client_replay.py
+++ b/chrome/test/chromedriver/log_replay/client_replay.py
@@ -129,7 +129,6 @@
     (Method.GET, "/session/:sessionId/chromium/network_conditions"),
     "GetNetworkConnection":
     (Method.GET, "/session/:sessionId/network_connection"),
-    "GetScreenOrientation": (Method.GET, "/session/:sessionId/orientation"),
     "GetSessionCapabilities": (Method.GET, "/session/:sessionId"),
     "GetSessionStorageItem":
     (Method.GET, "/session/:sessionId/session_storage/key/:key"),
@@ -196,7 +195,6 @@
     (Method.POST, "/session/:sessionId/chromium/network_conditions"),
     "SetNetworkConnection":
     (Method.POST, "/session/:sessionId/network_connection"),
-    "SetScreenOrientation": (Method.POST, "/session/:sessionId/orientation"),
     "SetScriptTimeout":
     (Method.POST, "/session/:sessionId/timeouts/async_script"),
     "SetSessionStorageItem":
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index ec43e01fb..3c80a219 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -514,20 +514,6 @@
                                    base::BindRepeating(&ExecuteGetElementSize),
                                    false /*w3c_standard_command*/)),
 
-      // No W3C equivalent.
-      CommandMapping(
-          kGet, "session/:sessionId/orientation",
-          WrapToCommand("GetScreenOrientation",
-                        base::BindRepeating(&ExecuteGetScreenOrientation),
-                        false /*w3c_standard_command*/)),
-
-      // No W3C equivalent.
-      CommandMapping(
-          kPost, "session/:sessionId/orientation",
-          WrapToCommand("SetScreenOrientation",
-                        base::BindRepeating(&ExecuteSetScreenOrientation),
-                        false /*w3c_standard_command*/)),
-
       // Similar to W3C GET /session/:sessionId/alert/text.
       CommandMapping(
           kGet, "session/:sessionId/alert_text",
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index 57e09d0..6e4a160 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -111,7 +111,7 @@
     }
   }
 
-  if (!params.HasKey("capabilities")) {
+  if (!params.HasKey("capabilities") && params.HasKey("desiredCapabilities")) {
     return false;
   }
 
@@ -1191,41 +1191,6 @@
   return Status(kUnknownCommand);
 }
 
-Status ExecuteGetScreenOrientation(Session* session,
-                                   const base::DictionaryValue& params,
-                                   std::unique_ptr<base::Value>* value) {
-  WebView* web_view = nullptr;
-  Status status = session->GetTargetWindow(&web_view);
-  if (status.IsError())
-    return status;
-
-  std::string screen_orientation;
-  status = web_view->GetScreenOrientation(&screen_orientation);
-  if (status.IsError())
-    return status;
-
-  base::DictionaryValue orientation_value;
-  orientation_value.SetString("orientation", screen_orientation);
-  value->reset(orientation_value.DeepCopy());
-  return Status(kOk);
-}
-
-Status ExecuteSetScreenOrientation(Session* session,
-                                   const base::DictionaryValue& params,
-                                   std::unique_ptr<base::Value>* value) {
-  WebView* web_view = nullptr;
-  Status status = session->GetTargetWindow(&web_view);
-  if (status.IsError())
-    return status;
-
-  std::string screen_orientation;
-  params.GetString("parameters.orientation", &screen_orientation);
-  status = web_view->SetScreenOrientation(screen_orientation);
-  if (status.IsError())
-    return status;
-  return Status(kOk);
-}
-
 Status ExecuteGenerateTestReport(Session* session,
                                  const base::DictionaryValue& params,
                                  std::unique_ptr<base::Value>* value) {
diff --git a/chrome/test/chromedriver/session_commands.h b/chrome/test/chromedriver/session_commands.h
index 4d21e343..2b457fb 100644
--- a/chrome/test/chromedriver/session_commands.h
+++ b/chrome/test/chromedriver/session_commands.h
@@ -168,14 +168,6 @@
                                    const base::DictionaryValue& params,
                                    std::unique_ptr<base::Value>* value);
 
-Status ExecuteGetScreenOrientation(Session* session,
-                                  const base::DictionaryValue& params,
-                                  std::unique_ptr<base::Value>* value);
-
-Status ExecuteSetScreenOrientation(Session* session,
-                                   const base::DictionaryValue& params,
-                                   std::unique_ptr<base::Value>* value);
-
 Status ExecuteGenerateTestReport(Session* session,
                                  const base::DictionaryValue& params,
                                  std::unique_ptr<base::Value>* value);
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 97af6ea..e214f90b 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -76,10 +76,6 @@
     'ChromeDriverTest.testEmulateNetworkConditionsSpeed',
     # crbug.com/469947
     'ChromeDriverTest.testTouchPinch',
-    # TODO: re-enable tests when DevTools supports ScreenOrientation commands.
-    'ChromeDriverAndroidTest.testScreenOrientation',
-    'ChromeDriverAndroidTest.testMultipleScreenOrientationChanges',
-    'ChromeDriverAndroidTest.testScreenOrientationAcrossMultipleTabs',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=833
     'ChromeDriverTest.testAlertOnNewWindow',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2532
@@ -2505,89 +2501,6 @@
     self._drivers[0].Quit()
     self._drivers[0] = self.CreateDriver()
 
-  def testScreenOrientation(self):
-    self._driver = self.CreateDriver()
-    self._driver.Load(
-      ChromeDriverTest.GetHttpUrlForFile('/chromedriver/orientation_test.html'))
-    screen_orientation_js = self._driver.ExecuteScript(
-        'return screen.orientation.type')
-    screen_orientation = self._driver.GetScreenOrientation()['orientation']
-    if screen_orientation == "LANDSCAPE":
-      screen_orientation = 'landscape-primary'
-    elif screen_orientation == "PORTRAIT":
-      screen_orientation = 'portrait-primary'
-    self.assertEqual(screen_orientation, screen_orientation_js)
-
-    self._driver.SetScreenOrientation("portrait-primary")
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.WaitForCondition(
-      lambda: 'orientation change 1' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    self.assertEqual(screen_orientation['orientation'], "PORTRAIT")
-
-    self._driver.SetScreenOrientation("portrait-secondary")
-    self.WaitForCondition(
-      lambda: 'orientation change 2' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(screen_orientation['orientation'], "PORTRAIT")
-
-    self._driver.SetScreenOrientation("PORTRAIT")
-    self.WaitForCondition(
-      lambda: 'orientation change 3' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(screen_orientation['orientation'], "PORTRAIT")
-
-    self._driver.SetScreenOrientation("landscape-primary")
-    self.WaitForCondition(
-      lambda: 'orientation change 4' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(screen_orientation['orientation'], "LANDSCAPE")
-
-    self._driver.SetScreenOrientation("landscape-secondary")
-    self.WaitForCondition(
-      lambda: 'orientation change 5' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(screen_orientation['orientation'], "LANDSCAPE")
-
-    self._driver.SetScreenOrientation("LANDSCAPE")
-    self.WaitForCondition(
-      lambda: 'orientation change 6' in self._driver.FindElement(
-        'tag name', 'div').GetText())
-    screen_orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(screen_orientation['orientation'], "LANDSCAPE")
-
-  def testMultipleScreenOrientationChanges(self):
-    self._driver = self.CreateDriver()
-
-    self._driver.SetScreenOrientation('PORTRAIT')
-    self.assertEqual(
-      self._driver.GetScreenOrientation()['orientation'], 'PORTRAIT')
-
-    self._driver.SetScreenOrientation('PORTRAIT')
-    self.assertEqual(
-      self._driver.GetScreenOrientation()['orientation'], 'PORTRAIT')
-
-  def testScreenOrientationAcrossMultipleTabs(self):
-    self._driver = self.CreateDriver()
-
-    self._driver.SetScreenOrientation('LANDSCAPE')
-    self._driver.Load(
-      ChromeDriverTest.GetHttpUrlForFile('/chromedriver/page_test.html'))
-    window1 = self._driver.GetCurrentWindowHandle()
-    self._driver.FindElement('css selector', '#link').Click()
-    orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(orientation['orientation'], 'LANDSCAPE')
-
-    self._driver.ExecuteScript('window.name = "oldWindow";')
-    self._driver.SwitchToWindow('oldWindow')
-    self.assertEqual(window1, self._driver.GetCurrentWindowHandle())
-    orientation = self._driver.GetScreenOrientation()
-    self.assertEqual(orientation['orientation'], 'LANDSCAPE')
-
   def testAndroidGetWindowSize(self):
     self._driver = self.CreateDriver()
     size = self._driver.GetWindowRect()
diff --git a/chrome/test/data/webui/cr_elements/cr_action_menu_test.js b/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
index 7839793..f57447767 100644
--- a/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_action_menu_test.js
@@ -242,42 +242,43 @@
     });
   }
 
-  test('close on Tab', function() {
-    return testFocusAfterClosing('Tab');
-  });
-  test('close on Escape', function() {
-    return testFocusAfterClosing('Escape');
-  });
+  test('close on Tab', () => testFocusAfterClosing('Tab'));
 
-  test('mouse movement focus options', function() {
-    function makeMouseoverEvent(node) {
-      const e = new MouseEvent('mouseover', {bubbles: true});
-      node.dispatchEvent(e);
-    }
+  test('close on Escape', () => testFocusAfterClosing('Escape'));
 
+  /** @param {!EventTarget} eventTarget */
+  function dispatchMouseoverEvent(eventTarget) {
+    eventTarget.dispatchEvent(new MouseEvent('mouseover', {bubbles: true}));
+  }
+
+  test('moving mouse on option 1 should focus it', () => {
     menu.showAt(dots);
-
-    // Moving mouse on option 1 should focus it.
     assertNotEquals(items[0], getDeepActiveElement());
-    makeMouseoverEvent(items[0]);
+    dispatchMouseoverEvent(items[0]);
     assertEquals(items[0], getDeepActiveElement());
+  });
 
-    // Moving mouse on the menu (not on option) should focus the menu.
-    makeMouseoverEvent(menu);
-    assertNotEquals(items[0], getDeepActiveElement());
-    assertEquals(menu, document.activeElement);
+  test('moving mouse on the menu (not on option) should focus the menu', () => {
+    menu.showAt(dots);
+    items[0].focus();
+    dispatchMouseoverEvent(menu);
+    assertEquals(dialog, getDeepActiveElement());
+  });
 
-    // Moving mouse on a disabled item should focus the menu.
+  test('moving mouse on a disabled item should focus the menu', () => {
+    menu.showAt(dots);
     items[2].toggleAttribute('disabled', true);
-    makeMouseoverEvent(items[2]);
-    assertNotEquals(checkboxFocusableElement, getDeepActiveElement());
-    assertEquals(menu, document.activeElement);
+    items[0].focus();
+    dispatchMouseoverEvent(items[2]);
+    assertEquals(dialog, getDeepActiveElement());
+  });
 
-    // Mouse movements should override keyboard focus.
-    down();
+  test('mouse movements should override keyboard focus', () => {
+    menu.showAt(dots);
+    items[0].focus();
     down();
     assertEquals(items[1], getDeepActiveElement());
-    makeMouseoverEvent(items[0]);
+    dispatchMouseoverEvent(items[0]);
     assertEquals(items[0], getDeepActiveElement());
   });
 
diff --git a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js b/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
index f1893fd..8121b66 100644
--- a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
+++ b/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
@@ -71,7 +71,9 @@
        *     called for the |words| property on an item of |complexArray|.
        */
       updateComplexArray(newArray) {
-        if (this.updateList('complexArray', x => x.letter, newArray)) {
+        if (this.updateList(
+                'complexArray', x => x.letter, newArray,
+                true /* uidBasedUpdate */)) {
           return {topArrayChanged: true, wordsArrayChanged: false};
         }
 
@@ -280,4 +282,24 @@
         assertTrue(result.wordsArrayChanged);
         assertComplexArrayEquals(testElement.complexArray, newArray);
       });
+
+  test('first item with same uid modified', () => {
+    const newArray = JSON.parse(JSON.stringify(testElement.complexArray));
+    assertTrue(newArray[0].words.length > 0);
+    assertNotEquals('apricot', newArray[0].words[0]);
+    newArray[0].words = ['apricot'];
+    assertTrue(testElement.updateList('complexArray', x => x.letter, newArray));
+    assertDeepEquals(['apricot'], testElement.complexArray[0].words);
+  });
+
+  test('first item modified with same uid and last item removed', () => {
+    const newArray = JSON.parse(JSON.stringify(testElement.complexArray));
+    assertTrue(newArray[0].words.length > 0);
+    assertNotEquals('apricot', newArray[0].words[0]);
+    newArray[0].words = ['apricot'];
+    assertTrue(newArray.length > 1);
+    newArray.pop();
+    assertTrue(testElement.updateList('complexArray', x => x.letter, newArray));
+    assertDeepEquals(['apricot'], testElement.complexArray[0].words);
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_advanced_page_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_advanced_page_browsertest.js
index 3256734..f440aa8e 100644
--- a/chrome/test/data/webui/settings/chromeos/os_advanced_page_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_advanced_page_browsertest.js
@@ -71,7 +71,7 @@
     const page = settingsMain.$$('os-settings-page');
     assertTrue(!!page);
     let sections =
-        ['privacy', 'languages', 'downloads', 'reset', 'dateTime', 'a11y'];
+        ['privacy', 'languages', 'files', 'reset', 'dateTime', 'a11y'];
 
     for (let i = 0; i < sections.length; i++) {
       const section = getSection(page, sections[i]);
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index d7848c4..ac7bb953 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -45,8 +45,7 @@
 var OSSettingsSmbPageTest = class extends OSSettingsBrowserTest {
   /** @override */
   get browsePreload() {
-    return super.browsePreload +
-        'chromeos/os_downloads_page/smb_shares_page.html';
+    return super.browsePreload + 'chromeos/os_files_page/smb_shares_page.html';
   }
 
   /** @override */
diff --git a/chromecast/browser/exo/wm_helper_cast_shell.cc b/chromecast/browser/exo/wm_helper_cast_shell.cc
index f9ab15d..61c54d4 100644
--- a/chromecast/browser/exo/wm_helper_cast_shell.cc
+++ b/chromecast/browser/exo/wm_helper_cast_shell.cc
@@ -108,7 +108,8 @@
 
 void WMHelperCastShell::OnDragExited() {}
 
-int WMHelperCastShell::OnPerformDrop(const ui::DropTargetEvent& event) {
+int WMHelperCastShell::OnPerformDrop(const ui::DropTargetEvent& event,
+                                     std::unique_ptr<ui::OSExchangeData> data) {
   NOTIMPLEMENTED();
   return ui::DragDropTypes::DRAG_MOVE;
 }
diff --git a/chromecast/browser/exo/wm_helper_cast_shell.h b/chromecast/browser/exo/wm_helper_cast_shell.h
index ade7b234..206bac3 100644
--- a/chromecast/browser/exo/wm_helper_cast_shell.h
+++ b/chromecast/browser/exo/wm_helper_cast_shell.h
@@ -104,7 +104,8 @@
   void OnDragEntered(const ui::DropTargetEvent& event) override;
   int OnDragUpdated(const ui::DropTargetEvent& event) override;
   void OnDragExited() override;
-  int OnPerformDrop(const ui::DropTargetEvent& event) override;
+  int OnPerformDrop(const ui::DropTargetEvent& event,
+                    std::unique_ptr<ui::OSExchangeData> data) override;
 
   // Overridden from VSyncTimingManager::Delegate:
   void AddVSyncParameterObserver(
diff --git a/chromecast/browser/extension_page.cc b/chromecast/browser/extension_page.cc
index d72bf754..197a9ac2 100644
--- a/chromecast/browser/extension_page.cc
+++ b/chromecast/browser/extension_page.cc
@@ -50,7 +50,6 @@
   window_->CreateWindowForWebContents(
       web_contents(), window_manager_, CastWindowManager::APP,
       chromecast::shell::VisibilityPriority::STICKY_ACTIVITY);
-  web_contents()->Focus();
 }
 
 void ExtensionPage::RenderViewCreated(
@@ -60,6 +59,7 @@
   if (view) {
     view->SetBackgroundColor(SK_ColorTRANSPARENT);
   }
+  web_contents()->Focus();
 }
 
 }  // namespace chromecast
diff --git a/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h b/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
index f9c2cb3..c1304eb 100644
--- a/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
+++ b/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
@@ -74,6 +74,8 @@
     event_bundle_sink_ = sink;
   }
 
+  int32_t GetIDFromWindow(aura::Window* window) { return cache_.GetID(window); }
+
  private:
   friend class base::NoDestructor<AutomationManagerAura>;
 
diff --git a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
index 4df11a8..6ca2402 100644
--- a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
+++ b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
@@ -4,9 +4,15 @@
 
 #include "chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.h"
 
+#include "chromecast/browser/accessibility/accessibility_manager.h"
+#include "chromecast/browser/cast_browser_process.h"
+#include "chromecast/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/accessibility/ax_tree_data.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
 #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
 
 AXTreeSourceAura::AXTreeSourceAura(views::AXAuraObjWrapper* root,
@@ -19,19 +25,20 @@
 bool AXTreeSourceAura::GetTreeData(ui::AXTreeData* tree_data) const {
   AXTreeSourceViews::GetTreeData(tree_data);
 
-  // TODO(b/111911092): AXTreeData::focus_id represents the node within the
-  // tree with 'keyboard focus'.  We have no keyboard focus on chromecast so
-  // this is being left as -1. This prevents getFocus calls from the chromevox
-  // background page from finding any window in focus and interferes with
-  // gesture event processing.  Since we only ever have one top level window
-  // and one ax tree, temporarily returning 1 here to indicate the root node
-  // is always the focused window. A better solution would be to fix the focus
-  // issues on chromecast which relies on a) the root window to be focused via
-  // Focus() and 2) a native widget being registered with the root window so
-  // the above GetFocus call will work.  When this code is re-unified with
-  // chrome, this will need to be a special case for chromecast unless the
-  // better solution described above is implemented.
-  tree_data->focus_id = 1;
+  aura::Window* root_window =
+      chromecast::shell::CastBrowserProcess::GetInstance()
+          ->accessibility_manager()
+          ->window_tree_host()
+          ->window();
+  if (root_window) {
+    aura::client::FocusClient* focus_client =
+        aura::client::GetFocusClient(root_window);
+    if (focus_client) {
+      aura::Window* window = focus_client->GetFocusedWindow();
+      tree_data->focus_id =
+          AutomationManagerAura::GetInstance()->GetIDFromWindow(window);
+    }
+  }
   return true;
 }
 
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index bf11007..045f5899 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -258,6 +258,7 @@
     ":volume_map",
     "//base",
     "//chromecast/base",
+    "//chromecast/base:thread_health_checker",
     "//chromecast/media/audio:libcast_external_audio_pipeline_1.0",
     "//chromecast/media/base",
     "//chromecast/media/cma/base",
diff --git a/chromecast/media/cma/backend/stream_mixer.cc b/chromecast/media/cma/backend/stream_mixer.cc
index d621ac2..af8955f2 100644
--- a/chromecast/media/cma/backend/stream_mixer.cc
+++ b/chromecast/media/cma/backend/stream_mixer.cc
@@ -22,6 +22,7 @@
 #include "build/build_config.h"
 #include "chromecast/base/chromecast_switches.h"
 #include "chromecast/base/serializers.h"
+#include "chromecast/base/thread_health_checker.h"
 #include "chromecast/media/base/audio_device_ids.h"
 #include "chromecast/media/cma/backend/audio_output_redirector.h"
 #include "chromecast/media/cma/backend/cast_audio_json.h"
@@ -56,6 +57,11 @@
 namespace chromecast {
 namespace media {
 
+constexpr base::TimeDelta kMixerThreadCheckTimeout =
+    base::TimeDelta::FromSeconds(10);
+constexpr base::TimeDelta kHealthCheckInterval =
+    base::TimeDelta::FromSeconds(5);
+
 class StreamMixer::ExternalLoopbackAudioObserver
     : public CastMediaShlib::LoopbackAudioObserver {
  public:
@@ -221,6 +227,13 @@
     loopback_options.priority = base::ThreadPriority::REALTIME_AUDIO;
     loopback_thread_->StartWithOptions(loopback_options);
     loopback_task_runner_ = loopback_thread_->task_runner();
+
+    health_checker_ = std::make_unique<ThreadHealthChecker>(
+        mixer_task_runner_, loopback_task_runner_, kHealthCheckInterval,
+        kMixerThreadCheckTimeout,
+        base::BindRepeating(&StreamMixer::OnHealthCheckFailed,
+                            base::Unretained(this)));
+    LOG(INFO) << "Mixer health checker started";
   } else {
     loopback_task_runner_ = mixer_task_runner_;
   }
@@ -249,6 +262,10 @@
   }
 }
 
+void StreamMixer::OnHealthCheckFailed() {
+  LOG(FATAL) << "Crash on mixer thread health check failure!";
+}
+
 void StreamMixer::ResetPostProcessors(CastMediaShlib::ResultCallback callback) {
   RUN_ON_MIXER_THREAD(ResetPostProcessorsOnThread, std::move(callback), "");
 }
diff --git a/chromecast/media/cma/backend/stream_mixer.h b/chromecast/media/cma/backend/stream_mixer.h
index d917684..8f575ac 100644
--- a/chromecast/media/cma/backend/stream_mixer.h
+++ b/chromecast/media/cma/backend/stream_mixer.h
@@ -28,6 +28,8 @@
 #include "chromecast/public/volume_control.h"
 
 namespace chromecast {
+class ThreadHealthChecker;
+
 namespace media {
 
 class AudioOutputRedirector;
@@ -195,6 +197,9 @@
   scoped_refptr<base::SingleThreadTaskRunner> mixer_task_runner_;
   std::unique_ptr<base::Thread> loopback_thread_;
   scoped_refptr<base::SingleThreadTaskRunner> loopback_task_runner_;
+  std::unique_ptr<ThreadHealthChecker> health_checker_;
+
+  void OnHealthCheckFailed();
 
   int num_output_channels_;
   const int low_sample_rate_cutoff_;
diff --git a/chromeos/dbus/update_engine_client.cc b/chromeos/dbus/update_engine_client.cc
index d6e05b3d..7e2206e 100644
--- a/chromeos/dbus/update_engine_client.cc
+++ b/chromeos/dbus/update_engine_client.cc
@@ -120,12 +120,15 @@
                      weak_ptr_factory_.GetWeakPtr(), callback));
       return;
     }
-    dbus::MethodCall method_call(
-        update_engine::kUpdateEngineInterface,
-        update_engine::kAttemptUpdate);
+    // TODO(crbug.com/982438): Use newer version of kAttemptUpdate instead once
+    // it was enhanced with protobuf arguments.
+    dbus::MethodCall method_call(update_engine::kUpdateEngineInterface,
+                                 update_engine::kAttemptUpdateWithFlags);
     dbus::MessageWriter writer(&method_call);
-    writer.AppendString("");  // Unused.
-    writer.AppendString("");  // Unused.
+    writer.AppendString("");  // app_version
+    writer.AppendString("");  // omaha_url
+    writer.AppendInt32(0);    // flags, default is 0 (interactive). See
+                              // org.chromium.UpdateEngineInterface.dbus-xml.
 
     VLOG(1) << "Requesting an update check";
     update_engine_proxy_->CallMethod(
diff --git a/chromeos/printing/ppd_provider.cc b/chromeos/printing/ppd_provider.cc
index 7958605..44b035d 100644
--- a/chromeos/printing/ppd_provider.cc
+++ b/chromeos/printing/ppd_provider.cc
@@ -491,9 +491,17 @@
         RunPpdReferenceResolutionSucceeded(std::move(next.cb),
                                            kEpsonGenericPPD);
       } else {
-        // We don't have anything else left to try. We've reached unsupported
-        // USB printer, try to grab the manufacturer name.
-        ResolveUsbManufacturer(std::move(next.cb), search_data.usb_vendor_id);
+        // We don't have anything else left to try.
+        if (search_data.discovery_type ==
+            PrinterSearchData::PrinterDiscoveryType::kUsb) {
+          // We've reached unsupported USB printer, try to grab the manufacturer
+          // name.
+          ResolveUsbManufacturer(std::move(next.cb), search_data.usb_vendor_id);
+        } else {
+          // Non-USB printer, so we fail resolution normally.
+          RunPpdReferenceResolutionNotFound(std::move(next.cb),
+                                            "" /* Empty Manufacturer */);
+        }
       }
     }
     // Didn't start any fetches.
diff --git a/chromeos/printing/ppd_provider_unittest.cc b/chromeos/printing/ppd_provider_unittest.cc
index 3ea200b..b78427a5 100644
--- a/chromeos/printing/ppd_provider_unittest.cc
+++ b/chromeos/printing/ppd_provider_unittest.cc
@@ -35,6 +35,8 @@
 
 namespace {
 
+using PrinterDiscoveryType = PrinterSearchData::PrinterDiscoveryType;
+
 // Name of the fake server we're resolving ppds from.
 const char kPpdServer[] = "bogus.google.com";
 
@@ -402,12 +404,15 @@
   auto provider = CreateProvider("en", false);
 
   PrinterSearchData unrecognized_printer;
+  unrecognized_printer.discovery_type = PrinterDiscoveryType::kManual;
   unrecognized_printer.make_and_model = {"Printer Printer"};
 
   PrinterSearchData recognized_printer;
+  recognized_printer.discovery_type = PrinterDiscoveryType::kManual;
   recognized_printer.make_and_model = {"printer_a_ref"};
 
   PrinterSearchData mixed;
+  mixed.discovery_type = PrinterDiscoveryType::kManual;
   mixed.make_and_model = {"printer_a_ref", "Printer Printer"};
 
   // Resolve the same thing repeatedly.
@@ -440,6 +445,7 @@
   auto provider = CreateProvider("en", false);
 
   PrinterSearchData search_data;
+  search_data.discovery_type = PrinterDiscoveryType::kUsb;
 
   // Should get back "Some canonical reference"
   search_data.usb_vendor_id = 0x031f;
@@ -954,6 +960,7 @@
   auto provider = CreateProvider("en", false);
 
   PrinterSearchData search_data;
+  search_data.discovery_type = PrinterDiscoveryType::kUsb;
 
   // Vendor id that exists, nonexistent device id, should get a NOT_FOUND.
   // Although this is an unsupported printer model, we can still expect to get
diff --git a/chromeos/services/assistant/platform/audio_stream_handler.cc b/chromeos/services/assistant/platform/audio_stream_handler.cc
index 8b9d58d..00e2afa88 100644
--- a/chromeos/services/assistant/platform/audio_stream_handler.cc
+++ b/chromeos/services/assistant/platform/audio_stream_handler.cc
@@ -112,7 +112,7 @@
     if (!stopped_)
       OnError(assistant_client::AudioOutput::Error::FATAL_ERROR);
 
-    std::move(start_device_owner_on_main_thread_);
+    start_device_owner_on_main_thread_.Reset();
     return;
   }
 
@@ -169,7 +169,7 @@
 void AudioStreamHandler::OnFillBufferOnThread(
     assistant_client::Callback1<int> on_filled,
     int num_bytes) {
-  std::move(on_filled)(num_bytes);
+  on_filled(num_bytes);
 }
 
 void AudioStreamHandler::DecodeOnThread() {
diff --git a/chromeos/services/device_sync/cryptauth_device_sync_result.cc b/chromeos/services/device_sync/cryptauth_device_sync_result.cc
index 0ceca42a..1affb80a 100644
--- a/chromeos/services/device_sync/cryptauth_device_sync_result.cc
+++ b/chromeos/services/device_sync/cryptauth_device_sync_result.cc
@@ -10,8 +10,11 @@
 
 CryptAuthDeviceSyncResult::CryptAuthDeviceSyncResult(
     ResultCode result_code,
+    bool did_device_registry_change,
     const base::Optional<cryptauthv2::ClientDirective>& client_directive)
-    : result_code_(result_code), client_directive_(client_directive) {}
+    : result_code_(result_code),
+      did_device_registry_change_(did_device_registry_change),
+      client_directive_(client_directive) {}
 
 CryptAuthDeviceSyncResult::CryptAuthDeviceSyncResult(
     const CryptAuthDeviceSyncResult& other) = default;
@@ -45,8 +48,146 @@
     case ResultCode::kSuccess:
       stream << "[Success]";
       break;
-    case ResultCode::kError:
-      stream << "[Error]";
+    case ResultCode::kErrorMissingUserKeyPair:
+      stream << "[Error: No user key pair in registry]";
+      break;
+    case ResultCode::kErrorEncryptingDeviceMetadata:
+      stream << "[Error: Could not encrypt local device metadata]";
+      break;
+    case ResultCode::kErrorEstablishingGroupPublicKey:
+      stream << "[Error: Could not establish group public key]";
+      break;
+    case ResultCode::kErrorNoMetadataInResponse:
+      stream << "[Error: No encrypted metadata in SyncMetadata response]";
+      break;
+    case ResultCode::kErrorInvalidMetadataInResponse:
+      stream << "[Error: Invalid DeviceMetadataPacket in SyncMetadata "
+             << "response]";
+      break;
+    case ResultCode::kErrorDuplicateDeviceIdsInResponse:
+      stream << "[Error: Duplicate device IDs in SyncMetadata response]";
+      break;
+    case ResultCode::kErrorNoLocalDeviceMetadataInResponse:
+      stream << "[Error: No local device metadata in SyncMetadata response]";
+      break;
+    case ResultCode::kErrorMissingFeatureStatuses:
+      stream << "[Error: Feature statuses not received for device(s)]";
+      break;
+    case ResultCode::kErrorMissingLocalDeviceSyncBetterTogetherKey:
+      stream << "[Error: No DeviceSync:BetterTogether key in registry]";
+      break;
+    case ResultCode::kErrorDecryptingGroupPrivateKey:
+      stream << "[Error: Could not decrypt group private key]";
+      break;
+    case ResultCode::kErrorInconsistentGroupPrivateKeys:
+      stream << "[Error: Group private key from SyncMetadata response "
+             << "unexpectedly disagrees with the one in local storage]";
+      break;
+    case ResultCode::kErrorDecryptingMetadata:
+      stream << "[Error: Could not decrypt device metadata]";
+      break;
+    case ResultCode::kErrorParsingMetadata:
+      stream << "[Error: Could not parse device metadata]";
+      break;
+    case ResultCode::kErrorInconsistentLocalDeviceMetadata:
+      stream << "[Error: Local device metadata disagrees with that in "
+             << "SyncMetadata response]";
+      break;
+    case ResultCode::kErrorEncryptingGroupPrivateKey:
+      stream << "[Error: Could not encrypt group private key]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallOffline:
+      stream << "[SyncMetadata API call failed: Offline]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallEndpointNotFound:
+      stream << "[SyncMetadata API call failed: Endpoint not found]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallAuthenticationError:
+      stream << "[SyncMetadata API call failed: Authentication error]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallBadRequest:
+      stream << "[SyncMetadata API call failed: Bad request]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallResponseMalformed:
+      stream << "[SyncMetadata API call failed: Response malformed]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallInternalServerError:
+      stream << "[SyncMetadata API call failed: Internal server error]";
+      break;
+    case ResultCode::kErrorSyncMetadataApiCallUnknownError:
+      stream << "[SyncMetadata API call failed: Unknown error]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallOffline:
+      stream << "[BatchGetFeatureStatuses API call failed: Offline]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallEndpointNotFound:
+      stream << "[BatchGetFeatureStatuses API call failed: Endpoint not found]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallAuthenticationError:
+      stream << "[BatchGetFeatureStatuses API call failed: Authentication "
+             << "error]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallBadRequest:
+      stream << "[BatchGetFeatureStatuses API call failed: Bad request]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallResponseMalformed:
+      stream << "[BatchGetFeatureStatuses API call failed: Response malformed]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallInternalServerError:
+      stream << "[BatchGetFeatureStatuses API call failed: Internal server "
+             << "error]";
+      break;
+    case ResultCode::kErrorBatchGetFeatureStatusesApiCallUnknownError:
+      stream << "[BatchGetFeatureStatuses API call failed: Unknown error]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallOffline:
+      stream << "[ShareGroupPrivateKey API call failed: Offline]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallEndpointNotFound:
+      stream << "[ShareGroupPrivateKey API call failed: Endpoint not found]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallAuthenticationError:
+      stream << "[ShareGroupPrivateKey API call failed: Authentication error]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallBadRequest:
+      stream << "[ShareGroupPrivateKey API call failed: Bad request]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallResponseMalformed:
+      stream << "[ShareGroupPrivateKey API call failed: Response malformed]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallInternalServerError:
+      stream << "[ShareGroupPrivateKey API call failed: Internal server error]";
+      break;
+    case ResultCode::kErrorShareGroupPrivateKeyApiCallUnknownError:
+      stream << "[ShareGroupPrivateKey API call failed: Unknown error]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForGroupKeyCreation:
+      stream << "[Error: Timeout waiting for group key creation]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForLocalDeviceMetadataEncryption:
+      stream << "[Error: Timeout waiting for local device metadata encryption]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForFirstSyncMetadataResponse:
+      stream << "[Error: Timeout waiting for first SyncMetadata response]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForSecondSyncMetadataResponse:
+      stream << "[Error: Timeout waiting for second SyncMetadata response]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForGroupPrivateKeyDecryption:
+      stream << "[Error: Timeout waiting for group private key decryption]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForDeviceMetadataDecryption:
+      stream << "[Error: Timeout waiting for remote device metadata "
+             << "decryption]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForBatchGetFeatureStatusesResponse:
+      stream << "[Error: Timeout waiting for BatchGetFeatureStatuses response]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForGroupPrivateKeyEncryption:
+      stream << "[Error: Timeout waiting for group private key encryption]";
+      break;
+    case ResultCode::kErrorTimeoutWaitingForShareGroupPrivateKeyResponse:
+      stream << "[Error: Timeout waiting for ShareGroupPrivateKey response]";
       break;
   }
 
diff --git a/chromeos/services/device_sync/cryptauth_device_sync_result.h b/chromeos/services/device_sync/cryptauth_device_sync_result.h
index 651fe1b..f6dc61a 100644
--- a/chromeos/services/device_sync/cryptauth_device_sync_result.h
+++ b/chromeos/services/device_sync/cryptauth_device_sync_result.h
@@ -15,24 +15,67 @@
 namespace device_sync {
 
 // Information about the result of a CryptAuth v2 DeviceSync attempt.
-// TODO(nohle): Add a HaveDevicesChanged() function that returns true if the
-// synced-device registry changes.
 class CryptAuthDeviceSyncResult {
  public:
   // Enum class to denote the result of a CryptAuth v2 DeviceSync attempt.
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused. If entries are added, kMaxValue
   // should be updated.
-  // TODO(nohle): Add more values after DeviceSync flow is implemented.
+  // TODO(nohle): Add numeric values.
   enum class ResultCode {
-    kSuccess = 0,
-    kError = 1,
+    kSuccess,
+    kErrorMissingUserKeyPair,
+    kErrorEncryptingDeviceMetadata,
+    kErrorEstablishingGroupPublicKey,
+    kErrorNoMetadataInResponse,
+    kErrorInvalidMetadataInResponse,
+    kErrorDuplicateDeviceIdsInResponse,
+    kErrorNoLocalDeviceMetadataInResponse,
+    kErrorMissingFeatureStatuses,
+    kErrorMissingLocalDeviceSyncBetterTogetherKey,
+    kErrorDecryptingGroupPrivateKey,
+    kErrorInconsistentGroupPrivateKeys,
+    kErrorDecryptingMetadata,
+    kErrorParsingMetadata,
+    kErrorInconsistentLocalDeviceMetadata,
+    kErrorEncryptingGroupPrivateKey,
+    kErrorSyncMetadataApiCallOffline,
+    kErrorSyncMetadataApiCallEndpointNotFound,
+    kErrorSyncMetadataApiCallAuthenticationError,
+    kErrorSyncMetadataApiCallBadRequest,
+    kErrorSyncMetadataApiCallResponseMalformed,
+    kErrorSyncMetadataApiCallInternalServerError,
+    kErrorSyncMetadataApiCallUnknownError,
+    kErrorBatchGetFeatureStatusesApiCallOffline,
+    kErrorBatchGetFeatureStatusesApiCallEndpointNotFound,
+    kErrorBatchGetFeatureStatusesApiCallAuthenticationError,
+    kErrorBatchGetFeatureStatusesApiCallBadRequest,
+    kErrorBatchGetFeatureStatusesApiCallResponseMalformed,
+    kErrorBatchGetFeatureStatusesApiCallInternalServerError,
+    kErrorBatchGetFeatureStatusesApiCallUnknownError,
+    kErrorShareGroupPrivateKeyApiCallOffline,
+    kErrorShareGroupPrivateKeyApiCallEndpointNotFound,
+    kErrorShareGroupPrivateKeyApiCallAuthenticationError,
+    kErrorShareGroupPrivateKeyApiCallBadRequest,
+    kErrorShareGroupPrivateKeyApiCallResponseMalformed,
+    kErrorShareGroupPrivateKeyApiCallInternalServerError,
+    kErrorShareGroupPrivateKeyApiCallUnknownError,
+    kErrorTimeoutWaitingForGroupKeyCreation,
+    kErrorTimeoutWaitingForLocalDeviceMetadataEncryption,
+    kErrorTimeoutWaitingForFirstSyncMetadataResponse,
+    kErrorTimeoutWaitingForSecondSyncMetadataResponse,
+    kErrorTimeoutWaitingForGroupPrivateKeyDecryption,
+    kErrorTimeoutWaitingForDeviceMetadataDecryption,
+    kErrorTimeoutWaitingForBatchGetFeatureStatusesResponse,
+    kErrorTimeoutWaitingForGroupPrivateKeyEncryption,
+    kErrorTimeoutWaitingForShareGroupPrivateKeyResponse,
     // Used for UMA logs.
-    kMaxValue = kError
+    kMaxValue = kErrorTimeoutWaitingForShareGroupPrivateKeyResponse
   };
 
   CryptAuthDeviceSyncResult(
       ResultCode result_code,
+      bool did_device_registry_change,
       const base::Optional<cryptauthv2::ClientDirective>& client_directive);
   CryptAuthDeviceSyncResult(const CryptAuthDeviceSyncResult& other);
 
@@ -44,6 +87,10 @@
     return client_directive_;
   }
 
+  bool did_device_registry_change() const {
+    return did_device_registry_change_;
+  }
+
   bool IsSuccess() const;
 
   bool operator==(const CryptAuthDeviceSyncResult& other) const;
@@ -51,6 +98,7 @@
 
  private:
   ResultCode result_code_;
+  bool did_device_registry_change_;
   base::Optional<cryptauthv2::ClientDirective> client_directive_;
 };
 
diff --git a/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
index 0a48a8d..fedf1c9 100644
--- a/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_scheduler_impl_unittest.cc
@@ -497,7 +497,9 @@
         CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded,
         base::nullopt /* client_directive */));
     scheduler()->HandleDeviceSyncResult(
-        CryptAuthDeviceSyncResult(CryptAuthDeviceSyncResult::ResultCode::kError,
+        CryptAuthDeviceSyncResult(CryptAuthDeviceSyncResult::ResultCode::
+                                      kErrorSyncMetadataApiCallBadRequest,
+                                  false /* device_registry_changed */,
                                   base::nullopt /* client_directive */));
 
     expected_request.set_retry_count(attempt);
@@ -582,7 +584,9 @@
       CryptAuthEnrollmentResult::ResultCode::kErrorCryptAuthServerOverloaded,
       cryptauthv2::GetClientDirectiveForTest()));
   scheduler()->HandleDeviceSyncResult(
-      CryptAuthDeviceSyncResult(CryptAuthDeviceSyncResult::ResultCode::kError,
+      CryptAuthDeviceSyncResult(CryptAuthDeviceSyncResult::ResultCode::
+                                    kErrorSyncMetadataApiCallBadRequest,
+                                false /* device_registry_changed */,
                                 cryptauthv2::GetClientDirectiveForTest()));
 
   // Pending request scheduled after current attempt finishes, even if it fails.
diff --git a/components/autofill/core/browser/autofill_field.h b/components/autofill/core/browser/autofill_field.h
index eeb64b7..e46ff91 100644
--- a/components/autofill/core/browser/autofill_field.h
+++ b/components/autofill/core/browser/autofill_field.h
@@ -132,6 +132,9 @@
   void set_default_value(const std::string& value) { default_value_ = value; }
   const std::string& default_value() const { return default_value_; }
 
+  void set_initial_value_hash(uint32_t value) { initial_value_hash_ = value; }
+  base::Optional<uint32_t> initial_value_hash() { return initial_value_hash_; }
+
   void set_credit_card_number_offset(size_t position) {
     credit_card_number_offset_ = position;
   }
@@ -224,6 +227,10 @@
   // The default value returned by the Autofill server.
   std::string default_value_;
 
+  // A low-entropy hash of the field's initial value before user-interactions or
+  // automatic fillings. This field is used to detect static placeholders.
+  base::Optional<uint32_t> initial_value_hash_;
+
   // Used to hold the position of the first digit to be copied as a substring
   // from credit card number.
   size_t credit_card_number_offset_;
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 020fd9f..98c2230 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -1886,6 +1886,10 @@
       added_field->set_vote_type(field->vote_type());
     }
 
+    if (field->initial_value_hash()) {
+      added_field->set_initial_value_hash(field->initial_value_hash().value());
+    }
+
     added_field->set_signature(field->GetFieldSignature());
 
     if (field->properties_mask)
diff --git a/components/autofill/core/browser/proto/server.proto b/components/autofill/core/browser/proto/server.proto
index a77adf9..67002a1 100644
--- a/components/autofill/core/browser/proto/server.proto
+++ b/components/autofill/core/browser/proto/server.proto
@@ -186,7 +186,7 @@
 
 // This message contains information about the field types in a single form.
 // It is sent by the toolbar to contribute to the field type statistics.
-// Next available id: 40
+// Next available id: 41
 message AutofillUploadContents {
   required string client_version = 1;
   required fixed64 form_signature = 2;
@@ -305,6 +305,11 @@
     // A list of possible types for the field with their corresponding validity
     // states based on the user's data.
     repeated AutofillTypeValiditiesPair autofill_type_validities = 35;
+
+    // A low-entropy hash of the field's initial value before user-interactions
+    // or automatic fillings. This field is used to detect static
+    // placeholders.
+    optional uint32 initial_value_hash = 40;
   }
   // Signature of the form action host (e.g. Hash64Bit("example.com")).
   optional fixed64 action_signature = 13;
@@ -320,7 +325,7 @@
   // The form name.
   optional string form_name = 16;
 
-  // True iff the the non-obfuscated password values were shown to the user.
+  // True if the non-obfuscated password values were shown to the user.
   optional bool passwords_revealed = 24;
 
   // The section of noisified data about password.
@@ -341,10 +346,10 @@
   // Whether the password has any special symbol.
   optional bool password_has_special_symbol = 28;
 
-  // Noisifed password length.
+  // Noisified password length.
   optional uint32 password_length = 29;
 
-  // If |password_has_special_symbol| is true, this field contains nosified
+  // If |password_has_special_symbol| is true, this field contains noisified
   // information about a special symbol used in a user-created password stored
   // in ASCII code.
   // Otherwise, this field is unset.
diff --git a/components/cloud_devices/common/printer_description.cc b/components/cloud_devices/common/printer_description.cc
index f96b8f9..922a9688 100644
--- a/components/cloud_devices/common/printer_description.cc
+++ b/components/cloud_devices/common/printer_description.cc
@@ -583,13 +583,13 @@
 
 void RangeVendorCapability::SaveTo(base::Value* dict) const {
   DCHECK(IsValid());
-  dict->SetKey(
+  dict->SetStringKey(
       kKeyValueType,
-      base::Value(TypeToString(kRangeVendorCapabilityTypeNames, value_type_)));
-  dict->SetKey(kVendorCapabilityMinValue, base::Value(min_value_));
-  dict->SetKey(kVendorCapabilityMaxValue, base::Value(max_value_));
+      TypeToString(kRangeVendorCapabilityTypeNames, value_type_));
+  dict->SetStringKey(kVendorCapabilityMinValue, min_value_);
+  dict->SetStringKey(kVendorCapabilityMaxValue, max_value_);
   if (!default_value_.empty())
-    dict->SetKey(kVendorCapabilityDefaultValue, base::Value(default_value_));
+    dict->SetStringKey(kVendorCapabilityDefaultValue, default_value_);
 }
 
 SelectVendorCapabilityOption::SelectVendorCapabilityOption() = default;
@@ -670,14 +670,14 @@
 
 void TypedValueVendorCapability::SaveTo(base::Value* dict) const {
   DCHECK(IsValid());
-  dict->SetKey(kKeyValueType,
-               base::Value(TypeToString(kTypedValueVendorCapabilityTypeNames,
-                                        value_type_)));
+  dict->SetStringKey(
+      kKeyValueType,
+      TypeToString(kTypedValueVendorCapabilityTypeNames, value_type_));
   if (!default_value_.empty())
-    dict->SetKey(kVendorCapabilityDefaultValue, base::Value(default_value_));
+    dict->SetStringKey(kVendorCapabilityDefaultValue, default_value_);
 }
 
-VendorCapability::VendorCapability() = default;
+VendorCapability::VendorCapability() : type_(Type::NONE) {}
 
 VendorCapability::VendorCapability(const std::string& id,
                                    const std::string& display_name,
@@ -704,108 +704,170 @@
       display_name_(display_name),
       typed_value_capability_(std::move(typed_value_capability)) {}
 
-VendorCapability::VendorCapability(VendorCapability&& other) = default;
+VendorCapability::VendorCapability(VendorCapability&& other)
+    : type_(other.type_), id_(other.id_), display_name_(other.display_name_) {
+  switch (type_) {
+    case Type::NONE:
+      // No-op;
+      break;
+    case Type::RANGE:
+      new (&range_capability_)
+          RangeVendorCapability(std::move(other.range_capability_));
+      break;
+    case Type::SELECT:
+      new (&select_capability_)
+          SelectVendorCapability(std::move(other.select_capability_));
+      break;
+    case Type::TYPED_VALUE:
+      new (&typed_value_capability_)
+          TypedValueVendorCapability(std::move(other.typed_value_capability_));
+      break;
+    default:
+      NOTREACHED();
+  }
+}
 
-VendorCapability::~VendorCapability() = default;
+VendorCapability::~VendorCapability() {
+  InternalCleanup();
+}
+
+void VendorCapability::InternalCleanup() {
+  switch (type_) {
+    case Type::NONE:
+      break;
+    case Type::RANGE:
+      range_capability_.~RangeVendorCapability();
+      break;
+    case Type::SELECT:
+      select_capability_.~SelectVendorCapability();
+      break;
+    case Type::TYPED_VALUE:
+      typed_value_capability_.~TypedValueVendorCapability();
+      break;
+    default:
+      NOTREACHED();
+  }
+  type_ = Type::NONE;
+}
 
 bool VendorCapability::operator==(const VendorCapability& other) const {
-  return type_ == other.type_ && id_ == other.id_ &&
-         display_name_ == other.display_name_ &&
-         range_capability_ == other.range_capability_ &&
-         select_capability_ == other.select_capability_ &&
-         typed_value_capability_ == other.typed_value_capability_;
+  if (type_ != other.type_ || id_ != other.id_ ||
+      display_name_ != other.display_name_) {
+    return false;
+  }
+  switch (type_) {
+    case Type::NONE:
+      return true;
+    case Type::RANGE:
+      return range_capability_ == other.range_capability_;
+    case Type::SELECT:
+      return select_capability_ == other.select_capability_;
+    case Type::TYPED_VALUE:
+      return typed_value_capability_ == other.typed_value_capability_;
+  }
+  NOTREACHED() << "Bad vendor capability type";
 }
 
 bool VendorCapability::IsValid() const {
   if (id_.empty() || display_name_.empty())
     return false;
   switch (type_) {
+    case Type::NONE:
+      return false;
     case Type::RANGE:
-      return !select_capability_ && !typed_value_capability_ &&
-             range_capability_ && range_capability_.value().IsValid();
+      return range_capability_.IsValid();
     case Type::SELECT:
-      return !range_capability_ && !typed_value_capability_ &&
-             select_capability_ && select_capability_.value().IsValid();
+      return select_capability_.IsValid();
     case Type::TYPED_VALUE:
-      return !range_capability_ && !select_capability_ &&
-             typed_value_capability_ &&
-             typed_value_capability_.value().IsValid();
+      return typed_value_capability_.IsValid();
   }
   NOTREACHED() << "Bad vendor capability type";
   return false;
 }
 
 bool VendorCapability::LoadFrom(const base::Value& dict) {
+  InternalCleanup();
   const std::string* type_str = dict.FindStringKey(kKeyType);
+  Type type;
   if (!type_str ||
-      !TypeFromString(kVendorCapabilityTypeNames, *type_str, &type_)) {
+      !TypeFromString(kVendorCapabilityTypeNames, *type_str, &type)) {
     return false;
   }
+
   const std::string* id_str = dict.FindStringKey(kKeyId);
   if (!id_str)
     return false;
+
   id_ = *id_str;
   const std::string* display_name_str = dict.FindStringKey(kKeyDisplayName);
   if (!display_name_str)
     return false;
-  display_name_ = *display_name_str;
 
+  display_name_ = *display_name_str;
   const base::Value* range_capability_value =
-      dict.FindKey(kOptionRangeCapability);
-  if (range_capability_value) {
-    if (!range_capability_value->is_dict())
-      return false;
-    range_capability_ = RangeVendorCapability();
-    if (!range_capability_.value().LoadFrom(*range_capability_value))
-      return false;
-  }
+      dict.FindDictKey(kOptionRangeCapability);
+  if (!range_capability_value == (type == Type::RANGE))
+    return false;
 
   const base::Value* select_capability_value =
-      dict.FindKey(kOptionSelectCapability);
-  if (select_capability_value) {
-    if (!select_capability_value->is_dict())
-      return false;
-    select_capability_ = SelectVendorCapability();
-    if (!select_capability_.value().LoadFrom(*select_capability_value))
-      return false;
-  }
+      dict.FindDictKey(kOptionSelectCapability);
+  if (!select_capability_value == (type == Type::SELECT))
+    return false;
 
   const base::Value* typed_value_capability_value =
-      dict.FindKey(kOptionTypedValueCapability);
-  if (typed_value_capability_value) {
-    if (!typed_value_capability_value->is_dict())
-      return false;
-    typed_value_capability_ = TypedValueVendorCapability();
-    if (!typed_value_capability_.value().LoadFrom(
-            *typed_value_capability_value)) {
-      return false;
-    }
+      dict.FindDictKey(kOptionTypedValueCapability);
+  if (!typed_value_capability_value == (type == Type::TYPED_VALUE))
+    return false;
+
+  type_ = type;
+  switch (type_) {
+    case Type::NONE:
+    default:
+      NOTREACHED();
+      break;
+    case Type::RANGE:
+      new (&range_capability_) RangeVendorCapability();
+      return range_capability_.LoadFrom(*range_capability_value);
+    case Type::SELECT:
+      new (&select_capability_) SelectVendorCapability();
+      return select_capability_.LoadFrom(*select_capability_value);
+    case Type::TYPED_VALUE:
+      new (&typed_value_capability_) TypedValueVendorCapability();
+      return typed_value_capability_.LoadFrom(*typed_value_capability_value);
   }
 
-  return IsValid();
+  return false;
 }
 
 void VendorCapability::SaveTo(base::Value* dict) const {
   DCHECK(IsValid());
-  dict->SetKey(kKeyType,
-               base::Value(TypeToString(kVendorCapabilityTypeNames, type_)));
-  dict->SetKey(kKeyId, base::Value(id_));
-  dict->SetKey(kKeyDisplayName, base::Value(display_name_));
+  dict->SetStringKey(kKeyType, TypeToString(kVendorCapabilityTypeNames, type_));
+  dict->SetStringKey(kKeyId, id_);
+  dict->SetStringKey(kKeyDisplayName, display_name_);
 
-  if (range_capability_) {
-    base::Value range_capability_value(base::Value::Type::DICTIONARY);
-    range_capability_.value().SaveTo(&range_capability_value);
-    dict->SetKey(kOptionRangeCapability, std::move(range_capability_value));
-  } else if (select_capability_) {
-    base::Value select_capability_value(base::Value::Type::DICTIONARY);
-    select_capability_.value().SaveTo(&select_capability_value);
-    dict->SetKey(kOptionSelectCapability, std::move(select_capability_value));
-  } else {
-    DCHECK(typed_value_capability_);
-    base::Value typed_value_capability_value(base::Value::Type::DICTIONARY);
-    typed_value_capability_.value().SaveTo(&typed_value_capability_value);
-    dict->SetKey(kOptionTypedValueCapability,
-                 std::move(typed_value_capability_value));
+  switch (type_) {
+    case Type::NONE:
+      NOTREACHED();
+      break;
+    case Type::RANGE: {
+      base::Value range_capability_value(base::Value::Type::DICTIONARY);
+      range_capability_.SaveTo(&range_capability_value);
+      dict->SetKey(kOptionRangeCapability, std::move(range_capability_value));
+      break;
+    }
+    case Type::SELECT: {
+      base::Value select_capability_value(base::Value::Type::DICTIONARY);
+      select_capability_.SaveTo(&select_capability_value);
+      dict->SetKey(kOptionSelectCapability, std::move(select_capability_value));
+      break;
+    }
+    case Type::TYPED_VALUE: {
+      base::Value typed_value_capability_value(base::Value::Type::DICTIONARY);
+      typed_value_capability_.SaveTo(&typed_value_capability_value);
+      dict->SetKey(kOptionTypedValueCapability,
+                   std::move(typed_value_capability_value));
+      break;
+    }
   }
 }
 
diff --git a/components/cloud_devices/common/printer_description.h b/components/cloud_devices/common/printer_description.h
index 786b5e5..6807559 100644
--- a/components/cloud_devices/common/printer_description.h
+++ b/components/cloud_devices/common/printer_description.h
@@ -11,7 +11,6 @@
 #include <vector>
 
 #include "base/logging.h"
-#include "base/optional.h"
 #include "build/build_config.h"
 #include "components/cloud_devices/common/description_items.h"
 
@@ -143,6 +142,7 @@
 class VendorCapability {
  public:
   enum class Type {
+    NONE,
     RANGE,
     SELECT,
     TYPED_VALUE,
@@ -171,14 +171,18 @@
   void SaveTo(base::Value* dict) const;
 
  private:
+  void InternalCleanup();
+
   Type type_;
   std::string id_;
   std::string display_name_;
 
-  // If the CDD is valid, exactly one of the capabilities has non-nullopt value.
-  base::Optional<RangeVendorCapability> range_capability_;
-  base::Optional<SelectVendorCapability> select_capability_;
-  base::Optional<TypedValueVendorCapability> typed_value_capability_;
+  // If the CDD is valid, exactly one of the capabilities has a value.
+  union {
+    RangeVendorCapability range_capability_;
+    SelectVendorCapability select_capability_;
+    TypedValueVendorCapability typed_value_capability_;
+  };
 
   DISALLOW_COPY_AND_ASSIGN(VendorCapability);
 };
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
index 4fcd53d..ff7f3596 100644
--- a/components/flags_ui/resources/flags.html
+++ b/components/flags_ui/resources/flags.html
@@ -99,7 +99,7 @@
                   <textarea class="experiment-origin-list-value"
                       jsvalues=".internal_name:internal_name; .value:origin_list_value;
                           aria-labelledby:internal_name + '_name'"
-                      tabindex="7"></textarea>
+                      tabindex="6"></textarea>
                 </div>
                 <a class="permalink" jsvalues="href: '#' + internal_name"
                     jscontent="'#' + internal_name" tabindex="6"></a>
@@ -146,7 +146,7 @@
                   <textarea class="experiment-origin-list-value"
                       jsvalues=".internal_name:internal_name; .value:origin_list_value;
                           aria-labelledby:internal_name + '_name'"
-                      tabindex="7"></textarea>
+                      tabindex="6"></textarea>
                 </div>
                 <a class="permalink" jsvalues="href: '#' + internal_name"
                     jscontent="'#' + internal_name" tabindex="6"></a>
diff --git a/components/metrics/persistent_histograms.cc b/components/metrics/persistent_histograms.cc
index 4071b83..29a54f5 100644
--- a/components/metrics/persistent_histograms.cc
+++ b/components/metrics/persistent_histograms.cc
@@ -43,12 +43,11 @@
 // then fails to remove them. See https://crbug.com/934164
 void DeleteOldWindowsTempFiles(const base::FilePath& dir) {
   // Look for any temp files older than one day and remove them. The time check
-  // unsures that nothing in active transition gets deleted; these names only
+  // ensures that nothing in active transition gets deleted; these names only
   // exists on the order of milliseconds when working properly so "one day" is
   // generous but still ensures no big build up of these files. This is an
   // I/O intensive task so do it in the background (enforced by "file" calls).
   base::Time one_day_ago = base::Time::Now() - base::TimeDelta::FromDays(1);
-  int delete_count = 0;
   base::FileEnumerator file_iter(dir, /*recursive=*/false,
                                  base::FileEnumerator::FILES);
   for (base::FilePath path = file_iter.Next(); !path.empty();
@@ -67,11 +66,7 @@
       continue;
 
     base::DeleteFile(path, /*recursive=*/false);
-    delete_count++;
   }
-
-  base::UmaHistogramCounts1000("UMA.PersistentHistograms.TmpRemovals",
-                               delete_count);
 }
 
 // How much time after startup to run the above function. Two minutes is
diff --git a/components/module_installer/android/BUILD.gn b/components/module_installer/android/BUILD.gn
index e781862b..41d2ab4c 100644
--- a/components/module_installer/android/BUILD.gn
+++ b/components/module_installer/android/BUILD.gn
@@ -10,11 +10,12 @@
 # build.
 android_library("module_installer_java") {
   java_files = [
-    "java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java",
+    "java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java",
+    "java/src-common/org/chromium/components/module_installer/ModuleInstaller.java",
     "java/src-common/org/chromium/components/module_installer/OnModuleInstallFinishedListener.java",
     "java/src-common/org/chromium/components/module_installer/Module.java",
   ]
-  jar_excluded_patterns = [ "*/ModuleInstaller.class" ]
+  jar_excluded_patterns = [ "*/ModuleInstallerImpl.class" ]
   deps = [
     "//base:base_java",
   ]
@@ -24,7 +25,7 @@
 # Contains stub implementation to be used for builds not supporting modules
 # (e.g. APKs).
 android_library("module_installer_stub_java") {
-  java_files = [ "java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java" ]
+  java_files = [ "java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java" ]
   deps = [
     ":module_installer_java",
     "//base:base_java",
@@ -36,7 +37,7 @@
 # bundles).
 android_library("module_installer_impl_java") {
   java_files = [
-    "java/src-impl/org/chromium/components/module_installer/ModuleInstaller.java",
+    "java/src-impl/org/chromium/components/module_installer/ModuleInstallerImpl.java",
     "java/src-impl/org/chromium/components/module_installer/ModuleInstallerBackend.java",
     "java/src-impl/org/chromium/components/module_installer/FakeModuleInstallerBackend.java",
     "java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java",
@@ -50,10 +51,12 @@
 }
 
 android_library("module_installer_test_java") {
-  java_files = [ "java/src-test/org/chromium/components/module_installer/ModuleInstaller.java" ]
+  testonly = true
+  java_files = [ "javatests/src/org/chromium/components/module_installer/ModuleInstallerRule.java" ]
   deps = [
     ":module_installer_java",
     "//base:base_java",
+    "//third_party/junit",
   ]
   jacoco_never_instrument = true
 }
diff --git a/components/module_installer/android/java/src-common/org/chromium/components/module_installer/Module.java b/components/module_installer/android/java/src-common/org/chromium/components/module_installer/Module.java
index 9e19273..ccd6e5ab 100644
--- a/components/module_installer/android/java/src-common/org/chromium/components/module_installer/Module.java
+++ b/components/module_installer/android/java/src-common/org/chromium/components/module_installer/Module.java
@@ -56,7 +56,7 @@
         // Accessing classes in the module may cause its DEX file to be loaded. And on some devices
         // that causes a read mode violation.
         try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
-            ModuleInstaller.init();
+            ModuleInstaller.getInstance().init();
             Class.forName(mImplClassName);
             return true;
         } catch (ClassNotFoundException e) {
@@ -64,18 +64,18 @@
         }
     }
 
-    /** Requests install of the module. See {@link ModuleInstaller#install} for more details. */
+    /** Requests install of the module. See {@link ModuleInstallerImpl#install} for more details. */
     public void install(OnModuleInstallFinishedListener onFinishedListener) {
         assert !isInstalled();
-        ModuleInstaller.install(mName, onFinishedListener);
+        ModuleInstaller.getInstance().install(mName, onFinishedListener);
     }
 
     /**
-     * Requests deferred install of the module. See {@link ModuleInstaller#installDeferred} for
+     * Requests deferred install of the module. See {@link ModuleInstallerImpl#installDeferred} for
      * more details.
      */
     public void installDeferred() {
-        ModuleInstaller.installDeferred(mName);
+        ModuleInstaller.getInstance().installDeferred(mName);
     }
 
     /**
@@ -85,7 +85,7 @@
     public T getImpl() {
         assert isInstalled();
         if (mImpl == null) {
-            ModuleInstaller.init();
+            ModuleInstaller.getInstance().init();
             // Accessing classes in the module may cause its DEX file to be loaded. And on some
             // devices that causes a read mode violation.
             try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
diff --git a/components/module_installer/android/java/src-common/org/chromium/components/module_installer/ModuleInstaller.java b/components/module_installer/android/java/src-common/org/chromium/components/module_installer/ModuleInstaller.java
new file mode 100644
index 0000000..b5083a5
--- /dev/null
+++ b/components/module_installer/android/java/src-common/org/chromium/components/module_installer/ModuleInstaller.java
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.module_installer;
+
+import android.content.Context;
+
+import org.chromium.base.VisibleForTesting;
+
+/**
+ * This interface contains all the necessary methods to orchestrate the installation of dynamic
+ * feature modules (DFMs).
+ */
+public interface ModuleInstaller {
+    /** Returns the singleton instance from the correct implementation. */
+    static ModuleInstaller getInstance() {
+        return ModuleInstallerImpl.getInstance();
+    }
+
+    @VisibleForTesting
+    static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
+        ModuleInstallerImpl.setInstanceForTesting(moduleInstaller);
+    }
+
+    /** Needs to be called before trying to access a module. */
+    default void init() {}
+
+    /**
+     * Needs to be called in attachBaseContext of the activities that want to have access to
+     * splits prior to application restart.
+     *
+     * For details, see:
+     * https://developer.android.com/reference/com/google/android/play/core/splitcompat/SplitCompat.html#install(android.content.Context)
+     */
+    default void initActivity(Context context) {}
+
+    /**
+     * Records via UMA all modules that have been requested and are currently installed. The intent
+     * is to measure the install penetration of each module.
+     */
+    default void recordModuleAvailability() {}
+
+    /** Writes fully installed and emulated modules to crash keys. */
+    default void updateCrashKeys() {}
+
+    /**
+     * Requests the install of a module. The install will be performed asynchronously.
+     *
+     * @param moduleName Name of the module as defined in GN.
+     * @param onFinishedListener Listener to be called once installation is finished.
+     */
+    default void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {}
+
+    /**
+     * Asynchronously installs module in the background when on unmetered connection and charging.
+     * Install is best effort and may fail silently. Upon success, the module will only be available
+     * after Chrome restarts.
+     *
+     * @param moduleName Name of the module.
+     */
+    default void installDeferred(String moduleName) {}
+}
diff --git a/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstaller.java b/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstallerImpl.java
similarity index 64%
rename from components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstaller.java
rename to components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstallerImpl.java
index 5b73565..1fbc3c5 100644
--- a/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstaller.java
+++ b/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/ModuleInstallerImpl.java
@@ -30,53 +30,54 @@
 import java.util.TreeSet;
 
 /** Installs dynamic feature modules (DFMs). */
-public class ModuleInstaller {
+public class ModuleInstallerImpl implements ModuleInstaller {
     /** Command line switch for activating the fake backend.  */
     private static final String FAKE_FEATURE_MODULE_INSTALL = "fake-feature-module-install";
-    private static final Map<String, List<OnModuleInstallFinishedListener>> sModuleNameListenerMap =
+    private static ModuleInstaller sInstance = new ModuleInstallerImpl();
+    private static boolean sAppContextSplitCompatted;
+    private final Map<String, List<OnModuleInstallFinishedListener>> mModuleNameListenerMap =
             new HashMap<>();
-    private static ModuleInstallerBackend sBackend;
-    private static boolean sSplitCompatted;
+    private ModuleInstallerBackend mBackend;
 
-    /** Needs to be called before trying to access a module. */
-    public static void init() {
-        if (sSplitCompatted) return;
+    /** Returns the singleton instance. */
+    public static ModuleInstaller getInstance() {
+        return sInstance;
+    }
+
+    public static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
+        sInstance = moduleInstaller;
+    }
+
+    @Override
+    public void init() {
+        if (sAppContextSplitCompatted) return;
         // SplitCompat.install may copy modules into Chrome's internal folder or clean them up.
         try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
             SplitCompat.install(ContextUtils.getApplicationContext());
-            sSplitCompatted = true;
+            sAppContextSplitCompatted = true;
         }
         // SplitCompat.install may add emulated modules. Thus, update crash keys.
         updateCrashKeys();
     }
 
-    /**
-     * Needs to be called in attachBaseContext of the activities that want to have access to
-     * splits prior to application restart.
-     *
-     * For details, see:
-     * https://developer.android.com/reference/com/google/android/play/core/splitcompat/SplitCompat.html#install(android.content.Context)
-     */
-    public static void initActivity(Context context) {
+    @Override
+    public void initActivity(Context context) {
         SplitCompat.install(context);
     }
 
-    /**
-     * Records via UMA all modules that have been requested and are currently installed. The intent
-     * is to measure the install penetration of each module.
-     */
-    public static void recordModuleAvailability() {
+    @Override
+    public void recordModuleAvailability() {
         if (!CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)) {
             PlayCoreModuleInstallerBackend.recordModuleAvailability();
         }
     }
 
-    /** Writes fully installed and emulated modules to crash keys. */
-    public static void updateCrashKeys() {
+    @Override
+    public void updateCrashKeys() {
         Context context = ContextUtils.getApplicationContext();
 
         // Get modules that are fully installed as split APKs (excluding base which is always
-        // intalled). Tree set to have ordered and, thus, deterministic results.
+        // installed). Tree set to have ordered and, thus, deterministic results.
         Set<String> fullyInstalledModules = new TreeSet<>();
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             // Split APKs are only supported on Android L+.
@@ -97,7 +98,7 @@
         // emulation of later modules won't work. If splitcompat has not been called no modules are
         // emulated. Therefore, use an empty set in that case.
         Set<String> emulatedModules = new TreeSet<>();
-        if (sSplitCompatted) {
+        if (sAppContextSplitCompatted) {
             emulatedModules.addAll(
                     SplitInstallManagerFactory.create(context).getInstalledModules());
             emulatedModules.removeAll(fullyInstalledModules);
@@ -109,21 +110,15 @@
                 CrashKeyIndex.EMULATED_MODULES, encodeCrashKeyValue(emulatedModules));
     }
 
-    /**
-     * Requests the install of a module. The install will be performed asynchronously.
-     *
-     * @param moduleName Name of the module as defined in GN.
-     * @param onFinishedListener Listener to be called once installation is finished.
-     */
-    public static void install(
-            String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
+    @Override
+    public void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
         ThreadUtils.assertOnUiThread();
 
-        if (!sModuleNameListenerMap.containsKey(moduleName)) {
-            sModuleNameListenerMap.put(moduleName, new LinkedList<>());
+        if (!mModuleNameListenerMap.containsKey(moduleName)) {
+            mModuleNameListenerMap.put(moduleName, new LinkedList<>());
         }
         List<OnModuleInstallFinishedListener> onFinishedListeners =
-                sModuleNameListenerMap.get(moduleName);
+                mModuleNameListenerMap.get(moduleName);
         onFinishedListeners.add(onFinishedListener);
         if (onFinishedListeners.size() > 1) {
             // Request is already running.
@@ -132,56 +127,50 @@
         getBackend().install(moduleName);
     }
 
-    /**
-     * Asynchronously installs module in the background when on unmetered connection and charging.
-     * Install is best effort and may fail silently. Upon success, the module will only be available
-     * after Chrome restarts.
-     *
-     * @param moduleName Name of the module.
-     */
-    public static void installDeferred(String moduleName) {
+    @Override
+    public void installDeferred(String moduleName) {
         ThreadUtils.assertOnUiThread();
         getBackend().installDeferred(moduleName);
     }
 
-    private static void onFinished(boolean success, List<String> moduleNames) {
+    private void onFinished(boolean success, List<String> moduleNames) {
         ThreadUtils.assertOnUiThread();
 
         for (String moduleName : moduleNames) {
             List<OnModuleInstallFinishedListener> onFinishedListeners =
-                    sModuleNameListenerMap.get(moduleName);
+                    mModuleNameListenerMap.get(moduleName);
             if (onFinishedListeners == null) continue;
 
             for (OnModuleInstallFinishedListener listener : onFinishedListeners) {
                 listener.onFinished(success);
             }
-            sModuleNameListenerMap.remove(moduleName);
+            mModuleNameListenerMap.remove(moduleName);
         }
 
-        if (sModuleNameListenerMap.isEmpty()) {
-            sBackend.close();
-            sBackend = null;
+        if (mModuleNameListenerMap.isEmpty()) {
+            mBackend.close();
+            mBackend = null;
         }
 
         updateCrashKeys();
     }
 
-    private static ModuleInstallerBackend getBackend() {
-        if (sBackend == null) {
-            ModuleInstallerBackend.OnFinishedListener listener = ModuleInstaller::onFinished;
-            sBackend = CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)
+    private ModuleInstallerBackend getBackend() {
+        if (mBackend == null) {
+            ModuleInstallerBackend.OnFinishedListener listener = this::onFinished;
+            mBackend = CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)
                     ? new FakeModuleInstallerBackend(listener)
                     : new PlayCoreModuleInstallerBackend(listener);
         }
-        return sBackend;
+        return mBackend;
     }
 
-    private static String encodeCrashKeyValue(Set<String> moduleNames) {
+    private String encodeCrashKeyValue(Set<String> moduleNames) {
         if (moduleNames.isEmpty()) return "<none>";
         // Values with dots are interpreted as URLs. Some module names have dots in them. Make sure
         // they don't get sanitized.
         return TextUtils.join(",", moduleNames).replace('.', '$');
     }
 
-    private ModuleInstaller() {}
+    private ModuleInstallerImpl() {}
 }
diff --git a/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java b/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java
index 00b1712..b64387c 100644
--- a/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java
+++ b/components/module_installer/android/java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java
@@ -20,7 +20,6 @@
 import org.chromium.base.Log;
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.components.module_installer.ModuleInstallerBackend.OnFinishedListener;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -93,7 +92,7 @@
     /** Records via UMA all modules that have been requested and are currently installed. */
     public static void recordModuleAvailability() {
         // MUST call init before creating a SplitInstallManager.
-        ModuleInstaller.init();
+        ModuleInstaller.getInstance().init();
         SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
         Set<String> requestedModules = new HashSet<>();
         requestedModules.addAll(
@@ -128,7 +127,7 @@
     public PlayCoreModuleInstallerBackend(OnFinishedListener listener) {
         super(listener);
         // MUST call init before creating a SplitInstallManager.
-        ModuleInstaller.init();
+        ModuleInstaller.getInstance().init();
         mManager = SplitInstallManagerFactory.create(ContextUtils.getApplicationContext());
         mManager.registerListener(this);
     }
diff --git a/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java b/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java
deleted file mode 100644
index f88b9ca..0000000
--- a/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java
+++ /dev/null
@@ -1,36 +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.components.module_installer;
-
-import android.content.Context;
-
-import org.chromium.base.VisibleForTesting;
-
-/** Dummy fallback of ModuleInstaller for APK builds. */
-public class ModuleInstaller {
-    public static void init() {}
-
-    public static void initActivity(Context context) {}
-
-    public static void recordModuleAvailability() {}
-
-    public static void updateCrashKeys(){};
-
-    public static void install(
-            String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
-        throw new UnsupportedOperationException("Cannot install module if APK");
-    }
-
-    public static void installDeferred(String moduleName) {
-        throw new UnsupportedOperationException("Cannot deferred install module if APK");
-    }
-
-    @VisibleForTesting
-    public static boolean didRequestDeferred(String moduleName) {
-        throw new UnsupportedOperationException();
-    }
-
-    private ModuleInstaller() {}
-}
diff --git a/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java b/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java
new file mode 100644
index 0000000..f4bca10
--- /dev/null
+++ b/components/module_installer/android/java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java
@@ -0,0 +1,32 @@
+// 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.components.module_installer;
+
+/** Dummy fallback of ModuleInstaller for APK builds. */
+public class ModuleInstallerImpl implements ModuleInstaller {
+    /** A valid singleton instance is necessary for tests to swap it out. */
+    private static ModuleInstaller sInstance = new ModuleInstallerImpl();
+
+    /** Returns the singleton instance. */
+    public static ModuleInstaller getInstance() {
+        return sInstance;
+    }
+
+    public static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
+        sInstance = moduleInstaller;
+    }
+
+    @Override
+    public void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
+        throw new UnsupportedOperationException("Cannot install module if APK");
+    }
+
+    @Override
+    public void installDeferred(String moduleName) {
+        throw new UnsupportedOperationException("Cannot deferred install module if APK");
+    }
+
+    private ModuleInstallerImpl() {}
+}
diff --git a/components/module_installer/android/java/src-test/org/chromium/components/module_installer/ModuleInstaller.java b/components/module_installer/android/java/src-test/org/chromium/components/module_installer/ModuleInstaller.java
deleted file mode 100644
index 02b8519..0000000
--- a/components/module_installer/android/java/src-test/org/chromium/components/module_installer/ModuleInstaller.java
+++ /dev/null
@@ -1,39 +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.
-
-package org.chromium.components.module_installer;
-
-import android.content.Context;
-
-import org.chromium.base.VisibleForTesting;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** Mock ModuleInstaller for use in tests. */
-public class ModuleInstaller {
-    private static Set<String> sModulesRequestedDeffered = new HashSet<>();
-
-    public static void init() {}
-
-    public static void initActivity(Context context) {}
-
-    public static void recordModuleAvailability() {}
-
-    public static void updateCrashKeys(){};
-
-    public static void install(
-            String moduleName, OnModuleInstallFinishedListener onFinishedListener) {}
-
-    public static void installDeferred(String moduleName) {
-        sModulesRequestedDeffered.add(moduleName);
-    }
-
-    @VisibleForTesting
-    public static boolean didRequestDeferred(String moduleName) {
-        return sModulesRequestedDeffered.contains(moduleName);
-    }
-
-    private ModuleInstaller() {}
-}
diff --git a/components/module_installer/android/javatests/src/org/chromium/components/module_installer/ModuleInstallerRule.java b/components/module_installer/android/javatests/src/org/chromium/components/module_installer/ModuleInstallerRule.java
new file mode 100644
index 0000000..f9a9418
--- /dev/null
+++ b/components/module_installer/android/javatests/src/org/chromium/components/module_installer/ModuleInstallerRule.java
@@ -0,0 +1,33 @@
+// 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.
+
+package org.chromium.components.module_installer;
+
+import org.junit.rules.ExternalResource;
+
+/**
+ * This rule allows the caller to specify their own {@link ModuleInstaller} for the duration of the
+ * test and resets it back to what it was before.
+ *
+ * TODO(wnwen): This should eventually become ModuleConfigRule.
+ */
+public class ModuleInstallerRule extends ExternalResource {
+    private ModuleInstaller mOldModuleInstaller;
+    private final ModuleInstaller mMockModuleInstaller;
+
+    public ModuleInstallerRule(ModuleInstaller mockModuleInstaller) {
+        mMockModuleInstaller = mockModuleInstaller;
+    }
+
+    @Override
+    protected void before() {
+        mOldModuleInstaller = ModuleInstaller.getInstance();
+        ModuleInstaller.setInstanceForTesting(mMockModuleInstaller);
+    }
+
+    @Override
+    protected void after() {
+        ModuleInstaller.setInstanceForTesting(mOldModuleInstaller);
+    }
+}
diff --git a/components/omnibox/browser/omnibox_popup_model_unittest.cc b/components/omnibox/browser/omnibox_popup_model_unittest.cc
index 2a65f52c..1273add 100644
--- a/components/omnibox/browser/omnibox_popup_model_unittest.cc
+++ b/components/omnibox/browser/omnibox_popup_model_unittest.cc
@@ -96,6 +96,9 @@
 }
 
 TEST_F(OmniboxPopupModelTest, PopupPositionChanging) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(omnibox::kOmniboxWrapPopupPosition);
+
   ACMatches matches;
   for (size_t i = 0; i < 3; ++i) {
     AutocompleteMatch match(nullptr, 1000, false,
@@ -125,7 +128,7 @@
   model()->OnUpOrDownKeyPressed(-3);
   EXPECT_EQ(0u, model()->popup_model()->selected_line());
 
-  base::test::ScopedFeatureList feature_list;
+  feature_list.Reset();
   feature_list.InitAndEnableFeature(omnibox::kOmniboxWrapPopupPosition);
   // Test wrapping.
   model()->OnUpOrDownKeyPressed(-1);
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 8ac55bf8..8dda09e9d 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -118,10 +118,10 @@
 #endif
 };
 
-// Feature that enables wrapping the Omnibox position between top and
-// bottom.
-const base::Feature kOmniboxWrapPopupPosition{
-    "OmniboxWrapPopupPosition", base::FEATURE_DISABLED_BY_DEFAULT};
+// Feature that enables wrapping the Omnibox position between top and bottom.
+// The feature is enabled by default, but remains as a kill-switch.
+const base::Feature kOmniboxWrapPopupPosition{"OmniboxWrapPopupPosition",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Feature used to reverse the sense of the tab switch button. Selecting the
 // suggestion will switch to the tab, while the button will navigate
@@ -282,6 +282,11 @@
 const base::Feature kOmniboxDisableInstantExtendedLimit{
     "OmniboxDisableInstantExtendedLimit", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Show the search engine logo in the omnibox on Android (desktop already does
+// this).
+const base::Feature kOmniboxSearchEngineLogo{"OmniboxSearchEngineLogo",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Feature to configure on-focus suggestions provided by ZeroSuggestProvider.
 // This feature's main job is to contain some field trial parameters such as:
 //  - "ZeroSuggestVariant" configures the per-page-classification mode of
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index d95912c..a7c23ff 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -51,6 +51,7 @@
 extern const base::Feature kOmniboxPopupShortcutIconsInZeroState;
 extern const base::Feature kOmniboxMaterialDesignWeatherIcons;
 extern const base::Feature kOmniboxDisableInstantExtendedLimit;
+extern const base::Feature kOmniboxSearchEngineLogo;
 
 // On-Focus Suggestions a.k.a. ZeroSuggest.
 extern const base::Feature kOnFocusSuggestions;
diff --git a/components/password_manager/core/browser/new_password_form_manager.cc b/components/password_manager/core/browser/new_password_form_manager.cc
index 14d42c2..e498e9f 100644
--- a/components/password_manager/core/browser/new_password_form_manager.cc
+++ b/components/password_manager/core/browser/new_password_form_manager.cc
@@ -143,6 +143,7 @@
   observed_form_ = observed_form;
   metrics_recorder_->RecordFormSignature(CalculateFormSignature(observed_form));
   form_fetcher_->AddConsumer(this);
+  votes_uploader_.StoreInitialFieldValues(observed_form);
 }
 
 NewPasswordFormManager::NewPasswordFormManager(
diff --git a/components/password_manager/core/browser/votes_uploader.cc b/components/password_manager/core/browser/votes_uploader.cc
index bdd4ee5..97b30d3 100644
--- a/components/password_manager/core/browser/votes_uploader.cc
+++ b/components/password_manager/core/browser/votes_uploader.cc
@@ -7,14 +7,18 @@
 #include <ctype.h>
 
 #include <algorithm>
-
+#include <utility>
+#include "base/hash/hash.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/autofill_download_manager.h"
+#include "components/autofill/core/browser/autofill_field.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/randomized_encoder.h"
 #include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
@@ -23,6 +27,7 @@
 using autofill::AutofillField;
 using autofill::AutofillUploadContents;
 using autofill::FormData;
+using autofill::FormFieldData;
 using autofill::FormStructure;
 using autofill::PasswordForm;
 using autofill::RandomizedEncoder;
@@ -35,6 +40,8 @@
 namespace password_manager {
 
 namespace {
+// Number of distinct low-entropy hash values.
+constexpr uint32_t kNumberOfLowEntropyHashValues = 64;
 
 // Contains all special symbols considered for password-generation.
 constexpr char kSpecialSymbols[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
@@ -100,14 +107,13 @@
              field->vote_type() !=
                  AutofillUploadContents::Field::NO_INFORMATION);
     }
-
     ServerFieldTypeSet types;
     types.insert(type);
     field->set_possible_types(types);
   }
 }
 
-// Returns true iff |credentials| has the same password as an entry in |matches|
+// Returns true if |credentials| has the same password as an entry in |matches|
 // which doesn't have a username.
 bool IsAddingUsernameToExistingMatch(
     const PasswordForm& credentials,
@@ -154,6 +160,11 @@
   return symbols[base::RandGenerator(symbols.size())];
 }
 
+size_t GetLowEntropyHashValue(const base::string16& value) {
+  return base::PersistentHash(base::UTF16ToUTF8(value)) %
+         kNumberOfLowEntropyHashValues;
+}
+
 }  // namespace
 
 VotesUploader::VotesUploader(PasswordManagerClient* client,
@@ -398,11 +409,33 @@
   form_structure.set_randomized_encoder(
       RandomizedEncoder::Create(client_->GetPrefs()));
 
+  SetInitialHashValueOfUsernameField(
+      form_to_upload.username_element_renderer_id, &form_structure);
+
   download_manager->StartUploadRequest(
       form_structure, false /* was_autofilled */, available_field_types,
       std::string(), true /* observed_submission */, nullptr /* prefs */);
 }
 
+void VotesUploader::SetInitialHashValueOfUsernameField(
+    uint32_t username_element_renderer_id,
+    FormStructure* form_structure) {
+  auto it = initial_values_.find(username_element_renderer_id);
+
+  if (it == initial_values_.end() || it->second.empty())
+    return;
+
+  for (const auto& field : *form_structure) {
+    if (field && field->unique_renderer_id == username_element_renderer_id) {
+      const base::string16 form_signature =
+          base::UTF8ToUTF16(form_structure->FormSignatureAsStr());
+      const base::string16 seeded_input = it->second.append(form_signature);
+      field->set_initial_value_hash(GetLowEntropyHashValue(seeded_input));
+      break;
+    }
+  }
+}
+
 void VotesUploader::AddGeneratedVote(FormStructure* form_structure) {
   DCHECK(form_structure);
   DCHECK(generation_popup_was_shown_);
@@ -571,4 +604,13 @@
   form_structure->set_password_length_vote(randomized_length);
 }
 
+void VotesUploader::StoreInitialFieldValues(
+    const autofill::FormData& observed_form) {
+  for (const auto& field : observed_form.fields) {
+    if (!field.value.empty())
+      initial_values_.insert(
+          std::make_pair(field.unique_renderer_id, field.value));
+  }
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/votes_uploader.h b/components/password_manager/core/browser/votes_uploader.h
index 2eb9565..abe6879 100644
--- a/components/password_manager/core/browser/votes_uploader.h
+++ b/components/password_manager/core/browser/votes_uploader.h
@@ -87,6 +87,17 @@
   void GeneratePasswordAttributesVote(const base::string16& password_value,
                                       autofill::FormStructure* form_structure);
 
+  // Stores the |unique_renderer_id| and |values| of the fields in
+  // |observed_form| to |initial_field_values_|.
+  void StoreInitialFieldValues(const autofill::FormData& observed_form);
+
+  // Sets the low-entropy hash value of the values stored in |initial_values_|
+  // for the detected |username| field to the corresponding field in
+  // |form_structure|.
+  void SetInitialHashValueOfUsernameField(
+      uint32_t username_element_renderer_id,
+      autofill::FormStructure* form_structure);
+
   bool get_generation_popup_was_shown() const {
     return generation_popup_was_shown_;
   }
@@ -197,6 +208,10 @@
 
   // Whether this form has a generated password changed by user.
   bool generated_password_changed_ = false;
+
+  // Maps an |unique_renderer_id| to the initial value of the fields of an
+  // observed form.
+  std::map<uint32_t, base::string16> initial_values_;
 };
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/votes_uploader_unittest.cc b/components/password_manager/core/browser/votes_uploader_unittest.cc
index e272850..a72bbf42 100644
--- a/components/password_manager/core/browser/votes_uploader_unittest.cc
+++ b/components/password_manager/core/browser/votes_uploader_unittest.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <utility>
 
+#include "base/hash/hash.h"
 #include "base/optional.h"
 #include "base/rand_util.h"
 #include "base/strings/string16.h"
@@ -15,6 +16,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "components/autofill/core/browser/autofill_download_manager.h"
 #include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/common/form_data.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
 #include "components/password_manager/core/browser/vote_uploads_test_matchers.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -22,12 +24,16 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using autofill::AutofillDownloadManager;
 using autofill::CONFIRMATION_PASSWORD;
+using autofill::FormData;
 using autofill::FormFieldData;
 using autofill::FormStructure;
 using autofill::NEW_PASSWORD;
 using autofill::PASSWORD;
+using autofill::PasswordAttribute;
 using autofill::PasswordForm;
+using autofill::ServerFieldType;
 using autofill::ServerFieldTypeSet;
 using autofill::mojom::SubmissionIndicatorEvent;
 using base::ASCIIToUTF16;
@@ -41,9 +47,9 @@
 namespace {
 
 constexpr int kNumberOfPasswordAttributes =
-    static_cast<int>(autofill::PasswordAttribute::kPasswordAttributesCount);
+    static_cast<int>(PasswordAttribute::kPasswordAttributesCount);
 
-class MockAutofillDownloadManager : public autofill::AutofillDownloadManager {
+class MockAutofillDownloadManager : public AutofillDownloadManager {
  public:
   MockAutofillDownloadManager()
       : AutofillDownloadManager(nullptr, &fake_observer) {}
@@ -51,7 +57,7 @@
   MOCK_METHOD6(StartUploadRequest,
                bool(const FormStructure&,
                     bool,
-                    const autofill::ServerFieldTypeSet&,
+                    const ServerFieldTypeSet&,
                     const std::string&,
                     bool,
                     PrefService*));
@@ -69,8 +75,7 @@
 
 class MockPasswordManagerClient : public StubPasswordManagerClient {
  public:
-  MOCK_METHOD0(GetAutofillDownloadManager,
-               autofill::AutofillDownloadManager*());
+  MOCK_METHOD0(GetAutofillDownloadManager, AutofillDownloadManager*());
 };
 
 }  // namespace
@@ -166,6 +171,52 @@
       form_to_upload_, submitted_form_, PASSWORD, login_form_signature_));
 }
 
+TEST_F(VotesUploaderTest, InitialValueDetection) {
+  // Tests if the initial value of the (predicted to be the) username field
+  // in |form_data| is persistently stored and if it's low-entropy hash is
+  // correctly written to the corresponding field in the |form_structure|.
+  // Note that the value of the username field is deliberately altered before
+  // the |form_structure| is generated from |form_data| to test the persistence.
+  base::string16 prefilled_username = ASCIIToUTF16("prefilled_username");
+  uint32_t username_field_renderer_id = 123456;
+  const uint32_t kNumberOfHashValues = 64;
+  FormData form_data;
+
+  FormFieldData username_field;
+  username_field.value = prefilled_username;
+  username_field.unique_renderer_id = username_field_renderer_id;
+
+  FormFieldData other_field;
+  other_field.value = ASCIIToUTF16("some_field");
+  other_field.unique_renderer_id = 3234;
+
+  form_data.fields = {other_field, username_field};
+
+  VotesUploader votes_uploader(&client_, true);
+  votes_uploader.StoreInitialFieldValues(form_data);
+
+  form_data.fields.at(1).value = ASCIIToUTF16("user entered value");
+  FormStructure form_structure(form_data);
+
+  PasswordForm password_form;
+  password_form.username_element_renderer_id = username_field_renderer_id;
+
+  votes_uploader.SetInitialHashValueOfUsernameField(username_field_renderer_id,
+                                                    &form_structure);
+
+  const uint32_t expected_hash = 1377800651 % kNumberOfHashValues;
+
+  int found_fields = 0;
+  for (auto& f : form_structure) {
+    if (f->unique_renderer_id == username_field_renderer_id) {
+      found_fields++;
+      ASSERT_TRUE(f->initial_value_hash());
+      EXPECT_EQ(f->initial_value_hash().value(), expected_hash);
+    }
+  }
+  EXPECT_EQ(found_fields, 1);
+}
+
 TEST_F(VotesUploaderTest, GeneratePasswordAttributesVote) {
   VotesUploader votes_uploader(&client_, true);
   // Checks that randomization distorts information about present and missed
@@ -183,8 +234,8 @@
     if (password_value.empty())
       continue;
 
-    autofill::FormData form;
-    autofill::FormStructure form_structure(form);
+    FormData form;
+    FormStructure form_structure(form);
     int reported_false[kNumberOfPasswordAttributes] = {0, 0, 0, 0};
     int reported_true[kNumberOfPasswordAttributes] = {0, 0, 0, 0};
 
@@ -196,7 +247,7 @@
     for (int i = 0; i < kNumberOfRuns; ++i) {
       votes_uploader.GeneratePasswordAttributesVote(password_value,
                                                     &form_structure);
-      base::Optional<std::pair<autofill::PasswordAttribute, bool>> vote =
+      base::Optional<std::pair<PasswordAttribute, bool>> vote =
           form_structure.get_password_attributes_vote_for_testing();
       int attribute_index = static_cast<int>(vote->first);
       if (vote->second)
@@ -242,18 +293,18 @@
   const int kNumberOfRuns = 2000;
   const int kSpecialSymbolsAttribute = 3;
 
-  autofill::FormData form;
+  FormData form;
 
   int correct_symbol_reported = 0;
   int wrong_symbol_reported = 0;
   int number_of_symbol_votes = 0;
 
   for (int i = 0; i < kNumberOfRuns; ++i) {
-    autofill::FormStructure form_structure(form);
+    FormStructure form_structure(form);
 
     votes_uploader.GeneratePasswordAttributesVote(password_value,
                                                   &form_structure);
-    base::Optional<std::pair<autofill::PasswordAttribute, bool>> vote =
+    base::Optional<std::pair<PasswordAttribute, bool>> vote =
         form_structure.get_password_attributes_vote_for_testing();
 
     // Continue if the vote is not about special symbols or implies that no
@@ -279,12 +330,12 @@
 TEST_F(VotesUploaderTest, GeneratePasswordAttributesVote_OneCharacterPassword) {
   // |VotesUploader::GeneratePasswordAttributesVote| shouldn't crash if a
   // password has only one character.
-  autofill::FormData form;
-  autofill::FormStructure form_structure(form);
+  FormData form;
+  FormStructure form_structure(form);
   VotesUploader votes_uploader(&client_, true);
   votes_uploader.GeneratePasswordAttributesVote(ASCIIToUTF16("1"),
                                                 &form_structure);
-  base::Optional<std::pair<autofill::PasswordAttribute, bool>> vote =
+  base::Optional<std::pair<PasswordAttribute, bool>> vote =
       form_structure.get_password_attributes_vote_for_testing();
   EXPECT_TRUE(vote.has_value());
   size_t reported_length =
@@ -293,14 +344,14 @@
 }
 
 TEST_F(VotesUploaderTest, GeneratePasswordAttributesVote_AllAsciiCharacters) {
-  autofill::FormData form;
-  autofill::FormStructure form_structure(form);
+  FormData form;
+  FormStructure form_structure(form);
   VotesUploader votes_uploader(&client_, true);
   votes_uploader.GeneratePasswordAttributesVote(
       base::UTF8ToUTF16("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr"
                         "stuvwxyz!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
       &form_structure);
-  base::Optional<std::pair<autofill::PasswordAttribute, bool>> vote =
+  base::Optional<std::pair<PasswordAttribute, bool>> vote =
       form_structure.get_password_attributes_vote_for_testing();
   EXPECT_TRUE(vote.has_value());
 }
@@ -312,12 +363,12 @@
        {"пароль1", "パスワード", "münchen", "סיסמה-A", "Σ-12345",
         "գաղտնաբառըTTT", "Slaptažodis", "密碼", "كلمهالسر", "mậtkhẩu!",
         "ລະຫັດຜ່ານ-l", "စကားဝှက်ကို3", "პაროლი", "पारण शब्द"}) {
-    autofill::FormData form;
-    autofill::FormStructure form_structure(form);
+    FormData form;
+    FormStructure form_structure(form);
     VotesUploader votes_uploader(&client_, true);
     votes_uploader.GeneratePasswordAttributesVote(base::UTF8ToUTF16(password),
                                                   &form_structure);
-    base::Optional<std::pair<autofill::PasswordAttribute, bool>> vote =
+    base::Optional<std::pair<PasswordAttribute, bool>> vote =
         form_structure.get_password_attributes_vote_for_testing();
 
     EXPECT_FALSE(vote.has_value()) << password;
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index a38cd36..1c92e1b 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -96,6 +96,7 @@
     "//components/payments/content/icon",
     "//components/payments/content/utility",
     "//components/payments/core",
+    "//components/payments/core:error_strings",
     "//components/strings",
     "//components/webdata/common",
     "//content/public/browser",
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
index 6d8dbfb..897167f 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
@@ -35,9 +35,13 @@
         @CalledByNative("ManifestDownloadCallback")
         void onWebAppManifestDownloadSuccess(String content);
 
-        /** Called on failed download. */
+        /**
+         * Called on failed download.
+         *
+         * @param errorMessage The error message, which could be empty or null.
+         */
         @CalledByNative("ManifestDownloadCallback")
-        void onManifestDownloadFailure();
+        void onManifestDownloadFailure(String errorMessage);
     }
 
     private long mNativeObject;
diff --git a/components/payments/content/android/payment_manifest_downloader_android.cc b/components/payments/content/android/payment_manifest_downloader_android.cc
index 9aa99568..bc4a9ef 100644
--- a/components/payments/content/android/payment_manifest_downloader_android.cc
+++ b/components/payments/content/android/payment_manifest_downloader_android.cc
@@ -29,11 +29,14 @@
   ~DownloadCallback() {}
 
   void OnPaymentMethodManifestDownload(const GURL& url_after_redirects,
-                                       const std::string& content) {
+                                       const std::string& content,
+                                       const std::string& error_message) {
     JNIEnv* env = base::android::AttachCurrentThread();
 
     if (content.empty()) {
-      Java_ManifestDownloadCallback_onManifestDownloadFailure(env, jcallback_);
+      Java_ManifestDownloadCallback_onManifestDownloadFailure(
+          env, jcallback_,
+          base::android::ConvertUTF8ToJavaString(env, error_message));
     } else {
       Java_ManifestDownloadCallback_onPaymentMethodManifestDownloadSuccess(
           env, jcallback_,
@@ -42,11 +45,14 @@
   }
 
   void OnWebAppManifestDownload(const GURL& url_after_redirects,
-                                const std::string& content) {
+                                const std::string& content,
+                                const std::string& error_message) {
     JNIEnv* env = base::android::AttachCurrentThread();
 
     if (content.empty()) {
-      Java_ManifestDownloadCallback_onManifestDownloadFailure(env, jcallback_);
+      Java_ManifestDownloadCallback_onManifestDownloadFailure(
+          env, jcallback_,
+          base::android::ConvertUTF8ToJavaString(env, error_message));
     } else {
       Java_ManifestDownloadCallback_onWebAppManifestDownloadSuccess(
           env, jcallback_,
diff --git a/components/payments/content/installable_payment_app_crawler.cc b/components/payments/content/installable_payment_app_crawler.cc
index 2dc98edc..c1bff8503 100644
--- a/components/payments/content/installable_payment_app_crawler.cc
+++ b/components/payments/content/installable_payment_app_crawler.cc
@@ -7,9 +7,11 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "components/payments/content/icon/icon_size.h"
+#include "components/payments/core/native_error_strings.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -103,7 +105,8 @@
 void InstallablePaymentAppCrawler::OnPaymentMethodManifestDownloaded(
     const GURL& method_manifest_url,
     const GURL& method_manifest_url_after_redirects,
-    const std::string& content) {
+    const std::string& content,
+    const std::string& error_message) {
   // Enforced in PaymentManifestDownloader.
   DCHECK(net::registry_controlled_domains::SameDomainOrHost(
       method_manifest_url, method_manifest_url_after_redirects,
@@ -111,6 +114,7 @@
 
   number_of_payment_method_manifest_to_download_--;
   if (content.empty()) {
+    SetFirstError(error_message);
     FinishCrawlingPaymentAppsIfReady();
     return;
   }
@@ -148,11 +152,12 @@
 
     if (!IsSameOriginWith(method_manifest_url_after_redirects,
                           web_app_manifest_url)) {
-      log_.Error("Installable payment handler from \"" +
-                 web_app_manifest_url.spec() +
-                 "\" is not allowed for the method \"" +
-                 method_manifest_url_after_redirects.spec() +
-                 "\" because of different origin.");
+      std::string error_message = base::ReplaceStringPlaceholders(
+          errors::kCrossOriginWebAppManifestNotAllowed,
+          {web_app_manifest_url.spec(),
+           method_manifest_url_after_redirects.spec()},
+          nullptr);
+      SetFirstError(error_message);
       continue;
     }
 
@@ -182,7 +187,8 @@
     const GURL& method_manifest_url,
     const GURL& web_app_manifest_url,
     const GURL& web_app_manifest_url_after_redirects,
-    const std::string& content) {
+    const std::string& content,
+    const std::string& error_message) {
 #if DCHECK_IS_ON()
   GURL::Replacements replacements;
   if (ignore_port_in_origin_comparison_for_testing_)
@@ -196,6 +202,7 @@
 
   number_of_web_app_manifest_to_download_--;
   if (content.empty()) {
+    SetFirstError(error_message);
     FinishCrawlingPaymentAppsIfReady();
     return;
   }
@@ -233,13 +240,8 @@
   if (app_info == nullptr)
     return false;
 
-  std::string log_prefix = "Web app manifest \"" + web_app_manifest_url.spec() +
-                           "\" for payment method manifest \"" +
-                           method_manifest_url.spec() + "\": ";
   if (app_info->sw_js_url.empty() || !base::IsStringUTF8(app_info->sw_js_url)) {
-    log_.Error(log_prefix +
-               "The installable payment handler's service worker JavaScript "
-               "file URL is not a non-empty UTF8 string.");
+    SetFirstError(errors::kInvalidServiceWorkerUrl);
     return false;
   }
 
@@ -247,29 +249,23 @@
   if (!GURL(app_info->sw_js_url).is_valid()) {
     GURL absolute_url = web_app_manifest_url.Resolve(app_info->sw_js_url);
     if (!absolute_url.is_valid()) {
-      log_.Error(log_prefix +
-                 "Failed to resolve the installable payment handler's service "
-                 "worker JavaScript file URL \"" +
-                 app_info->sw_js_url + "\".");
+      SetFirstError(base::ReplaceStringPlaceholders(
+          errors::kCannotResolveServiceWorkerUrl,
+          {app_info->sw_js_url, web_app_manifest_url.spec()}, nullptr));
       return false;
     }
     app_info->sw_js_url = absolute_url.spec();
   }
 
   if (!IsSameOriginWith(web_app_manifest_url, GURL(app_info->sw_js_url))) {
-    log_.Error(log_prefix +
-               "Installable payment handler's service worker JavaScript file "
-               "URL \"" +
-               app_info->sw_js_url +
-               "\" is not allowed for the web app manifest file \"" +
-               web_app_manifest_url.spec() + "\" because of different origin.");
+    SetFirstError(base::ReplaceStringPlaceholders(
+        errors::kCrossOriginServiceWorkerUrlNotAllowed,
+        {app_info->sw_js_url, web_app_manifest_url.spec()}, nullptr));
     return false;
   }
 
   if (!app_info->sw_scope.empty() && !base::IsStringUTF8(app_info->sw_scope)) {
-    log_.Error(log_prefix +
-               "The installable payment handler's service worker scope is not "
-               "a UTF8 string.");
+    SetFirstError(errors::kInvalidServiceWorkerScope);
     return false;
   }
 
@@ -277,21 +273,18 @@
     GURL absolute_scope =
         web_app_manifest_url.GetWithoutFilename().Resolve(app_info->sw_scope);
     if (!absolute_scope.is_valid()) {
-      log_.Error(log_prefix +
-                 "Failed to resolve the installable payment handler's "
-                 "registration scope \"" +
-                 app_info->sw_scope + "\".");
+      SetFirstError(base::ReplaceStringPlaceholders(
+          errors::kCannotResolveServiceWorkerScope,
+          {app_info->sw_scope, web_app_manifest_url.spec()}, nullptr));
       return false;
     }
     app_info->sw_scope = absolute_scope.spec();
   }
 
   if (!IsSameOriginWith(web_app_manifest_url, GURL(app_info->sw_scope))) {
-    log_.Error(log_prefix +
-               "Installable payment handler's registration scope \"" +
-               app_info->sw_scope +
-               "\" is not allowed for the web app manifest file \"" +
-               web_app_manifest_url.spec() + "\" because of different origin.");
+    SetFirstError(base::ReplaceStringPlaceholders(
+        errors::kCrossOriginServiceWorkerScopeNotAllowed,
+        {app_info->sw_scope, web_app_manifest_url.spec()}, nullptr));
     return false;
   }
 
@@ -299,16 +292,14 @@
   if (!content::PaymentAppProvider::GetInstance()->IsValidInstallablePaymentApp(
           web_app_manifest_url, GURL(app_info->sw_js_url),
           GURL(app_info->sw_scope), &error_message)) {
-    log_.Error(log_prefix + error_message);
+    SetFirstError(error_message);
     return false;
   }
 
   // TODO(crbug.com/782270): Support multiple installable payment apps for a
   // payment method.
   if (installable_apps_.find(method_manifest_url) != installable_apps_.end()) {
-    log_.Error(log_prefix +
-               "Multiple installable payment handlers from for a single "
-               "payment method are not yet supported.");
+    SetFirstError(errors::kInstallingMultipleDefaultAppsNotSupported);
     return false;
   }
 
@@ -333,7 +324,7 @@
     const GURL& web_app_manifest_url,
     std::unique_ptr<std::vector<PaymentManifestParser::WebAppIcon>> icons) {
   if (icons == nullptr || icons->empty()) {
-    log_.Error(
+    log_.Warn(
         "No valid icon information for installable payment handler found in "
         "web app manifest \"" +
         web_app_manifest_url.spec() + "\" for payment handler manifest \"" +
@@ -377,16 +368,16 @@
   }
 
   if (manifest_icons.empty()) {
-    log_.Error("No valid icons found in web app manifest \"" +
-               web_app_manifest_url.spec() +
-               "\" for payment handler manifest \"" +
-               method_manifest_url.spec() + "\".");
+    log_.Warn("No valid icons found in web app manifest \"" +
+              web_app_manifest_url.spec() +
+              "\" for payment handler manifest \"" +
+              method_manifest_url.spec() + "\".");
     return;
   }
 
   // Stop if the web_contents is gone.
   if (web_contents() == nullptr) {
-    log_.Error(
+    log_.Warn(
         "Cannot download icons after the webpage has been closed (web app "
         "manifest \"" +
         web_app_manifest_url.spec() + "\" for payment handler manifest \"" +
@@ -401,10 +392,10 @@
       content::ManifestIconDownloader::kMaxWidthToHeightRatio,
       blink::Manifest::ImageResource::Purpose::ANY);
   if (!best_icon_url.is_valid()) {
-    log_.Error("No suitable icon found in web app manifest \"" +
-               web_app_manifest_url.spec() +
-               "\" for payment handler manifest \"" +
-               method_manifest_url.spec() + "\".");
+    log_.Warn("No suitable icon found in web app manifest \"" +
+              web_app_manifest_url.spec() +
+              "\" for payment handler manifest \"" +
+              method_manifest_url.spec() + "\".");
     return;
   }
 
@@ -451,8 +442,15 @@
     return;
   }
 
-  std::move(callback_).Run(std::move(installable_apps_));
+  std::move(callback_).Run(std::move(installable_apps_), first_error_message_);
   std::move(finished_using_resources_).Run();
 }
 
+void InstallablePaymentAppCrawler::SetFirstError(
+    const std::string& error_message) {
+  log_.Error(error_message);
+  if (first_error_message_.empty())
+    first_error_message_ = error_message;
+}
+
 }  // namespace payments.
diff --git a/components/payments/content/installable_payment_app_crawler.h b/components/payments/content/installable_payment_app_crawler.h
index 601667c9..78372308 100644
--- a/components/payments/content/installable_payment_app_crawler.h
+++ b/components/payments/content/installable_payment_app_crawler.h
@@ -36,7 +36,8 @@
 class InstallablePaymentAppCrawler : public content::WebContentsObserver {
  public:
   using FinishedCrawlingCallback = base::OnceCallback<void(
-      std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>)>;
+      std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>,
+      const std::string& error_message)>;
 
   // The owner of InstallablePaymentAppCrawler owns |downloader|, |parser| and
   // |cache|. They should live until |finished_using_resources| parameter to
@@ -65,7 +66,8 @@
   void OnPaymentMethodManifestDownloaded(
       const GURL& method_manifest_url,
       const GURL& method_manifest_url_after_redirects,
-      const std::string& content);
+      const std::string& content,
+      const std::string& error_message);
   void OnPaymentMethodManifestParsed(
       const GURL& method_manifest_url,
       const GURL& method_manifest_url_after_redirects,
@@ -76,7 +78,8 @@
       const GURL& method_manifest_url,
       const GURL& web_app_manifest_url,
       const GURL& web_app_manifest_url_after_redirects,
-      const std::string& content);
+      const std::string& content,
+      const std::string& error_message);
   void OnPaymentWebAppInstallationInfo(
       const GURL& method_manifest_url,
       const GURL& web_app_manifest_url,
@@ -94,6 +97,7 @@
                                              const GURL& web_app_manifest_url,
                                              const SkBitmap& icon);
   void FinishCrawlingPaymentAppsIfReady();
+  void SetFirstError(const std::string& error_message);
 
   DeveloperConsoleLogger log_;
   PaymentManifestDownloader* downloader_;
@@ -109,6 +113,10 @@
   std::set<GURL> downloaded_web_app_manifests_;
   std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> installable_apps_;
 
+  // The first error message (if any) to be forwarded to the merchant when
+  // rejecting the promise returned from PaymentRequest.show().
+  std::string first_error_message_;
+
   bool ignore_port_in_origin_comparison_for_testing_ = false;
 
   base::WeakPtrFactory<InstallablePaymentAppCrawler> weak_ptr_factory_;
diff --git a/components/payments/content/manifest_verifier.cc b/components/payments/content/manifest_verifier.cc
index 550f9024..1600aa9 100644
--- a/components/payments/content/manifest_verifier.cc
+++ b/components/payments/content/manifest_verifier.cc
@@ -144,7 +144,8 @@
       manifests_to_download.size();
   if (number_of_manifests_to_verify_ == 0) {
     RemoveInvalidPaymentApps();
-    std::move(finished_verification_callback_).Run(std::move(apps_));
+    std::move(finished_verification_callback_)
+        .Run(std::move(apps_), first_error_message_);
     std::move(finished_using_resources_callback_).Run();
     return;
   }
@@ -199,7 +200,8 @@
     cached_manifest_urls_.insert(method_manifest_url);
     if (--number_of_manifests_to_verify_ == 0) {
       RemoveInvalidPaymentApps();
-      std::move(finished_verification_callback_).Run(std::move(apps_));
+      std::move(finished_verification_callback_)
+          .Run(std::move(apps_), first_error_message_);
     }
   }
 
@@ -212,15 +214,19 @@
 void ManifestVerifier::OnPaymentMethodManifestDownloaded(
     const GURL& method_manifest_url,
     const GURL& unused_method_manifest_url_after_redirects,
-    const std::string& content) {
+    const std::string& content,
+    const std::string& error_message) {
   DCHECK_LT(0U, number_of_manifests_to_download_);
 
   if (content.empty()) {
+    if (first_error_message_.empty())
+      first_error_message_ = error_message;
     if (cached_manifest_urls_.find(method_manifest_url) ==
             cached_manifest_urls_.end() &&
         --number_of_manifests_to_verify_ == 0) {
       RemoveInvalidPaymentApps();
-      std::move(finished_verification_callback_).Run(std::move(apps_));
+      std::move(finished_verification_callback_)
+          .Run(std::move(apps_), first_error_message_);
     }
 
     if (--number_of_manifests_to_download_ == 0)
@@ -256,7 +262,8 @@
 
     if (--number_of_manifests_to_verify_ == 0) {
       RemoveInvalidPaymentApps();
-      std::move(finished_verification_callback_).Run(std::move(apps_));
+      std::move(finished_verification_callback_)
+          .Run(std::move(apps_), first_error_message_);
     }
   }
 
diff --git a/components/payments/content/manifest_verifier.h b/components/payments/content/manifest_verifier.h
index 8292e0d..4a6ad23 100644
--- a/components/payments/content/manifest_verifier.h
+++ b/components/payments/content/manifest_verifier.h
@@ -58,7 +58,8 @@
   // remaining. If a payment handler does not have any valid payment method
   // names, then it's not included in the returned set of handlers.
   using VerifyCallback =
-      base::OnceCallback<void(content::PaymentAppProvider::PaymentApps)>;
+      base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
+                              const std::string& error_message)>;
 
   // Creates the verifier and starts up the parser utility process.
   // The owner of ManifestVerifier owns |downloader|, |parser| and
@@ -88,7 +89,8 @@
   void OnPaymentMethodManifestDownloaded(
       const GURL& method_manifest_url,
       const GURL& unused_method_manifest_url_after_redirects,
-      const std::string& content);
+      const std::string& content,
+      const std::string& error_message);
 
   // Called when a manifest is parsed.
   void OnPaymentMethodManifestParsed(
@@ -153,6 +155,10 @@
   // Once this number reaches 0, the resource usage callback is invoked.
   size_t number_of_manifests_to_download_;
 
+  // The first error message (if any) to be forwarded to the merchant when
+  // rejecting the promise returned from PaymentRequest.show().
+  std::string first_error_message_;
+
   base::WeakPtrFactory<ManifestVerifier> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ManifestVerifier);
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 1771e48..40ee346 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -240,7 +240,7 @@
   if (!state_) {
     // SSL is not valid. Reject show with NotSupportedError, disconnect the
     // mojo pipe, and destroy this object.
-    AreRequestedMethodsSupportedCallback(false);
+    AreRequestedMethodsSupportedCallback(false, reject_show_error_message_);
     return;
   }
 
@@ -480,7 +480,8 @@
 }
 
 void PaymentRequest::AreRequestedMethodsSupportedCallback(
-    bool methods_supported) {
+    bool methods_supported,
+    const std::string& error_message) {
   if (methods_supported) {
     if (SatisfiesSkipUIConstraints())
       Pay();
@@ -490,9 +491,8 @@
     journey_logger_.SetNotShown(
         JourneyLogger::NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD);
     client_->OnError(mojom::PaymentErrorReason::NOT_SUPPORTED,
-                     !reject_show_error_message_.empty()
-                         ? reject_show_error_message_
-                         : GetNotSupportedErrorMessage(spec_.get()));
+                     GetNotSupportedErrorMessage(spec_.get()) +
+                         (error_message.empty() ? "" : " " + error_message));
     if (observer_for_testing_)
       observer_for_testing_->OnNotSupportedError();
     OnConnectionTerminated();
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index 33eb60e1..55f410e7 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -156,7 +156,8 @@
                                      bool has_enrolled_instrument);
 
   // The callback for PaymentRequestState::AreRequestedMethodsSupported.
-  void AreRequestedMethodsSupportedCallback(bool methods_supported);
+  void AreRequestedMethodsSupportedCallback(bool methods_supported,
+                                            const std::string& error_message);
 
   // Sends either HAS_ENROLLED_INSTRUMENT or HAS_NO_ENROLLED_INSTRUMENT to the
   // renderer, depending on |has_enrolled_instrument| value. Does not check
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index 361769d..dc75de75 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -94,9 +94,11 @@
     const GURL& top_level_origin,
     const GURL& frame_origin,
     content::PaymentAppProvider::PaymentApps apps,
-    ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps) {
+    ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps,
+    const std::string& error_message) {
   number_of_pending_sw_payment_instruments_ =
       apps.size() + installable_apps.size();
+  get_all_payment_apps_error_ = error_message;
   if (number_of_pending_sw_payment_instruments_ == 0U) {
     FinishedGetAllSWPaymentInstruments();
     return;
@@ -273,7 +275,7 @@
 }
 
 void PaymentRequestState::AreRequestedMethodsSupported(
-    StatusCallback callback) {
+    MethodsSupportedCallback callback) {
   if (!get_all_instruments_finished_) {
     are_requested_methods_supported_callback_ = std::move(callback);
     return;
@@ -286,10 +288,11 @@
 }
 
 void PaymentRequestState::CheckRequestedMethodsSupported(
-    StatusCallback callback) {
+    MethodsSupportedCallback callback) {
   DCHECK(get_all_instruments_finished_);
 
-  std::move(callback).Run(are_requested_methods_supported_);
+  std::move(callback).Run(are_requested_methods_supported_,
+                          get_all_payment_apps_error_);
 }
 
 std::string PaymentRequestState::GetAuthenticatedEmail() const {
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index c0fc680..7cabf2db 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -102,6 +102,9 @@
   };
 
   using StatusCallback = base::OnceCallback<void(bool)>;
+  using MethodsSupportedCallback =
+      base::OnceCallback<void(bool methods_supported,
+                              const std::string& error_message)>;
 
   PaymentRequestState(content::WebContents* web_contents,
                       const GURL& top_level_origin,
@@ -136,7 +139,7 @@
   // Checks if the payment methods that the merchant website have
   // requested are supported asynchronously. For example, may return true for
   // "basic-card", but false for "https://bobpay.com".
-  void AreRequestedMethodsSupported(StatusCallback callback);
+  void AreRequestedMethodsSupported(MethodsSupportedCallback callback);
 
   // Returns authenticated user email, or empty string.
   std::string GetAuthenticatedEmail() const;
@@ -293,7 +296,8 @@
       const GURL& top_level_origin,
       const GURL& frame_origin,
       content::PaymentAppProvider::PaymentApps apps,
-      ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps);
+      ServiceWorkerPaymentAppFactory::InstallablePaymentApps installable_apps,
+      const std::string& error_message);
 
   // The ServiceWorkerPaymentInstrument::ValidateCanMakePaymentCallback.
   void OnSWPaymentInstrumentValidated(
@@ -312,7 +316,7 @@
 
   // Checks if the payment methods that the merchant website have
   // requested are supported and call the |callback| to return the result.
-  void CheckRequestedMethodsSupported(StatusCallback callback);
+  void CheckRequestedMethodsSupported(MethodsSupportedCallback callback);
 
   void OnAddressNormalized(bool success,
                            const autofill::AutofillProfile& normalized_profile);
@@ -341,8 +345,9 @@
 
   StatusCallback can_make_payment_callback_;
   StatusCallback has_enrolled_instrument_callback_;
-  StatusCallback are_requested_methods_supported_callback_;
+  MethodsSupportedCallback are_requested_methods_supported_callback_;
   bool are_requested_methods_supported_;
+  std::string get_all_payment_apps_error_;
 
   autofill::AutofillProfile* selected_shipping_profile_;
   autofill::AutofillProfile* selected_shipping_option_error_profile_;
diff --git a/components/payments/content/service_worker_payment_app_factory.cc b/components/payments/content/service_worker_payment_app_factory.cc
index eefbab9..0e582e8 100644
--- a/components/payments/content/service_worker_payment_app_factory.cc
+++ b/components/payments/content/service_worker_payment_app_factory.cc
@@ -153,7 +153,7 @@
     ServiceWorkerPaymentAppFactory::RemoveAppsWithoutMatchingMethodData(
         requested_method_data_, &apps);
     if (apps.empty()) {
-      OnPaymentAppsVerified(std::move(apps));
+      OnPaymentAppsVerified(std::move(apps), first_error_message_);
       OnPaymentAppsVerifierFinishedUsingResources();
       return;
     }
@@ -172,7 +172,10 @@
                        base::Unretained(this)));
   }
 
-  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps) {
+  void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
+                             const std::string& error_message) {
+    if (first_error_message_.empty())
+      first_error_message_ = error_message;
     if (apps.empty() && crawler_ != nullptr) {
       // Crawls installable web payment apps if no web payment apps have been
       // installed.
@@ -193,13 +196,17 @@
 
     std::move(callback_).Run(
         std::move(apps),
-        ServiceWorkerPaymentAppFactory::InstallablePaymentApps());
+        ServiceWorkerPaymentAppFactory::InstallablePaymentApps(),
+        first_error_message_);
   }
 
   void OnPaymentAppsCrawled(
-      std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> apps_info) {
+      std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> apps_info,
+      const std::string& error_message) {
+    if (first_error_message_.empty())
+      first_error_message_ = error_message;
     std::move(callback_).Run(content::PaymentAppProvider::PaymentApps(),
-                             std::move(apps_info));
+                             std::move(apps_info), first_error_message_);
   }
 
   void OnPaymentAppsCrawlerFinishedUsingResources() {
@@ -234,6 +241,7 @@
   std::vector<mojom::PaymentMethodDataPtr> requested_method_data_;
   ServiceWorkerPaymentAppFactory::GetAllPaymentAppsCallback callback_;
   base::OnceClosure finished_using_resources_callback_;
+  std::string first_error_message_;
 
   std::unique_ptr<ManifestVerifier> verifier_;
   bool is_payment_verifier_finished_using_resources_ = true;
diff --git a/components/payments/content/service_worker_payment_app_factory.h b/components/payments/content/service_worker_payment_app_factory.h
index 496ca9e9..036e5e48 100644
--- a/components/payments/content/service_worker_payment_app_factory.h
+++ b/components/payments/content/service_worker_payment_app_factory.h
@@ -42,7 +42,8 @@
       std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>;
   using GetAllPaymentAppsCallback =
       base::OnceCallback<void(content::PaymentAppProvider::PaymentApps,
-                              InstallablePaymentApps)>;
+                              InstallablePaymentApps,
+                              const std::string& error_message)>;
 
   static ServiceWorkerPaymentAppFactory* GetInstance();
 
diff --git a/components/payments/core/BUILD.gn b/components/payments/core/BUILD.gn
index 9688cb5..bdd65fd 100644
--- a/components/payments/core/BUILD.gn
+++ b/components/payments/core/BUILD.gn
@@ -71,6 +71,7 @@
   }
 
   deps = [
+    ":error_strings",
     "//base",
     "//components/autofill/core/browser",
     "//components/keyed_service/core",
diff --git a/components/payments/core/native_error_strings.cc b/components/payments/core/native_error_strings.cc
index d7c8221..fcc1cd41 100644
--- a/components/payments/core/native_error_strings.cc
+++ b/components/payments/core/native_error_strings.cc
@@ -23,14 +23,57 @@
 
 const char kCannotCompleteWithoutShow[] = "Attempted complete without show.";
 
+const char kCannotResolveServiceWorkerScope[] =
+    "Cannot resolve the \"serviceworker\".\"scope\" $1 in web app manifest $2.";
+
+const char kCannotResolveServiceWorkerUrl[] =
+    "Cannot resolve the \"serviceworker\".\"src\" $1 in web app manifest $2.";
+
 const char kCannotRetryWithoutInit[] =
     "Attempted retry without initialization.";
 
 const char kCannotRetryWithoutShow[] = "Attempted retry without show.";
 
+const char kCrossOriginPaymentMethodManifestNotAllowed[] =
+    "Cross-origin payment method manifest \"$1\" not allowed for the payment "
+    "method \"$2\".";
+
+const char kCrossOriginServiceWorkerScopeNotAllowed[] =
+    "Cross-origin \"serviceworker\".\"scope\" $1 not allowed in web app "
+    "manifest $2.";
+
+const char kCrossOriginServiceWorkerUrlNotAllowed[] =
+    "Cross-origin \"serviceworker\".\"src\" $1 not allowed in web app manifest "
+    "$2.";
+
+const char kCrossOriginWebAppManifestNotAllowed[] =
+    "Cross-origin default application $1 not allowed in payment method "
+    "manifest $2.";
+
 const char kDetailedInvalidSslCertificateMessageFormat[] =
     "SSL certificate is not valid. Security level: $.";
 
+const char kHttpHeadRequestFailed[] =
+    "Unable to make a HEAD request to \"$1\" for payment method manifest.";
+
+const char kHttpStatusCodeNotAllowed[] =
+    "HTTP status code $1 \"$2\" not allowed for payment method manifest "
+    "\"$3\".";
+
+const char kInstallingMultipleDefaultAppsNotSupported[] =
+    "Installing multiple payment handlers from a single payment method "
+    "manifest is not supported.";
+
+const char kInvalidManifestUrl[] =
+    "\"$1\" is not a valid payment manifest URL with HTTPS scheme (or HTTP "
+    "scheme for localhost).";
+
+const char kInvalidServiceWorkerScope[] =
+    "\"serviceworker\".\"scope\" must be a non-empty UTF8 string.";
+
+const char kInvalidServiceWorkerUrl[] =
+    "\"serviceworker\".\"src\" must be a non-empty UTF8 string.";
+
 const char kInvalidSslCertificate[] = "SSL certificate is not valid.";
 
 const char kMethodDataRequired[] = "Method data required.";
@@ -46,6 +89,9 @@
 const char kMultiplePaymentMethodsNotSupportedFormat[] =
     "The payment methods $ are not supported.";
 
+const char kNoLinkRelPaymentMethodManifestHttpHeader[] =
+    "No \"Link: rel=payment-method-manifest\" HTTP header found at \"$1\".";
+
 const char kNoResponseToPaymentEvent[] =
     "Payment handler did not respond to \"paymentrequest\" event.";
 
@@ -53,6 +99,13 @@
 
 const char kNotShown[] = "Not shown.";
 
+const char kPaymentManifestCrossSiteRedirectNotAllowed[] =
+    "Cross-site redirect from \"$1\" to \"$2\" not allowed for payment "
+    "manifests.";
+
+const char kPaymentManifestDownloadFailed[] =
+    "Unable to download payment manifest \"$1\".";
+
 const char kPaymentDetailsNotObject[] =
     "Payment app returned invalid response. \"details\" field is not a "
     "dictionary.";
@@ -74,6 +127,10 @@
     "PaymentRequestEvent.respondWith(). This is how payment handlers close "
     "their own window programmatically.";
 
+const char kReachedMaximumNumberOfRedirects[] =
+    "Unable to download the payment manifest because reached the maximum "
+    "number of redirects.";
+
 const char kPaymentEventServiceWorkerError[] =
     "Payment handler failed to provide a response because either the "
     "\"paymentrequest\" event took too long or the service worker stopped for "
diff --git a/components/payments/core/native_error_strings.h b/components/payments/core/native_error_strings.h
index 927d146..fa10b3a5 100644
--- a/components/payments/core/native_error_strings.h
+++ b/components/payments/core/native_error_strings.h
@@ -30,20 +30,71 @@
 // Mojo call PaymentRequest::Show() must precede PaymentRequest::Complete().
 extern const char kCannotCompleteWithoutShow[];
 
+// Used when "serviceworker"."scope" string A in web app manifest B is not a
+// valid URL and cannot be resolved as a relative URL either. This format should
+// be used with base::ReplaceStringPlaceholders(fmt, {A, B}, nullptr).
+extern const char kCannotResolveServiceWorkerScope[];
+
+// Used when "serviceworker"."src" string A in web app manifest B is not a valid
+// URL and cannot be resolved as a relative URL either. This format should be
+// used with base::ReplaceStringPlaceholders(fmt, {A, B}, nullptr).
+extern const char kCannotResolveServiceWorkerUrl[];
+
 // Mojo call PaymentRequest::Init() must precede PaymentRequest::Retry().
 extern const char kCannotRetryWithoutInit[];
 
 // Mojo call PaymentRequest::Show() must precede PaymentRequest::Retry().
 extern const char kCannotRetryWithoutShow[];
 
-// Mojo call PaymentRequest::Show() must precede PaymentRequest::UpdateWith().
-extern const char kCannotUpdateWithoutShow[];
+// Used when a payment method A has a cross-origin "Link:
+// rel=payment-method-manifest" to the manifest B. This format should be used
+// with base::ReplaceStringPlaceholders(fmt, {B, A}, nullptr).
+extern const char kCrossOriginPaymentMethodManifestNotAllowed[];
+
+// The URL A in web app manifest B's "serviceworker"."scope" must be of the same
+// origin as the web app manifest itself. This format should be used with
+// base::ReplaceStringPlaceholders(fmt, {A, B}, nullptr).
+extern const char kCrossOriginServiceWorkerScopeNotAllowed[];
+
+// The URL A in web app manifest B's "serviceworker"."src" must be of the same
+// origin as the web app manifest itself. This format should be used with
+// base::ReplaceStringPlaceholders(fmt, {A, B}, nullptr).
+extern const char kCrossOriginServiceWorkerUrlNotAllowed[];
+
+// The "default_applications" list on origin A is not allowed to contain a URL
+// from origin B. This format should be used with
+// base::ReplaceStringPlaceholders(format, {B, A}, nullptr).
+extern const char kCrossOriginWebAppManifestNotAllowed[];
 
 // The format for a detailed message about invalid SSL certificate. This format
 // should be used with base::ReplaceChars() function, where "$" is the character
 // to replace.
 extern const char kDetailedInvalidSslCertificateMessageFormat[];
 
+// Used when a HEAD request for URL A fails. This format should be used with
+// base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
+extern const char kHttpHeadRequestFailed[];
+
+// Used for HTTP redirects that are prohibited for payment method manifests.
+// This format should be used with base::ReplaceStringPlaceholders(fmt,
+// {http_code, http_code_phrase, original_url}, nullptr).
+extern const char kHttpStatusCodeNotAllowed[];
+
+// The "default_applications" list should contain exactly one URL for JIT
+// install feature to work.
+extern const char kInstallingMultipleDefaultAppsNotSupported[];
+
+// Used to let the web developer know about an invalid payment manifest URL A.
+// This format should be used with base::ReplaceStringPlaceholders(fmt, {A},
+// nullptr).
+extern const char kInvalidManifestUrl[];
+
+// Web app manifest contains an empty or non-UTF8 service worker scope.
+extern const char kInvalidServiceWorkerScope[];
+
+// Web app manifest contains an empty or non-UTF8 service worker URL.
+extern const char kInvalidServiceWorkerUrl[];
+
 // Chrome refuses to provide any payment information to a website with an
 // invalid SSL certificate.
 extern const char kInvalidSslCertificate[];
@@ -66,6 +117,11 @@
 // where "$" is the character to replace.
 extern const char kMultiplePaymentMethodsNotSupportedFormat[];
 
+// Used when the payment method URL A does not have a "Link:
+// rel=payment-method-manifest" HTTP header. This format should be used with
+// base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
+extern const char kNoLinkRelPaymentMethodManifestHttpHeader[];
+
 // Payment handler did not respond to the "paymentrequest" event.
 extern const char kNoResponseToPaymentEvent[];
 
@@ -75,6 +131,14 @@
 // Used when PaymentRequest::Show() has not been called, but should have been.
 extern const char kNotShown[];
 
+// Used for errors about cross-site redirects from A to B. This format should be
+// used with base::ReplaceStringPlaceholders(fmt, {A, B}, nullptr).
+extern const char kPaymentManifestCrossSiteRedirectNotAllowed[];
+
+// Used when downloading payment manifest URL A has failed. This format should
+// be used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
+extern const char kPaymentManifestDownloadFailed[];
+
 // Payment handler passed a non-object field "details" in response to the
 // "paymentrequest" event.
 extern const char kPaymentDetailsNotObject[];
@@ -106,6 +170,9 @@
 // PaymentRequestEvent.respondWith() method.
 extern const char kPaymentEventRejected[];
 
+// Used when maximum number of redirects has been reached.
+extern const char kReachedMaximumNumberOfRedirects[];
+
 // The format for the message about a single payment method that is not
 // supported. This format should be used with base::ReplaceChars() function,
 // where "$" is the character to replace.
diff --git a/components/payments/core/payment_manifest_downloader.cc b/components/payments/core/payment_manifest_downloader.cc
index 814db83..5da3730 100644
--- a/components/payments/core/payment_manifest_downloader.cc
+++ b/components/payments/core/payment_manifest_downloader.cc
@@ -11,10 +11,12 @@
 #include "base/logging.h"
 #include "base/optional.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/string_util.h"
 #include "components/link_header_util/link_header_util.h"
 #include "components/payments/core/error_logger.h"
+#include "components/payments/core/native_error_strings.h"
 #include "components/payments/core/url_util.h"
 #include "net/base/load_flags.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
@@ -32,28 +34,31 @@
 
 GURL ParseResponseHeader(const GURL& url,
                          scoped_refptr<net::HttpResponseHeaders> headers,
-                         const ErrorLogger& log) {
+                         const ErrorLogger& log,
+                         std::string* out_error_message) {
   if (!headers) {
-    log.Error(base::StringPrintf(
-        "No HTTP headers found on \"%s\" for payment method manifest.",
-        url.spec().c_str()));
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()},
+        nullptr);
+    log.Error(*out_error_message);
     return GURL();
   }
 
   int response_code = headers->response_code();
   if (response_code != net::HTTP_OK && response_code != net::HTTP_NO_CONTENT) {
-    log.Error(base::StringPrintf(
-        "Unable to make a HEAD request to \"%s\" for payment method manifest.",
-        url.spec().c_str()));
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kHttpHeadRequestFailed, {url.spec()}, nullptr),
+    log.Error(*out_error_message);
     return GURL();
   }
 
   std::string link_header;
   headers->GetNormalizedHeader("link", &link_header);
   if (link_header.empty()) {
-    log.Error(base::StringPrintf(
-        "No HTTP Link headers found on \"%s\" for payment method manifest.",
-        url.spec().c_str()));
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()},
+        nullptr);
+    log.Error(*out_error_message);
     return GURL();
   }
 
@@ -76,40 +81,45 @@
       return url.Resolve(payment_method_manifest_url);
   }
 
-  log.Error(
-      base::StringPrintf("No rel=\"payment-method-manifest\" HTTP Link headers "
-                         "found on \"%s\" for payment method manifest.",
-                         url.spec().c_str()));
+  *out_error_message = base::ReplaceStringPlaceholders(
+      errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, nullptr);
+  log.Error(*out_error_message);
   return GURL();
 }
 
-bool IsValidManifestUrl(const GURL& url, const ErrorLogger& log) {
+bool IsValidManifestUrl(const GURL& url,
+                        const ErrorLogger& log,
+                        std::string* out_error_message) {
   bool is_valid = UrlUtil::IsValidManifestUrl(url);
   if (!is_valid) {
-    log.Error(
-        base::StringPrintf("\"%s\" is not a valid payment manifest URL with "
-                           "HTTPS scheme (or HTTP scheme for localhost).",
-                           url.spec().c_str()));
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kInvalidManifestUrl, {url.spec()}, nullptr);
+    log.Error(*out_error_message);
   }
   return is_valid;
 }
 
 GURL ParseRedirectUrl(const net::RedirectInfo& redirect_info,
-                      const ErrorLogger& log) {
-  // Do not follow net::HTTP_MULTIPLE_CHOICES, net::HTTP_NOT_MODIFIED and
-  // net::HTTP_USE_PROXY redirects. (306 is no longer used.)
+                      const GURL& original_url,
+                      const ErrorLogger& log,
+                      std::string* out_error_message) {
   if (redirect_info.status_code != net::HTTP_MOVED_PERMANENTLY &&   // 301
       redirect_info.status_code != net::HTTP_FOUND &&               // 302
       redirect_info.status_code != net::HTTP_SEE_OTHER &&           // 303
       redirect_info.status_code != net::HTTP_TEMPORARY_REDIRECT &&  // 307
       redirect_info.status_code != net::HTTP_PERMANENT_REDIRECT) {  // 308
-    log.Error(
-        "Cannot follow HTTP_MULTIPLE_CHOICES (300), HTTP_NOT_MODIFIED (304), "
-        "and HTTP_USE_PROXY (305) redirects for payment manifests.");
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kHttpStatusCodeNotAllowed,
+        {base::NumberToString(redirect_info.status_code),
+         net::GetHttpReasonPhrase(
+             static_cast<net::HttpStatusCode>(redirect_info.status_code)),
+         original_url.spec()},
+        nullptr);
+    log.Error(*out_error_message);
     return GURL();
   }
 
-  if (!IsValidManifestUrl(redirect_info.new_url, log))
+  if (!IsValidManifestUrl(redirect_info.new_url, log, out_error_message))
     return GURL();
 
   return redirect_info.new_url;
@@ -119,10 +129,12 @@
     const GURL& final_url,
     const std::string& response_body,
     scoped_refptr<net::HttpResponseHeaders> headers,
-    const ErrorLogger& log) {
+    const ErrorLogger& log,
+    std::string* out_error_message) {
   if (!headers || headers->response_code() != net::HTTP_OK) {
-    log.Error(base::StringPrintf("Unable to download payment manifest \"%s\".",
-                                 final_url.spec().c_str()));
+    *out_error_message = base::ReplaceStringPlaceholders(
+        errors::kPaymentManifestDownloadFailed, {final_url.spec()}, nullptr);
+    log.Error(*out_error_message);
     return std::string();
   }
 
@@ -144,7 +156,7 @@
 void PaymentManifestDownloader::DownloadPaymentMethodManifest(
     const GURL& url,
     PaymentManifestDownloadCallback callback) {
-  DCHECK(IsValidManifestUrl(url, *log_));
+  DCHECK(UrlUtil::IsValidManifestUrl(url));
   // Restrict number of redirects for efficiency and breaking circle.
   InitiateDownload(url, "HEAD",
                    /*allowed_number_of_redirects=*/3, std::move(callback));
@@ -153,7 +165,7 @@
 void PaymentManifestDownloader::DownloadWebAppManifest(
     const GURL& url,
     PaymentManifestDownloadCallback callback) {
-  DCHECK(IsValidManifestUrl(url, *log_));
+  DCHECK(UrlUtil::IsValidManifestUrl(url));
   InitiateDownload(url, "GET", /*allowed_number_of_redirects=*/0,
                    std::move(callback));
 }
@@ -178,9 +190,11 @@
   downloads_.erase(download_it);
 
   // Manually follow some type of redirects.
+  std::string error_message;
   if (download->allowed_number_of_redirects > 0) {
     DCHECK(download->method == "HEAD");
-    GURL redirect_url = ParseRedirectUrl(redirect_info, *log_);
+    GURL redirect_url = ParseRedirectUrl(redirect_info, download->original_url,
+                                         *log_, &error_message);
     if (!redirect_url.is_empty()) {
       // Do not allow cross site redirects.
       if (net::registry_controlled_domains::SameDomainOrHost(
@@ -191,18 +205,16 @@
                          std::move(download->callback));
         return;
       }
-      log_->Error(base::StringPrintf(
-          "Cross-site redirect from \"%s\" to \"%s\" not allowed for payment "
-          "manifests.",
-          download->original_url.spec().c_str(), redirect_url.spec().c_str()));
+      error_message = base::ReplaceStringPlaceholders(
+          errors::kPaymentManifestCrossSiteRedirectNotAllowed,
+          {download->original_url.spec(), redirect_url.spec()}, nullptr);
     }
   } else {
-    log_->Error(
-        "Unable to download the payment manifest because reached the maximum "
-        "number of redirects.");
+    error_message = errors::kReachedMaximumNumberOfRedirects;
   }
-
-  std::move(download->callback).Run(download->original_url, std::string());
+  log_->Error(error_message);
+  std::move(download->callback)
+      .Run(download->original_url, std::string(), error_message);
 }
 
 void PaymentManifestDownloader::OnURLLoaderComplete(
@@ -233,29 +245,33 @@
   std::unique_ptr<Download> download = std::move(download_it->second);
   downloads_.erase(download_it);
 
+  std::string error_message;
   if (download->method == "GET") {
-    std::move(download->callback)
-        .Run(final_url,
-             ParseResponseContent(final_url, response_body, headers, *log_));
+    std::string content = ParseResponseContent(final_url, response_body,
+                                               headers, *log_, &error_message);
+    std::move(download->callback).Run(final_url, content, error_message);
     return;
   }
 
   DCHECK_EQ("HEAD", download->method);
   GURL payment_method_manifest_url =
-      ParseResponseHeader(final_url, headers, *log_);
+      ParseResponseHeader(final_url, headers, *log_, &error_message);
+  if (!payment_method_manifest_url.is_valid()) {
+    std::move(download->callback).Run(final_url, std::string(), error_message);
+    return;
+  }
 
-  if (!payment_method_manifest_url.is_valid() ||
-      !IsValidManifestUrl(payment_method_manifest_url, *log_)) {
-    std::move(download->callback).Run(final_url, std::string());
+  if (!IsValidManifestUrl(payment_method_manifest_url, *log_, &error_message)) {
+    std::move(download->callback).Run(final_url, std::string(), error_message);
     return;
   }
 
   if (!url::IsSameOriginWith(final_url, payment_method_manifest_url)) {
-    log_->Error(base::StringPrintf(
-        "Payment method manifest \"%s\" is not allowed for the method \"%s\" "
-        "because of different origin.",
-        payment_method_manifest_url.spec().c_str(), final_url.spec().c_str()));
-    std::move(download->callback).Run(final_url, std::string());
+    error_message = base::ReplaceStringPlaceholders(
+        errors::kCrossOriginPaymentMethodManifestNotAllowed,
+        {payment_method_manifest_url.spec(), final_url.spec()}, nullptr);
+    log_->Error(error_message);
+    std::move(download->callback).Run(final_url, std::string(), error_message);
     return;
   }
 
@@ -279,7 +295,7 @@
     const std::string& method,
     int allowed_number_of_redirects,
     PaymentManifestDownloadCallback callback) {
-  DCHECK(IsValidManifestUrl(url, *log_));
+  DCHECK(UrlUtil::IsValidManifestUrl(url));
 
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("payment_manifest_downloader", R"(
diff --git a/components/payments/core/payment_manifest_downloader.h b/components/payments/core/payment_manifest_downloader.h
index de5a4605..753e186 100644
--- a/components/payments/core/payment_manifest_downloader.h
+++ b/components/payments/core/payment_manifest_downloader.h
@@ -49,7 +49,9 @@
 // In the case of a web app manifest download, can also also fail when:
 //  - There's a redirect.
 using PaymentManifestDownloadCallback =
-    base::OnceCallback<void(const GURL& url, const std::string& contents)>;
+    base::OnceCallback<void(const GURL& url,
+                            const std::string& contents,
+                            const std::string& error_message)>;
 
 // Downloader of the payment method manifest and web-app manifest based on the
 // payment method name that is a URL with HTTPS scheme, e.g.,
diff --git a/components/payments/core/payment_manifest_downloader_unittest.cc b/components/payments/core/payment_manifest_downloader_unittest.cc
index 421c5e4..b460cb7 100644
--- a/components/payments/core/payment_manifest_downloader_unittest.cc
+++ b/components/payments/core/payment_manifest_downloader_unittest.cc
@@ -22,6 +22,8 @@
 
 using testing::_;
 
+static const char kNoError[] = "";
+
 }  // namespace
 
 class PaymentMethodManifestDownloaderTest : public testing::Test {
@@ -41,9 +43,10 @@
 
   ~PaymentMethodManifestDownloaderTest() override {}
 
-  MOCK_METHOD2(OnManifestDownload,
+  MOCK_METHOD3(OnManifestDownload,
                void(const GURL& unused_url_after_redirects,
-                    const std::string& content));
+                    const std::string& content,
+                    const std::string& error_message));
 
   void CallComplete(int response_code = 200,
                     const std::string& link_header = std::string(),
@@ -52,7 +55,10 @@
     scoped_refptr<net::HttpResponseHeaders> headers;
     if (send_headers) {
       headers = base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
-      headers->ReplaceStatusLine(base::StringPrintf("%d", response_code));
+      headers->ReplaceStatusLine(base::StringPrintf(
+          "HTTP/1.1 %d %s", response_code,
+          net::GetHttpReasonPhrase(
+              static_cast<net::HttpStatusCode>(response_code))));
     }
 
     if (!link_header.empty())
@@ -87,19 +93,29 @@
 };
 
 TEST_F(PaymentMethodManifestDownloaderTest, HttpHeadResponse404IsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this,
+              OnManifestDownload(
+                  _, std::string(),
+                  "Unable to make a HEAD request to \"https://bobpay.com/\" "
+                  "for payment method manifest."));
 
   CallComplete(404);
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, NoHttpHeadersIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200, std::string(), std::string(), false);
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpHeaderIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200);
 }
@@ -107,26 +123,38 @@
 TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpLinkHeaderIsFailure) {
   scoped_refptr<net::HttpResponseHeaders> headers(
       new net::HttpResponseHeaders(std::string()));
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200, "Link:");
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, NoRelInHttpLinkHeaderIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200, "Link: <manifest.json>");
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, NoUrlInHttpLinkHeaderIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200, "Link: rel=payment-method-manifest");
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest,
        NoManifestRellInHttpLinkHeaderIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this, OnManifestDownload(_, std::string(),
+                                "No \"Link: rel=payment-method-manifest\" HTTP "
+                                "header found at \"https://bobpay.com/\"."));
 
   CallComplete(200, "Link: <manifest.json>; rel=web-app-manifest");
 }
@@ -136,7 +164,10 @@
       new net::HttpResponseHeaders(std::string()));
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this,
+              OnManifestDownload(_, std::string(),
+                                 "Unable to download payment manifest "
+                                 "\"https://bobpay.com/manifest.json\"."));
 
   CallComplete(404);
 }
@@ -144,7 +175,10 @@
 TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpGetResponseIsFailure) {
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this,
+              OnManifestDownload(_, std::string(),
+                                 "Unable to download payment manifest "
+                                 "\"https://bobpay.com/manifest.json\"."));
 
   CallComplete(200, std::string(), std::string(), false);
 }
@@ -152,7 +186,7 @@
 TEST_F(PaymentMethodManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) {
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, std::string(), "manifest content");
 }
@@ -160,7 +194,7 @@
 TEST_F(PaymentMethodManifestDownloaderTest, HeaderResponseCode204IsSuccess) {
   CallComplete(204, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, std::string(), "manifest content");
 }
@@ -180,7 +214,12 @@
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpHeaderLinkUrl) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this,
+      OnManifestDownload(
+          _, std::string(),
+          "\"http://bobpay.com/manifest.json\" is not a valid payment manifest "
+          "URL with HTTPS scheme (or HTTP scheme for localhost)."));
 
   CallComplete(200,
                "Link: <http://bobpay.com/manifest.json>; "
@@ -188,7 +227,11 @@
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, 300IsUnsupportedRedirect) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this,
+              OnManifestDownload(
+                  _, std::string(),
+                  "HTTP status code 300 \"Multiple Choices\" not allowed for "
+                  "payment method manifest \"https://bobpay.com/\"."));
 
   CallRedirect(300, GURL("https://pay.bobpay.com"));
 }
@@ -204,7 +247,7 @@
 
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, std::string(), "manifest content");
 }
@@ -220,19 +263,26 @@
 
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, std::string(), "manifest content");
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, 304IsUnsupportedRedirect) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this,
+              OnManifestDownload(
+                  _, std::string(),
+                  "HTTP status code 304 \"Not Modified\" not allowed for "
+                  "payment method manifest \"https://bobpay.com/\"."));
 
   CallRedirect(304, GURL("https://pay.bobpay.com"));
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, 305IsUnsupportedRedirect) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this, OnManifestDownload(
+                         _, std::string(),
+                         "HTTP status code 305 \"Use Proxy\" not allowed for "
+                         "payment method manifest \"https://bobpay.com/\"."));
 
   CallRedirect(305, GURL("https://pay.bobpay.com"));
 }
@@ -248,7 +298,7 @@
 
   CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest");
 
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, std::string(), "manifest content");
 }
@@ -266,19 +316,30 @@
 
   EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com"));
 
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this, OnManifestDownload(
+                         _, std::string(),
+                         "Unable to download the payment manifest because "
+                         "reached the maximum number of redirects."));
 
   CallRedirect(308, GURL("https://newpay.bobpay.com"));
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, InvalidRedirectUrlIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this, OnManifestDownload(
+                         _, std::string(),
+                         "\"\" is not a valid payment manifest URL with HTTPS "
+                         "scheme (or HTTP scheme for localhost)."));
 
   CallRedirect(308, GURL("pay.bobpay.com"));
 }
 
 TEST_F(PaymentMethodManifestDownloaderTest, NotAllowCrossSiteRedirects) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this,
+      OnManifestDownload(
+          _, std::string(),
+          "Cross-site redirect from \"https://bobpay.com/\" to "
+          "\"https://alicepay.com/\" not allowed for payment manifests."));
 
   CallRedirect(301, GURL("https://alicepay.com"));
 }
@@ -300,14 +361,19 @@
 
   ~WebAppManifestDownloaderTest() override {}
 
-  MOCK_METHOD2(OnManifestDownload,
-               void(const GURL& url, const std::string& content));
+  MOCK_METHOD3(OnManifestDownload,
+               void(const GURL& url,
+                    const std::string& content,
+                    const std::string& error_message));
 
   void CallComplete(int response_code,
                     const std::string& response_body = std::string()) {
     scoped_refptr<net::HttpResponseHeaders> headers =
         base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
-    headers->ReplaceStatusLine(base::StringPrintf("%d", response_code));
+    headers->ReplaceStatusLine(base::StringPrintf(
+        "HTTP/1.1 %d %s", response_code,
+        net::GetHttpReasonPhrase(
+            static_cast<net::HttpStatusCode>(response_code))));
     downloader_.OnURLLoaderCompleteInternal(downloader_.GetLoaderForTesting(),
                                             test_url_, response_body, headers,
                                             net::OK);
@@ -324,19 +390,23 @@
 };
 
 TEST_F(WebAppManifestDownloaderTest, HttpGetResponse404IsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(
+      *this,
+      OnManifestDownload(
+          _, std::string(),
+          "Unable to download payment manifest \"https://bobpay.com/\"."));
 
   CallComplete(404);
 }
 
 TEST_F(WebAppManifestDownloaderTest, EmptyHttpGetResponseIsFailure) {
-  EXPECT_CALL(*this, OnManifestDownload(_, std::string()));
+  EXPECT_CALL(*this, OnManifestDownload(_, std::string(), kNoError));
 
   CallComplete(200);
 }
 
 TEST_F(WebAppManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) {
-  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content"));
+  EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
 
   CallComplete(200, "manifest content");
 }
diff --git a/components/resources/terms/terms_am.html b/components/resources/terms/terms_am.html
index ccd72ff..fcf55438 100644
--- a/components/resources/terms/terms_am.html
+++ b/components/resources/terms/terms_am.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>የGoogle Chrome የአግልግሎት ስምምነት ውሎች </title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ar.html b/components/resources/terms/terms_ar.html
index 8354244..6e5920a 100644
--- a/components/resources/terms/terms_ar.html
+++ b/components/resources/terms/terms_ar.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>بنود خدمة Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_bg.html b/components/resources/terms/terms_bg.html
index bec309d..9aaeaf6 100644
--- a/components/resources/terms/terms_bg.html
+++ b/components/resources/terms/terms_bg.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Общи условия на Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_bn.html b/components/resources/terms/terms_bn.html
index 22082df4..09f7d6b 100644
--- a/components/resources/terms/terms_bn.html
+++ b/components/resources/terms/terms_bn.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome পরিষেবার চুক্তি</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ca.html b/components/resources/terms/terms_ca.html
index fdebae5..d673e888 100644
--- a/components/resources/terms/terms_ca.html
+++ b/components/resources/terms/terms_ca.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Condicions d'ús de Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_chromium.html b/components/resources/terms/terms_chromium.html
index 7b39fb5..87690e9 100644
--- a/components/resources/terms/terms_chromium.html
+++ b/components/resources/terms/terms_chromium.html
@@ -1,6 +1,9 @@
 <html>
 <head>
-  <title></title>
+<title></title>
+<style>
+:root { color-scheme: light dark; }
+</style>
 </head>
 <body>
 <h2>This Space Intentionally Blank</h2>
diff --git a/components/resources/terms/terms_cs.html b/components/resources/terms/terms_cs.html
index 8d5a1299..1248e063 100644
--- a/components/resources/terms/terms_cs.html
+++ b/components/resources/terms/terms_cs.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Smluvní podmínky aplikace Google Chrome </title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_da.html b/components/resources/terms/terms_da.html
index f4e9bc624..e1e80fc 100644
--- a/components/resources/terms/terms_da.html
+++ b/components/resources/terms/terms_da.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome Servicevilkår</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_de.html b/components/resources/terms/terms_de.html
index 6a90a917..b71895d0 100644
--- a/components/resources/terms/terms_de.html
+++ b/components/resources/terms/terms_de.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome – Nutzungsbedingungen</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_el.html b/components/resources/terms/terms_el.html
index 74145f8..f402c5ba 100644
--- a/components/resources/terms/terms_el.html
+++ b/components/resources/terms/terms_el.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Όροι Παροχής Υπηρεσιών του Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_en-GB.html b/components/resources/terms/terms_en-GB.html
index a709979..c91430d7 100644
--- a/components/resources/terms/terms_en-GB.html
+++ b/components/resources/terms/terms_en-GB.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome Terms of Service</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_en.html b/components/resources/terms/terms_en.html
index fe96cc092..ca6a0d9 100644
--- a/components/resources/terms/terms_en.html
+++ b/components/resources/terms/terms_en.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome Terms of Service</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_es-419.html b/components/resources/terms/terms_es-419.html
index 4b0b516..0e30e33f 100644
--- a/components/resources/terms/terms_es-419.html
+++ b/components/resources/terms/terms_es-419.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Condiciones del servicio de Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_es.html b/components/resources/terms/terms_es.html
index 970e41d..cfa57df9 100644
--- a/components/resources/terms/terms_es.html
+++ b/components/resources/terms/terms_es.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Condiciones del servicio de Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_et.html b/components/resources/terms/terms_et.html
index a541169f..5d5a9bd 100644
--- a/components/resources/terms/terms_et.html
+++ b/components/resources/terms/terms_et.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome'i teenusetingimused</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_fa.html b/components/resources/terms/terms_fa.html
index 9337b1f..140d846e 100644
--- a/components/resources/terms/terms_fa.html
+++ b/components/resources/terms/terms_fa.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>شرایط سرویس Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_fi.html b/components/resources/terms/terms_fi.html
index 279218c..c945a98 100644
--- a/components/resources/terms/terms_fi.html
+++ b/components/resources/terms/terms_fi.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chromen käyttöehdot</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_fil.html b/components/resources/terms/terms_fil.html
index 4954c0f..847ce1a3 100644
--- a/components/resources/terms/terms_fil.html
+++ b/components/resources/terms/terms_fil.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Mga Tuntunin ng Serbisyo ng Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_fr.html b/components/resources/terms/terms_fr.html
index ac305761..8b67f318 100644
--- a/components/resources/terms/terms_fr.html
+++ b/components/resources/terms/terms_fr.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Conditions d'utilisation de Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_gu.html b/components/resources/terms/terms_gu.html
index c2647d73..097ac76 100644
--- a/components/resources/terms/terms_gu.html
+++ b/components/resources/terms/terms_gu.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome સેવાની શરતો</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_he.html b/components/resources/terms/terms_he.html
index 3caedd2..486983f 100644
--- a/components/resources/terms/terms_he.html
+++ b/components/resources/terms/terms_he.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>התנאים וההגבלות של Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_hi.html b/components/resources/terms/terms_hi.html
index 56ea72b8..631a576c 100644
--- a/components/resources/terms/terms_hi.html
+++ b/components/resources/terms/terms_hi.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome सेवा की शर्तें</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_hr.html b/components/resources/terms/terms_hr.html
index a38ad02..f5edf10 100644
--- a/components/resources/terms/terms_hr.html
+++ b/components/resources/terms/terms_hr.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome uvjeti pružanja usluge</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_hu.html b/components/resources/terms/terms_hu.html
index 1f53f0a..6223c93 100644
--- a/components/resources/terms/terms_hu.html
+++ b/components/resources/terms/terms_hu.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome - Általános Szerződési Feltételek</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_id.html b/components/resources/terms/terms_id.html
index 98021e02..6122b160 100644
--- a/components/resources/terms/terms_id.html
+++ b/components/resources/terms/terms_id.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Persyaratan Layanan Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_it.html b/components/resources/terms/terms_it.html
index f9c76f98..d78debd 100644
--- a/components/resources/terms/terms_it.html
+++ b/components/resources/terms/terms_it.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Termini di servizio di Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ja.html b/components/resources/terms/terms_ja.html
index f617373..99dffbe 100644
--- a/components/resources/terms/terms_ja.html
+++ b/components/resources/terms/terms_ja.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome 利用規約</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_kn.html b/components/resources/terms/terms_kn.html
index 740a7e9..19436f3f 100644
--- a/components/resources/terms/terms_kn.html
+++ b/components/resources/terms/terms_kn.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome ಸೇವಾ ನಿಯಮಗಳು</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ko.html b/components/resources/terms/terms_ko.html
index 61ee4d14..35cbd34 100644
--- a/components/resources/terms/terms_ko.html
+++ b/components/resources/terms/terms_ko.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google 크롬 서비스 약관</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_lt.html b/components/resources/terms/terms_lt.html
index 79d890d..fd7dc36 100644
--- a/components/resources/terms/terms_lt.html
+++ b/components/resources/terms/terms_lt.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>„Google Chrome“ paslaugų teikimo sąlygos</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_lv.html b/components/resources/terms/terms_lv.html
index e34eed73..e315109 100644
--- a/components/resources/terms/terms_lv.html
+++ b/components/resources/terms/terms_lv.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome pakalpojumu sniegšanas noteikumi</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ml.html b/components/resources/terms/terms_ml.html
index 7a1501a..103041f3 100644
--- a/components/resources/terms/terms_ml.html
+++ b/components/resources/terms/terms_ml.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome സേവനനിബന്ധനകള്‍‌</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_mr.html b/components/resources/terms/terms_mr.html
index 3b05d5c0..3fc4eb0 100644
--- a/components/resources/terms/terms_mr.html
+++ b/components/resources/terms/terms_mr.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome सेवा अटी</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_nb.html b/components/resources/terms/terms_nb.html
index fb8b09d..5981a39 100644
--- a/components/resources/terms/terms_nb.html
+++ b/components/resources/terms/terms_nb.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Vilkår for bruk av Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_nl.html b/components/resources/terms/terms_nl.html
index 3054a67..2ed125b 100644
--- a/components/resources/terms/terms_nl.html
+++ b/components/resources/terms/terms_nl.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome - Servicevoorwaarden</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_pl.html b/components/resources/terms/terms_pl.html
index 756fd29a..f2f719c 100644
--- a/components/resources/terms/terms_pl.html
+++ b/components/resources/terms/terms_pl.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome – Warunki korzystania z usługi</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_pt-BR.html b/components/resources/terms/terms_pt-BR.html
index f823b88..4a8bf3b 100644
--- a/components/resources/terms/terms_pt-BR.html
+++ b/components/resources/terms/terms_pt-BR.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Termos de Serviço do Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_pt-PT.html b/components/resources/terms/terms_pt-PT.html
index feda817..bdde37c0 100644
--- a/components/resources/terms/terms_pt-PT.html
+++ b/components/resources/terms/terms_pt-PT.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Termos de utilização do Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ro.html b/components/resources/terms/terms_ro.html
index d27a1cf0..79df577e 100644
--- a/components/resources/terms/terms_ro.html
+++ b/components/resources/terms/terms_ro.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Termeni şi condiţii Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_ru.html b/components/resources/terms/terms_ru.html
index 46bd51e..a4e1b89 100644
--- a/components/resources/terms/terms_ru.html
+++ b/components/resources/terms/terms_ru.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Условия предоставления услуг Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_sk.html b/components/resources/terms/terms_sk.html
index 31cfe62..70f76af8 100644
--- a/components/resources/terms/terms_sk.html
+++ b/components/resources/terms/terms_sk.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome – Zmluvné podmienky</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_sl.html b/components/resources/terms/terms_sl.html
index 09ba674..1c2ef68f 100644
--- a/components/resources/terms/terms_sl.html
+++ b/components/resources/terms/terms_sl.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Pogoji storitve Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_sr.html b/components/resources/terms/terms_sr.html
index b48a5da..a7db88e97 100644
--- a/components/resources/terms/terms_sr.html
+++ b/components/resources/terms/terms_sr.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome услови коришћења услуге</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_sv.html b/components/resources/terms/terms_sv.html
index 588b668..0922201 100644
--- a/components/resources/terms/terms_sv.html
+++ b/components/resources/terms/terms_sv.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Användarvillkor för Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_sw.html b/components/resources/terms/terms_sw.html
index 8b95b79..99e33ee 100644
--- a/components/resources/terms/terms_sw.html
+++ b/components/resources/terms/terms_sw.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Sheria na Masharti ya Google Chrome</title>
 <style>
+:root { color-scheme: light dark; }
 body { font-family:Arial; font-size:13px; }
 </style>
 </head>
diff --git a/components/resources/terms/terms_ta.html b/components/resources/terms/terms_ta.html
index d691a51..94aceeab 100644
--- a/components/resources/terms/terms_ta.html
+++ b/components/resources/terms/terms_ta.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome சேவை விதிமுறைகள்</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_te.html b/components/resources/terms/terms_te.html
index 08fed586..bd54510 100644
--- a/components/resources/terms/terms_te.html
+++ b/components/resources/terms/terms_te.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome సేవా నిబంధనలు</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_th.html b/components/resources/terms/terms_th.html
index 16234202..14fc430 100644
--- a/components/resources/terms/terms_th.html
+++ b/components/resources/terms/terms_th.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>ข้อกำหนดในการให้บริการของ Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_tr.html b/components/resources/terms/terms_tr.html
index 6670f3f..c4361bd 100644
--- a/components/resources/terms/terms_tr.html
+++ b/components/resources/terms/terms_tr.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google Chrome Hizmet Şartları</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_uk.html b/components/resources/terms/terms_uk.html
index 871deafe..a042d27 100644
--- a/components/resources/terms/terms_uk.html
+++ b/components/resources/terms/terms_uk.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Загальні положення та умови Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_vi.html b/components/resources/terms/terms_vi.html
index 632e66d..20d70be 100644
--- a/components/resources/terms/terms_vi.html
+++ b/components/resources/terms/terms_vi.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Ðiều khoản Dịch vụ của Google Chrome</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_zh-CN.html b/components/resources/terms/terms_zh-CN.html
index 15fa16f..b527cb59 100644
--- a/components/resources/terms/terms_zh-CN.html
+++ b/components/resources/terms/terms_zh-CN.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>谷歌浏览器服务条款</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/resources/terms/terms_zh-TW.html b/components/resources/terms/terms_zh-TW.html
index ac039ce..4c633438 100644
--- a/components/resources/terms/terms_zh-TW.html
+++ b/components/resources/terms/terms_zh-TW.html
@@ -6,6 +6,7 @@
 <meta name="viewport" content="initial-scale=1.0">
 <title>Google 瀏覽器服務條款</title>
 <style>
+:root { color-scheme: light dark }
 body { font-family:Arial; font-size:13px; }
 h2 { font-size:1em; margin-top:0 }
 </style>
diff --git a/components/safe_browsing/android/remote_database_manager.cc b/components/safe_browsing/android/remote_database_manager.cc
index 7b004773..9976bfa 100644
--- a/components/safe_browsing/android/remote_database_manager.cc
+++ b/components/safe_browsing/android/remote_database_manager.cc
@@ -120,7 +120,7 @@
   if (ints_str.empty()) {
     // By default, we check all types except a few.
     static_assert(content::ResourceType::kMaxValue ==
-                      content::ResourceType::kNavigationPreload,
+                      content::ResourceType::kNavigationPreloadSubFrame,
                   "Decide if new resource type should be skipped on mobile.");
     for (int t_int = 0;
          t_int <= static_cast<int>(content::ResourceType::kMaxValue); t_int++) {
diff --git a/components/safe_browsing/renderer/websocket_sb_handshake_throttle.h b/components/safe_browsing/renderer/websocket_sb_handshake_throttle.h
index c6f0487..cfdfcaf 100644
--- a/components/safe_browsing/renderer/websocket_sb_handshake_throttle.h
+++ b/components/safe_browsing/renderer/websocket_sb_handshake_throttle.h
@@ -15,7 +15,6 @@
 #include "base/time/time.h"
 #include "components/safe_browsing/common/safe_browsing.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
-#include "third_party/blink/public/platform/web_callbacks.h"
 #include "third_party/blink/public/platform/websocket_handshake_throttle.h"
 #include "url/gurl.h"
 
diff --git a/components/search_engines/template_url_fetcher.cc b/components/search_engines/template_url_fetcher.cc
index d136273..40b4abb7 100644
--- a/components/search_engines/template_url_fetcher.cc
+++ b/components/search_engines/template_url_fetcher.cc
@@ -19,6 +19,18 @@
 
 namespace {
 
+// In some network environments, silent failure can be avoided by retrying
+// request on network change. This helps OpenSearch get through in such cases.
+// See https://crbug.com/956689 for context.
+constexpr int kOpenSearchRetryCount = 3;
+
+// Timeout for OpenSearch description document (OSDD) fetch request.
+// Requests for a particular resource are limited to one.
+// Requests may not receive a response, and in that case no
+// further requests would be allowed. The timeout cleans up failed requests
+// so that later attempts to fetch the OSDD can be made.
+constexpr int kOpenSearchTimeoutSeconds = 30;
+
 // Traffic annotation for RequestDelegate.
 const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("open_search", R"(
@@ -116,6 +128,10 @@
   simple_url_loader_ = network::SimpleURLLoader::Create(
       std::move(resource_request), kTrafficAnnotation);
   simple_url_loader_->SetAllowHttpErrorResults(true);
+  simple_url_loader_->SetTimeoutDuration(
+      base::TimeDelta::FromSeconds(kOpenSearchTimeoutSeconds));
+  simple_url_loader_->SetRetryOptions(
+      kOpenSearchRetryCount, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
   simple_url_loader_->DownloadToString(
       url_loader_factory,
       base::BindOnce(
diff --git a/components/signin/internal/identity_manager/device_accounts_synchronizer_impl.cc b/components/signin/internal/identity_manager/device_accounts_synchronizer_impl.cc
index 4e234eaf..87e00c3 100644
--- a/components/signin/internal/identity_manager/device_accounts_synchronizer_impl.cc
+++ b/components/signin/internal/identity_manager/device_accounts_synchronizer_impl.cc
@@ -18,13 +18,12 @@
 DeviceAccountsSynchronizerImpl::~DeviceAccountsSynchronizerImpl() = default;
 
 void DeviceAccountsSynchronizerImpl::ReloadAllAccountsFromSystem() {
-  token_service_delegate_->ReloadAccountsFromSystem(
-      /*primary_account_id=*/CoreAccountId());
+  token_service_delegate_->ReloadAllAccountsFromSystem();
 }
 
 void DeviceAccountsSynchronizerImpl::ReloadAccountFromSystem(
     const CoreAccountId& account_id) {
-  token_service_delegate_->AddAccountFromSystem(account_id);
+  token_service_delegate_->ReloadAccountFromSystem(account_id);
 }
 
 }  // namespace identity
diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
index f5cdef5d0..5780184c 100644
--- a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
+++ b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
@@ -49,10 +49,9 @@
   // Subsequent calls to |RefreshTokenIsAvailable| will return |false|.
   void RevokeAllCredentials() override;
 
-  void AddAccountFromSystem(const CoreAccountId& account_id) override;
+  void ReloadAllAccountsFromSystem() override;
 
-  void ReloadAccountsFromSystem(
-      const CoreAccountId& primary_account_id) override;
+  void ReloadAccountFromSystem(const CoreAccountId& account_id) override;
 
   // Adds |account_id| to |accounts_| if it does not exist or udpates
   // the auth error state of |account_id| if it exists. Fires
diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
index 8e2cb1f..dca7adf 100644
--- a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
+++ b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
@@ -266,14 +266,13 @@
   ClearExcludedSecondaryAccounts();
 }
 
-void ProfileOAuth2TokenServiceIOSDelegate::AddAccountFromSystem(
-    const CoreAccountId& account_id) {
-  AddOrUpdateAccount(account_id);
+void ProfileOAuth2TokenServiceIOSDelegate::ReloadAllAccountsFromSystem() {
+  ReloadCredentials();
 }
 
-void ProfileOAuth2TokenServiceIOSDelegate::ReloadAccountsFromSystem(
-    const CoreAccountId& /* ignored */) {
-  ReloadCredentials();
+void ProfileOAuth2TokenServiceIOSDelegate::ReloadAccountFromSystem(
+    const CoreAccountId& account_id) {
+  AddOrUpdateAccount(account_id);
 }
 
 std::unique_ptr<OAuth2AccessTokenFetcher>
diff --git a/components/tracing/common/trace_startup_config.cc b/components/tracing/common/trace_startup_config.cc
index cdb0a32..babe9cc1 100644
--- a/components/tracing/common/trace_startup_config.cc
+++ b/components/tracing/common/trace_startup_config.cc
@@ -31,9 +31,6 @@
 const size_t kTraceConfigFileSizeLimit = 64 * 1024;
 const int kDefaultStartupDuration = 5;
 
-// 95th percentile size of current startup traces size uploaded.
-const size_t kMaxStartupTraceSizeInKb = 300;
-
 // Trace config file path:
 // - Android: /data/local/chrome-trace-config.json
 // - Others: specified by --trace-config-file flag.
@@ -42,7 +39,8 @@
     FILE_PATH_LITERAL("/data/local/chrome-trace-config.json");
 
 const char kDefaultStartupCategories[] =
-    "startup,browser,toplevel,EarlyJava,cc,Java,navigation,loading,gpu,"
+    "startup,browser,toplevel,disabled-by-default-toplevel.flow,ipc,disabled-"
+    "by-default-ipc.flow,EarlyJava,cc,Java,navigation,loading,gpu,"
     "disabled-by-default-cpu_profiler,download_service,-*";
 #else
 const char kDefaultStartupCategories[] =
@@ -74,7 +72,6 @@
       {base::GetCurrentProcId()});
   // First 10k events at start are sufficient to debug startup traces.
   trace_config.SetTraceBufferSizeInEvents(10000);
-  trace_config.SetTraceBufferSizeInKb(kMaxStartupTraceSizeInKb);
   trace_config.SetProcessFilterConfig(process_config);
   // Enable argument filter since we could be background tracing.
   trace_config.EnableArgumentFilter();
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc
index 8a83711..d3e8a48 100644
--- a/components/translate/core/browser/translate_manager.cc
+++ b/components/translate/core/browser/translate_manager.cc
@@ -283,12 +283,13 @@
         TranslateErrors::IDENTICAL_LANGUAGES, triggered_from_menu);
     NotifyTranslateError(TranslateErrors::IDENTICAL_LANGUAGES);
     return;
+  } else {
+    // Trigger the "translating now" UI.
+    translate_client_->ShowTranslateUI(
+        translate::TRANSLATE_STEP_TRANSLATING, source_lang, target_lang,
+        TranslateErrors::NONE, triggered_from_menu);
   }
 
-  translate_client_->ShowTranslateUI(
-      translate::TRANSLATE_STEP_TRANSLATING, source_lang, target_lang,
-      TranslateErrors::NONE, triggered_from_menu);
-
   TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
   DCHECK(script != nullptr);
 
diff --git a/components/translate/core/browser/translate_prefs.cc b/components/translate/core/browser/translate_prefs.cc
index edd414b..42858fc 100644
--- a/components/translate/core/browser/translate_prefs.cc
+++ b/components/translate/core/browser/translate_prefs.cc
@@ -83,13 +83,7 @@
                                  base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kTranslateMobileManualTrigger{
-  "TranslateAndroidManualTrigger",
-#if defined(OS_IOS)
-      base::FEATURE_DISABLED_BY_DEFAULT
-#else
-      base::FEATURE_ENABLED_BY_DEFAULT
-#endif
-};
+    "TranslateAndroidManualTrigger", base::FEATURE_ENABLED_BY_DEFAULT};
 
 DenialTimeUpdate::DenialTimeUpdate(PrefService* prefs,
                                    const std::string& language,
diff --git a/components/ui_devtools/css_agent.cc b/components/ui_devtools/css_agent.cc
index 10b88b2c..37e9a10 100644
--- a/components/ui_devtools/css_agent.cc
+++ b/components/ui_devtools/css_agent.cc
@@ -32,6 +32,15 @@
       .build();
 }
 
+std::unique_ptr<protocol::Array<int>> BuildDefaultMatchingSelectors() {
+  auto matching_selectors = std::make_unique<protocol::Array<int>>();
+
+  // Add index 0 to matching delectors array, so frontend uses the class mame
+  // from the selectors array as the header for the properties section
+  matching_selectors->emplace_back(0);
+  return matching_selectors;
+}
+
 std::unique_ptr<CSS::CSSProperty> BuildCSSProperty(const std::string& name,
                                                    int value) {
   return CSS::CSSProperty::create()
@@ -60,6 +69,16 @@
   return cssProperties;
 }
 
+std::unique_ptr<Array<CSS::CSSProperty>> BuildCSSProperties(
+    const std::vector<UIElement::UIProperty>& properties_vector) {
+  auto css_properties = std::make_unique<Array<protocol::CSS::CSSProperty>>();
+  for (const auto& property : properties_vector) {
+    css_properties->emplace_back(
+        BuildCSSProperty(property.name_, property.value_));
+  }
+  return css_properties;
+}
+
 std::unique_ptr<CSS::CSSStyle> BuildCSSStyle(UIElement* ui_element) {
   gfx::Rect bounds;
   ui_element->GetBounds(&bounds);
@@ -86,6 +105,77 @@
       .build();
 }
 
+std::unique_ptr<CSS::CSSStyle> BuildCSSStyle(
+    std::string stylesheet_uid,
+    const std::vector<UIElement::UIProperty>& properties) {
+  return protocol::CSS::CSSStyle::create()
+      .setRange(BuildDefaultSourceRange())
+      .setCssProperties(BuildCSSProperties(properties))
+      .setShorthandEntries(
+          std::make_unique<Array<protocol::CSS::ShorthandEntry>>())
+      .setStyleSheetId(stylesheet_uid)
+      .build();
+}
+
+std::unique_ptr<protocol::Array<protocol::CSS::Value>> BuildSelectors(
+    const std::string& name) {
+  auto selectors = std::make_unique<protocol::Array<protocol::CSS::Value>>();
+  selectors->emplace_back(protocol::CSS::Value::create().setText(name).build());
+  return selectors;
+}
+
+std::unique_ptr<protocol::CSS::SelectorList> BuildSelectorList(
+    const std::string& name) {
+  return protocol::CSS::SelectorList::create()
+      .setSelectors(BuildSelectors(name))
+      .build();
+}
+
+std::unique_ptr<protocol::CSS::CSSRule> BuildCSSRule(
+    std::string stylesheet_uid,
+    const UIElement::ClassProperties& class_properties) {
+  return protocol::CSS::CSSRule::create()
+      .setSelectorList(BuildSelectorList(class_properties.class_name_))
+      .setStyle(BuildCSSStyle(stylesheet_uid, class_properties.properties_))
+      .build();
+}
+
+std::vector<UIElement::ClassProperties> GetClassPropertiesWithBounds(
+    UIElement* ui_element) {
+  std::vector<UIElement::ClassProperties> properties_vector =
+      ui_element->GetCustomPropertiesForMatchedStyle();
+
+  // If GetCustomPropertiesForMatchedStyle not overridden to return custom
+  // properties, populate vector with bounds properties.
+  if (properties_vector.empty()) {
+    gfx::Rect bounds;
+    ui_element->GetBounds(&bounds);
+    std::vector<UIElement::UIProperty> bound_properties;
+    bound_properties.emplace_back(kX, base::NumberToString(bounds.x()));
+    bound_properties.emplace_back(kY, base::NumberToString(bounds.y()));
+    bound_properties.emplace_back(kWidth, base::NumberToString(bounds.width()));
+    bound_properties.emplace_back(kHeight,
+                                  base::NumberToString(bounds.height()));
+    if (ui_element->type() != VIEW) {
+      bool visible;
+      ui_element->GetVisible(&visible);
+      bound_properties.emplace_back(kVisibility, visible ? "true" : "false");
+    }
+    properties_vector.emplace_back(ui_element->GetTypeName(), bound_properties);
+  }
+
+  // Set base stylesheet ID to the last index in the vector, so when bounds
+  // properties are modified, CSSAgent can update and return the right
+  // properties section
+  ui_element->SetBaseStylesheetId(properties_vector.size() - 1);
+  return properties_vector;
+}
+
+std::string BuildStylesheetUId(int node_id, int stylesheet_id) {
+  return base::NumberToString(node_id) + "_" +
+         base::NumberToString(stylesheet_id);
+}
+
 Response NodeNotFoundError(int node_id) {
   return Response::Error("Node with id=" + std::to_string(node_id) +
                          " not found");
@@ -138,12 +228,14 @@
   return Response::OK();
 }
 
-Response CSSAgent::getMatchedStylesForNode(int node_id,
-                                           Maybe<CSS::CSSStyle>* inline_style) {
+Response CSSAgent::getMatchedStylesForNode(
+    int node_id,
+    protocol::Maybe<protocol::Array<protocol::CSS::RuleMatch>>*
+        matched_css_rules) {
   UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id);
-  *inline_style = GetStylesForUIElement(ui_element);
-  if (!inline_style)
+  if (!ui_element)
     return NodeNotFoundError(node_id);
+  *matched_css_rules = BuildMatchedStyles(ui_element);
   return Response::OK();
 }
 
@@ -153,10 +245,19 @@
   auto updated_styles = std::make_unique<Array<CSS::CSSStyle>>();
   for (const auto& edit : *edits) {
     int node_id;
-    if (!base::StringToInt(edit->getStyleSheetId(), &node_id))
-      return Response::Error("Invalid node id");
+    int stylesheet_id;
+
+    std::vector<std::string> ids =
+        base::SplitString(edit->getStyleSheetId(), "_", base::TRIM_WHITESPACE,
+                          base::SPLIT_WANT_NONEMPTY);
+    if (ids.size() < 2 || !base::StringToInt(ids[0], &node_id) ||
+        !base::StringToInt(ids[1], &stylesheet_id))
+      return Response::Error("Invalid stylesheet id");
 
     UIElement* ui_element = dom_agent_->GetElementFromNodeId(node_id);
+
+    if (!ui_element)
+      return Response::Error("Node id not found");
     // Handle setting properties from metadata for View.
     if (ui_element->type() == VIEW)
       ui_element->SetPropertiesFromString(edit->getText());
@@ -174,7 +275,10 @@
     if (!SetPropertiesForUIElement(ui_element, updated_bounds, visible))
       return NodeNotFoundError(node_id);
 
-    updated_styles->emplace_back(BuildCSSStyle(ui_element));
+    updated_styles->emplace_back(BuildCSSStyle(
+        edit->getStyleSheetId(), GetClassPropertiesWithBounds(ui_element)
+                                     .at(stylesheet_id)
+                                     .properties_));
   }
   *result = std::move(updated_styles);
   return Response::OK();
@@ -194,8 +298,10 @@
 }
 
 void CSSAgent::InvalidateStyleSheet(UIElement* ui_element) {
-  // The stylesheetId for each node is equivalent to its node_id (as a string).
-  frontend()->styleSheetChanged(base::NumberToString(ui_element->node_id()));
+  // The stylesheetId for each node is equivalent to a string of its
+  // node_id + "_" + index of CSS::RuleMatch in vector.
+  frontend()->styleSheetChanged(BuildStylesheetUId(
+      ui_element->node_id(), ui_element->GetBaseStylesheetId()));
 }
 
 bool CSSAgent::GetPropertiesForUIElement(UIElement* ui_element,
@@ -221,4 +327,22 @@
   return false;
 }
 
+std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>>
+CSSAgent::BuildMatchedStyles(UIElement* ui_element) {
+  auto result = std::make_unique<protocol::Array<protocol::CSS::RuleMatch>>();
+  std::vector<UIElement::ClassProperties> properties_vector =
+      GetClassPropertiesWithBounds(ui_element);
+
+  for (size_t i = 0; i < properties_vector.size(); i++) {
+    result->emplace_back(
+        protocol::CSS::RuleMatch::create()
+            .setRule(BuildCSSRule(BuildStylesheetUId(ui_element->node_id(), i),
+                                  properties_vector[i]))
+            .setMatchingSelectors(BuildDefaultMatchingSelectors())
+            .build());
+  }
+
+  return result;
+}
+
 }  // namespace ui_devtools
diff --git a/components/ui_devtools/css_agent.h b/components/ui_devtools/css_agent.h
index 1b651d2..b4b4ac9 100644
--- a/components/ui_devtools/css_agent.h
+++ b/components/ui_devtools/css_agent.h
@@ -29,7 +29,8 @@
   protocol::Response disable() override;
   protocol::Response getMatchedStylesForNode(
       int node_id,
-      protocol::Maybe<protocol::CSS::CSSStyle>* inline_style) override;
+      protocol::Maybe<protocol::Array<protocol::CSS::RuleMatch>>*
+          matched_css_rules) override;
   protocol::Response setStyleTexts(
       std::unique_ptr<protocol::Array<protocol::CSS::StyleDeclarationEdit>>
           edits,
@@ -49,6 +50,9 @@
   bool SetPropertiesForUIElement(UIElement* ui_element,
                                  const gfx::Rect& bounds,
                                  bool visible);
+  std::unique_ptr<protocol::Array<protocol::CSS::RuleMatch>> BuildMatchedStyles(
+      UIElement* ui_element);
+
   DOMAgent* const dom_agent_;
 
   DISALLOW_COPY_AND_ASSIGN(CSSAgent);
diff --git a/components/ui_devtools/protocol.json b/components/ui_devtools/protocol.json
index df34c0ef..b030b48 100644
--- a/components/ui_devtools/protocol.json
+++ b/components/ui_devtools/protocol.json
@@ -261,10 +261,13 @@
                     ],
                     "returns": [
                         {
-                            "name": "inlineStyle",
-                            "$ref": "CSSStyle",
+                            "name": "matchedCSSRules",
+                            "type": "array",
+                            "items": {
+                                "$ref": "RuleMatch"
+                            },
                             "optional": true,
-                            "description": "Inline style for the specified DOM node."
+                            "description": "CSS rules matching this node, from all applicable stylesheets."
                         }
                     ]
                 },
@@ -718,4 +721,4 @@
         "major": "1",
         "minor": "3"
     }
-}
+}
\ No newline at end of file
diff --git a/components/ui_devtools/ui_element.cc b/components/ui_devtools/ui_element.cc
index 2802951..4b964d9 100644
--- a/components/ui_devtools/ui_element.cc
+++ b/components/ui_devtools/ui_element.cc
@@ -16,6 +16,16 @@
 
 }  // namespace
 
+UIElement::ClassProperties::ClassProperties(
+    std::string class_name,
+    std::vector<UIElement::UIProperty> properties)
+    : class_name_(class_name), properties_(properties) {}
+
+UIElement::ClassProperties::ClassProperties(
+    const UIElement::ClassProperties& other) = default;
+
+UIElement::ClassProperties::~ClassProperties() = default;
+
 // static
 void UIElement::ResetNodeId() {
   node_ids = 0;
@@ -107,6 +117,11 @@
   return 0;
 }
 
+std::vector<UIElement::ClassProperties>
+UIElement::GetCustomPropertiesForMatchedStyle() const {
+  return {};
+}
+
 UIElement::UIElement(const UIElementType type,
                      UIElementDelegate* delegate,
                      UIElement* parent)
diff --git a/components/ui_devtools/ui_element.h b/components/ui_devtools/ui_element.h
index ecc8a4cd..214c967 100644
--- a/components/ui_devtools/ui_element.h
+++ b/components/ui_devtools/ui_element.h
@@ -24,6 +24,22 @@
 
 class UI_DEVTOOLS_EXPORT UIElement {
  public:
+  struct UI_DEVTOOLS_EXPORT UIProperty {
+    UIProperty(std::string name, std::string value)
+        : name_(name), value_(value) {}
+
+    std::string name_;
+    std::string value_;
+  };
+  struct UI_DEVTOOLS_EXPORT ClassProperties {
+    ClassProperties(std::string name, std::vector<UIProperty> properties);
+    ClassProperties(const ClassProperties& copy);
+    ~ClassProperties();
+
+    std::string class_name_;
+    std::vector<UIProperty> properties_;
+  };
+
   using UIElements = std::vector<UIElement*>;
 
   // resets node ids to 0 so that they are reusable
@@ -40,6 +56,8 @@
   bool is_updating() const { return is_updating_; }
   void set_is_updating(bool is_updating) { is_updating_ = is_updating; }
   void set_owns_children(bool owns_children) { owns_children_ = owns_children; }
+  int GetBaseStylesheetId() const { return base_stylesheet_id_; }
+  void SetBaseStylesheetId(int id) { base_stylesheet_id_ = id; }
 
   using ElementCompare = bool (*)(const UIElement*, const UIElement*);
 
@@ -73,6 +91,10 @@
   virtual std::vector<std::pair<std::string, std::string>> GetCustomProperties()
       const = 0;
 
+  // Returns properties grouped by the class they are from.
+  virtual std::vector<ClassProperties> GetCustomPropertiesForMatchedStyle()
+      const;
+
   virtual void GetBounds(gfx::Rect* bounds) const = 0;
   virtual void SetBounds(const gfx::Rect& bounds) = 0;
   virtual void GetVisible(bool* visible) const = 0;
@@ -113,6 +135,7 @@
   UIElementDelegate* delegate_;
   bool is_updating_ = false;
   bool owns_children_ = true;
+  int base_stylesheet_id_;
 
   DISALLOW_COPY_AND_ASSIGN(UIElement);
 };
diff --git a/components/ui_devtools/views/element_utility.cc b/components/ui_devtools/views/element_utility.cc
index ddc9a4cf..f315b4cf 100644
--- a/components/ui_devtools/views/element_utility.cc
+++ b/components/ui_devtools/views/element_utility.cc
@@ -9,6 +9,7 @@
 
 namespace ui_devtools {
 
+// TODO(khamasaki): Remove AppendLayerProperties.
 void AppendLayerProperties(
     const ui::Layer* layer,
     std::vector<std::pair<std::string, std::string>>* ret) {
@@ -49,4 +50,45 @@
   }
 }
 
+void AppendLayerPropertiesMatchedStyle(
+    const ui::Layer* layer,
+    std::vector<UIElement::UIProperty>* ret) {
+  ret->emplace_back("layer-type",
+                    std::string(LayerTypeToString(layer->type())));
+  ret->emplace_back("has-layer-mask",
+                    layer->layer_mask_layer() ? "true" : "false");
+  ret->emplace_back("layer-opacity", base::NumberToString((layer->opacity())));
+  ret->emplace_back("layer-combined-opacity",
+                    base::NumberToString(layer->GetCombinedOpacity()));
+  ret->emplace_back("background-blur",
+                    base::NumberToString(layer->background_blur()));
+  ret->emplace_back("layer-blur", base::NumberToString(layer->layer_blur()));
+  ret->emplace_back("layer-saturation",
+                    base::NumberToString(layer->layer_saturation()));
+  ret->emplace_back("layer-brightness",
+                    base::NumberToString(layer->layer_brightness()));
+  ret->emplace_back("layer-grayscale",
+                    base::NumberToString(layer->layer_grayscale()));
+  const ui::Layer::ShapeRects* alpha_shape_bounds = layer->alpha_shape();
+  if (alpha_shape_bounds && alpha_shape_bounds->size()) {
+    gfx::Rect bounding_box;
+    for (auto& shape_bound : *alpha_shape_bounds)
+      bounding_box.Union(shape_bound);
+    ret->emplace_back("alpha-shape-bounding-box", bounding_box.ToString());
+  }
+  const cc::Layer* cc_layer = layer->cc_layer_for_testing();
+  if (cc_layer) {
+    // Property trees must be updated in order to get valid render surface
+    // reasons.
+    if (!cc_layer->layer_tree_host() ||
+        cc_layer->layer_tree_host()->property_trees()->needs_rebuild)
+      return;
+    cc::RenderSurfaceReason render_surface = cc_layer->GetRenderSurfaceReason();
+    if (render_surface != cc::RenderSurfaceReason::kNone) {
+      ret->emplace_back("render-surface-reason",
+                        cc::RenderSurfaceReasonToString(render_surface));
+    }
+  }
+}
+
 }  // namespace ui_devtools
diff --git a/components/ui_devtools/views/element_utility.h b/components/ui_devtools/views/element_utility.h
index d6697d4..b80a628 100644
--- a/components/ui_devtools/views/element_utility.h
+++ b/components/ui_devtools/views/element_utility.h
@@ -8,6 +8,8 @@
 #include <string>
 #include <vector>
 
+#include "components/ui_devtools/ui_element.h"
+
 namespace ui {
 class Layer;
 }
@@ -23,6 +25,9 @@
     const ui::Layer* layer,
     std::vector<std::pair<std::string, std::string>>* ret);
 
+void AppendLayerPropertiesMatchedStyle(const ui::Layer* layer,
+                                       std::vector<UIElement::UIProperty>* ret);
+
 }  // namespace ui_devtools
 
 #endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_ELEMENT_UTILITY_H_
diff --git a/components/ui_devtools/views/view_element.cc b/components/ui_devtools/views/view_element.cc
index 2fc9ae2..fbad7fc 100644
--- a/components/ui_devtools/views/view_element.cc
+++ b/components/ui_devtools/views/view_element.cc
@@ -83,6 +83,49 @@
   return ret;
 }
 
+std::vector<UIElement::ClassProperties>
+ViewElement::GetCustomPropertiesForMatchedStyle() const {
+  std::vector<UIElement::ClassProperties> ret;
+
+  ui::Layer* layer = view_->layer();
+  if (layer) {
+    std::vector<UIElement::UIProperty> layer_properties;
+    AppendLayerPropertiesMatchedStyle(layer, &layer_properties);
+    ret.emplace_back("Layer", layer_properties);
+  }
+
+  std::vector<UIElement::UIProperty> class_properties;
+  views::metadata::ClassMetaData* metadata = view_->GetClassMetaData();
+  for (auto member = metadata->begin(); member != metadata->end(); member++) {
+    if (member.GetCurrentCollectionName() == "View" &&
+        class_properties.empty()) {
+      gfx::Rect bounds = view_->bounds();
+      class_properties.emplace_back("x", base::NumberToString(bounds.x()));
+      class_properties.emplace_back("y", base::NumberToString(bounds.y()));
+      class_properties.emplace_back("width",
+                                    base::NumberToString(bounds.width()));
+      class_properties.emplace_back("height",
+                                    base::NumberToString(bounds.height()));
+      class_properties.emplace_back("is-drawn",
+                                    view_->IsDrawn() ? "true" : "false");
+      base::string16 description = view_->GetTooltipText(gfx::Point());
+      if (!description.empty())
+        class_properties.emplace_back("tooltip",
+                                      base::UTF16ToUTF8(description));
+    }
+
+    class_properties.emplace_back(
+        (*member)->member_name(),
+        base::UTF16ToUTF8((*member)->GetValueAsString(view_)));
+
+    if (member.IsLastMember()) {
+      ret.emplace_back(member.GetCurrentCollectionName(), class_properties);
+      class_properties.clear();
+    }
+  }
+  return ret;
+}
+
 void ViewElement::GetBounds(gfx::Rect* bounds) const {
   *bounds = view_->bounds();
 }
diff --git a/components/ui_devtools/views/view_element.h b/components/ui_devtools/views/view_element.h
index afe1b4f..acc3313 100644
--- a/components/ui_devtools/views/view_element.h
+++ b/components/ui_devtools/views/view_element.h
@@ -33,6 +33,8 @@
   // UIElement:
   std::vector<std::pair<std::string, std::string>> GetCustomProperties()
       const override;
+  std::vector<UIElement::ClassProperties> GetCustomPropertiesForMatchedStyle()
+      const override;
   void GetBounds(gfx::Rect* bounds) const override;
   void SetBounds(const gfx::Rect& bounds) override;
   void GetVisible(bool* visible) const override;
diff --git a/components/ui_devtools/views/window_element.cc b/components/ui_devtools/views/window_element.cc
index 649a5a0..d6cd15d 100644
--- a/components/ui_devtools/views/window_element.cc
+++ b/components/ui_devtools/views/window_element.cc
@@ -87,6 +87,43 @@
   return ret;
 }
 
+std::vector<UIElement::ClassProperties>
+WindowElement::GetCustomPropertiesForMatchedStyle() const {
+  std::vector<UIElement::ClassProperties> ret;
+  std::vector<UIElement::UIProperty> cur_properties;
+
+  ui::Layer* layer = window_->layer();
+  if (layer) {
+    AppendLayerPropertiesMatchedStyle(layer, &cur_properties);
+    ret.emplace_back("Layer", cur_properties);
+    cur_properties.clear();
+  }
+
+  gfx::Rect bounds;
+  GetBounds(&bounds);
+  cur_properties.emplace_back("x", base::NumberToString(bounds.x()));
+  cur_properties.emplace_back("y", base::NumberToString(bounds.y()));
+  cur_properties.emplace_back("width", base::NumberToString(bounds.width()));
+  cur_properties.emplace_back("height", base::NumberToString(bounds.height()));
+
+  std::string state_str =
+      aura::Window::OcclusionStateToString(window_->occlusion_state());
+  // change OcclusionState::UNKNOWN to UNKNOWN
+  state_str = state_str.substr(state_str.find("::") + 2);
+  cur_properties.emplace_back("occlusion-state", state_str);
+  cur_properties.emplace_back("surface",
+                              window_->GetSurfaceId().is_valid()
+                                  ? window_->GetSurfaceId().ToString()
+                                  : "none");
+  cur_properties.emplace_back("capture",
+                              window_->HasCapture() ? "true" : "false");
+  cur_properties.emplace_back(
+      "is-activatable", wm::CanActivateWindow(window_) ? "true" : "false");
+
+  ret.emplace_back("Window", cur_properties);
+  return ret;
+}
+
 void WindowElement::GetBounds(gfx::Rect* bounds) const {
   *bounds = window_->bounds();
 }
diff --git a/components/ui_devtools/views/window_element.h b/components/ui_devtools/views/window_element.h
index 20b375c..9b063624 100644
--- a/components/ui_devtools/views/window_element.h
+++ b/components/ui_devtools/views/window_element.h
@@ -36,6 +36,8 @@
   // UIElement:
   std::vector<std::pair<std::string, std::string>> GetCustomProperties()
       const override;
+  std::vector<UIElement::ClassProperties> GetCustomPropertiesForMatchedStyle()
+      const override;
   void GetBounds(gfx::Rect* bounds) const override;
   void SetBounds(const gfx::Rect& bounds) override;
   void GetVisible(bool* visible) const override;
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 3f6def06..e8981817 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -314,8 +314,6 @@
     "display_embedder/skia_output_surface_dependency_impl.h",
     "display_embedder/skia_output_surface_impl.cc",
     "display_embedder/skia_output_surface_impl.h",
-    "display_embedder/skia_output_surface_impl_non_ddl.cc",
-    "display_embedder/skia_output_surface_impl_non_ddl.h",
     "display_embedder/skia_output_surface_impl_on_gpu.cc",
     "display_embedder/skia_output_surface_impl_on_gpu.h",
     "gl/gpu_service_impl.cc",
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index fd4dbff3..2ca528a 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -789,10 +789,12 @@
 gfx::Rect DirectRenderer::ComputeScissorRectForRenderPass(
     const RenderPass* render_pass) const {
   const RenderPass* root_render_pass = current_frame()->root_render_pass;
-  const gfx::Rect root_damage_rect = current_frame()->root_damage_rect;
+  gfx::Rect root_damage_rect = current_frame()->root_damage_rect;
 
-  if (render_pass == root_render_pass)
+  if (render_pass == root_render_pass) {
+    root_damage_rect.Union(output_surface_->GetCurrentFramebufferDamage());
     return root_damage_rect;
+  }
 
   // If the root damage rect has been expanded due to overlays, all the other
   // damage rect calculations are incorrect.
diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
index 81f92a2..097fdcac 100644
--- a/components/viz/service/display/output_surface.cc
+++ b/components/viz/service/display/output_surface.cc
@@ -16,6 +16,7 @@
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/swap_result.h"
 
 namespace viz {
@@ -35,6 +36,10 @@
 
 OutputSurface::~OutputSurface() = default;
 
+gfx::Rect OutputSurface::GetCurrentFramebufferDamage() const {
+  return gfx::Rect();
+}
+
 SkiaOutputSurface* OutputSurface::AsSkiaOutputSurface() {
   return nullptr;
 }
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index 343e490b..564c4d2 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -26,6 +26,7 @@
 
 namespace gfx {
 class ColorSpace;
+class Rect;
 class Size;
 struct SwapResponse;
 }  // namespace gfx
@@ -136,6 +137,12 @@
   // after returning from this method in order to unblock the next frame.
   virtual void SwapBuffers(OutputSurfaceFrame frame) = 0;
 
+  // Returns a rectangle whose contents may have changed since the current
+  // buffer was last submitted and they need to be redrawn. For partial swap,
+  // the contents outside this rectangle can be considered valid and do not need
+  // to be redrawn.
+  virtual gfx::Rect GetCurrentFramebufferDamage() const;
+
   // Updates the GpuFence associated with this surface. The id of a newly
   // created GpuFence is returned, or if an error occurs, or fences are not
   // supported, the special id of 0 (meaning "no fence") is returned.  In all
diff --git a/components/viz/service/display_embedder/buffer_queue.cc b/components/viz/service/display_embedder/buffer_queue.cc
index e874cd7..7003bbb 100644
--- a/components/viz/service/display_embedder/buffer_queue.cc
+++ b/components/viz/service/display_embedder/buffer_queue.cc
@@ -12,11 +12,8 @@
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
-#include "third_party/skia/include/core/SkRect.h"
-#include "third_party/skia/include/core/SkRegion.h"
 #include "ui/display/types/display_snapshot.h"
 #include "ui/gfx/gpu_memory_buffer.h"
-#include "ui/gfx/skia_util.h"
 
 namespace viz {
 
@@ -52,21 +49,6 @@
   return 0u;
 }
 
-void BufferQueue::CopyBufferDamage(unsigned texture,
-                                   unsigned source_texture,
-                                   const gfx::Rect& new_damage,
-                                   const gfx::Rect& old_damage) {
-  SkRegion region(gfx::RectToSkIRect(old_damage));
-  if (!region.op(gfx::RectToSkIRect(new_damage), SkRegion::kDifference_Op))
-    return;
-  for (SkRegion::Iterator it(region); !it.done(); it.next()) {
-    const SkIRect& rect = it.rect();
-    gl_->CopySubTextureCHROMIUM(
-        source_texture, 0, texture_target_, texture, 0, rect.x(), rect.y(),
-        rect.x(), rect.y(), rect.width(), rect.height(), false, false, false);
-  }
-}
-
 void BufferQueue::UpdateBufferDamage(const gfx::Rect& damage) {
   if (displayed_surface_)
     displayed_surface_->damage.Union(damage);
@@ -78,40 +60,15 @@
   }
 }
 
-void BufferQueue::CopyDamageForCurrentSurface(const gfx::Rect& damage) {
-  if (!current_surface_)
-    return;
-
-  if (damage != gfx::Rect(size_)) {
-    // Copy damage from the most recently swapped buffer. In the event that
-    // the buffer was destroyed and failed to recreate, pick from the most
-    // recently available buffer.
-    unsigned texture_id = 0;
-    for (auto& surface : base::Reversed(in_flight_surfaces_)) {
-      if (surface) {
-        texture_id = surface->texture;
-        break;
-      }
-    }
-    if (!texture_id && displayed_surface_)
-      texture_id = displayed_surface_->texture;
-
-    if (texture_id) {
-      CopyBufferDamage(current_surface_->texture, texture_id, damage,
-                       current_surface_->damage);
-    }
-  }
-  current_surface_->damage = gfx::Rect();
+gfx::Rect BufferQueue::CurrentBufferDamage() const {
+  DCHECK(current_surface_);
+  return current_surface_->damage;
 }
 
 void BufferQueue::SwapBuffers(const gfx::Rect& damage) {
-  if (damage.IsEmpty()) {
-    in_flight_surfaces_.push_back(std::move(current_surface_));
-    return;
-  }
-
-  DCHECK(!current_surface_ || current_surface_->damage.IsEmpty());
   UpdateBufferDamage(damage);
+  if (current_surface_)
+    current_surface_->damage = gfx::Rect();
   in_flight_surfaces_.push_back(std::move(current_surface_));
 }
 
diff --git a/components/viz/service/display_embedder/buffer_queue.h b/components/viz/service/display_embedder/buffer_queue.h
index 4baa6acb..41d28e72 100644
--- a/components/viz/service/display_embedder/buffer_queue.h
+++ b/components/viz/service/display_embedder/buffer_queue.h
@@ -53,10 +53,16 @@
   // current buffer and one could not be created.
   unsigned GetCurrentBuffer(unsigned* stencil);
 
+  // Returns a rectangle whose contents may have changed since the current
+  // buffer was last submitted and they need to be redrawn. For partial swap,
+  // only the contents outside this rectangle can be considered valid and do not
+  // need to be redrawn.
+  gfx::Rect CurrentBufferDamage() const;
+
   // Called by the user of this object to indicate that the buffer currently
-  // marked for drawing should be moved to the list of in-flight buffers. If
-  // |damage| is not empty, the damage rectangle for all buffers except the
-  // one currently marked for drawing is unioned with |damage|.
+  // marked for drawing should be moved to the list of in-flight buffers.
+  // |damage| represents the rectangle containing the damaged area since the
+  // last SwapBuffers.
   void SwapBuffers(const gfx::Rect& damage);
 
   // Called by the user of this object to indicate that a previous request to
@@ -78,10 +84,6 @@
                const gfx::ColorSpace& color_space,
                bool use_stencil);
 
-  // Copies the damage from the most recently swapped available buffer into the
-  // current buffer.
-  void CopyDamageForCurrentSurface(const gfx::Rect& damage);
-
   uint32_t internal_format() const { return internal_format_; }
   gfx::BufferFormat buffer_format() const { return format_; }
   uint32_t texture_target() const { return texture_target_; }
@@ -108,13 +110,6 @@
 
   void FreeSurfaceResources(AllocatedSurface* surface);
 
-  // Copy everything that is in |copy_rect|, except for what is in
-  // |exclude_rect| from |source_texture| to |texture|.
-  virtual void CopyBufferDamage(unsigned texture,
-                                unsigned source_texture,
-                                const gfx::Rect& new_damage,
-                                const gfx::Rect& old_damage);
-
   void UpdateBufferDamage(const gfx::Rect& damage);
 
   // Return a surface, available to be drawn into.
diff --git a/components/viz/service/display_embedder/buffer_queue_unittest.cc b/components/viz/service/display_embedder/buffer_queue_unittest.cc
index a6ecd16..47a709c 100644
--- a/components/viz/service/display_embedder/buffer_queue_unittest.cc
+++ b/components/viz/service/display_embedder/buffer_queue_unittest.cc
@@ -104,20 +104,6 @@
 const unsigned int kBufferQueueInternalformat = GL_RGBA;
 const gfx::BufferFormat kBufferQueueFormat = gfx::BufferFormat::RGBA_8888;
 
-class MockBufferQueue : public BufferQueue {
- public:
-  MockBufferQueue(gpu::gles2::GLES2Interface* gl,
-                  gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
-                  const gpu::Capabilities& capabilities)
-      : BufferQueue(gl,
-                    kBufferQueueFormat,
-                    gpu_memory_buffer_manager,
-                    kFakeSurfaceHandle,
-                    capabilities) {}
-  MOCK_METHOD4(CopyBufferDamage,
-               void(unsigned, unsigned, const gfx::Rect&, const gfx::Rect&));
-};
-
 class BufferQueueTest : public ::testing::Test {
  public:
   BufferQueueTest() {}
@@ -130,9 +116,10 @@
     context_provider_ = TestContextProvider::Create(std::move(context));
     context_provider_->BindToCurrentThread();
     gpu_memory_buffer_manager_.reset(new StubGpuMemoryBufferManager);
-    mock_output_surface_ = new MockBufferQueue(
-        context_provider_->ContextGL(), gpu_memory_buffer_manager_.get(),
-        context_provider_->ContextCapabilities());
+    mock_output_surface_ =
+        new BufferQueue(context_provider_->ContextGL(), kBufferQueueFormat,
+                        gpu_memory_buffer_manager_.get(), kFakeSurfaceHandle,
+                        context_provider_->ContextCapabilities());
     output_surface_.reset(mock_output_surface_);
   }
 
@@ -184,7 +171,6 @@
   }
 
   void SwapBuffers(const gfx::Rect& damage) {
-    output_surface_->CopyDamageForCurrentSurface(damage);
     output_surface_->SwapBuffers(damage);
   }
 
@@ -214,7 +200,7 @@
   scoped_refptr<TestContextProvider> context_provider_;
   std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager_;
   std::unique_ptr<BufferQueue> output_surface_;
-  MockBufferQueue* mock_output_surface_;
+  BufferQueue* mock_output_surface_;
 };
 
 namespace {
@@ -300,43 +286,9 @@
   EXPECT_GT(output_surface->GetCurrentBuffer(&stencil), 0u);
 }
 
-TEST(BufferQueueStandaloneTest, CopyBufferDamage) {
-  scoped_refptr<TestContextProvider> context_provider =
-      TestContextProvider::Create();
-  context_provider->BindToCurrentThread();
-  std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager;
-  std::unique_ptr<BufferQueue> output_surface;
-  gpu_memory_buffer_manager.reset(new StubGpuMemoryBufferManager);
-
-  output_surface.reset(
-      new BufferQueue(context_provider->ContextGL(), kBufferQueueFormat,
-                      gpu_memory_buffer_manager.get(), kFakeSurfaceHandle,
-                      context_provider->ContextCapabilities()));
-  EXPECT_TRUE(
-      output_surface->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false));
-  // Trigger a sub-buffer copy to exercise all paths.
-  unsigned stencil;
-  EXPECT_GT(output_surface->GetCurrentBuffer(&stencil), 0u);
-  output_surface->CopyDamageForCurrentSurface(screen_rect);
-  output_surface->SwapBuffers(screen_rect);
-  output_surface->PageFlipComplete();
-  EXPECT_GT(output_surface->GetCurrentBuffer(&stencil), 0u);
-  output_surface->CopyDamageForCurrentSurface(small_damage);
-  output_surface->SwapBuffers(small_damage);
-}
-
 TEST_F(BufferQueueTest, PartialSwapReuse) {
   EXPECT_TRUE(
       output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false));
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, small_damage, screen_rect))
-      .Times(1);
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, small_damage, small_damage))
-      .Times(1);
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, large_damage, small_damage))
-      .Times(1);
   SendFullFrame();
   SendDamagedFrame(small_damage);
   SendDamagedFrame(small_damage);
@@ -348,9 +300,6 @@
 TEST_F(BufferQueueTest, PartialSwapFullFrame) {
   EXPECT_TRUE(
       output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false));
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, small_damage, screen_rect))
-      .Times(1);
   SendFullFrame();
   SendDamagedFrame(small_damage);
   SendFullFrame();
@@ -358,15 +307,37 @@
   EXPECT_EQ(next_frame()->damage, screen_rect);
 }
 
+// Make sure that each time we swap buffers, the damage gets propagated to the
+// previously swapped buffers.
+TEST_F(BufferQueueTest, PartialSwapWithTripleBuffering) {
+  unsigned stencil = 0;
+  EXPECT_TRUE(
+      output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false));
+  SendFullFrame();
+  SendFullFrame();
+  // Let's triple buffer.
+  EXPECT_GT(output_surface_->GetCurrentBuffer(&stencil), 0u);
+  SwapBuffers(small_damage);
+  EXPECT_GT(output_surface_->GetCurrentBuffer(&stencil), 0u);
+  EXPECT_EQ(3, CountBuffers());
+  // The whole buffer needs to be redrawn since it's a newly allocated buffer
+  EXPECT_EQ(output_surface_->CurrentBufferDamage(), screen_rect);
+
+  SendDamagedFrame(overlapping_damage);
+  // The next buffer should include damage from |overlapping_damage| and
+  // |small_damage|.
+  EXPECT_GT(output_surface_->GetCurrentBuffer(&stencil), 0u);
+  const auto current_buffer_damage = output_surface_->CurrentBufferDamage();
+  EXPECT_TRUE(current_buffer_damage.Contains(overlapping_damage));
+  EXPECT_TRUE(current_buffer_damage.Contains(small_damage));
+
+  // Let's make sure the damage is not trivially the whole screen.
+  EXPECT_NE(current_buffer_damage, screen_rect);
+}
+
 TEST_F(BufferQueueTest, PartialSwapOverlapping) {
   EXPECT_TRUE(
       output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false));
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, small_damage, screen_rect))
-      .Times(1);
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(_, _, overlapping_damage, small_damage))
-      .Times(1);
 
   SendFullFrame();
   SendDamagedFrame(small_damage);
@@ -596,38 +567,20 @@
   SwapBuffers(screen_rect);
   EXPECT_FALSE(current_frame());
 
-  // Try another swap. It should copy the buffer damage from the back
-  // surface.
   gpu_memory_buffer_manager_->set_allocate_succeeds(true);
   EXPECT_GT(output_surface_->GetCurrentBuffer(&stencil), 0u);
-  unsigned int source_texture = in_flight_surfaces().front()->texture;
-  unsigned int target_texture = current_frame()->texture;
-  testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(target_texture, source_texture, small_damage, _))
-      .Times(1);
   SwapBuffers(small_damage);
-  testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
 
-  // Destroy the just-created buffer, and try another swap. The copy should
-  // come from the displayed surface (because both in-flight surfaces are
-  // gone now).
+  // Destroy the just-created buffer, and try another swap.
   output_surface_->PageFlipComplete();
   in_flight_surfaces().back().reset();
   EXPECT_EQ(2u, in_flight_surfaces().size());
   for (auto& surface : in_flight_surfaces())
     EXPECT_FALSE(surface);
   EXPECT_GT(output_surface_->GetCurrentBuffer(&stencil), 0u);
-  source_texture = displayed_frame()->texture;
   EXPECT_TRUE(current_frame());
   EXPECT_TRUE(displayed_frame());
-  target_texture = current_frame()->texture;
-  testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
-  EXPECT_CALL(*mock_output_surface_,
-              CopyBufferDamage(target_texture, source_texture, small_damage, _))
-      .Times(1);
   SwapBuffers(small_damage);
-  testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
 }
 
 }  // namespace
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
index 01db6191..8c125590 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.cc
@@ -100,11 +100,6 @@
   }
 }
 
-void GLOutputSurfaceBufferQueue::SetDrawRectangle(const gfx::Rect& damage) {
-  GLOutputSurface::SetDrawRectangle(damage);
-  buffer_queue_->CopyDamageForCurrentSurface(damage);
-}
-
 void GLOutputSurfaceBufferQueue::SwapBuffers(OutputSurfaceFrame frame) {
   DCHECK(buffer_queue_);
 
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.h b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.h
index 594b38b..0642ecd0 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue.h
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue.h
@@ -61,7 +61,6 @@
   bool IsDisplayedAsOverlayPlane() const override;
   unsigned GetOverlayTextureId() const override;
   gfx::BufferFormat GetOverlayBufferFormat() const override;
-  void SetDrawRectangle(const gfx::Rect& damage) override;
 
   // GLOutputSurface:
   void DidReceiveSwapBuffersAck(const gfx::SwapResponse& response) override;
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc
index abc5488..15e1edf3 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc
@@ -20,7 +20,6 @@
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
-#include "components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h"
 #include "components/viz/service/display_embedder/software_output_surface.h"
 #include "components/viz/service/display_embedder/viz_process_context_provider.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
@@ -111,33 +110,7 @@
     return nullptr;
 #else
     if (renderer_settings.use_skia_renderer_non_ddl) {
-      bool is_gles2 =
-          (gl::GetGLImplementation() == gl::kGLImplementationEGLGLES2) ||
-          (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE);
-      DCHECK(is_gles2) << "SkiaRendererNonDDL is only supported with GLES2.";
-      auto gl_surface = gpu::ImageTransportSurface::CreateNativeSurface(
-          nullptr, surface_handle, gl::GLSurfaceFormat());
-      if (!shared_context_state_) {
-        auto gl_share_group = base::MakeRefCounted<gl::GLShareGroup>();
-        auto gl_context = gl::init::CreateGLContext(
-            gl_share_group.get(), gl_surface.get(), gl::GLContextAttribs());
-        gl_context->MakeCurrent(gl_surface.get());
-        shared_context_state_ = base::MakeRefCounted<gpu::SharedContextState>(
-            std::move(gl_share_group), gl_surface, std::move(gl_context),
-            false /* use_virtualized_gl_contexts */, base::DoNothing::Once(),
-            nullptr /* vulkan_context_provider */);
-        shared_context_state_->InitializeGrContext(
-            gpu::GpuDriverBugWorkarounds(), nullptr /* gr_shader_cache */);
-        mailbox_manager_ = gpu::gles2::CreateMailboxManager(
-            gpu_service_impl_->gpu_preferences());
-        DCHECK(mailbox_manager_->UsesSync());
-      }
-      output_surface = std::make_unique<SkiaOutputSurfaceImplNonDDL>(
-          std::move(gl_surface), shared_context_state_, mailbox_manager_.get(),
-          gpu_service_impl_->shared_image_manager(),
-          gpu_service_impl_->sync_point_manager(),
-          true /* need_swapbuffers_ack */);
-
+      NOTIMPLEMENTED();
     } else {
       output_surface = std::make_unique<SkiaOutputSurfaceImpl>(
           std::make_unique<SkiaOutputSurfaceDependencyImpl>(gpu_service_impl_,
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc b/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc
deleted file mode 100644
index 770ba596..0000000
--- a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.cc
+++ /dev/null
@@ -1,534 +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.
-
-#include "components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h"
-
-#include <utility>
-
-#include "base/atomic_sequence_num.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/callback_helpers.h"
-#include "base/synchronization/waitable_event.h"
-#include "components/viz/common/frame_sinks/begin_frame_source.h"
-#include "components/viz/common/frame_sinks/copy_output_request.h"
-#include "components/viz/common/gpu/vulkan_context_provider.h"
-#include "components/viz/common/resources/resource_format_utils.h"
-#include "components/viz/service/display/output_surface_client.h"
-#include "components/viz/service/display/output_surface_frame.h"
-#include "components/viz/service/display/resource_metadata.h"
-#include "components/viz/service/display_embedder/image_context.h"
-#include "gpu/command_buffer/common/shared_image_usage.h"
-#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
-#include "gpu/command_buffer/service/mailbox_manager.h"
-#include "gpu/command_buffer/service/shared_image_factory.h"
-#include "gpu/command_buffer/service/shared_image_representation.h"
-#include "gpu/command_buffer/service/skia_utils.h"
-#include "gpu/command_buffer/service/sync_point_manager.h"
-#include "gpu/command_buffer/service/texture_base.h"
-#include "gpu/vulkan/buildflags.h"
-#include "third_party/skia/include/core/SkPromiseImageTexture.h"
-#include "third_party/skia/include/gpu/GrBackendSemaphore.h"
-#include "ui/gfx/skia_util.h"
-#include "ui/gl/color_space_utils.h"
-#include "ui/gl/gl_bindings.h"
-#include "ui/gl/gl_context.h"
-#include "ui/gl/gl_gl_api_implementation.h"
-#include "ui/gl/gl_surface.h"
-#include "ui/gl/gl_version_info.h"
-
-#if BUILDFLAG(ENABLE_VULKAN)
-#include "third_party/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h"
-#endif
-
-namespace viz {
-
-namespace {
-
-scoped_refptr<gpu::SyncPointClientState> CreateSyncPointClientState(
-    gpu::SyncPointManager* sync_point_manager,
-    gpu::SequenceId sequence_id) {
-  static uint64_t next_command_buffer_id = 0u;
-  auto command_buffer_id =
-      gpu::CommandBufferId::FromUnsafeValue(++next_command_buffer_id);
-  return sync_point_manager->CreateSyncPointClientState(
-      gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE_NON_DDL,
-      command_buffer_id, sequence_id);
-}
-
-std::unique_ptr<gpu::SharedImageRepresentationFactory>
-CreateSharedImageRepresentationFactory(gpu::SharedImageManager* manager) {
-  if (!manager)
-    return nullptr;
-  // TODO(https://crbug.com/899905): Use a real MemoryTracker, not nullptr.
-  return std::make_unique<gpu::SharedImageRepresentationFactory>(
-      manager, nullptr /* tracker */);
-}
-
-}  // namespace
-
-SkiaOutputSurfaceImplNonDDL::SkiaOutputSurfaceImplNonDDL(
-    scoped_refptr<gl::GLSurface> gl_surface,
-    scoped_refptr<gpu::SharedContextState> shared_context_state,
-    gpu::MailboxManager* mailbox_manager,
-    gpu::SharedImageManager* shared_image_manager,
-    gpu::SyncPointManager* sync_point_manager,
-    bool need_swapbuffers_ack)
-    : gl_surface_(std::move(gl_surface)),
-      shared_context_state_(std::move(shared_context_state)),
-      mailbox_manager_(mailbox_manager),
-      sir_factory_(
-          CreateSharedImageRepresentationFactory(shared_image_manager)),
-      sync_point_order_data_(sync_point_manager->CreateSyncPointOrderData()),
-      sync_point_client_state_(
-          CreateSyncPointClientState(sync_point_manager,
-                                     sync_point_order_data_->sequence_id())),
-      need_swapbuffers_ack_(need_swapbuffers_ack),
-      weak_ptr_factory_(this) {}
-
-SkiaOutputSurfaceImplNonDDL::~SkiaOutputSurfaceImplNonDDL() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-}
-
-void SkiaOutputSurfaceImplNonDDL::EnsureBackbuffer() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  NOTIMPLEMENTED();
-}
-
-void SkiaOutputSurfaceImplNonDDL::DiscardBackbuffer() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  NOTIMPLEMENTED();
-}
-
-void SkiaOutputSurfaceImplNonDDL::Reshape(const gfx::Size& size,
-                                          float device_scale_factor,
-                                          const gfx::ColorSpace& color_space,
-                                          bool has_alpha,
-                                          bool use_stencil) {
-  reshape_surface_size_ = size;
-  reshape_device_scale_factor_ = device_scale_factor;
-  reshape_color_space_ = color_space;
-  reshape_has_alpha_ = has_alpha;
-  reshape_use_stencil_ = use_stencil;
-
-  if (shared_context_state_->GrContextIsVulkan()) {
-    auto* context_provider = shared_context_state_->vk_context_provider();
-    DCHECK(context_provider->GetGrSecondaryCBDrawContext());
-  } else if (shared_context_state_->GrContextIsGL()) {
-    gl::GLSurface::ColorSpace surface_color_space =
-        gl::ColorSpaceUtils::GetGLSurfaceColorSpace(color_space);
-    gl_surface_->Resize(size, device_scale_factor, surface_color_space,
-                        has_alpha);
-
-    backing_framebuffer_object_ = gl_surface_->GetBackingFramebufferObject();
-
-    SkSurfaceProps surface_props =
-        SkSurfaceProps(0, SkSurfaceProps::kLegacyFontHost_InitType);
-
-    GrGLFramebufferInfo framebuffer_info;
-    framebuffer_info.fFBOID = backing_framebuffer_object_;
-    framebuffer_info.fFormat = GL_RGBA8;
-
-    GrBackendRenderTarget render_target(size.width(), size.height(), 0, 8,
-                                        framebuffer_info);
-
-    sk_surface_ = SkSurface::MakeFromBackendRenderTarget(
-        gr_context(), render_target, kBottomLeft_GrSurfaceOrigin,
-        kRGBA_8888_SkColorType, color_space.ToSkColorSpace(), &surface_props);
-    DCHECK(sk_surface_);
-  } else {
-    NOTIMPLEMENTED();
-  }
-}
-
-SkCanvas* SkiaOutputSurfaceImplNonDDL::BeginPaintCurrentFrame() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK_EQ(current_render_pass_id_, 0u);
-  DCHECK(!scoped_gpu_task_);
-  scoped_gpu_task_.emplace(sync_point_order_data_.get());
-
-  if (shared_context_state_->GrContextIsVulkan()) {
-#if BUILDFLAG(ENABLE_VULKAN)
-    DCHECK(!draw_context_);
-    draw_context_ = shared_context_state_->vk_context_provider()
-                        ->GetGrSecondaryCBDrawContext();
-    DCHECK(draw_context_);
-    return draw_context_->getCanvas();
-#else
-    NOTREACHED();
-    return nullptr;
-#endif
-  } else {
-    DCHECK(sk_surface_);
-    // If FBO is changed, we need call Reshape() to recreate |sk_surface_|.
-    if (backing_framebuffer_object_ !=
-        gl_surface_->GetBackingFramebufferObject()) {
-      Reshape(reshape_surface_size_, reshape_device_scale_factor_,
-              reshape_color_space_, reshape_has_alpha_, reshape_use_stencil_);
-    }
-    sk_current_surface_ = sk_surface_.get();
-    return sk_current_surface_->getCanvas();
-  }
-}
-
-sk_sp<SkImage> SkiaOutputSurfaceImplNonDDL::MakePromiseSkImage(
-    const ResourceMetadata& metadata) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  if (metadata.mailbox_holder.mailbox.IsSharedImage() && sir_factory_) {
-    auto& image_context = promise_image_cache_[metadata.resource_id];
-    if (!image_context)
-      image_context = MakeSkImageFromSharedImage(metadata);
-    if (image_context) {
-      if (!image_context->representation_is_being_accessed) {
-        std::vector<GrBackendSemaphore> begin_semaphores;
-        auto promise_image_texture =
-            image_context->representation->BeginReadAccess(
-                &begin_semaphores, &pending_semaphores_);
-        // The image has been created and cached. It is too late to tell skia
-        // the backing of the cached image is not accessible right now, so crash
-        // for now.
-        // TODO(penghuang): find a way to notify skia.
-        CHECK(promise_image_texture);
-        image_context->representation_is_being_accessed = true;
-        WaitSemaphores(std::move(begin_semaphores));
-      }
-      images_in_current_paint_.push_back(image_context.get());
-    }
-    return image_context ? image_context->image : nullptr;
-  }
-
-  GrBackendTexture backend_texture;
-  if (!GetGrBackendTexture(metadata, &backend_texture)) {
-    DLOG(ERROR) << "Failed to GetGrBackendTexture from mailbox.";
-    return nullptr;
-  }
-
-  auto sk_color_type = ResourceFormatToClosestSkColorType(
-      true /* gpu_compositing */, metadata.resource_format);
-  return SkImage::MakeFromTexture(
-      gr_context(), backend_texture, kTopLeft_GrSurfaceOrigin, sk_color_type,
-      metadata.alpha_type, metadata.color_space.ToSkColorSpace());
-}
-
-sk_sp<SkImage> SkiaOutputSurfaceImplNonDDL::MakePromiseSkImageFromYUV(
-    const std::vector<ResourceMetadata>& metadatas,
-    SkYUVColorSpace yuv_color_space,
-    sk_sp<SkColorSpace> dst_color_space,
-    bool has_alpha) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK((has_alpha && (metadatas.size() == 3 || metadatas.size() == 4)) ||
-         (!has_alpha && (metadatas.size() == 2 || metadatas.size() == 3)));
-
-  SkYUVAIndex indices[4] = {};
-  PrepareYUVATextureIndices(metadatas, has_alpha, indices);
-
-  GrBackendTexture yuva_textures[4] = {};
-  for (size_t i = 0; i < metadatas.size(); ++i) {
-    const auto& metadata = metadatas[i];
-    if (!GetGrBackendTexture(metadata, &yuva_textures[i]))
-      DLOG(ERROR) << "Failed to GetGrBackendTexture from a mailbox.";
-  }
-
-  return SkImage::MakeFromYUVATextures(
-      gr_context(), yuv_color_space, yuva_textures, indices,
-      SkISize::Make(yuva_textures[0].width(), yuva_textures[1].height()),
-      kTopLeft_GrSurfaceOrigin, dst_color_space);
-}
-
-void SkiaOutputSurfaceImplNonDDL::ReleaseCachedResources(
-    const std::vector<ResourceId>& ids) {
-  if (ids.empty())
-    return;
-  for (auto id : ids) {
-    auto it = promise_image_cache_.find(id);
-    if (it == promise_image_cache_.end())
-      continue;
-    it->second->image = nullptr;
-    promise_image_cache_.erase(it);
-  }
-}
-
-void SkiaOutputSurfaceImplNonDDL::SkiaSwapBuffers(OutputSurfaceFrame frame) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  gfx::SwapTimings swap_timings;
-  swap_timings.swap_start = base::TimeTicks::Now();
-  gl_surface_->SwapBuffers(
-      base::BindOnce(&SkiaOutputSurfaceImplNonDDL::BufferPresented,
-                     weak_ptr_factory_.GetWeakPtr()));
-  swap_timings.swap_end = base::TimeTicks::Now();
-  if (need_swapbuffers_ack_)
-    client_->DidReceiveSwapBuffersAck(swap_timings);
-}
-
-SkCanvas* SkiaOutputSurfaceImplNonDDL::BeginPaintRenderPass(
-    const RenderPassId& id,
-    const gfx::Size& surface_size,
-    ResourceFormat format,
-    bool mipmap,
-    sk_sp<SkColorSpace> color_space) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // Make sure there is no unsubmitted PaintFrame or PaintRenderPass.
-  DCHECK_EQ(current_render_pass_id_, 0u);
-  DCHECK(!scoped_gpu_task_);
-  scoped_gpu_task_.emplace(sync_point_order_data_.get());
-  current_render_pass_id_ = id;
-  auto& sk_surface = offscreen_sk_surfaces_[id];
-
-  if (!sk_surface) {
-    SkColorType color_type =
-        ResourceFormatToClosestSkColorType(true /* gpu_compositing */, format);
-    SkImageInfo image_info = SkImageInfo::Make(
-        surface_size.width(), surface_size.height(), color_type,
-        kPremul_SkAlphaType, std::move(color_space));
-    sk_surface =
-        SkSurface::MakeRenderTarget(gr_context(), SkBudgeted::kNo, image_info);
-  }
-
-  sk_current_surface_ = sk_surface.get();
-  return sk_current_surface_->getCanvas();
-}
-
-gpu::SyncToken SkiaOutputSurfaceImplNonDDL::SubmitPaint(
-    base::OnceClosure on_finished) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  // To make sure sync_token is valid, we need make sure we are processing a gpu
-  // task.
-  DCHECK(scoped_gpu_task_);
-  gpu::SyncToken sync_token(
-      gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE_NON_DDL,
-      sync_point_client_state_->command_buffer_id(), ++sync_fence_release_);
-  sync_token.SetVerifyFlush();
-
-  if (!sk_current_surface_) {
-#if BUILDFLAG(ENABLE_VULKAN)
-    DCHECK(shared_context_state_->GrContextIsVulkan());
-    DCHECK(draw_context_);
-    draw_context_->flush();
-    draw_context_ = nullptr;
-    // Enqueue vk_semaphores which will be signalled when SecondaryCB is
-    // executed and finished.
-    std::vector<VkSemaphore> vk_semaphores;
-    vk_semaphores.reserve(pending_semaphores_.size());
-    for (const auto& semaphore : pending_semaphores_) {
-      DCHECK(semaphore.vkSemaphore() != VK_NULL_HANDLE);
-      vk_semaphores.push_back(semaphore.vkSemaphore());
-    }
-    pending_semaphores_.clear();
-    auto* vk_context_provider = shared_context_state_->vk_context_provider();
-    vk_context_provider->EnqueueSecondaryCBSemaphores(std::move(vk_semaphores));
-    // Enqueue FinishPaint which will be executed when the SecondaryCB is
-    // submitted and all enqueued semaphores have been submitted for signalling.
-    // WebView will not destroy OutputSurface when DrawVk is pending
-    // (PostDrawVK is not called), so it is safe to use base::Unretaied() here.
-    vk_context_provider->EnqueueSecondaryCBPostSubmitTask(
-        base::BindOnce(&SkiaOutputSurfaceImplNonDDL::FinishPaint,
-                       base::Unretained(this), sync_fence_release_));
-    return sync_token;
-#else
-    NOTREACHED();
-    return gpu::SyncToken();
-#endif
-  }
-
-  auto access = current_render_pass_id_ == 0
-                    ? SkSurface::BackendSurfaceAccess::kPresent
-                    : SkSurface::BackendSurfaceAccess::kNoAccess;
-  GrFlushInfo flush_info = {
-      .fFlags = kNone_GrFlushFlags,
-      .fNumSemaphores = pending_semaphores_.size(),
-      .fSignalSemaphores = pending_semaphores_.data(),
-  };
-
-  gpu::AddVulkanCleanupTaskForSkiaFlush(
-      shared_context_state_->vk_context_provider(), &flush_info);
-  if (on_finished)
-    gpu::AddCleanupTaskForSkiaFlush(std::move(on_finished), &flush_info);
-
-  auto result = sk_current_surface_->flush(access, flush_info);
-  DCHECK_EQ(result, GrSemaphoresSubmitted::kYes);
-  pending_semaphores_.clear();
-  sk_current_surface_ = nullptr;
-
-  FinishPaint(sync_fence_release_);
-  return sync_token;
-}
-
-sk_sp<SkImage> SkiaOutputSurfaceImplNonDDL::MakePromiseSkImageFromRenderPass(
-    const RenderPassId& id,
-    const gfx::Size& size,
-    ResourceFormat format,
-    bool mipmap,
-    sk_sp<SkColorSpace> color_space) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  auto it = offscreen_sk_surfaces_.find(id);
-  DCHECK(it != offscreen_sk_surfaces_.end());
-  return it->second->makeImageSnapshot();
-}
-
-void SkiaOutputSurfaceImplNonDDL::RemoveRenderPassResource(
-    std::vector<RenderPassId> ids) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(!ids.empty());
-
-  for (const auto& id : ids) {
-    auto it = offscreen_sk_surfaces_.find(id);
-    DCHECK(it != offscreen_sk_surfaces_.end());
-    offscreen_sk_surfaces_.erase(it);
-  }
-}
-
-void SkiaOutputSurfaceImplNonDDL::CopyOutput(
-    RenderPassId id,
-    const copy_output::RenderPassGeometry& geometry,
-    const gfx::ColorSpace& color_space,
-    std::unique_ptr<CopyOutputRequest> request) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  NOTIMPLEMENTED();
-}
-
-bool SkiaOutputSurfaceImplNonDDL::WaitSyncToken(
-    const gpu::SyncToken& sync_token) {
-  base::WaitableEvent event;
-  if (!sync_point_client_state_->Wait(
-          sync_token, base::BindOnce(&base::WaitableEvent::Signal,
-                                     base::Unretained(&event)))) {
-    return false;
-  }
-  event.Wait();
-  return true;
-}
-
-std::unique_ptr<ImageContext>
-SkiaOutputSurfaceImplNonDDL::MakeSkImageFromSharedImage(
-    const ResourceMetadata& metadata) {
-  auto image_context = std::make_unique<ImageContext>(metadata);
-  WaitSyncToken(image_context->sync_token);
-  image_context->representation = sir_factory_->ProduceSkia(
-      image_context->mailbox, shared_context_state_.get());
-  if (!image_context->representation) {
-    DLOG(ERROR) << "Failed to make the SkImage - SharedImage mailbox not found "
-                   "in SharedImageManager.";
-    return nullptr;
-  }
-
-  if (!(image_context->representation->usage() &
-        gpu::SHARED_IMAGE_USAGE_DISPLAY)) {
-    DLOG(ERROR) << "Failed to make the SkImage - SharedImage was not created "
-                   "with display usage.";
-    return nullptr;
-  }
-
-  std::vector<GrBackendSemaphore> begin_semaphores;
-  image_context->promise_image_texture =
-      image_context->representation->BeginReadAccess(&begin_semaphores,
-                                                     &pending_semaphores_);
-  if (!image_context->promise_image_texture) {
-    DLOG(ERROR)
-        << "Failed to make the SkImage - SharedImage begin access failed.";
-    return nullptr;
-  }
-  image_context->representation_is_being_accessed = true;
-  WaitSemaphores(std::move(begin_semaphores));
-
-  SkColorType color_type = ResourceFormatToClosestSkColorType(
-      true /* gpu_compositing */, metadata.resource_format);
-
-  image_context->image = SkImage::MakeFromTexture(
-      gr_context(), image_context->promise_image_texture->backendTexture(),
-      kTopLeft_GrSurfaceOrigin, color_type, image_context->alpha_type,
-      image_context->color_space);
-
-  if (!image_context->image) {
-    DLOG(ERROR) << "Failed to create the SkImage";
-    return nullptr;
-  }
-  return image_context;
-}
-
-bool SkiaOutputSurfaceImplNonDDL::GetGrBackendTexture(
-    const ResourceMetadata& metadata,
-    GrBackendTexture* backend_texture) {
-  DCHECK(!metadata.mailbox_holder.mailbox.IsZero());
-  if (WaitSyncToken(metadata.mailbox_holder.sync_token)) {
-    if (shared_context_state_->GrContextIsGL()) {
-      DCHECK(mailbox_manager_->UsesSync());
-      mailbox_manager_->PullTextureUpdates(metadata.mailbox_holder.sync_token);
-    }
-  }
-
-  auto* texture_base =
-      mailbox_manager_->ConsumeTexture(metadata.mailbox_holder.mailbox);
-  if (!texture_base) {
-    DLOG(ERROR) << "Failed to make the SkImage";
-    return false;
-  }
-
-  auto* gl_version_info =
-      shared_context_state_->real_context()->GetVersionInfo();
-  return gpu::GetGrBackendTexture(gl_version_info, texture_base->target(),
-                                  metadata.size, texture_base->service_id(),
-                                  metadata.resource_format, backend_texture);
-}
-
-void SkiaOutputSurfaceImplNonDDL::FinishPaint(uint64_t sync_fence_release) {
-  DCHECK(scoped_gpu_task_);
-  for (auto* image_context : images_in_current_paint_) {
-    if (!image_context->representation_is_being_accessed)
-      continue;
-    DCHECK(image_context->representation);
-    image_context->representation->EndReadAccess();
-    image_context->representation_is_being_accessed = false;
-  }
-  images_in_current_paint_.clear();
-
-  if (shared_context_state_->GrContextIsGL()) {
-    DCHECK(mailbox_manager_->UsesSync());
-    gpu::SyncToken sync_token(
-        gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE_NON_DDL,
-        sync_point_client_state_->command_buffer_id(), sync_fence_release);
-    sync_token.SetVerifyFlush();
-    mailbox_manager_->PushTextureUpdates(sync_token);
-  }
-  sync_point_client_state_->ReleaseFenceSync(sync_fence_release);
-  scoped_gpu_task_.reset();
-  current_render_pass_id_ = 0;
-}
-
-void SkiaOutputSurfaceImplNonDDL::BufferPresented(
-    const gfx::PresentationFeedback& feedback) {
-  client_->DidReceivePresentationFeedback(feedback);
-}
-
-void SkiaOutputSurfaceImplNonDDL::WaitSemaphores(
-    std::vector<GrBackendSemaphore> semaphores) {
-  if (semaphores.empty())
-    return;
-#if BUILDFLAG(ENABLE_VULKAN)
-  DCHECK(sk_current_surface_ || draw_context_);
-  auto result =
-      sk_current_surface_
-          ? sk_current_surface_->wait(semaphores.size(), semaphores.data())
-          : draw_context_->wait(semaphores.size(), semaphores.data());
-#else
-  DCHECK(sk_current_surface_);
-  auto result = sk_current_surface_->wait(semaphores.size(), semaphores.data());
-#endif
-
-  DCHECK(result);
-}
-
-SkiaOutputSurfaceImplNonDDL::ScopedGpuTask::ScopedGpuTask(
-    gpu::SyncPointOrderData* sync_point_order_data)
-    : sync_point_order_data_(sync_point_order_data),
-      order_num_(sync_point_order_data_->GenerateUnprocessedOrderNumber()) {
-  sync_point_order_data_->BeginProcessingOrderNumber(order_num_);
-}
-
-SkiaOutputSurfaceImplNonDDL::ScopedGpuTask::~ScopedGpuTask() {
-  sync_point_order_data_->FinishProcessingOrderNumber(order_num_);
-}
-
-}  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h b/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h
deleted file mode 100644
index d2635f7..0000000
--- a/components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h
+++ /dev/null
@@ -1,168 +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 COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_IMPL_NON_DDL_H_
-#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_IMPL_NON_DDL_H_
-
-#include <memory>
-#include <vector>
-
-#include "base/containers/flat_map.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/optional.h"
-#include "components/viz/service/display_embedder/skia_output_surface_base.h"
-#include "components/viz/service/viz_service_export.h"
-#include "gpu/command_buffer/common/sync_token.h"
-#include "gpu/command_buffer/service/shared_context_state.h"
-#include "gpu/vulkan/buildflags.h"
-
-#if BUILDFLAG(ENABLE_VULKAN)
-class GrVkSecondaryCBDrawContext;
-#endif
-
-namespace gfx {
-struct PresentationFeedback;
-}
-
-namespace gl {
-class GLSurface;
-}
-
-namespace gpu {
-class MailboxManager;
-class SharedImageManager;
-class SharedImageRepresentationFactory;
-class SyncPointClientState;
-class SyncPointManager;
-class SyncPointOrderData;
-}  // namespace gpu
-
-namespace viz {
-
-// A SkiaOutputSurface implementation for running SkiaRenderer on GpuThread.
-// Comparing to SkiaOutputSurfaceImpl, it will issue skia draw operations
-// against OS graphics API (GL, Vulkan, etc) instead of recording deferred
-// display list first.
-class VIZ_SERVICE_EXPORT SkiaOutputSurfaceImplNonDDL
-    : public SkiaOutputSurfaceBase {
- public:
-  SkiaOutputSurfaceImplNonDDL(
-      scoped_refptr<gl::GLSurface> gl_surface,
-      scoped_refptr<gpu::SharedContextState> shared_context_state,
-      gpu::MailboxManager* mailbox_manager,
-      gpu::SharedImageManager* shared_image_manager,
-      gpu::SyncPointManager* sync_point_manager,
-      bool need_swapbuffers_ack);
-  ~SkiaOutputSurfaceImplNonDDL() override;
-
-  // OutputSurface implementation:
-  void EnsureBackbuffer() override;
-  void DiscardBackbuffer() override;
-  void Reshape(const gfx::Size& size,
-               float device_scale_factor,
-               const gfx::ColorSpace& color_space,
-               bool has_alpha,
-               bool use_stencil) override;
-
-  // SkiaOutputSurface implementation:
-  SkCanvas* BeginPaintCurrentFrame() override;
-  sk_sp<SkImage> MakePromiseSkImageFromYUV(
-      const std::vector<ResourceMetadata>& metadatas,
-      SkYUVColorSpace yuv_color_space,
-      sk_sp<SkColorSpace> dst_color_space,
-      bool has_alpha) override;
-  void SkiaSwapBuffers(OutputSurfaceFrame frame) override;
-  SkCanvas* BeginPaintRenderPass(const RenderPassId& id,
-                                 const gfx::Size& surface_size,
-                                 ResourceFormat format,
-                                 bool mipmap,
-                                 sk_sp<SkColorSpace> color_space) override;
-  gpu::SyncToken SubmitPaint(base::OnceClosure on_finished) override;
-  sk_sp<SkImage> MakePromiseSkImage(const ResourceMetadata& metadata) override;
-  sk_sp<SkImage> MakePromiseSkImageFromRenderPass(
-      const RenderPassId& id,
-      const gfx::Size& size,
-      ResourceFormat format,
-      bool mipmap,
-      sk_sp<SkColorSpace> color_space) override;
-  void RemoveRenderPassResource(std::vector<RenderPassId> ids) override;
-  void CopyOutput(RenderPassId id,
-                  const copy_output::RenderPassGeometry& geometry,
-                  const gfx::ColorSpace& color_space,
-                  std::unique_ptr<CopyOutputRequest> request) override;
-
-  // ExternalUseClient implementation:
-  void ReleaseCachedResources(const std::vector<ResourceId>& ids) override;
-
- private:
-  class ScopedGpuTask {
-   public:
-    explicit ScopedGpuTask(gpu::SyncPointOrderData* sync_point_order_data);
-    ~ScopedGpuTask();
-
-   private:
-    gpu::SyncPointOrderData* const sync_point_order_data_;
-    const uint32_t order_num_;
-
-    DISALLOW_COPY_AND_ASSIGN(ScopedGpuTask);
-  };
-
-  GrContext* gr_context() { return shared_context_state_->gr_context(); }
-
-  bool WaitSyncToken(const gpu::SyncToken& sync_token);
-  std::unique_ptr<ImageContext> MakeSkImageFromSharedImage(
-      const ResourceMetadata& metadata);
-  bool GetGrBackendTexture(const ResourceMetadata& metadata,
-                           GrBackendTexture* backend_texture);
-  void FinishPaint(uint64_t sync_fence_release);
-  void BufferPresented(const gfx::PresentationFeedback& feedback);
-  void WaitSemaphores(std::vector<GrBackendSemaphore> semaphores);
-
-  uint64_t sync_fence_release_ = 0;
-
-  // Stuffs for running with |task_executor_| instead of |gpu_service_|.
-  scoped_refptr<gl::GLSurface> gl_surface_;
-  scoped_refptr<gpu::SharedContextState> shared_context_state_;
-  gpu::MailboxManager* const mailbox_manager_;
-  std::unique_ptr<gpu::SharedImageRepresentationFactory> sir_factory_;
-  scoped_refptr<gpu::SyncPointOrderData> sync_point_order_data_;
-  scoped_refptr<gpu::SyncPointClientState> sync_point_client_state_;
-  const bool need_swapbuffers_ack_;
-  base::Optional<ScopedGpuTask> scoped_gpu_task_;
-
-  unsigned int backing_framebuffer_object_ = 0;
-  gfx::Size reshape_surface_size_;
-  float reshape_device_scale_factor_ = 0.f;
-  gfx::ColorSpace reshape_color_space_;
-  bool reshape_has_alpha_ = false;
-  bool reshape_use_stencil_ = false;
-
-  // The current render pass id set by BeginPaintRenderPass.
-  RenderPassId current_render_pass_id_ = 0;
-
-  // The SkSurface for the framebuffer.
-  sk_sp<SkSurface> sk_surface_;
-
-#if BUILDFLAG(ENABLE_VULKAN)
-  // The |draw_context_| for the current frame.
-  GrVkSecondaryCBDrawContext* draw_context_ = nullptr;
-#endif
-
-  SkSurface* sk_current_surface_ = nullptr;
-
-  // Offscreen SkSurfaces for render passes.
-  base::flat_map<RenderPassId, sk_sp<SkSurface>> offscreen_sk_surfaces_;
-
-  // Semaphores which need to be signalled for the current paint.
-  std::vector<GrBackendSemaphore> pending_semaphores_;
-
-  base::WeakPtrFactory<SkiaOutputSurfaceImplNonDDL> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(SkiaOutputSurfaceImplNonDDL);
-};
-
-}  // namespace viz
-
-#endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_IMPL_NON_DDL_H_
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 5b0adbca..9801435 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -491,8 +491,8 @@
     CreateGpuMemoryBufferCallback callback) {
   DCHECK(io_runner_->BelongsToCurrentThread());
   // This needs to happen in the IO thread.
-  std::move(callback).Run(gpu_memory_buffer_factory_->CreateGpuMemoryBuffer(
-      id, size, format, usage, client_id, surface_handle));
+  gpu_memory_buffer_factory_->CreateGpuMemoryBufferAsync(
+      id, size, format, usage, client_id, surface_handle, std::move(callback));
 }
 
 void GpuServiceImpl::DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
diff --git a/content/browser/accessibility/accessibility_auralinux_browsertest.cc b/content/browser/accessibility/accessibility_auralinux_browsertest.cc
index ea5df567..ebaf887 100644
--- a/content/browser/accessibility/accessibility_auralinux_browsertest.cc
+++ b/content/browser/accessibility/accessibility_auralinux_browsertest.cc
@@ -17,16 +17,8 @@
 #include "content/test/content_browser_test_utils_internal.h"
 #include "ui/accessibility/platform/ax_platform_node_auralinux.h"
 
-// TODO(crbug.com/961029): Fix memory leaks in tests and re-enable on LSAN.
-#if defined(LEAK_SANITIZER)
-#define MAYBE_TestAtkTextListItem DISABLED_TestAtkTextListItem
-#else
-#define MAYBE_TestAtkTextListItem TestAtkTextListItem
-#endif
-
-// TODO(crbug.com/961029): Fix memory leaks in tests and re-enable on LSAN.
 // TODO(crbug.com/981913): This flakes on linux tsan.
-#if defined(LEAK_SANITIZER) || defined(THREAD_SANITIZER)
+#if defined(THREAD_SANITIZER)
 #define MAYBE_TestSetCaretSetsSequentialFocusNavigationStartingPoint \
   DISABLED_TestSetCaretSetsSequentialFocusNavigationStartingPoint
 #else
@@ -741,8 +733,7 @@
   g_object_unref(atk_text);
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
-                       MAYBE_TestAtkTextListItem) {
+IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest, TestAtkTextListItem) {
   LoadInitialAccessibilityTreeFromHtml(
       R"HTML(<!DOCTYPE html>
       <html>
@@ -781,6 +772,7 @@
   g_free(text);
 
   g_object_unref(list_item_1);
+  g_object_unref(list_item_2);
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
@@ -928,6 +920,7 @@
   g_object_unref(child_2);
   g_object_unref(child_3);
   g_object_unref(child_7);
+  g_object_unref(parent_div);
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityAuraLinuxBrowserTest,
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.cc b/content/browser/accessibility/accessibility_tree_formatter_base.cc
index 2a231537..02f44d16 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.cc
@@ -201,18 +201,36 @@
 }
 
 base::string16 AccessibilityTreeFormatterBase::FormatCoordinates(
-    const char* name,
-    const char* x_name,
-    const char* y_name,
-    const base::DictionaryValue& value) {
+    const base::DictionaryValue& value,
+    const std::string& name,
+    const std::string& x_name,
+    const std::string& y_name) {
   int x, y;
   value.GetInteger(x_name, &x);
   value.GetInteger(y_name, &y);
-  std::string xy_str(base::StringPrintf("%s=(%d, %d)", name, x, y));
+  std::string xy_str(base::StringPrintf("%s=(%d, %d)", name.c_str(), x, y));
 
   return base::UTF8ToUTF16(xy_str);
 }
 
+base::string16 AccessibilityTreeFormatterBase::FormatRectangle(
+    const base::DictionaryValue& value,
+    const std::string& name,
+    const std::string& left_name,
+    const std::string& top_name,
+    const std::string& width_name,
+    const std::string& height_name) {
+  int left, top, width, height;
+  value.GetInteger(left_name, &left);
+  value.GetInteger(top_name, &top);
+  value.GetInteger(width_name, &width);
+  value.GetInteger(height_name, &height);
+  std::string rect_str(base::StringPrintf("%s=(%d, %d, %d, %d)", name.c_str(),
+                                          left, top, width, height));
+
+  return base::UTF8ToUTF16(rect_str);
+}
+
 bool AccessibilityTreeFormatterBase::WriteAttribute(bool include_by_default,
                                                     const std::string& attr,
                                                     base::string16* line) {
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.h b/content/browser/accessibility/accessibility_tree_formatter_base.h
index d2430c04..584c15e 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.h
@@ -73,10 +73,17 @@
   // Utility functions to be used by each platform.
   //
 
-  base::string16 FormatCoordinates(const char* name,
-                                   const char* x_name,
-                                   const char* y_name,
-                                   const base::DictionaryValue& value);
+  base::string16 FormatCoordinates(const base::DictionaryValue& value,
+                                   const std::string& name,
+                                   const std::string& x_name,
+                                   const std::string& y_name);
+
+  base::string16 FormatRectangle(const base::DictionaryValue& value,
+                                 const std::string& name,
+                                 const std::string& left_name,
+                                 const std::string& top_name,
+                                 const std::string& width_name,
+                                 const std::string& height_name);
 
   // Writes the given attribute string out to |line| if it matches the property
   // filters.
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index 2ba2aa0..ec7637d 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -403,9 +403,9 @@
     WriteAttribute(false, STATE_FOCUSED, &line);
 
   WriteAttribute(
-      false, FormatCoordinates("location", "boundsX", "boundsY", dict), &line);
+      false, FormatCoordinates(dict, "location", "boundsX", "boundsY"), &line);
   WriteAttribute(false,
-                 FormatCoordinates("size", "boundsWidth", "boundsHeight", dict),
+                 FormatCoordinates(dict, "size", "boundsWidth", "boundsHeight"),
                  &line);
 
   bool ignored = false;
@@ -413,20 +413,21 @@
   if (!ignored) {
     WriteAttribute(
         false,
-        FormatCoordinates("pageLocation", "pageBoundsX", "pageBoundsY", dict),
+        FormatCoordinates(dict, "pageLocation", "pageBoundsX", "pageBoundsY"),
         &line);
     WriteAttribute(false,
-                   FormatCoordinates("pageSize", "pageBoundsWidth",
-                                     "pageBoundsHeight", dict),
+                   FormatCoordinates(dict, "pageSize", "pageBoundsWidth",
+                                     "pageBoundsHeight"),
                    &line);
     WriteAttribute(false,
-                   FormatCoordinates("unclippedLocation", "unclippedBoundsX",
-                                     "unclippedBoundsY", dict),
+                   FormatCoordinates(dict, "unclippedLocation",
+                                     "unclippedBoundsX", "unclippedBoundsY"),
                    &line);
-    WriteAttribute(false,
-                   FormatCoordinates("unclippedSize", "unclippedBoundsWidth",
-                                     "unclippedBoundsHeight", dict),
-                   &line);
+    WriteAttribute(
+        false,
+        FormatCoordinates(dict, "unclippedSize", "unclippedBoundsWidth",
+                          "unclippedBoundsHeight"),
+        &line);
   }
 
   bool transform;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
index 71b71384..ded69c1c 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm
+++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -349,14 +349,14 @@
   const base::DictionaryValue* d_value = NULL;
   if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
     WriteAttribute(false,
-                   FormatCoordinates(kPositionDictAttr, kXCoordDictAttr,
-                                     kYCoordDictAttr, *d_value),
+                   FormatCoordinates(*d_value, kPositionDictAttr,
+                                     kXCoordDictAttr, kYCoordDictAttr),
                    &line);
   }
   if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
     WriteAttribute(false,
-                   FormatCoordinates(kSizeDictAttr, kWidthDictAttr,
-                                     kHeightDictAttr, *d_value),
+                   FormatCoordinates(*d_value, kSizeDictAttr, kWidthDictAttr,
+                                     kHeightDictAttr),
                    &line);
   }
 
diff --git a/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc b/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
index 4d0fc61e..34efb96 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_uia_win.cc
@@ -348,6 +348,10 @@
   uia_->ElementFromHandle(hwnd, &root);
   CHECK(root.Get());
 
+  // Get the bounds of the root element, to pass into tree building later.
+  RECT root_bounds = {0};
+  root->get_CurrentBoundingRectangle(&root_bounds);
+
   // The root element is provided by AXFragmentRootWin, whose RuntimeId is not
   // in the same form as elements provided by BrowserAccessibility.
   // Find the root element's first child, which should be provided by
@@ -393,7 +397,8 @@
   // Build an accessibility tree starting from that element.
   std::unique_ptr<base::DictionaryValue> tree =
       std::make_unique<base::DictionaryValue>();
-  RecursiveBuildAccessibilityTree(start_element.Get(), tree.get());
+  RecursiveBuildAccessibilityTree(start_element.Get(), root_bounds.left,
+                                  root_bounds.top, tree.get());
   return tree;
 }
 
@@ -415,9 +420,13 @@
   uia_->ElementFromHandle(hwnd, &root);
   CHECK(root.Get());
 
+  RECT root_bounds = {0};
+  root->get_CurrentBoundingRectangle(&root_bounds);
+
   std::unique_ptr<base::DictionaryValue> tree =
       std::make_unique<base::DictionaryValue>();
-  RecursiveBuildAccessibilityTree(root.Get(), tree.get());
+  RecursiveBuildAccessibilityTree(root.Get(), root_bounds.left, root_bounds.top,
+                                  tree.get());
   return tree;
 }
 
@@ -431,9 +440,11 @@
 
 void AccessibilityTreeFormatterUia::RecursiveBuildAccessibilityTree(
     IUIAutomationElement* uncached_node,
+    int root_x,
+    int root_y,
     base::DictionaryValue* dict) {
   // Process this node.
-  AddProperties(uncached_node, dict);
+  AddProperties(uncached_node, root_x, root_y, dict);
 
   // Update the cache to get children
   Microsoft::WRL::ComPtr<IUIAutomationElement> parent;
@@ -451,7 +462,8 @@
     std::unique_ptr<base::DictionaryValue> child_dict =
         std::make_unique<base::DictionaryValue>();
     if (SUCCEEDED(children->GetElement(i, &child))) {
-      RecursiveBuildAccessibilityTree(child.Get(), child_dict.get());
+      RecursiveBuildAccessibilityTree(child.Get(), root_x, root_y,
+                                      child_dict.get());
     } else {
       child_dict->SetString("error", L"[Error retrieving child]");
     }
@@ -462,6 +474,8 @@
 
 void AccessibilityTreeFormatterUia::AddProperties(
     IUIAutomationElement* uncached_node,
+    int root_x,
+    int root_y,
     base::DictionaryValue* dict) {
   // Update the cache for this node's information.
   Microsoft::WRL::ComPtr<IUIAutomationElement> node;
@@ -471,7 +485,7 @@
   for (long i : properties_) {
     base::win::ScopedVariant variant;
     if (SUCCEEDED(node->GetCachedPropertyValue(i, variant.Receive()))) {
-      WriteProperty(i, variant, dict);
+      WriteProperty(i, variant, root_x, root_y, dict);
     }
   }
   // Add control pattern specific properties
@@ -772,6 +786,8 @@
 void AccessibilityTreeFormatterUia::WriteProperty(
     long propertyId,
     const base::win::ScopedVariant& var,
+    int root_x,
+    int root_y,
     base::DictionaryValue* dict) {
   switch (var.type()) {
     case VT_EMPTY:
@@ -820,17 +836,14 @@
     case VT_UNKNOWN:
       WriteUnknownProperty(propertyId, var.ptr()->punkVal, dict);
       break;
-    case VT_DISPATCH:
-    case VT_ERROR:
-    case VT_CY:
-    case VT_DATE:
-    case VT_VARIANT:
-    case VT_DECIMAL:
-    case VT_INT:
-    case VT_UINT:
-    case VT_ARRAY:
-    case VT_BYREF:
     default:
+      switch (propertyId) {
+        case UIA_BoundingRectanglePropertyId:
+          WriteRectangleProperty(propertyId, var, root_x, root_y, dict);
+          break;
+        default:
+          break;
+      }
       break;
   }
 }
@@ -885,6 +898,27 @@
   }
 }
 
+void AccessibilityTreeFormatterUia::WriteRectangleProperty(
+    long propertyId,
+    const VARIANT& value,
+    int root_x,
+    int root_y,
+    base::DictionaryValue* dict) {
+  CHECK(value.vt == (VT_ARRAY | VT_R8));
+
+  double* data = nullptr;
+  SafeArrayAccessData(value.parray, reinterpret_cast<void**>(&data));
+
+  auto rectangle = std::make_unique<base::DictionaryValue>();
+  rectangle->SetInteger("left", data[0] - root_x);
+  rectangle->SetInteger("top", data[1] - root_y);
+  rectangle->SetInteger("width", data[2]);
+  rectangle->SetInteger("height", data[3]);
+  dict->Set(UiaIdentifierToCondensedString(propertyId), std::move(rectangle));
+
+  SafeArrayUnaccessData(value.parray);
+}
+
 void AccessibilityTreeFormatterUia::WriteElementArray(
     long propertyId,
     IUIAutomationElementArray* array,
@@ -1084,6 +1118,21 @@
         filtered_result->SetDouble(name, double_value);
       break;
     }
+    case base::Value::Type::DICTIONARY: {
+      const base::DictionaryValue* dict_value = nullptr;
+      value->GetAsDictionary(&dict_value);
+      bool did_pass_filters = false;
+      if (name == "BoundingRectangle") {
+        did_pass_filters =
+            WriteAttribute(false,
+                           FormatRectangle(*dict_value, "BoundingRectangle",
+                                           "left", "top", "width", "height"),
+                           &line);
+      }
+      if (filtered_result && did_pass_filters)
+        filtered_result->SetKey(name, dict_value->Clone());
+      break;
+    }
     default:
       NOTREACHED();
       break;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_uia_win.h b/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
index e6abfb7..f6016e42 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_uia_win.h
@@ -48,9 +48,14 @@
   static const long patterns_[];
   static const long pattern_properties_[];
   void RecursiveBuildAccessibilityTree(IUIAutomationElement* node,
+                                       int root_x,
+                                       int root_y,
                                        base::DictionaryValue* dict);
   void BuildCacheRequests();
-  void AddProperties(IUIAutomationElement* node, base::DictionaryValue* dict);
+  void AddProperties(IUIAutomationElement* node,
+                     int root_x,
+                     int root_y,
+                     base::DictionaryValue* dict);
   void AddExpandCollapseProperties(IUIAutomationElement* node,
                                    base::DictionaryValue* dict);
   void AddGridProperties(IUIAutomationElement* node,
@@ -75,12 +80,19 @@
                            base::DictionaryValue* dict);
   void WriteProperty(long propertyId,
                      const base::win::ScopedVariant& var,
+                     int root_x,
+                     int root_y,
                      base::DictionaryValue* dict);
   // UIA enums have type I4, print formatted string for these when possible
   void WriteI4Property(long propertyId, long lval, base::DictionaryValue* dict);
   void WriteUnknownProperty(long propertyId,
                             IUnknown* unk,
                             base::DictionaryValue* dict);
+  void WriteRectangleProperty(long propertyId,
+                              const VARIANT& value,
+                              int root_x,
+                              int root_y,
+                              base::DictionaryValue* dict);
   void WriteElementArray(long propertyId,
                          IUIAutomationElementArray* array,
                          base::DictionaryValue* dict);
diff --git a/content/browser/accessibility/accessibility_tree_formatter_win.cc b/content/browser/accessibility/accessibility_tree_formatter_win.cc
index 41e4ba70..c8f91b6 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_win.cc
@@ -995,11 +995,11 @@
         bool did_pass_filters = false;
         if (strcmp(attribute_name, "size") == 0) {
           did_pass_filters = WriteAttribute(
-              false, FormatCoordinates("size", "width", "height", *dict_value),
+              false, FormatCoordinates(*dict_value, "size", "width", "height"),
               &line);
         } else if (strcmp(attribute_name, "location") == 0) {
           did_pass_filters = WriteAttribute(
-              false, FormatCoordinates("location", "x", "y", *dict_value),
+              false, FormatCoordinates(*dict_value, "location", "x", "y"),
               &line);
         }
         if (filtered_dict_result && did_pass_filters)
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 36e2898..9785cbf8 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -1152,10 +1152,8 @@
   // API either.
   // TODO(lukasza): Audit whether CanAccessDataForOrigin can be used directly
   // here.
-  if (!CanCommitURL(child_id, filesystem_url.origin().GetURL())) {
-    UMA_HISTOGRAM_BOOLEAN("FileSystem.OriginFailedCanCommitURL", true);
+  if (!CanCommitURL(child_id, filesystem_url.origin().GetURL()))
     return false;
-  }
 
   int found_permissions = 0;
   {
diff --git a/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.cc b/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.cc
index f695189..b4357afa 100644
--- a/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.cc
+++ b/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.cc
@@ -121,6 +121,12 @@
   }
 }
 
+gfx::Rect
+GpuSurfacelessBrowserCompositorOutputSurface::GetCurrentFramebufferDamage()
+    const {
+  return buffer_queue_->CurrentBufferDamage();
+}
+
 GLenum GpuSurfacelessBrowserCompositorOutputSurface::
     GetFramebufferCopyTextureFormat() {
   return buffer_queue_->internal_format();
@@ -180,12 +186,6 @@
   return gpu_fence_id_;
 }
 
-void GpuSurfacelessBrowserCompositorOutputSurface::SetDrawRectangle(
-    const gfx::Rect& damage) {
-  GpuBrowserCompositorOutputSurface::SetDrawRectangle(damage);
-  buffer_queue_->CopyDamageForCurrentSurface(damage);
-}
-
 gpu::SurfaceHandle
 GpuSurfacelessBrowserCompositorOutputSurface::GetSurfaceHandle() const {
   return surface_handle_;
diff --git a/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.h b/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.h
index 49c1e62..7caf285 100644
--- a/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.h
+++ b/content/browser/compositor/gpu_surfaceless_browser_compositor_output_surface.h
@@ -36,6 +36,7 @@
   // viz::OutputSurface implementation.
   void SwapBuffers(viz::OutputSurfaceFrame frame) override;
   void BindFramebuffer() override;
+  gfx::Rect GetCurrentFramebufferDamage() const override;
   uint32_t GetFramebufferCopyTextureFormat() override;
   void Reshape(const gfx::Size& size,
                float device_scale_factor,
@@ -46,7 +47,6 @@
   unsigned GetOverlayTextureId() const override;
   gfx::BufferFormat GetOverlayBufferFormat() const override;
   unsigned UpdateGpuFence() override;
-  void SetDrawRectangle(const gfx::Rect& damage) override;
   gpu::SurfaceHandle GetSurfaceHandle() const override;
 
   // BrowserCompositorOutputSurface implementation.
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 6aa1956..84e85870 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -8802,9 +8802,10 @@
   EXPECT_TRUE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
   EXPECT_FALSE(controller.GetEntryAtIndex(4)->should_skip_on_back_forward_ui());
 
-  // Simulate a user gesture.
-  root->UpdateUserActivationState(
-      blink::UserActivationUpdateType::kNotifyActivation);
+  // Simulate a user gesture. ExecuteScript internally also sends a user
+  // gesture.
+  script = "a=5";
+  EXPECT_TRUE(content::ExecuteScript(shell()->web_contents(), script));
 
   // We now have (After user gesture)
   // [skippable_url(skip), redirected_url, push_state_url1*, push_state_url2,
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index aef415fe..54ff6d5 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -405,23 +405,6 @@
       new ServiceWorkerNavigationHandle(service_worker_context));
 }
 
-void NavigationHandleImpl::RunCompleteCallback(
-    NavigationThrottle::ThrottleCheckResult result) {
-  DCHECK(result.action() != NavigationThrottle::DEFER);
-
-  ThrottleChecksFinishedCallback callback = std::move(complete_callback_);
-  complete_callback_.Reset();
-
-  if (!complete_callback_for_testing_.is_null())
-    std::move(complete_callback_for_testing_).Run(result);
-
-  if (!callback.is_null())
-    std::move(callback).Run(result);
-
-  // No code after running the callback, as it might have resulted in our
-  // destruction.
-}
-
 void NavigationHandleImpl::RenderProcessBlockedStateChanged(bool blocked) {
   if (blocked)
     StopCommitTimeout();
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index bd8de94..de8a792 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -184,11 +184,6 @@
     response_headers_for_testing_ = response_headers;
   }
 
-  void set_complete_callback_for_testing(
-      ThrottleChecksFinishedCallback callback) {
-    complete_callback_for_testing_ = std::move(callback);
-  }
-
   CSPDisposition should_check_main_world_csp() const {
     return navigation_request_->common_params()
         .initiator_csp_info.should_check_main_world_csp;
@@ -233,14 +228,6 @@
                        int pending_nav_entry_id,
                        net::HttpRequestHeaders request_headers);
 
-  // Helper function to run and reset the |complete_callback_|. This marks the
-  // end of a round of NavigationThrottleChecks.
-  void RunCompleteCallback(NavigationThrottle::ThrottleCheckResult result);
-
-  void SetCompleteCallback(ThrottleChecksFinishedCallback callback) {
-    complete_callback_ = std::move(callback);
-  }
-
   NavigationRequest::NavigationHandleState state() const {
     return navigation_request_->handle_state();
   }
@@ -280,16 +267,6 @@
   // The unique id of the corresponding NavigationEntry.
   int pending_nav_entry_id_;
 
-  // This callback will be run when all throttle checks have been performed. Be
-  // careful about relying on it as the member may be removed as part of the
-  // PlzNavigate refactoring.
-  ThrottleChecksFinishedCallback complete_callback_;
-
-  // This test-only callback will be run when all throttle checks have been
-  // performed.
-  // TODO(clamy): Revisit the unit test architecture when PlzNavigate ships.
-  ThrottleChecksFinishedCallback complete_callback_for_testing_;
-
   // Manages the lifetime of a pre-created ServiceWorkerProviderHost until a
   // corresponding provider is created in the renderer.
   std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle_;
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 3667d34..ccbd052 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -2500,9 +2500,9 @@
   else
     handle_state_ = CANCELING;
 
-  // TODO(zetamoo): Remove CompleteCallback from NavigationHandleImpl, and call
-  // the NavigationRequest methods directly.
-  navigation_handle_->RunCompleteCallback(result);
+  // TODO(zetamoo): Remove CompleteCallback, and call NavigationRequest methods
+  // directly.
+  RunCompleteCallback(result);
 }
 
 void NavigationRequest::OnWillRedirectRequestProcessed(
@@ -2518,7 +2518,7 @@
   } else {
     handle_state_ = NavigationRequest::CANCELING;
   }
-  navigation_handle_->RunCompleteCallback(result);
+  RunCompleteCallback(result);
 }
 
 void NavigationRequest::OnWillFailRequestProcessed(
@@ -2532,7 +2532,7 @@
   } else {
     handle_state_ = CANCELING;
   }
-  navigation_handle_->RunCompleteCallback(result);
+  RunCompleteCallback(result);
 }
 
 void NavigationRequest::OnWillProcessResponseProcessed(
@@ -2550,7 +2550,7 @@
   } else {
     handle_state_ = NavigationRequest::CANCELING;
   }
-  navigation_handle_->RunCompleteCallback(result);
+  RunCompleteCallback(result);
 }
 
 NavigatorDelegate* NavigationRequest::GetDelegate() const {
@@ -2613,7 +2613,7 @@
   TRACE_EVENT_ASYNC_STEP_INTO0("navigation", "NavigationRequest", this,
                                "CancelDeferredNavigation");
   handle_state_ = NavigationRequest::CANCELING;
-  navigation_handle_->RunCompleteCallback(result);
+  RunCompleteCallback(result);
 }
 
 void NavigationRequest::WillStartRequest(
@@ -2623,16 +2623,16 @@
   // WillStartRequest should only be called once.
   if (handle_state_ != INITIAL) {
     handle_state_ = CANCELING;
-    navigation_handle_->RunCompleteCallback(NavigationThrottle::CANCEL);
+    RunCompleteCallback(NavigationThrottle::CANCEL);
     return;
   }
 
   handle_state_ = PROCESSING_WILL_START_REQUEST;
-  navigation_handle_->SetCompleteCallback(std::move(callback));
+  complete_callback_ = std::move(callback);
 
   if (IsSelfReferentialURL()) {
     handle_state_ = CANCELING;
-    navigation_handle_->RunCompleteCallback(NavigationThrottle::CANCEL);
+    RunCompleteCallback(NavigationThrottle::CANCEL);
     return;
   }
 
@@ -2664,7 +2664,7 @@
 
   if (IsSelfReferentialURL()) {
     handle_state_ = CANCELING;
-    navigation_handle_->RunCompleteCallback(NavigationThrottle::CANCEL);
+    RunCompleteCallback(NavigationThrottle::CANCEL);
     return;
   }
 
@@ -2680,7 +2680,7 @@
   TRACE_EVENT_ASYNC_STEP_INTO0("navigation", "NavigationRequest", this,
                                "WillFailRequest");
 
-  navigation_handle_->SetCompleteCallback(std::move(callback));
+  complete_callback_ = std::move(callback);
   handle_state_ = PROCESSING_WILL_FAIL_REQUEST;
 
   // Notify each throttle of the request.
@@ -2696,7 +2696,7 @@
                                "WillProcessResponse");
 
   handle_state_ = PROCESSING_WILL_PROCESS_RESPONSE;
-  navigation_handle_->SetCompleteCallback(std::move(callback));
+  complete_callback_ = std::move(callback);
 
   // Notify each throttle of the response.
   throttle_runner_->ProcessNavigationEvent(
@@ -2828,7 +2828,7 @@
   navigation_handle_proxy_->DidRedirect();
 #endif
 
-  navigation_handle_->SetCompleteCallback(std::move(callback));
+  complete_callback_ = std::move(callback);
 }
 
 void NavigationRequest::SetNavigationClient(
@@ -2897,4 +2897,20 @@
   return handle_state_ == NavigationRequest::READY_TO_COMMIT;
 }
 
+void NavigationRequest::RunCompleteCallback(
+    NavigationThrottle::ThrottleCheckResult result) {
+  DCHECK(result.action() != NavigationThrottle::DEFER);
+
+  ThrottleChecksFinishedCallback callback = std::move(complete_callback_);
+
+  if (!complete_callback_for_testing_.is_null())
+    std::move(complete_callback_for_testing_).Run(result);
+
+  if (!callback.is_null())
+    std::move(callback).Run(result);
+
+  // No code after running the callback, as it might have resulted in our
+  // destruction.
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 090e589..e1a89aa 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -450,6 +450,11 @@
     return is_same_process_;
   }
 
+  void set_complete_callback_for_testing(
+      ThrottleChecksFinishedCallback callback) {
+    complete_callback_for_testing_ = std::move(callback);
+  }
+
  private:
   // TODO(clamy): Transform NavigationHandleImplTest into NavigationRequestTest
   // once NavigationHandleImpl has become a wrapper around NavigationRequest.
@@ -726,6 +731,11 @@
   // navigation or an error page.
   bool IsWaitingToCommit();
 
+  // Helper function to run and reset the |complete_callback_|. This marks the
+  // end of a round of NavigationThrottleChecks.
+  // TODO(zetamoo): This can be removed once the navigation states are merged.
+  void RunCompleteCallback(NavigationThrottle::ThrottleCheckResult result);
+
   FrameTreeNode* frame_tree_node_;
 
   // Invariant: At least one of |loader_| or |render_frame_host_| is null.
@@ -921,6 +931,15 @@
   // Set in ReadyToCommitNavigation.
   bool is_same_process_ = true;
 
+  // This callback will be run when all throttle checks have been performed.
+  // TODO(zetamoo): This can be removed once the navigation states are merged.
+  ThrottleChecksFinishedCallback complete_callback_;
+
+  // This test-only callback will be run when all throttle checks have been
+  // performed.
+  // TODO(clamy): Revisit the unit test architecture.
+  ThrottleChecksFinishedCallback complete_callback_for_testing_;
+
   base::WeakPtrFactory<NavigationRequest> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(NavigationRequest);
diff --git a/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
index 70f10609..7e2774d 100644
--- a/content/browser/frame_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
@@ -1097,6 +1097,7 @@
     if (navigation_handle->GetURL().path() != "/title2.html")
       return;
     static_cast<NavigationHandleImpl*>(navigation_handle)
+        ->navigation_request()
         ->set_complete_callback_for_testing(
             base::Bind(&NavigationHandleGrabber::SendingNavigationCommitted,
                        base::Unretained(this), navigation_handle));
diff --git a/content/browser/frame_host/sec_fetch_browsertest.cc b/content/browser/frame_host/sec_fetch_browsertest.cc
index bc038189..e5a3ba7 100644
--- a/content/browser/frame_host/sec_fetch_browsertest.cc
+++ b/content/browser/frame_host/sec_fetch_browsertest.cc
@@ -12,6 +12,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
 #include "content/shell/browser/shell.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -97,4 +98,34 @@
   }
 }
 
+// Verify that cross-port navigations are treated as same-site by
+// Sec-Fetch-Site.
+IN_PROC_BROWSER_TEST_F(SecFetchBrowserTest, CrossPortNavigation) {
+  net::EmbeddedTestServer server2(net::EmbeddedTestServer::TYPE_HTTPS);
+  server2.AddDefaultHandlers(GetTestDataFilePath());
+  server2.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  ASSERT_TRUE(server2.Start());
+
+  GURL initial_url = server2.GetURL("/title1.html");
+  GURL final_url = GetSecFetchUrl();
+  EXPECT_EQ(initial_url.scheme(), final_url.scheme());
+  EXPECT_EQ(initial_url.host(), final_url.host());
+  EXPECT_NE(initial_url.port(), final_url.port());
+
+  // Navigate to (paraphrasing): https://foo.com:port1/...
+  ASSERT_TRUE(NavigateToURL(shell(), initial_url));
+
+  // Navigate to (paraphrasing): https://foo.com:port2/...  when the navigation
+  // is initiated from (paraphrasing): https://foo.com:port1/...
+  {
+    TestNavigationObserver nav_observer(shell()->web_contents());
+    ASSERT_TRUE(ExecJs(shell(), JsReplace("location = $1", final_url)));
+    nav_observer.Wait();
+  }
+
+  // Verify that https://foo.com:port1 is treated as same-site wrt
+  // https://foo.com:port2.
+  EXPECT_EQ("same-site", GetContent());
+}
+
 }  // namespace content
diff --git a/content/browser/idle/OWNERS b/content/browser/idle/OWNERS
index 8240ddd..fe122b5 100644
--- a/content/browser/idle/OWNERS
+++ b/content/browser/idle/OWNERS
@@ -1,3 +1,5 @@
+ayui@chromium.org
 jsbell@chromium.org
 reillyg@chromium.org
 
+# TEAM: fugu-dev@chromium.org
diff --git a/content/browser/indexed_db/scopes/disjoint_range_lock_manager.cc b/content/browser/indexed_db/scopes/disjoint_range_lock_manager.cc
index e278ba17..b40a5ab 100644
--- a/content/browser/indexed_db/scopes/disjoint_range_lock_manager.cc
+++ b/content/browser/indexed_db/scopes/disjoint_range_lock_manager.cc
@@ -19,13 +19,14 @@
     : requested_type(type),
       locks_holder(std::move(locks_holder)),
       acquired_callback(std::move(acquired_callback)) {}
-DisjointRangeLockManager::LockRequest::LockRequest(LockRequest&&) = default;
+DisjointRangeLockManager::LockRequest::LockRequest(LockRequest&&) noexcept =
+    default;
 DisjointRangeLockManager::LockRequest::~LockRequest() = default;
 DisjointRangeLockManager::Lock::Lock() = default;
-DisjointRangeLockManager::Lock::Lock(Lock&&) = default;
+DisjointRangeLockManager::Lock::Lock(Lock&&) noexcept = default;
 DisjointRangeLockManager::Lock::~Lock() = default;
 DisjointRangeLockManager::Lock& DisjointRangeLockManager::Lock::operator=(
-    DisjointRangeLockManager::Lock&&) = default;
+    DisjointRangeLockManager::Lock&&) noexcept = default;
 
 DisjointRangeLockManager::DisjointRangeLockManager(int level_count)
     : task_runner_(base::SequencedTaskRunnerHandle::Get()) {
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 577daea..b2a8edad 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -26,6 +26,7 @@
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
@@ -362,7 +363,7 @@
       is_shutdown_(false),
       enable_resource_scheduler_(enable_resource_scheduler),
       num_in_flight_requests_(0),
-      max_num_in_flight_requests_(base::SharedMemory::GetHandleLimit()),
+      max_num_in_flight_requests_(base::GetHandleLimit()),
       max_num_in_flight_requests_per_process_(static_cast<int>(
           max_num_in_flight_requests_ * kMaxRequestsPerProcessRatio)),
       max_outstanding_requests_cost_per_process_(
diff --git a/content/browser/media/android/OWNERS b/content/browser/media/android/OWNERS
deleted file mode 100644
index 5abf5a1..0000000
--- a/content/browser/media/android/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-qinmin@chromium.org
diff --git a/content/browser/native_file_system/file_system_chooser_browsertest.cc b/content/browser/native_file_system/file_system_chooser_browsertest.cc
index 614583b..dcc1139 100644
--- a/content/browser/native_file_system/file_system_chooser_browsertest.cc
+++ b/content/browser/native_file_system/file_system_chooser_browsertest.cc
@@ -5,9 +5,15 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "content/browser/native_file_system/file_system_chooser_test_helpers.h"
+#include "content/browser/native_file_system/mock_native_file_system_permission_context.h"
+#include "content/browser/native_file_system/native_file_system_manager_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -21,6 +27,9 @@
 
 namespace content {
 
+using base::test::RunOnceCallback;
+using blink::mojom::PermissionStatus;
+
 // This browser test implements end-to-end tests for the chooseFileSystemEntry
 // API.
 class FileSystemChooserBrowserTest : public ContentBrowserTest {
@@ -174,6 +183,37 @@
   EXPECT_EQ(ui::SelectFileDialog::SELECT_FOLDER, dialog_params.type);
 }
 
+IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest, OpenDirectory_DenyAccess) {
+  base::FilePath test_dir = CreateTestDir();
+  SelectFileDialogParams dialog_params;
+  ui::SelectFileDialog::SetFactory(
+      new FakeSelectFileDialogFactory({test_dir}, &dialog_params));
+
+  testing::StrictMock<MockNativeFileSystemPermissionContext> permission_context;
+  static_cast<NativeFileSystemManagerImpl*>(
+      BrowserContext::GetStoragePartition(
+          shell()->web_contents()->GetBrowserContext(),
+          shell()->web_contents()->GetSiteInstance())
+          ->GetNativeFileSystemEntryFactory())
+      ->SetPermissionContextForTesting(&permission_context);
+
+  EXPECT_CALL(
+      permission_context,
+      ConfirmDirectoryReadAccess_(
+          url::Origin::Create(embedded_test_server()->GetURL("/title1.html")),
+          test_dir,
+          shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(),
+          shell()->web_contents()->GetMainFrame()->GetRoutingID(), testing::_))
+      .WillOnce(RunOnceCallback<4>(PermissionStatus::DENIED));
+
+  ASSERT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
+  auto result =
+      EvalJs(shell(), "self.chooseFileSystemEntries({type: 'openDirectory'})");
+  EXPECT_TRUE(result.error.find("AbortError") != std::string::npos)
+      << result.error;
+}
+
 IN_PROC_BROWSER_TEST_F(FileSystemChooserBrowserTest, AcceptsOptions) {
   SelectFileDialogParams dialog_params;
   ui::SelectFileDialog::SetFactory(
diff --git a/content/browser/native_file_system/mock_native_file_system_permission_context.cc b/content/browser/native_file_system/mock_native_file_system_permission_context.cc
new file mode 100644
index 0000000..5755205
--- /dev/null
+++ b/content/browser/native_file_system/mock_native_file_system_permission_context.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/native_file_system/mock_native_file_system_permission_context.h"
+
+namespace content {
+
+MockNativeFileSystemPermissionContext::MockNativeFileSystemPermissionContext() =
+    default;
+MockNativeFileSystemPermissionContext::
+    ~MockNativeFileSystemPermissionContext() = default;
+
+void MockNativeFileSystemPermissionContext::ConfirmDirectoryReadAccess(
+    const url::Origin& origin,
+    const base::FilePath& path,
+    int process_id,
+    int frame_id,
+    base::OnceCallback<void(PermissionStatus)> callback) {
+  ConfirmDirectoryReadAccess_(origin, path, process_id, frame_id, callback);
+}
+
+}  // namespace content
diff --git a/content/browser/native_file_system/mock_native_file_system_permission_context.h b/content/browser/native_file_system/mock_native_file_system_permission_context.h
new file mode 100644
index 0000000..bcba4dc
--- /dev/null
+++ b/content/browser/native_file_system/mock_native_file_system_permission_context.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_NATIVE_FILE_SYSTEM_MOCK_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
+#define CONTENT_BROWSER_NATIVE_FILE_SYSTEM_MOCK_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
+
+#include "content/public/browser/native_file_system_permission_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+// Mock NativeFileSystemPermissionContext implementation.
+class MockNativeFileSystemPermissionContext
+    : public NativeFileSystemPermissionContext {
+ public:
+  MockNativeFileSystemPermissionContext();
+  ~MockNativeFileSystemPermissionContext();
+
+  MOCK_METHOD3(
+      GetWritePermissionGrant,
+      scoped_refptr<NativeFileSystemPermissionGrant>(const url::Origin& origin,
+                                                     const base::FilePath& path,
+                                                     bool is_directory));
+
+  void ConfirmDirectoryReadAccess(
+      const url::Origin& origin,
+      const base::FilePath& path,
+      int process_id,
+      int frame_id,
+      base::OnceCallback<void(PermissionStatus)> callback);
+  MOCK_METHOD5(ConfirmDirectoryReadAccess_,
+               void(const url::Origin& origin,
+                    const base::FilePath& path,
+                    int process_id,
+                    int frame_id,
+                    base::OnceCallback<void(PermissionStatus)>& callback));
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_NATIVE_FILE_SYSTEM_MOCK_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
diff --git a/content/browser/native_file_system/native_file_system_handle_base.cc b/content/browser/native_file_system/native_file_system_handle_base.cc
index 5f9ec024..6887c3cb 100644
--- a/content/browser/native_file_system/native_file_system_handle_base.cc
+++ b/content/browser/native_file_system/native_file_system_handle_base.cc
@@ -14,19 +14,23 @@
 class NativeFileSystemHandleBase::UsageIndicatorTracker
     : public WebContentsObserver {
  public:
-  UsageIndicatorTracker(int process_id, int frame_id, bool is_directory)
+  UsageIndicatorTracker(int process_id,
+                        int frame_id,
+                        bool is_directory,
+                        const base::FilePath& directory_path)
       : WebContentsObserver(
             WebContentsImpl::FromRenderFrameHostID(process_id, frame_id)),
-        is_directory_(is_directory) {
+        is_directory_(is_directory),
+        directory_path_(directory_path) {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     if (web_contents() && is_directory_)
-      web_contents()->IncrementNativeFileSystemDirectoryHandleCount();
+      web_contents()->AddNativeFileSystemDirectoryHandle(directory_path_);
   }
 
   ~UsageIndicatorTracker() override {
     if (web_contents()) {
       if (is_directory_)
-        web_contents()->DecrementNativeFileSystemDirectoryHandleCount();
+        web_contents()->RemoveNativeFileSystemDirectoryHandle(directory_path_);
       if (is_writable_)
         web_contents()->DecrementWritableNativeFileSystemHandleCount();
     }
@@ -49,6 +53,7 @@
 
  private:
   const bool is_directory_;
+  const base::FilePath directory_path_;
   bool is_writable_ = false;
 };
 
@@ -74,9 +79,23 @@
       << url_.type();
   if (url_.type() == storage::kFileSystemTypeNativeLocal) {
     DCHECK_EQ(url_.mount_type(), storage::kFileSystemTypeIsolated);
+    base::FilePath directory_path;
+    if (is_directory) {
+      // For usage reporting purposes try to get the root path of the isolated
+      // file system, i.e. the path the user picked in a directory picker.
+      auto* isolated_context = storage::IsolatedContext::GetInstance();
+      if (!isolated_context->GetRegisteredPath(handle_state_.file_system.id(),
+                                               &directory_path)) {
+        // If for some reason the isolated file system no longer exists, fall
+        // back to the path of the handle itself, which could be a child of the
+        // originally picked path.
+        directory_path = url.path();
+      }
+    }
     usage_indicator_tracker_ = base::SequenceBound<UsageIndicatorTracker>(
         base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}),
-        context_.process_id, context_.frame_id, bool{is_directory});
+        context_.process_id, context_.frame_id, bool{is_directory},
+        base::FilePath(directory_path));
     UpdateWritableUsage();
   }
 }
diff --git a/content/browser/native_file_system/native_file_system_manager_impl.cc b/content/browser/native_file_system/native_file_system_manager_impl.cc
index 7a25bd3..499fa96d 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl.cc
+++ b/content/browser/native_file_system/native_file_system_manager_impl.cc
@@ -295,20 +295,50 @@
     std::move(callback).Run(std::move(result), std::move(result_entries));
     return;
   }
-  result_entries.reserve(entries.size());
-  for (const auto& entry : entries) {
-    if (type == blink::mojom::ChooseFileSystemEntryType::kOpenDirectory) {
-      result_entries.push_back(
-          CreateDirectoryEntryFromPath(binding_context, entry));
+
+  if (type == blink::mojom::ChooseFileSystemEntryType::kOpenDirectory) {
+    DCHECK_EQ(entries.size(), 1u);
+    if (permission_context_) {
+      permission_context_->ConfirmDirectoryReadAccess(
+          binding_context.origin, entries.front(), binding_context.process_id,
+          binding_context.frame_id,
+          base::BindOnce(&NativeFileSystemManagerImpl::DidChooseDirectory, this,
+                         binding_context, entries.front(),
+                         std::move(callback)));
     } else {
-      result_entries.push_back(CreateFileEntryFromPath(binding_context, entry));
+      DidChooseDirectory(binding_context, entries.front(), std::move(callback),
+                         PermissionStatus::GRANTED);
     }
+    return;
   }
+
+  result_entries.reserve(entries.size());
+  for (const auto& entry : entries)
+    result_entries.push_back(CreateFileEntryFromPath(binding_context, entry));
+
   // TODO(mek): Auto-grant write permission if this was a file as a result of a
   // save dialog.
   std::move(callback).Run(std::move(result), std::move(result_entries));
 }
 
+void NativeFileSystemManagerImpl::DidChooseDirectory(
+    const BindingContext& binding_context,
+    const base::FilePath& path,
+    ChooseEntriesCallback callback,
+    NativeFileSystemPermissionContext::PermissionStatus permission) {
+  std::vector<blink::mojom::NativeFileSystemEntryPtr> result_entries;
+  if (permission != PermissionStatus::GRANTED) {
+    std::move(callback).Run(
+        NativeFileSystemError::New(base::File::FILE_ERROR_ABORT),
+        std::move(result_entries));
+    return;
+  }
+
+  result_entries.push_back(CreateDirectoryEntryFromPath(binding_context, path));
+  std::move(callback).Run(NativeFileSystemError::New(base::File::FILE_OK),
+                          std::move(result_entries));
+}
+
 void NativeFileSystemManagerImpl::CreateTransferTokenImpl(
     const storage::FileSystemURL& url,
     const SharedHandleState& handle_state,
diff --git a/content/browser/native_file_system/native_file_system_manager_impl.h b/content/browser/native_file_system/native_file_system_manager_impl.h
index 1ae9483..3276206 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl.h
+++ b/content/browser/native_file_system/native_file_system_manager_impl.h
@@ -141,6 +141,11 @@
   }
   storage::FileSystemOperationRunner* operation_runner();
 
+  void SetPermissionContextForTesting(
+      NativeFileSystemPermissionContext* permission_context) {
+    permission_context_ = permission_context;
+  }
+
  private:
   ~NativeFileSystemManagerImpl() override;
   void DidOpenSandboxedFileSystem(const BindingContext& binding_context,
@@ -154,6 +159,11 @@
                         ChooseEntriesCallback callback,
                         blink::mojom::NativeFileSystemErrorPtr result,
                         std::vector<base::FilePath> entries);
+  void DidChooseDirectory(
+      const BindingContext& binding_context,
+      const base::FilePath& path,
+      ChooseEntriesCallback callback,
+      NativeFileSystemPermissionContext::PermissionStatus permission);
 
   void CreateTransferTokenImpl(
       const storage::FileSystemURL& url,
@@ -178,7 +188,7 @@
   const scoped_refptr<storage::FileSystemContext> context_;
   const scoped_refptr<ChromeBlobStorageContext> blob_context_;
   std::unique_ptr<storage::FileSystemOperationRunner> operation_runner_;
-  NativeFileSystemPermissionContext* const permission_context_;
+  NativeFileSystemPermissionContext* permission_context_;
 
   // All the mojo bindings for this NativeFileSystemManager itself. Keeps track
   // of associated origin and other state as well to not have to rely on the
diff --git a/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc b/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
index ddf45d21..4f7cbee 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
+++ b/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "content/browser/native_file_system/fixed_native_file_system_permission_grant.h"
+#include "content/browser/native_file_system/mock_native_file_system_permission_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "storage/browser/blob/blob_storage_context.h"
 #include "storage/browser/test/test_file_system_context.h"
@@ -22,17 +23,6 @@
 using blink::mojom::PermissionStatus;
 using storage::FileSystemURL;
 
-// Mock NativeFileSystemPermissionContext implementation.
-class MockNativeFileSystemPermissionContext
-    : public NativeFileSystemPermissionContext {
- public:
-  MOCK_METHOD3(
-      GetWritePermissionGrant,
-      scoped_refptr<NativeFileSystemPermissionGrant>(const url::Origin& origin,
-                                                     const base::FilePath& path,
-                                                     bool is_directory));
-};
-
 class NativeFileSystemManagerImplTest : public testing::Test {
  public:
   NativeFileSystemManagerImplTest()
diff --git a/content/browser/renderer_host/input/input_router_impl_unittest.cc b/content/browser/renderer_host/input/input_router_impl_unittest.cc
index f19d1ed..657e1a0 100644
--- a/content/browser/renderer_host/input/input_router_impl_unittest.cc
+++ b/content/browser/renderer_host/input/input_router_impl_unittest.cc
@@ -1274,10 +1274,10 @@
   EXPECT_EQ(0U, disposition_handler_->GetAndResetAckCount());
   EXPECT_EQ(1U, GetAndResetDispatchedMessages().size());
 
-  // The remainder of the touch sequence should be dropped.
+  // The remainder of the touch sequence should be forwarded.
   ReleaseTouchPoint(0);
   SendTouchEvent();
-  EXPECT_EQ(1U, disposition_handler_->GetAndResetAckCount());
+  EXPECT_EQ(0U, disposition_handler_->GetAndResetAckCount());
 
   PressAndSetTouchActionAuto();
   EXPECT_EQ(0U, GetAndResetDispatchedMessages().size());
@@ -2061,7 +2061,7 @@
 }
 
 // Tests that touch event stream validation passes when events are filtered
-// out. See crbug.com/581231 for details.
+// out. See https://crbug.com/581231 for details.
 TEST_P(InputRouterImplTest, TouchValidationPassesWithFilteredInputEvents) {
   // Touch sequence with touch handler.
   OnHasTouchEventHandlers(true);
@@ -2081,11 +2081,11 @@
   dispatched_messages[0]->ToEvent()->CallCallback(
       INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
 
-  // This event will be filtered out, since no consumer exists.
+  // This event will not be filtered out even though no consumer exists.
   ReleaseTouchPoint(1);
   SendTouchEvent();
   dispatched_messages = GetAndResetDispatchedMessages();
-  EXPECT_EQ(0U, dispatched_messages.size());
+  EXPECT_EQ(1U, dispatched_messages.size());
 
   // If the validator didn't see the filtered out release event, it will crash
   // now, upon seeing a press for a touch which it believes to be still pressed.
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
index dd26e35..238e3a1 100644
--- a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
@@ -47,7 +47,7 @@
     PassthroughTouchEventQueue::kSkipTouchEventFilterType{
         &features::kSkipTouchEventFilter,
         features::kSkipTouchEventFilterTypeParamName,
-        features::kSkipTouchEventFilterTypeParamValueDiscrete};
+        features::kSkipTouchEventFilterTypeParamValueAll};
 
 PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState::
     TouchEventWithLatencyInfoAndAckState(const TouchEventWithLatencyInfo& event)
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.h b/content/browser/renderer_host/input/passthrough_touch_event_queue.h
index 3399455..f62862c 100644
--- a/content/browser/renderer_host/input/passthrough_touch_event_queue.h
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.h
@@ -129,13 +129,13 @@
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            TouchScrollStartedUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
-                           TouchStartWithoutPageHandlersFiltered);
+                           TouchStartWithoutPageHandlersUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            TouchStartWithPageHandlersUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            TouchMoveFilteredAfterTimeout);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
-                           TouchMoveWithoutPageHandlersFiltered);
+                           TouchMoveWithoutPageHandlersUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            StationaryTouchMoveFiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
@@ -145,7 +145,7 @@
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            TouchMoveWithNonTouchMoveUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
-                           TouchMoveWithoutSequenceHandlerFiltered);
+                           TouchMoveWithoutSequenceHandlerUnfiltered);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
                            TouchMoveWithoutPageHandlersUnfilteredWithSkipFlag);
   FRIEND_TEST_ALL_PREFIXES(PassthroughTouchEventQueueTest,
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc
index 8d51360..9f9c2f4 100644
--- a/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc
@@ -495,36 +495,36 @@
   EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
 }
 
-// Tests that addition of a touch handler during a touch sequence will not cause
-// the remaining sequence to be forwarded.
+// Tests that addition of a touch handler during a touch sequence will continue
+// forwarding events.
 TEST_F(PassthroughTouchEventQueueTest,
-       ActiveSequenceNotForwardedWhenHandlersAdded) {
+       ActiveSequenceStillForwardedWhenHandlersAdded) {
   OnHasTouchEventHandlers(false);
 
   // Send a touch-press event while there is no handler.
   PressTouchPoint(1, 1);
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
 
   OnHasTouchEventHandlers(true);
 
-  // The remaining touch sequence should not be forwarded.
+  // The remaining touch sequence should be forwarded.
   MoveTouchPoint(0, 5, 5);
   ReleaseTouchPoint(0);
-  EXPECT_EQ(2U, GetAndResetAckedEventCount());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(3U, queued_event_count());
 
-  // A new touch sequence should resume forwarding.
+  // A new touch sequence should continue forwarding.
   PressTouchPoint(1, 1);
-  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(4U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 }
 
-// Tests that removal of a touch handler during a touch sequence will prevent
-// the remaining sequence from being forwarded, even if another touch handler is
-// registered during the same touch sequence.
+// Tests that removal of a touch handler during a touch sequence will not
+// prevent the remaining sequence from being forwarded, even if another touch
+// handler is registered during the same touch sequence.
 TEST_F(PassthroughTouchEventQueueTest,
        ActiveSequenceDroppedWhenHandlersRemoved) {
   // Send a touch-press event.
@@ -543,8 +543,7 @@
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(2U, queued_event_count());
 
-  // Repeated registration/unregstration of handlers should have no effect as
-  // we're still awaiting the ack arrival.
+  // Repeated registration/unregstration of handlers should have no effect.
   OnHasTouchEventHandlers(true);
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(2U, queued_event_count());
@@ -552,30 +551,30 @@
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(2U, queued_event_count());
 
-  // clear the queue .
+  // Clear the queue.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
   EXPECT_EQ(2U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, queued_event_count());
 
-  // Events should be dropped while there is no touch handler.
+  // Events should still be forwarded while there is no touch handler.
   MoveTouchPoint(0, 10, 10);
-  EXPECT_EQ(0U, queued_event_count());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
 
   // Simulate touch handler registration in the middle of a touch sequence.
   OnHasTouchEventHandlers(true);
 
   // The touch end for the interrupted sequence should be dropped.
   ReleaseTouchPoint(0);
-  EXPECT_EQ(0U, queued_event_count());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
 
   // A new touch sequence should be forwarded properly.
   PressTouchPoint(1, 1);
-  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(3U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 }
 
@@ -626,16 +625,14 @@
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
-// Tests that touch-move events are not sent to the renderer if the preceding
-// touch-press event did not have a consumer (and consequently, did not hit the
-// main thread in the renderer).
+// Tests that touch-move events are still sent to the renderer even if the
+// preceding touch-press event did not have a consumer.
 TEST_F(PassthroughTouchEventQueueTest, NoConsumer) {
   // The first touch-press should reach the renderer.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 
-  // The second touch should be sent since we don't know if there is
-  // a consumer or not.
+  // The second touch should be sent too.
   MoveTouchPoint(0, 5, 5);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(2U, queued_event_count());
@@ -647,14 +644,13 @@
   EXPECT_EQ(2U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
 
-  // Send a release event. This should not reach the renderer.
+  // Send a release event. This should reach the renderer.
   ReleaseTouchPoint(0);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(WebInputEvent::kTouchEnd, acked_event().GetType());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(WebInputEvent::kTouchMove, acked_event().GetType());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // Send a press-event, followed by move a following move should not
-  // be sent but held in the queue.
+  // Send a press-event, followed by a move should be sent.
   PressTouchPoint(10, 10);
   MoveTouchPoint(0, 5, 5);
 
@@ -663,11 +659,11 @@
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   MoveTouchPoint(0, 6, 5);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
-  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(3U, queued_event_count());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
-  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
 TEST_F(PassthroughTouchEventQueueTest, AckTouchEventInReverse) {
@@ -844,25 +840,26 @@
   EXPECT_EQ(WebInputEvent::kTouchMove, acked_event().GetType());
 }
 
-// Tests basic TouchEvent forwarding suppression.
+// Tests that basic TouchEvent forwarding suppression has been disabled.
 TEST_F(PassthroughTouchEventQueueTest, NoTouchBasic) {
-  // Disable TouchEvent forwarding.
+  // The old behaviour was to suppress events when there were no handlers.
+  // Signal the no-handler case and test that events still get forwarded.
   OnHasTouchEventHandlers(false);
   PressTouchPoint(30, 5);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // TouchMove should not be sent to renderer.
+  // TouchMove should be sent to renderer.
   MoveTouchPoint(0, 65, 10);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // TouchEnd should not be sent to renderer.
+  // TouchEnd should be sent to renderer.
   ReleaseTouchPoint(0);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // Enable TouchEvent forwarding.
+  // Signal handlers-present and make sure events are still getting forwarded.
   OnHasTouchEventHandlers(true);
 
   PressTouchPoint(80, 10);
@@ -1172,23 +1169,23 @@
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // Ack the cancel event.  Normally, this would resume touch forwarding,
+  // Ack the cancel event. Normally, this would resume touch forwarding,
   // but we're still within a scroll gesture so it remains disabled.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
   EXPECT_FALSE(IsTimeoutRunning());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // Try to forward touch events for the current sequence.
+  // Forward touch events for the current sequence.
   GetAndResetSentEventCount();
   GetAndResetAckedEventCount();
   MoveTouchPoint(0, 1, 1);
   ReleaseTouchPoint(0);
-  EXPECT_FALSE(IsTimeoutRunning());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
-  // Now end the scroll sequence, resuming touch handling.
+  // Now end the scroll sequence.
   SendGestureEvent(blink::WebInputEvent::kGestureScrollEnd);
   PressTouchPoint(0, 1);
   EXPECT_TRUE(IsTimeoutRunning());
@@ -1385,11 +1382,11 @@
   ASSERT_EQ(1U, GetAndResetSentEventCount());
   ASSERT_EQ(1U, GetAndResetAckedEventCount());
 
-  // Events should not be forwarded, as the point had no consumer.
+  // Events should be forwarded, even though the point had no consumer.
   MoveTouchPoint(0, 0, 15);
-  EXPECT_EQ(0U, queued_event_count());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 
   // Simulate a secondary pointer press.
   PressTouchPoint(20, 0);
@@ -1399,7 +1396,7 @@
 
   // TouchMove with a secondary pointer should not be suppressed.
   MoveTouchPoint(1, 25, 0);
-  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(2U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
@@ -1420,8 +1417,8 @@
                                   WebInputEvent::GetStaticTimeStampForTests());
   SetFollowupEvent(followup_scroll);
   MoveTouchPoint(0, 20, 5);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
 
   // The secondary pointer press should be forwarded.
@@ -1432,7 +1429,7 @@
 
   // TouchMove with a secondary pointer should also be forwarded.
   MoveTouchPoint(1, 25, 0);
-  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(2U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
@@ -1566,11 +1563,11 @@
   EXPECT_EQ(0U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
-  // Give the touchmove a valid id after release it shouldn't be sent.
+  // Give the touchmove a valid id after release; it should be sent.
   event.touches[0].id = press_id;
   SendTouchEvent(event);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
 // Tests that touch points states are correct in TouchMove events.
@@ -1795,14 +1792,14 @@
             FilterBeforeForwarding(event));
 }
 
-TEST_F(PassthroughTouchEventQueueTest, TouchStartWithoutPageHandlersFiltered) {
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchStartWithoutPageHandlersUnfiltered) {
   OnHasTouchEventHandlers(false);
   SyntheticWebTouchEvent event;
   event.PressPoint(1, 1);
 
-  EXPECT_EQ(
-      PassthroughTouchEventQueue::PreFilterResult::kFilteredNoPageHandlers,
-      FilterBeforeForwarding(event));
+  EXPECT_EQ(PassthroughTouchEventQueue::PreFilterResult::kUnfiltered,
+            FilterBeforeForwarding(event));
 }
 
 TEST_F(PassthroughTouchEventQueueTest, TouchStartWithPageHandlersUnfiltered) {
@@ -1831,7 +1828,7 @@
             FilterBeforeForwarding(event));
 }
 
-TEST_F(PassthroughTouchEventQueueTest, TouchMoveWithoutPageHandlersFiltered) {
+TEST_F(PassthroughTouchEventQueueTest, TouchMoveWithoutPageHandlersUnfiltered) {
   OnHasTouchEventHandlers(false);
   // Start the touch sequence.
   PressTouchPoint(1, 1);
@@ -1840,9 +1837,8 @@
   int id = event.PressPoint(1, 1);
   event.MovePoint(id, 2, 2);
 
-  EXPECT_EQ(
-      PassthroughTouchEventQueue::PreFilterResult::kFilteredNoPageHandlers,
-      FilterBeforeForwarding(event));
+  EXPECT_EQ(PassthroughTouchEventQueue::PreFilterResult::kUnfiltered,
+            FilterBeforeForwarding(event));
 }
 
 TEST_F(PassthroughTouchEventQueueTest, StationaryTouchMoveFiltered) {
@@ -1912,7 +1908,7 @@
 }
 
 TEST_F(PassthroughTouchEventQueueTest,
-       TouchMoveWithoutSequenceHandlerFiltered) {
+       TouchMoveWithoutSequenceHandlerUnfiltered) {
   OnHasTouchEventHandlers(true);
   // Start the touch sequence.
   PressTouchPoint(1, 1);
@@ -1920,13 +1916,12 @@
   // Send an ack indicating that there's no handler for the current sequence.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
 
-  // Any subsequent touches in the sequence should be filtered.
+  // Any subsequent touches in the sequence should be unfiltered.
   SyntheticWebTouchEvent event;
   int id = event.PressPoint(1, 1);
   event.MovePoint(id, 3, 3);
 
-  EXPECT_EQ(PassthroughTouchEventQueue::PreFilterResult::
-                kFilteredNoHandlerForSequence,
+  EXPECT_EQ(PassthroughTouchEventQueue::PreFilterResult::kUnfiltered,
             FilterBeforeForwarding(event));
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_browsertest.cc b/content/browser/renderer_host/render_widget_host_browsertest.cc
index 4d86c87..7a3ea100 100644
--- a/content/browser/renderer_host/render_widget_host_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -92,7 +92,8 @@
 
  private:
   EventTypeVector dispatched_events_;
-  blink::WebInputEvent::Type acked_touch_event_type_;
+  blink::WebInputEvent::Type acked_touch_event_type_ =
+      blink::WebInputEvent::Type::kUndefined;
 };
 
 class RenderWidgetHostTouchEmulatorBrowserTest : public ContentBrowserTest {
@@ -145,7 +146,7 @@
   RenderWidgetHostInputEventRouter* router_;
 
   base::TimeTicks last_simulated_event_time_;
-  base::TimeDelta simulated_event_time_delta_;
+  const base::TimeDelta simulated_event_time_delta_;
 };
 
 // Synthetic mouse events not allowed on Android.
@@ -211,8 +212,6 @@
 
 IN_PROC_BROWSER_TEST_F(RenderWidgetHostTouchEmulatorBrowserTest,
                        TouchEmulator) {
-  // All touches will be immediately acked instead of sending them to the
-  // renderer since the test page does not have a touch handler.
   host()->GetTouchEmulator()->Enable(
       TouchEmulator::Mode::kEmulatingTouchFromMouse,
       ui::GestureProviderConfigType::GENERIC_MOBILE);
@@ -220,6 +219,8 @@
   TestInputEventObserver observer;
   host()->AddInputEventObserver(&observer);
 
+  // Simulate a mouse move without any pressed buttons. This should not
+  // generate any touch events.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10, 0, false);
   TestInputEventObserver::EventTypeVector dispatched_events =
       observer.GetAndResetDispatchedEventTypes();
@@ -227,71 +228,62 @@
 
   // Mouse press becomes touch start which in turn becomes tap.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchStart,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
 
   // Mouse drag generates touch move, cancels tap and starts scroll.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(4u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
 
   // Mouse drag with shift becomes pinch.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 35,
                            blink::WebInputEvent::kShiftKey, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
 
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
 
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 50,
                            blink::WebInputEvent::kShiftKey, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
 
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
 
   // Mouse drag without shift becomes scroll again.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 60, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
 
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(3u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[2]);
 
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 70, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[1]);
 
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseUp, 10, 70, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchEnd, observer.acked_touch_event_type());
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
+            observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchEnd, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
 
   // Mouse move does nothing.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 80, 0, false);
@@ -300,50 +292,42 @@
 
   // Another mouse down continues scroll.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 80, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchStart,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 100, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(4u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
   EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
 
   // Another pinch.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 110,
                            blink::WebInputEvent::kShiftKey, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  EXPECT_EQ(2u, dispatched_events.size());
+  EXPECT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, dispatched_events[1]);
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 120,
                            blink::WebInputEvent::kShiftKey, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  EXPECT_EQ(2u, dispatched_events.size());
+  EXPECT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchUpdate, dispatched_events[1]);
 
   // Turn off emulation during a pinch.
   host()->GetTouchEmulator()->Disable();
-  EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(3u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, dispatched_events[1]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[2]);
 
   // Mouse event should pass untouched.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 10,
@@ -359,34 +343,29 @@
 
   // Another touch.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseDown, 10, 10, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchStart,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchStart, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, dispatched_events[1]);
 
   // Scroll.
   SimulateRoutedMouseEvent(blink::WebInputEvent::kMouseMove, 10, 30, 0, true);
-  EXPECT_EQ(blink::WebInputEvent::kTouchMove,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(4u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchMove, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureTapCancel, dispatched_events[1]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, dispatched_events[2]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollUpdate, dispatched_events[3]);
   EXPECT_EQ(0u, observer.GetAndResetDispatchedEventTypes().size());
 
   // Turn off emulation during a scroll.
   host()->GetTouchEmulator()->Disable();
-  EXPECT_EQ(blink::WebInputEvent::kTouchCancel,
+  EXPECT_EQ(blink::WebInputEvent::kUndefined,
             observer.acked_touch_event_type());
 
   dispatched_events = observer.GetAndResetDispatchedEventTypes();
-  ASSERT_EQ(2u, dispatched_events.size());
+  ASSERT_EQ(1u, dispatched_events.size());
   EXPECT_EQ(blink::WebInputEvent::kTouchCancel, dispatched_events[0]);
-  EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, dispatched_events[1]);
 
   host()->RemoveInputEventObserver(&observer);
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index cf362af0..977aa912 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -1489,20 +1489,22 @@
       ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), ui::EventTimeForNow(),
       ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
 
-  // The touch events should get forwarded from the view, but they should not
-  // reach the renderer.
+  // The touch events should get forwarded from the view and make it all the
+  // way to the renderer.
   view_->OnTouchEvent(&press);
   base::RunLoop().RunUntilIdle();
   MockWidgetInputHandler::MessageVector events =
       GetAndResetDispatchedMessages();
-  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(1U, events.size());
+  EXPECT_EQ("TouchStart", GetMessageNames(events));
   EXPECT_TRUE(press.synchronous_handling_disabled());
   EXPECT_EQ(ui::MotionEvent::Action::DOWN, pointer_state().GetAction());
 
   view_->OnTouchEvent(&move);
   base::RunLoop().RunUntilIdle();
   events = GetAndResetDispatchedMessages();
-  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(1U, events.size());
+  EXPECT_EQ("TouchMove", GetMessageNames(events));
   EXPECT_TRUE(press.synchronous_handling_disabled());
   EXPECT_EQ(ui::MotionEvent::Action::MOVE, pointer_state().GetAction());
   EXPECT_EQ(1U, pointer_state().GetPointerCount());
@@ -1510,7 +1512,8 @@
   view_->OnTouchEvent(&release);
   base::RunLoop().RunUntilIdle();
   events = GetAndResetDispatchedMessages();
-  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(1U, events.size());
+  EXPECT_EQ("TouchEnd", GetMessageNames(events));
   EXPECT_TRUE(press.synchronous_handling_disabled());
   EXPECT_EQ(0U, pointer_state().GetPointerCount());
 
@@ -1574,7 +1577,8 @@
   EXPECT_TRUE(press.synchronous_handling_disabled());
   EXPECT_EQ(0U, pointer_state().GetPointerCount());
   events = GetAndResetDispatchedMessages();
-  EXPECT_EQ(0U, events.size());
+  EXPECT_EQ(2U, events.size());
+  EXPECT_EQ("TouchMove TouchEnd", GetMessageNames(events));
 }
 
 // The DOM KeyCode map for Fuchsia maps all DomCodes to 0.  This means that
diff --git a/content/browser/renderer_host/render_widget_targeter.cc b/content/browser/renderer_host/render_widget_targeter.cc
index 0ee1b50..6fc0309e 100644
--- a/content/browser/renderer_host/render_widget_targeter.cc
+++ b/content/browser/renderer_host/render_widget_targeter.cc
@@ -5,8 +5,6 @@
 #include "content/browser/renderer_host/render_widget_targeter.h"
 
 #include "base/bind.h"
-#include "base/debug/crash_logging.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
@@ -14,10 +12,8 @@
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/input/one_shot_timeout_monitor.h"
-#include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
-#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/site_isolation_policy.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "ui/events/blink/blink_event_util.h"
@@ -55,29 +51,6 @@
 
 constexpr const char kTracingCategory[] = "input,latency";
 
-// This function helps with debugging the reasons of viz hit testing mismatch.
-void DumpWithoutCrashing(RenderWidgetHostViewBase* root_view,
-                         RenderWidgetHostViewBase* target,
-                         const base::Optional<gfx::PointF>& target_location) {
-  RenderViewHostImpl* rvh =
-      RenderViewHostImpl::From(root_view->GetRenderWidgetHost());
-  if (!rvh || !rvh->GetMainFrame())
-    return;
-  static auto* crash_key = base::debug::AllocateCrashKeyString(
-      "vizhittest-mismatch-v2-url", base::debug::CrashKeySize::Size256);
-  const std::string& url =
-      rvh->GetMainFrame()->GetLastCommittedURL().GetOrigin().spec();
-  base::debug::SetCrashKeyString(crash_key, url);
-
-  crash_key = base::debug::AllocateCrashKeyString(
-      "vizhittest-mismatch-v2-coordinate", base::debug::CrashKeySize::Size32);
-  const std::string& global_coordinate =
-      target->TransformPointToRootCoordSpaceF(target_location.value())
-          .ToString();
-  base::debug::SetCrashKeyString(crash_key, global_coordinate);
-  base::debug::DumpWithoutCrashing();
-}
-
 }  // namespace
 
 class TracingUmaTracker {
@@ -481,7 +454,6 @@
         // If the result did not change, it is likely that viz hit test finds
         // the wrong target.
         match_result = HitTestResultsMatch::kDoNotMatch;
-        DumpWithoutCrashing(root_view, target, target_location);
       } else {
         // Hit test data changed, so the result is no longer reliable.
         match_result = HitTestResultsMatch::kHitTestResultChanged;
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
index 5f7459ef..263d3864 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -658,8 +658,15 @@
     return false;
 
   network::ResourceRequest resource_request(original_request);
-  resource_request.resource_type =
-      static_cast<int>(ResourceType::kNavigationPreload);
+  if (resource_type_ == ResourceType::kMainFrame) {
+    resource_request.resource_type =
+        static_cast<int>(ResourceType::kNavigationPreloadMainFrame);
+  } else {
+    DCHECK_EQ(ResourceType::kSubFrame, resource_type_);
+    resource_request.resource_type =
+        static_cast<int>(ResourceType::kNavigationPreloadSubFrame);
+  }
+
   resource_request.skip_service_worker = true;
   resource_request.do_not_prompt_for_login = true;
 
diff --git a/content/browser/sms/OWNERS b/content/browser/sms/OWNERS
index a5caa2ed..9bb2ad2b 100644
--- a/content/browser/sms/OWNERS
+++ b/content/browser/sms/OWNERS
@@ -1,5 +1,6 @@
+ayui@chromium.org
 jsbell@chromium.org
 reillyg@chromium.org
 
 # COMPONENT: Blink>SMS
-# TEAM: fugu-dev@chromium.org
\ No newline at end of file
+# TEAM: fugu-dev@chromium.org
diff --git a/content/browser/sms/sms_provider.cc b/content/browser/sms/sms_provider.cc
index 3b95cd2..13ecffa2 100644
--- a/content/browser/sms/sms_provider.cc
+++ b/content/browser/sms/sms_provider.cc
@@ -54,10 +54,6 @@
   }
 }
 
-void SmsProvider::NotifyTimeout() {
-  observers_.begin()->OnTimeout();
-}
-
 bool SmsProvider::HasObservers() {
   return observers_.might_have_observers();
 }
diff --git a/content/browser/sms/sms_provider.h b/content/browser/sms/sms_provider.h
index 48eebfa..6502afd 100644
--- a/content/browser/sms/sms_provider.h
+++ b/content/browser/sms/sms_provider.h
@@ -28,7 +28,6 @@
     // Receive an |sms| from an origin. Return true if the message is
     // handled, which stops its propagation to other observers.
     virtual bool OnReceive(const url::Origin&, const std::string& sms) = 0;
-    virtual void OnTimeout() = 0;
   };
 
   SmsProvider();
@@ -44,7 +43,6 @@
   void RemoveObserver(const Observer*);
   void NotifyReceive(const url::Origin&, const std::string& sms);
   void NotifyReceive(const std::string& sms);
-  void NotifyTimeout();
   bool HasObservers();
 
  private:
diff --git a/content/browser/sms/sms_provider_android.cc b/content/browser/sms/sms_provider_android.cc
index 504c06b5..270d8f2 100644
--- a/content/browser/sms/sms_provider_android.cc
+++ b/content/browser/sms/sms_provider_android.cc
@@ -48,7 +48,6 @@
 void SmsProviderAndroid::OnTimeout(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj) {
-  NotifyTimeout();
 }
 
 }  // namespace content
diff --git a/content/browser/sms/sms_receiver_impl.cc b/content/browser/sms/sms_receiver_impl.cc
index 56b7f1d..f6fa8e2 100644
--- a/content/browser/sms/sms_receiver_impl.cc
+++ b/content/browser/sms/sms_receiver_impl.cc
@@ -21,48 +21,70 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
+SmsReceiverImpl::Request::Request(ReceiveCallback callback)
+    : callback(std::move(callback)) {}
+
+SmsReceiverImpl::Request::~Request() = default;
+
 void SmsReceiverImpl::Receive(base::TimeDelta timeout,
                               ReceiveCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  Push(std::move(callback));
-}
 
-void SmsReceiverImpl::Push(ReceiveCallback callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (callbacks_.empty())
+  if (requests_.empty())
     sms_provider_->AddObserver(this);
 
-  callbacks_.push(std::move(callback));
+  auto request = std::make_unique<Request>(std::move(callback));
+  // The |timer| is owned by |request|, and |request| is owned by |this|, so it
+  // is safe to hold raw pointers to |this| and |request| here in the callback.
+  request->timer.Start(FROM_HERE, timeout,
+                       base::BindOnce(&SmsReceiverImpl::OnTimeout,
+                                      base::Unretained(this), request.get()));
+  requests_.push_back(std::move(request));
   sms_provider_->Retrieve();
 }
 
-blink::mojom::SmsReceiver::ReceiveCallback SmsReceiverImpl::Pop() {
-  DCHECK(!callbacks_.empty()) << "Unexpected SMS received";
-
-  ReceiveCallback callback = std::move(callbacks_.front());
-  callbacks_.pop();
-
-  if (callbacks_.empty())
-    sms_provider_->RemoveObserver(this);
-
-  return callback;
-}
-
 bool SmsReceiverImpl::OnReceive(const url::Origin& origin,
                                 const std::string& sms) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (origin_ != origin)
     return false;
 
-  Pop().Run(blink::mojom::SmsStatus::kSuccess, sms);
+  return Pop(sms);
+}
+
+bool SmsReceiverImpl::Pop(const std::string& sms) {
+  DCHECK(!requests_.empty());
+
+  DCHECK(requests_.front()->timer.IsRunning());
+
+  requests_.front()->timer.Stop();
+  std::move(requests_.front()->callback)
+      .Run(blink::mojom::SmsStatus::kSuccess, sms);
+  requests_.pop_front();
+
+  if (requests_.empty())
+    sms_provider_->RemoveObserver(this);
 
   return true;
 }
 
-void SmsReceiverImpl::OnTimeout() {
+void SmsReceiverImpl::OnTimeout(Request* request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  Pop().Run(blink::mojom::SmsStatus::kTimeout, base::nullopt);
+  DCHECK(!request->timer.IsRunning());
+
+  std::move(request->callback)
+      .Run(blink::mojom::SmsStatus::kTimeout, base::nullopt);
+
+  // Remove the request from the list.
+  for (auto iter = requests_.begin(); iter != requests_.end(); ++iter) {
+    if ((*iter).get() == request) {
+      requests_.erase(iter);
+      if (requests_.empty())
+        sms_provider_->RemoveObserver(this);
+      return;
+    }
+  }
 }
 
 }  // namespace content
diff --git a/content/browser/sms/sms_receiver_impl.h b/content/browser/sms/sms_receiver_impl.h
index cf0298c8..717732d 100644
--- a/content/browser/sms/sms_receiver_impl.h
+++ b/content/browser/sms/sms_receiver_impl.h
@@ -5,13 +5,14 @@
 #ifndef CONTENT_BROWSER_SMS_SMS_RECEIVER_IMPL_H_
 #define CONTENT_BROWSER_SMS_SMS_RECEIVER_IMPL_H_
 
+#include <list>
 #include <memory>
-#include <queue>
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "content/browser/sms/sms_provider.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/sms/sms_receiver.mojom.h"
@@ -25,23 +26,33 @@
   SmsReceiverImpl(SmsProvider*, const url::Origin&);
   ~SmsReceiverImpl() override;
 
+  struct Request {
+    explicit Request(ReceiveCallback callback);
+    ~Request();
+
+    base::OneShotTimer timer;
+    ReceiveCallback callback;
+
+    DISALLOW_COPY_AND_ASSIGN(Request);
+  };
+
   // content::SmsProvider::Observer:
   bool OnReceive(const url::Origin&, const std::string& message) override;
-  void OnTimeout() override;
 
   // blink::mojom::SmsReceiver:
   void Receive(base::TimeDelta timeout, ReceiveCallback) override;
 
  private:
-  // Manages the queue of callbacks.
-  void Push(ReceiveCallback);
-  ReceiveCallback Pop();
+  bool Pop(const std::string& sms);
+  void OnTimeout(Request* request);
 
   // |sms_provider_| is safe because all instances of SmsReceiverImpl are owned
   // by SmsServiceImpl through a StrongBindingSet.
   SmsProvider* sms_provider_;
   const url::Origin origin_;
-  std::queue<ReceiveCallback> callbacks_;
+
+  using SmsRequestList = std::list<std::unique_ptr<Request>>;
+  SmsRequestList requests_;
 
   SEQUENCE_CHECKER(sequence_checker_);
   DISALLOW_COPY_AND_ASSIGN(SmsReceiverImpl);
diff --git a/content/browser/sms/sms_service_impl_unittest.cc b/content/browser/sms/sms_service_impl_unittest.cc
index abcff269..5cb666c 100644
--- a/content/browser/sms/sms_service_impl_unittest.cc
+++ b/content/browser/sms/sms_service_impl_unittest.cc
@@ -299,12 +299,8 @@
 
   base::RunLoop loop;
 
-  EXPECT_CALL(*mock, Retrieve()).WillOnce(Invoke([&mock]() {
-    mock->NotifyTimeout();
-  }));
-
   service_ptr->Receive(
-      base::TimeDelta::FromSeconds(10),
+      base::TimeDelta::FromSeconds(0),
       base::BindLambdaForTesting([&](blink::mojom::SmsStatus status,
                                      const base::Optional<std::string>& sms) {
         EXPECT_EQ(blink::mojom::SmsStatus::kTimeout, status);
@@ -340,7 +336,7 @@
       .WillOnce(Invoke([&listen]() { listen.Quit(); }));
 
   service_ptr1->Receive(
-      base::TimeDelta::FromSeconds(10),
+      base::TimeDelta::FromSeconds(0),
       base::BindLambdaForTesting([&](blink::mojom::SmsStatus status,
                                      const base::Optional<std::string>& sms) {
         sms_status1 = status;
@@ -359,9 +355,8 @@
 
   listen.Run();
 
-  // Timesout the first request.
-
-  mock->NotifyTimeout();
+  // The first request immediately times out because it uses TimeDelta of 0
+  // seconds.
 
   sms_loop1.Run();
 
@@ -377,4 +372,66 @@
   EXPECT_EQ(blink::mojom::SmsStatus::kSuccess, sms_status2);
 }
 
+TEST_F(SmsServiceImplTest, SecondRequestTimesOutEarlierThanFirstRequest) {
+  auto impl = std::make_unique<SmsServiceImpl>();
+  auto* mock = new NiceMock<MockSmsProvider>();
+
+  impl->SetSmsProviderForTest(base::WrapUnique(mock));
+
+  blink::mojom::SmsReceiverPtr service_ptr1;
+  GURL url1("http://a.com");
+  impl->Bind(mojo::MakeRequest(&service_ptr1), url::Origin::Create(url1));
+
+  blink::mojom::SmsReceiverPtr service_ptr2;
+  GURL url2("http://b.com");
+  impl->Bind(mojo::MakeRequest(&service_ptr2), url::Origin::Create(url2));
+
+  blink::mojom::SmsStatus sms_status1;
+  base::Optional<std::string> response1;
+  blink::mojom::SmsStatus sms_status2;
+  base::Optional<std::string> response2;
+
+  base::RunLoop listen, sms_loop1, sms_loop2;
+
+  EXPECT_CALL(*mock, Retrieve())
+      .WillOnce(testing::Return())
+      .WillOnce(Invoke([&listen]() { listen.Quit(); }));
+
+  service_ptr1->Receive(
+      base::TimeDelta::FromSeconds(10),
+      base::BindLambdaForTesting([&](blink::mojom::SmsStatus status,
+                                     const base::Optional<std::string>& sms) {
+        sms_status1 = status;
+        response1 = sms;
+        sms_loop1.Quit();
+      }));
+
+  service_ptr2->Receive(
+      base::TimeDelta::FromSeconds(0),
+      base::BindLambdaForTesting([&](blink::mojom::SmsStatus status,
+                                     const base::Optional<std::string>& sms) {
+        sms_status2 = status;
+        response2 = sms;
+        sms_loop2.Quit();
+      }));
+
+  listen.Run();
+
+  // The second request immediately times out because it uses TimeDelta of 0
+  // seconds.
+
+  sms_loop2.Run();
+
+  EXPECT_EQ(blink::mojom::SmsStatus::kTimeout, sms_status2);
+
+  // Delivers the first SMS.
+
+  mock->NotifyReceive(url::Origin::Create(url1), "first");
+
+  sms_loop1.Run();
+
+  EXPECT_EQ("first", response1.value());
+  EXPECT_EQ(blink::mojom::SmsStatus::kSuccess, sms_status1);
+}
+
 }  // namespace content
diff --git a/content/browser/tracing/background_tracing_config_impl.cc b/content/browser/tracing/background_tracing_config_impl.cc
index 2cbac09a..c5b9bf88 100644
--- a/content/browser/tracing/background_tracing_config_impl.cc
+++ b/content/browser/tracing/background_tracing_config_impl.cc
@@ -281,10 +281,10 @@
   chrome_config.SetTraceBufferSizeInKb(GetMaximumTraceBufferSizeKb());
 
 #if defined(OS_ANDROID)
-  // Set low trace buffer size on Android in order to upload small trace files.
+  // For legacy tracing backend, set low trace buffer size on Android in order
+  // to upload small trace files.
   if (tracing_mode() == BackgroundTracingConfigImpl::PREEMPTIVE) {
     chrome_config.SetTraceBufferSizeInEvents(20000);
-    chrome_config.SetTraceBufferSizeInKb(500);
   }
 #endif
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 702a9c4c..b912bd1 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -14,6 +14,7 @@
 #include "base/command_line.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/i18n/character_encoding.h"
 #include "base/lazy_instance.h"
 #include "base/location.h"
@@ -1575,7 +1576,16 @@
 }
 
 bool WebContentsImpl::HasNativeFileSystemDirectoryHandles() {
-  return native_file_system_directory_handle_count_ > 0;
+  return !native_file_system_directory_handles_.empty();
+}
+
+std::vector<base::FilePath>
+WebContentsImpl::GetNativeFileSystemDirectoryHandles() {
+  std::vector<base::FilePath> result;
+  result.reserve(native_file_system_directory_handles_.size());
+  for (auto const& entry : native_file_system_directory_handles_)
+    result.push_back(entry.first);
+  return result;
 }
 
 bool WebContentsImpl::HasWritableNativeFileSystemHandles() {
@@ -6882,7 +6892,8 @@
     NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
 }
 
-void WebContentsImpl::IncrementNativeFileSystemDirectoryHandleCount() {
+void WebContentsImpl::AddNativeFileSystemDirectoryHandle(
+    const base::FilePath& path) {
   // Trying to invalidate the tab state while being destroyed could result in a
   // use after free.
   if (IsBeingDestroyed())
@@ -6891,14 +6902,16 @@
   // Notify for UI updates if the state changes. Need both TYPE_TAB and TYPE_URL
   // to update both the tab-level usage indicator and the usage indicator in the
   // omnibox.
-  native_file_system_directory_handle_count_++;
-  if (native_file_system_directory_handle_count_ == 1) {
+  const bool was_empty = native_file_system_directory_handles_.empty();
+  native_file_system_directory_handles_[path]++;
+  if (was_empty) {
     NotifyNavigationStateChanged(static_cast<content::InvalidateTypes>(
         INVALIDATE_TYPE_TAB | INVALIDATE_TYPE_URL));
   }
 }
 
-void WebContentsImpl::DecrementNativeFileSystemDirectoryHandleCount() {
+void WebContentsImpl::RemoveNativeFileSystemDirectoryHandle(
+    const base::FilePath& path) {
   // Trying to invalidate the tab state while being destroyed could result in a
   // use after free.
   if (IsBeingDestroyed())
@@ -6907,9 +6920,13 @@
   // Notify for UI updates if the state changes. Need both TYPE_TAB and TYPE_URL
   // to update both the tab-level usage indicator and the usage indicator in the
   // omnibox.
-  DCHECK_NE(0u, native_file_system_directory_handle_count_);
-  native_file_system_directory_handle_count_--;
-  if (native_file_system_directory_handle_count_ == 0) {
+  auto it = native_file_system_directory_handles_.find(path);
+  DCHECK(it != native_file_system_directory_handles_.end());
+  DCHECK_NE(0u, it->second);
+  it->second--;
+  if (it->second == 0)
+    native_file_system_directory_handles_.erase(it);
+  if (native_file_system_directory_handles_.empty()) {
     NotifyNavigationStateChanged(static_cast<content::InvalidateTypes>(
         INVALIDATE_TYPE_TAB | INVALIDATE_TYPE_URL));
   }
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 327184c1..f638d3c 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -77,6 +77,10 @@
 #include "content/public/browser/android/child_process_importance.h"
 #endif
 
+namespace base {
+class FilePath;
+}
+
 namespace service_manager {
 class InterfaceProvider;
 }
@@ -352,6 +356,7 @@
   bool IsConnectedToBluetoothDevice() override;
   bool IsConnectedToSerialPort() override;
   bool HasNativeFileSystemDirectoryHandles() override;
+  std::vector<base::FilePath> GetNativeFileSystemDirectoryHandles() override;
   bool HasWritableNativeFileSystemHandles() override;
   bool HasPictureInPictureVideo() override;
   bool IsCrashed() override;
@@ -961,10 +966,11 @@
   void IncrementSerialActiveFrameCount();
   void DecrementSerialActiveFrameCount();
 
-  // Modify the counter of native file system directory handles for this
-  // WebContents.
-  void IncrementNativeFileSystemDirectoryHandleCount();
-  void DecrementNativeFileSystemDirectoryHandleCount();
+  // Add and remove a reference for a native file system directory handle for a
+  // certain |path|. Multiple Add calls should be balanced by the same number of
+  // Remove calls for the same |path|.
+  void AddNativeFileSystemDirectoryHandle(const base::FilePath& path);
+  void RemoveNativeFileSystemDirectoryHandle(const base::FilePath& path);
 
   // Modify the counter of native file system handles with write access for this
   // WebContents.
@@ -1806,7 +1812,7 @@
   size_t bluetooth_connected_device_count_ = 0;
   size_t serial_active_frame_count_ = 0;
 
-  size_t native_file_system_directory_handle_count_ = 0;
+  std::map<base::FilePath, size_t> native_file_system_directory_handles_;
   size_t native_file_system_writable_handle_count_ = 0;
 
   bool has_picture_in_picture_video_ = false;
diff --git a/content/browser/webauth/uv_preferred.md b/content/browser/webauth/uv_preferred.md
new file mode 100644
index 0000000..d38208d
--- /dev/null
+++ b/content/browser/webauth/uv_preferred.md
@@ -0,0 +1,11 @@
+# Advice to sites regarding `preferred` user verification
+
+The default value for [user verification](https://www.w3.org/TR/webauthn/#enumdef-userverificationrequirement) in [Web Authentication](https://www.w3.org/TR/webauthn/)'s [create](https://www.w3.org/TR/webauthn/#createCredential) and [get](https://www.w3.org/TR/webauthn/#getAssertion) calls is `preferred`. The semantics of this have been surprising to several sites and so, if no value is provided, Blink will emit a warning in the Javascript console. This warning can be silenced by explicitly setting any valid value for `userVerification`. For a `create` call, this requires setting a value for publicKey.[authenticatorSelection](https://www.w3.org/TR/webauthn/#authenticatorSelection).[userVerification](https://www.w3.org/TR/webauthn/#dom-authenticatorselectioncriteria-userverification) and, for a `get` call, setting publicKey.[userVerification](https://www.w3.org/TR/webauthn/#dom-publickeycredentialrequestoptions-userverification). The valid values are enumerated as [UserVerificationRequirement](https://www.w3.org/TR/webauthn/#enumdef-userverificationrequirement).
+
+The default, `preferred` value instructs the user-agent to acquire [user verification](https://www.w3.org/TR/webauthn/#user-verification) &ldquo;[if possible](https://www.w3.org/TR/webauthn/#dom-userverificationrequirement-preferred)&rdquo;. On modern versions of Windows this is interpreted such that a user will be prompted to configure a PIN during credential creation on an authenticator that supports it, and such that a user will be prompted for a PIN if one is already set. On other platforms, Chrome will not direct the user to create a PIN if one is not already set, but will be prompt for a PIN if one is configured. This is quite different from the traditional user experience of security keys.
+
+The traditional user experience is best replicated by setting `userVerification` to `discouraged`. This does not preclude the [UV flag](https://www.w3.org/TR/webauthn/#flags) being set in a signature because some authenticators may always do user verification. (For example, a built-in fingerprint reader.) But it will prevent PIN prompts from appearing.
+
+Sites that desire user verification should set `userVerification` to `required` to indicate this. This will, however, limit users to authenticators that support some form of user verification and many do not.
+
+There is not currently a way in WebAuthn to express that a site would like user verification if it's low-cost for the user so that later reauthentication may use WebAuthn rather than a password.
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index ba3d3ff..1667aa52 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -311,17 +311,17 @@
   url::Origin origin = instance_->constructor_origin();
   network::mojom::TrustedURLLoaderHeaderClientPtrInfo no_header_client;
 
-  // TODO(crbug.com/955476): network_isolation_key to be created using worker
-  // script's origin.
   if (GetCreateNetworkFactoryCallbackForSharedWorker().is_null()) {
-    process->CreateURLLoaderFactory(
-        origin, nullptr /* preferences */, net::NetworkIsolationKey(),
-        std::move(no_header_client), std::move(request));
+    process->CreateURLLoaderFactory(origin, nullptr /* preferences */,
+                                    net::NetworkIsolationKey(origin, origin),
+                                    std::move(no_header_client),
+                                    std::move(request));
   } else {
     network::mojom::URLLoaderFactoryPtr original_factory;
-    process->CreateURLLoaderFactory(
-        origin, nullptr /* preferences */, net::NetworkIsolationKey(),
-        std::move(no_header_client), mojo::MakeRequest(&original_factory));
+    process->CreateURLLoaderFactory(origin, nullptr /* preferences */,
+                                    net::NetworkIsolationKey(origin, origin),
+                                    std::move(no_header_client),
+                                    mojo::MakeRequest(&original_factory));
     GetCreateNetworkFactoryCallbackForSharedWorker().Run(
         std::move(request), process_id_, original_factory.PassInterface());
   }
diff --git a/content/browser/service_worker/service_worker_network_isolation_key_browsertest.cc b/content/browser/worker_network_isolation_key_browsertest.cc
similarity index 64%
rename from content/browser/service_worker/service_worker_network_isolation_key_browsertest.cc
rename to content/browser/worker_network_isolation_key_browsertest.cc
index f8f6264..5f980d5 100644
--- a/content/browser/service_worker/service_worker_network_isolation_key_browsertest.cc
+++ b/content/browser/worker_network_isolation_key_browsertest.cc
@@ -6,6 +6,7 @@
 #include "base/strings/strcat.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "content/public/common/content_paths.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -16,10 +17,28 @@
 
 namespace content {
 
-class ServiceWorkerNetworkIsolationKeyBrowserTest
+namespace {
+
+bool SupportsSharedWorker() {
+#if defined(OS_ANDROID)
+  // SharedWorkers are not enabled on Android. https://crbug.com/154571
+  return false;
+#else
+  return true;
+#endif
+}
+
+}  // namespace
+
+enum class WorkerType {
+  kServiceWorker,
+  kSharedWorker,
+};
+
+class WorkerNetworkIsolationKeyBrowserTest
     : public ContentBrowserTest,
       public ::testing::WithParamInterface<
-          bool /* test_same_network_isolation_key */> {
+          std::tuple<bool /* test_same_network_isolation_key */, WorkerType>> {
  public:
   void SetUp() override {
     feature_list_.InitAndEnableFeature(
@@ -42,11 +61,12 @@
     process_watcher.Wait();
   }
 
-  // Register a service worker |main_script_file| in the scope of
+  // Register a service/shared worker |main_script_file| in the scope of
   // |embedded_test_server|'s origin, that does
   // importScripts(|import_script_url|) and fetch(|fetch_url|).
-  void RegisterServiceWorkerThatDoesImportScriptsAndFetch(
+  void RegisterWorkerThatDoesImportScriptsAndFetch(
       const net::EmbeddedTestServer* embedded_test_server,
+      WorkerType worker_type,
       const std::string& main_script_file,
       const GURL& import_script_url,
       const GURL& fetch_url) {
@@ -54,8 +74,7 @@
         shell()->web_contents(), /*number_of_navigations*/ 1,
         content::MessageLoopRunner::QuitMode::DEFERRED);
     std::string subframe_url =
-        embedded_test_server
-            ->GetURL("/service_worker/create_service_worker.html")
+        embedded_test_server->GetURL("/workers/service_worker_setup.html")
             .spec();
 
     std::string subframe_name = GetUniqueSubframeName();
@@ -74,9 +93,18 @@
         {main_script_file, "?import_script_url=", import_script_url.spec(),
          "&fetch_url=", fetch_url.spec()});
 
-    EXPECT_EQ("DONE",
-              EvalJs(subframe_rfh,
-                     JsReplace("register($1)", main_script_file_with_param)));
+    switch (worker_type) {
+      case WorkerType::kServiceWorker:
+        EXPECT_EQ("ok",
+                  EvalJs(subframe_rfh,
+                         JsReplace("setup($1)", main_script_file_with_param)));
+        break;
+      case WorkerType::kSharedWorker:
+        EXPECT_EQ(nullptr, EvalJs(subframe_rfh,
+                                  JsReplace("let worker = new SharedWorker($1)",
+                                            main_script_file_with_param)));
+        break;
+    }
   }
 
  private:
@@ -89,17 +117,23 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Test that network isolation key is filled in correctly for service workers.
-// It checks the cache status of importScripts() as well as fetch() request from
-// two different service workers, where the two workers may be from the same or
-// different origin - network isolation key. When the origins are the same, we
-// expect the 2nd importScripts and/or fetch request to exist in the cache, and
-// when the origins are different, we expect the 2nd request to not exist in the
-// cache. The imported/fetched script are always the same as it's a control
-// variable for this test.
-IN_PROC_BROWSER_TEST_P(ServiceWorkerNetworkIsolationKeyBrowserTest,
+// Test that network isolation key is filled in correctly for service/shared
+// workers. The test navigates to "a.com" and creates two cross-origin iframes
+// that each start a worker. The frames/workers may have the same origin, so
+// worker1 is on "b.com" and worker2 is on either "b.com" or "c.com". The test
+// checks the cache status of importScripts() and a fetch() request from the
+// workers to another origin "d.com". When the workers had the same origin (the
+// same network isolation key), we expect the second importScripts() and fetch()
+// request to exist in the cache. When the origins are different, we expect the
+// second requests to not exist in the cache.
+IN_PROC_BROWSER_TEST_P(WorkerNetworkIsolationKeyBrowserTest,
                        ImportScriptsAndFetchRequest) {
-  bool test_same_network_isolation_key = GetParam();
+  bool test_same_network_isolation_key;
+  WorkerType worker_type;
+  std::tie(test_same_network_isolation_key, worker_type) = GetParam();
+
+  if (worker_type == WorkerType::kSharedWorker && !SupportsSharedWorker())
+    return;
 
   // Discard the old process to clear the in-memory cache.
   CrossProcessNavigation();
@@ -119,10 +153,8 @@
   net::EmbeddedTestServer resource_request_server;
   resource_request_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
   ASSERT_TRUE(resource_request_server.Start());
-  GURL import_script_url =
-      resource_request_server.GetURL("/service_worker/empty.js");
-  GURL fetch_url =
-      resource_request_server.GetURL("/service_worker/empty2.html");
+  GURL import_script_url = resource_request_server.GetURL("/workers/empty.js");
+  GURL fetch_url = resource_request_server.GetURL("/workers/empty.html");
 
   std::map<GURL, size_t> request_completed_count;
 
@@ -153,21 +185,24 @@
       {});
 
   NavigateToURLBlockUntilNavigationsComplete(
-      shell(),
-      embedded_test_server()->GetURL("/service_worker/frame_factory.html"), 1);
+      shell(), embedded_test_server()->GetURL("/workers/frame_factory.html"),
+      1);
 
-  RegisterServiceWorkerThatDoesImportScriptsAndFetch(
-      &cross_origin_server_1, "worker_with_import_and_fetch.js",
+  RegisterWorkerThatDoesImportScriptsAndFetch(
+      &cross_origin_server_1, worker_type, "worker_with_import_and_fetch.js",
       import_script_url, fetch_url);
-  RegisterServiceWorkerThatDoesImportScriptsAndFetch(
-      &cross_origin_server_2, "worker_with_import_and_fetch_2.js",
+  RegisterWorkerThatDoesImportScriptsAndFetch(
+      &cross_origin_server_2, worker_type, "worker_with_import_and_fetch_2.js",
       import_script_url, fetch_url);
 
   cache_status_waiter.Run();
 }
 
-INSTANTIATE_TEST_SUITE_P(/* no prefix */,
-                         ServiceWorkerNetworkIsolationKeyBrowserTest,
-                         testing::Bool());
+INSTANTIATE_TEST_SUITE_P(
+    /* no prefix */,
+    WorkerNetworkIsolationKeyBrowserTest,
+    ::testing::Combine(testing::Bool(),
+                       ::testing::Values(WorkerType::kServiceWorker,
+                                         WorkerType::kSharedWorker)));
 
 }  // namespace content
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc
index 4cf94cf3..296b3d1 100644
--- a/content/child/child_thread_impl.cc
+++ b/content/child/child_thread_impl.cc
@@ -198,7 +198,8 @@
 #elif defined(OS_MACOSX)
   auto* client = base::MachPortRendezvousClient::GetInstance();
   if (!client) {
-    LOG(ERROR) << "Mach rendezvous failed.";
+    LOG(ERROR) << "Mach rendezvous failed, terminating process (parent died?)";
+    base::Process::TerminateCurrentProcessImmediately(0);
     return {};
   }
   auto receive = client->TakeReceiveRight('mojo');
diff --git a/content/common/tab_switch_time_recorder.cc b/content/common/tab_switch_time_recorder.cc
index 1d051ca..9bac671 100644
--- a/content/common/tab_switch_time_recorder.cc
+++ b/content/common/tab_switch_time_recorder.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
@@ -62,6 +63,17 @@
   DCHECK(!tab_switch_start_state_);
   DCHECK(render_widget_visibility_request_timestamp_.is_null());
 
+  if (tab_switch_start_state_) {
+    // TabWasShown() is called multiple times without the tab being hidden in
+    // between. This shouldn't happen per the DCHECK above. Dump without
+    // crashing to gather more information for https://crbug.com/981757.
+    //
+    // TODO(fdoray): This code should be removed no later than August 30, 2019.
+    // https://crbug.com/981757
+    base::debug::DumpWithoutCrashing();
+    weak_ptr_factory_.InvalidateWeakPtrs();
+  }
+
   has_saved_frames_ = has_saved_frames;
   tab_switch_start_state_ = start_state;
   render_widget_visibility_request_timestamp_ =
diff --git a/content/public/browser/native_file_system_permission_context.h b/content/public/browser/native_file_system_permission_context.h
index f7af7aa..e1d4939 100644
--- a/content/public/browser/native_file_system_permission_context.h
+++ b/content/public/browser/native_file_system_permission_context.h
@@ -29,6 +29,16 @@
                           const base::FilePath& path,
                           bool is_directory) = 0;
 
+  // Displays a dialog to confirm that the user intended to give read access to
+  // a specific directory.
+  using PermissionStatus = blink::mojom::PermissionStatus;
+  virtual void ConfirmDirectoryReadAccess(
+      const url::Origin& origin,
+      const base::FilePath& path,
+      int process_id,
+      int frame_id,
+      base::OnceCallback<void(PermissionStatus)> callback) = 0;
+
  protected:
   virtual ~NativeFileSystemPermissionContext() = default;
 };
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index b0f5041..bf36de8d 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -483,6 +483,10 @@
   // directory handles.
   virtual bool HasNativeFileSystemDirectoryHandles() = 0;
 
+  // Returns the paths of all the native file system directory handles frames in
+  // this WebContents have access to.
+  virtual std::vector<base::FilePath> GetNativeFileSystemDirectoryHandles() = 0;
+
   // Indicates whether any frame in the WebContents has writable native file
   // system handles.
   virtual bool HasWritableNativeFileSystemHandles() = 0;
diff --git a/content/public/common/common_param_traits_macros.h b/content/public/common/common_param_traits_macros.h
index 8ef20bc..3e573cb8 100644
--- a/content/public/common/common_param_traits_macros.h
+++ b/content/public/common/common_param_traits_macros.h
@@ -205,6 +205,9 @@
   IPC_STRUCT_TRAITS_MEMBER(text_track_text_shadow)
   IPC_STRUCT_TRAITS_MEMBER(text_track_font_family)
   IPC_STRUCT_TRAITS_MEMBER(text_track_font_variant)
+  IPC_STRUCT_TRAITS_MEMBER(text_track_window_color)
+  IPC_STRUCT_TRAITS_MEMBER(text_track_window_padding)
+  IPC_STRUCT_TRAITS_MEMBER(text_track_window_radius)
   IPC_STRUCT_TRAITS_MEMBER(text_autosizing_enabled)
   IPC_STRUCT_TRAITS_MEMBER(double_tap_to_zoom_enabled)
   IPC_STRUCT_TRAITS_MEMBER(dont_send_key_events_to_javascript)
diff --git a/content/public/common/resource_type.h b/content/public/common/resource_type.h
index 24084d75d..9d910c8 100644
--- a/content/public/common/resource_type.h
+++ b/content/public/common/resource_type.h
@@ -11,26 +11,30 @@
 
 // Used in histograms; explicitly assign each type and do not re-use old values.
 enum class ResourceType {
-  kMainFrame = 0,           // top level page
-  kSubFrame = 1,            // frame or iframe
-  kStylesheet = 2,          // a CSS stylesheet
-  kScript = 3,              // an external script
-  kImage = 4,               // an image (jpg/gif/png/etc)
-  kFontResource = 5,        // a font
-  kSubResource = 6,         // an "other" subresource.
-  kObject = 7,              // an object (or embed) tag for a plugin.
-  kMedia = 8,               // a media resource.
-  kWorker = 9,              // the main resource of a dedicated worker.
-  kSharedWorker = 10,       // the main resource of a shared worker.
-  kPrefetch = 11,           // an explicitly requested prefetch
-  kFavicon = 12,            // a favicon
-  kXhr = 13,                // a XMLHttpRequest
-  kPing = 14,               // a ping request for <a ping>/sendBeacon.
-  kServiceWorker = 15,      // the main resource of a service worker.
-  kCspReport = 16,          // a report of Content Security Policy violations.
-  kPluginResource = 17,     // a resource that a plugin requested.
-  kNavigationPreload = 18,  // a service worker navigation preload request.
-  kMaxValue = kNavigationPreload,
+  kMainFrame = 0,        // top level page
+  kSubFrame = 1,         // frame or iframe
+  kStylesheet = 2,       // a CSS stylesheet
+  kScript = 3,           // an external script
+  kImage = 4,            // an image (jpg/gif/png/etc)
+  kFontResource = 5,     // a font
+  kSubResource = 6,      // an "other" subresource.
+  kObject = 7,           // an object (or embed) tag for a plugin.
+  kMedia = 8,            // a media resource.
+  kWorker = 9,           // the main resource of a dedicated worker.
+  kSharedWorker = 10,    // the main resource of a shared worker.
+  kPrefetch = 11,        // an explicitly requested prefetch
+  kFavicon = 12,         // a favicon
+  kXhr = 13,             // a XMLHttpRequest
+  kPing = 14,            // a ping request for <a ping>/sendBeacon.
+  kServiceWorker = 15,   // the main resource of a service worker.
+  kCspReport = 16,       // a report of Content Security Policy violations.
+  kPluginResource = 17,  // a resource that a plugin requested.
+  // kNavigationPreload = 18,  // Deprecated.
+  // a main-frame service worker navigation preload request.
+  kNavigationPreloadMainFrame = 19,
+  // a sub-frame service worker navigation preload request.
+  kNavigationPreloadSubFrame = 20,
+  kMaxValue = kNavigationPreloadSubFrame,
 };
 
 CONTENT_EXPORT bool IsResourceTypeFrame(ResourceType type);
diff --git a/content/public/common/resource_type.mojom b/content/public/common/resource_type.mojom
index 6d3c442..9c9a48d 100644
--- a/content/public/common/resource_type.mojom
+++ b/content/public/common/resource_type.mojom
@@ -24,5 +24,9 @@
   kServiceWorker = 15,      // the main resource of a service worker.
   kCspReport = 16,          // a report of Content Security Policy violations.
   kPluginResource = 17,     // a resource that a plugin requested.
-  kNavigationPreload = 18,  // a service worker navigation preload request.
+  // kNavigationPreload = 18,  // Deprecated.
+  // a main-frame service worker navigation preload request.
+  kNavigationPreloadMainFrame = 19,
+  // a sub-frame service worker navigation preload request.
+  kNavigationPreloadSubFrame = 20,
 };
diff --git a/content/public/common/resource_type_struct_traits.cc b/content/public/common/resource_type_struct_traits.cc
index 239ba20..0bd5e23 100644
--- a/content/public/common/resource_type_struct_traits.cc
+++ b/content/public/common/resource_type_struct_traits.cc
@@ -47,8 +47,10 @@
       return content::mojom::ResourceType::kCspReport;
     case content::ResourceType::kPluginResource:
       return content::mojom::ResourceType::kPluginResource;
-    case content::ResourceType::kNavigationPreload:
-      return content::mojom::ResourceType::kNavigationPreload;
+    case content::ResourceType::kNavigationPreloadMainFrame:
+      return content::mojom::ResourceType::kNavigationPreloadMainFrame;
+    case content::ResourceType::kNavigationPreloadSubFrame:
+      return content::mojom::ResourceType::kNavigationPreloadSubFrame;
   }
 
   NOTREACHED();
@@ -114,8 +116,11 @@
     case content::mojom::ResourceType::kPluginResource:
       *output = content::ResourceType::kPluginResource;
       return true;
-    case content::mojom::ResourceType::kNavigationPreload:
-      *output = content::ResourceType::kNavigationPreload;
+    case content::mojom::ResourceType::kNavigationPreloadMainFrame:
+      *output = content::ResourceType::kNavigationPreloadMainFrame;
+      return true;
+    case content::mojom::ResourceType::kNavigationPreloadSubFrame:
+      *output = content::ResourceType::kNavigationPreloadSubFrame;
       return true;
   }
   return false;
diff --git a/content/public/common/web_preferences.h b/content/public/common/web_preferences.h
index 6677d8e..1198b65 100644
--- a/content/public/common/web_preferences.h
+++ b/content/public/common/web_preferences.h
@@ -217,6 +217,15 @@
   // Specifies the value for CSS font-variant property.
   std::string text_track_font_variant;
 
+  // These fields specify values for CSS properties used to style the window
+  // around WebVTT text tracks.
+  // Window color can be any legal CSS color descriptor.
+  std::string text_track_window_color;
+  // Window padding is in em.
+  std::string text_track_window_padding;
+  // Window radius is in pixels.
+  std::string text_track_window_radius;
+
   // Specifies the margin for WebVTT text tracks as a percentage of media
   // element height/width (for horizontal/vertical text respectively).
   // Cues will not be placed in this margin area.
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index f93096e..f0427f28 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -409,8 +409,6 @@
     "storage_util.h",
     "stream_texture_host_android.cc",
     "stream_texture_host_android.h",
-    "text_input_client_observer.cc",
-    "text_input_client_observer.h",
     "theme_helper_mac.h",
     "theme_helper_mac.mm",
     "top_level_blame_context.cc",
@@ -656,6 +654,13 @@
     ]
   }
 
+  if (is_mac) {
+    sources += [
+      "text_input_client_observer.cc",
+      "text_input_client_observer.h",
+    ]
+  }
+
   if (is_fuchsia) {
     sources += [
       "render_view_fuchsia.cc",
diff --git a/content/renderer/media/android/OWNERS b/content/renderer/media/android/OWNERS
deleted file mode 100644
index 5abf5a1..0000000
--- a/content/renderer/media/android/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-qinmin@chromium.org
diff --git a/content/renderer/media/stream/media_stream_audio_processor.cc b/content/renderer/media/stream/media_stream_audio_processor.cc
index 437d207..57c858a9 100644
--- a/content/renderer/media/stream/media_stream_audio_processor.cc
+++ b/content/renderer/media/stream/media_stream_audio_processor.cc
@@ -81,19 +81,6 @@
   }
 }
 
-// Used by UMA histograms and entries shouldn't be re-ordered or removed.
-enum AudioTrackProcessingStates {
-  AUDIO_PROCESSING_ENABLED = 0,
-  AUDIO_PROCESSING_DISABLED,
-  AUDIO_PROCESSING_IN_WEBRTC,
-  AUDIO_PROCESSING_MAX
-};
-
-void RecordProcessingState(AudioTrackProcessingStates state) {
-  UMA_HISTOGRAM_ENUMERATION("Media.AudioTrackProcessingStates",
-                            state, AUDIO_PROCESSING_MAX);
-}
-
 // Checks if the default minimum starting volume value for the AGC is overridden
 // on the command line.
 base::Optional<int> GetStartupMinVolumeForAgc() {
@@ -547,7 +534,6 @@
     // Sanity-check: WouldModifyAudio() should return true iff
     // |audio_mirroring_| is true.
     DCHECK_EQ(audio_mirroring_, WouldModifyAudio(properties));
-    RecordProcessingState(AUDIO_PROCESSING_DISABLED);
     return;
   }
 
@@ -639,8 +625,6 @@
   }
 
   audio_processing_->ApplyConfig(apm_config);
-
-  RecordProcessingState(AUDIO_PROCESSING_ENABLED);
 }
 
 void MediaStreamAudioProcessor::InitializeCaptureFifo(
diff --git a/content/renderer/media/webrtc/webrtc_media_stream_track_adapter_unittest.cc b/content/renderer/media/webrtc/webrtc_media_stream_track_adapter_unittest.cc
index 0b4f4dc38..3a444f5 100644
--- a/content/renderer/media/webrtc/webrtc_media_stream_track_adapter_unittest.cc
+++ b/content/renderer/media/webrtc/webrtc_media_stream_track_adapter_unittest.cc
@@ -141,8 +141,7 @@
       track_adapter_->webrtc_track());
 }
 
-// Flaky, see https://crbug.com/982200.
-TEST_F(WebRtcMediaStreamTrackAdapterTest, DISABLED_LocalVideoTrack) {
+TEST_F(WebRtcMediaStreamTrackAdapterTest, LocalVideoTrack) {
   track_adapter_ = WebRtcMediaStreamTrackAdapter::CreateLocalTrackAdapter(
       dependency_factory_.get(), main_thread_, CreateLocalVideoTrack());
   EXPECT_TRUE(track_adapter_->is_initialized());
diff --git a/content/renderer/render_view_browsertest_mac.mm b/content/renderer/render_view_browsertest_mac.mm
index 535c15a..d9b0e8f 100644
--- a/content/renderer/render_view_browsertest_mac.mm
+++ b/content/renderer/render_view_browsertest_mac.mm
@@ -171,13 +171,13 @@
   // RenderWidget which forwards them to the TextInputClientObserver
   using Range = gfx::Range;
   using Point = gfx::Point;
-  view->OnMessageReceived(
+  view->GetWidget()->OnMessageReceived(
       TextInputClientMsg_CharacterIndexForPoint(routing_id, Point()));
-  view->OnMessageReceived(
+  view->GetWidget()->OnMessageReceived(
       TextInputClientMsg_FirstRectForCharacterRange(routing_id, Range()));
-  view->OnMessageReceived(
+  view->GetWidget()->OnMessageReceived(
       TextInputClientMsg_StringForRange(routing_id, Range()));
-  view->OnMessageReceived(
+  view->GetWidget()->OnMessageReceived(
       TextInputClientMsg_CharacterIndexForPoint(routing_id, Point()));
 }
 
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 9e03aaf40..ed526425 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -812,6 +812,12 @@
   settings->SetTextTrackFontVariant(
       WebString::FromASCII(prefs.text_track_font_variant));
   settings->SetTextTrackMarginPercentage(prefs.text_track_margin_percentage);
+  settings->SetTextTrackWindowColor(
+      WebString::FromASCII(prefs.text_track_window_color));
+  settings->SetTextTrackWindowPadding(
+      WebString::FromASCII(prefs.text_track_window_padding));
+  settings->SetTextTrackWindowRadius(
+      WebString::FromASCII(prefs.text_track_window_radius));
 
   // Needs to happen before SetDefaultPageScaleLimits below since that'll
   // recalculate the final page scale limits and that depends on this setting.
diff --git a/content/renderer/text_input_client_observer.cc b/content/renderer/text_input_client_observer.cc
index 1cfa062..21c4924 100644
--- a/content/renderer/text_input_client_observer.cc
+++ b/content/renderer/text_input_client_observer.cc
@@ -10,6 +10,7 @@
 
 #include "build/build_config.h"
 #include "content/common/text_input_client_messages.h"
+#include "content/public/renderer/render_thread.h"
 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
 #include "content/renderer/render_frame_impl.h"
 #include "content/renderer/render_view_impl.h"
@@ -39,8 +40,7 @@
 TextInputClientObserver::TextInputClientObserver(RenderWidget* render_widget)
     : render_widget_(render_widget) {}
 
-TextInputClientObserver::~TextInputClientObserver() {
-}
+TextInputClientObserver::~TextInputClientObserver() = default;
 
 bool TextInputClientObserver::OnMessageReceived(const IPC::Message& message) {
   bool handled = true;
@@ -58,33 +58,39 @@
 }
 
 bool TextInputClientObserver::Send(IPC::Message* message) {
-  return render_widget_->Send(message);
+  // This class is attached to the main frame RenderWidget, but sends and
+  // receives messages while the main frame is remote (and the RenderWidget is
+  // frozen). The messages are not received on RenderWidgetHostImpl, so there's
+  // no need to send through RenderWidget or use its routing id. We avoid this
+  // problem then by sending directly through RenderThread instead of through
+  // RenderWidget::Send().
+  // TODO(crbug.com/669219): This class should not be used while the main frame
+  // is remote.
+  return RenderThread::Get()->Send(message);
 }
 
 blink::WebFrameWidget* TextInputClientObserver::GetWebFrameWidget() const {
   blink::WebWidget* widget = render_widget_->GetWebWidget();
-  if (!widget->IsWebFrameWidget()) {
-    // When a page navigation occurs, for a brief period
-    // RenderViewImpl::GetWebWidget() will return a WebViewImpl instead of a
-    // WebViewFrameWidget. Therefore, casting to WebFrameWidget is invalid and
-    // could cause crashes. Also, WebView::mainFrame() could be a remote frame
-    // which will yield a nullptr for localRoot() (https://crbug.com/664890).
+  // While the main frame is remote, the WebWidget of the main frame's
+  // RenderWidget is not a WebFrameWidget.
+  // TODO(crbug.com/669219): The browser shouldn't be sending IPCs that land
+  // in this class when the main frame is remote.
+  // TODO(danakj): This should instead be checking:
+  //   if (render_widget_->is_frozen())
+  // But is_frozen() is currently true also for provisional frames, and in that
+  // case there is actually a WebFrameWidget to be used. In the future we should
+  // separate these states and then this can return null if frozen *and there is
+  // no provisional main frame attached to the RenderWidget*.
+  if (!widget->IsWebFrameWidget())
     return nullptr;
-  }
   return static_cast<blink::WebFrameWidget*>(widget);
 }
 
 blink::WebLocalFrame* TextInputClientObserver::GetFocusedFrame() const {
   if (auto* frame_widget = GetWebFrameWidget()) {
-    blink::WebLocalFrame* localRoot = frame_widget->LocalRoot();
-    RenderFrameImpl* render_frame = RenderFrameImpl::FromWebFrame(localRoot);
-    if (!render_frame) {
-      // TODO(ekaramad): Can this ever be nullptr? (https://crbug.com/664890).
-      return nullptr;
-    }
-    blink::WebLocalFrame* focused =
-        render_frame->render_view()->webview()->FocusedFrame();
-    return focused->LocalRoot() == localRoot ? focused : nullptr;
+    blink::WebLocalFrame* local_root = frame_widget->LocalRoot();
+    blink::WebLocalFrame* focused = local_root->View()->FocusedFrame();
+    return focused->LocalRoot() == local_root ? focused : nullptr;
   }
   return nullptr;
 }
@@ -92,28 +98,27 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
 PepperPluginInstanceImpl* TextInputClientObserver::GetFocusedPepperPlugin()
     const {
-  blink::WebLocalFrame* focusedFrame = GetFocusedFrame();
-  return focusedFrame
-             ? RenderFrameImpl::FromWebFrame(focusedFrame)
-                   ->focused_pepper_plugin()
-             : nullptr;
+  blink::WebLocalFrame* frame = GetFocusedFrame();
+  if (!frame)
+    return nullptr;
+  return RenderFrameImpl::FromWebFrame(frame)->focused_pepper_plugin();
 }
 #endif
 
 void TextInputClientObserver::OnStringAtPoint(gfx::Point point) {
 #if defined(OS_MACOSX)
-  blink::WebPoint baselinePoint;
+  blink::WebPoint baseline_point;
   NSAttributedString* string = nil;
 
   if (auto* frame_widget = GetWebFrameWidget()) {
     string = blink::WebSubstringUtil::AttributedWordAtPoint(frame_widget, point,
-                                                            baselinePoint);
+                                                            baseline_point);
   }
 
   std::unique_ptr<const mac::AttributedStringCoder::EncodedString> encoded(
       mac::AttributedStringCoder::Encode(string));
   Send(new TextInputClientReplyMsg_GotStringAtPoint(
-      render_widget_->routing_id(), *encoded.get(), baselinePoint));
+      MSG_ROUTING_NONE, *encoded.get(), baseline_point));
 #else
   NOTIMPLEMENTED();
 #endif
@@ -125,8 +130,8 @@
   if (auto* frame = GetFocusedFrame())
     index = static_cast<uint32_t>(frame->CharacterIndexForPoint(web_point));
 
-  Send(new TextInputClientReplyMsg_GotCharacterIndexForPoint(
-      render_widget_->routing_id(), index));
+  Send(new TextInputClientReplyMsg_GotCharacterIndexForPoint(MSG_ROUTING_NONE,
+                                                             index));
 }
 
 void TextInputClientObserver::OnFirstRectForCharacterRange(gfx::Range range) {
@@ -151,25 +156,25 @@
       rect = web_rect;
     }
   }
-  Send(new TextInputClientReplyMsg_GotFirstRectForRange(
-      render_widget_->routing_id(), rect));
+  Send(
+      new TextInputClientReplyMsg_GotFirstRectForRange(MSG_ROUTING_NONE, rect));
 }
 
 void TextInputClientObserver::OnStringForRange(gfx::Range range) {
 #if defined(OS_MACOSX)
-  blink::WebPoint baselinePoint;
+  blink::WebPoint baseline_point;
   NSAttributedString* string = nil;
   blink::WebLocalFrame* frame = GetFocusedFrame();
   // TODO(yabinh): Null check should not be necessary.
   // See crbug.com/304341
   if (frame) {
     string = blink::WebSubstringUtil::AttributedSubstringInRange(
-        frame, range.start(), range.length(), &baselinePoint);
+        frame, range.start(), range.length(), &baseline_point);
   }
   std::unique_ptr<const mac::AttributedStringCoder::EncodedString> encoded(
       mac::AttributedStringCoder::Encode(string));
   Send(new TextInputClientReplyMsg_GotStringForRange(
-      render_widget_->routing_id(), *encoded.get(), baselinePoint));
+      MSG_ROUTING_NONE, *encoded.get(), baseline_point));
 #else
   NOTIMPLEMENTED();
 #endif
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 88ed64c..7a867e3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -54,6 +54,8 @@
     "../browser/media/session/mock_media_session_service_impl.h",
     "../browser/native_file_system/file_system_chooser_test_helpers.cc",
     "../browser/native_file_system/file_system_chooser_test_helpers.h",
+    "../browser/native_file_system/mock_native_file_system_permission_context.cc",
+    "../browser/native_file_system/mock_native_file_system_permission_context.h",
     "../browser/native_file_system/mock_native_file_system_permission_grant.cc",
     "../browser/native_file_system/mock_native_file_system_permission_grant.h",
     "../browser/renderer_host/input/mock_input_router.cc",
@@ -983,7 +985,6 @@
     "../browser/service_worker/service_worker_browsertest.cc",
     "../browser/service_worker/service_worker_clients_api_browsertest.cc",
     "../browser/service_worker/service_worker_file_upload_browsertest.cc",
-    "../browser/service_worker/service_worker_network_isolation_key_browsertest.cc",
     "../browser/service_worker/service_worker_no_best_effort_tasks_browsertest.cc",
     "../browser/session_history_browsertest.cc",
     "../browser/shape_detection/shape_detection_browsertest.cc",
@@ -1036,6 +1037,7 @@
     "../browser/webrtc/webrtc_webcam_browsertest.h",
     "../browser/webui/web_ui_mojo_browsertest.cc",
     "../browser/worker_host/worker_browsertest.cc",
+    "../browser/worker_network_isolation_key_browsertest.cc",
     "../renderer/accessibility/render_accessibility_impl_browsertest.cc",
     "../renderer/blink_platform_audio_hardware_browsertest.cc",
     "../renderer/gin_browsertest.cc",
diff --git a/content/test/data/accessibility/css/transform-expected-uia-win.txt b/content/test/data/accessibility/css/transform-expected-uia-win.txt
new file mode 100644
index 0000000..22b62242
--- /dev/null
+++ b/content/test/data/accessibility/css/transform-expected-uia-win.txt
@@ -0,0 +1,4 @@
+document
+++group BoundingRectangle=(0, 50, 48, 18)
+++++group BoundingRectangle=(0, 50, 48, 18)
+++++++description BoundingRectangle=(0, 50, 48, 17) Name='content'
diff --git a/content/test/data/accessibility/css/transform-expected-win.txt b/content/test/data/accessibility/css/transform-expected-win.txt
new file mode 100644
index 0000000..b270fecb
--- /dev/null
+++ b/content/test/data/accessibility/css/transform-expected-win.txt
@@ -0,0 +1,4 @@
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE location=(0, 0)
+++IA2_ROLE_SECTION location=(0, 50)
+++++IA2_ROLE_PARAGRAPH location=(0, 50)
+++++++ROLE_SYSTEM_STATICTEXT name='content' location=(0, 50)
diff --git a/content/test/data/accessibility/css/transform.html b/content/test/data/accessibility/css/transform.html
index 334f1f6..9c3a47a 100644
--- a/content/test/data/accessibility/css/transform.html
+++ b/content/test/data/accessibility/css/transform.html
@@ -1,8 +1,12 @@
 <!--
 
-@BLINK-ALLOW:pageLocation=(0, 0)
-@BLINK-ALLOW:pageLocation=(0, 50)
-@WAIT-FOR:(0, 50)
+@BLINK-ALLOW:pageLocation=*
+@WIN-ALLOW:location=*
+@UIA-WIN-ALLOW:BoundingRectangle=(0, 50,*
+
+The closing parenthesis is intentionally omitted from WAIT-FOR because UIA
+represents location as part of BoundingRectangle which has four coordinates.
+@WAIT-FOR:(0, 50
 -->
 <!DOCTYPE html>
 <html>
diff --git a/content/test/data/gpu/pixel_video_mp4.html b/content/test/data/gpu/pixel_video_mp4.html
index fd842a2..574158f3 100644
--- a/content/test/data/gpu/pixel_video_mp4.html
+++ b/content/test/data/gpu/pixel_video_mp4.html
@@ -8,6 +8,7 @@
 
 <html>
 <head>
+<meta name="viewport" content="initial-scale=1">
 <title>MP4 Video test</title>
 <style type="text/css">
 .nomargin {
diff --git a/content/test/data/gpu/pixel_video_mp4_four_colors_aspect_4x3.html b/content/test/data/gpu/pixel_video_mp4_four_colors_aspect_4x3.html
index cc624f6..4cba526bb 100644
--- a/content/test/data/gpu/pixel_video_mp4_four_colors_aspect_4x3.html
+++ b/content/test/data/gpu/pixel_video_mp4_four_colors_aspect_4x3.html
@@ -8,6 +8,7 @@
 
 <html>
 <head>
+<meta name="viewport" content="initial-scale=1">
 <title>MP4 Video with Aspect 4x3 Test</title>
 <style type="text/css">
 .nomargin {
diff --git a/content/test/data/gpu/pixel_video_mp4_four_colors_rot_180.html b/content/test/data/gpu/pixel_video_mp4_four_colors_rot_180.html
index 37a0f97..eadd247 100644
--- a/content/test/data/gpu/pixel_video_mp4_four_colors_rot_180.html
+++ b/content/test/data/gpu/pixel_video_mp4_four_colors_rot_180.html
@@ -8,6 +8,7 @@
 
 <html>
 <head>
+<meta name="viewport" content="initial-scale=1">
 <title>MP4 Video with 180 Degree Rotation Test</title>
 <style type="text/css">
 .nomargin {
diff --git a/content/test/data/service_worker/empty2.html b/content/test/data/service_worker/empty2.html
deleted file mode 100644
index e69de29..0000000
--- a/content/test/data/service_worker/empty2.html
+++ /dev/null
diff --git a/content/test/data/service_worker/empty2.html.mock-http-headers b/content/test/data/workers/empty.html.mock-http-headers
similarity index 100%
rename from content/test/data/service_worker/empty2.html.mock-http-headers
rename to content/test/data/workers/empty.html.mock-http-headers
diff --git a/content/test/data/service_worker/empty.js.mock-http-headers b/content/test/data/workers/empty.js.mock-http-headers
similarity index 100%
rename from content/test/data/service_worker/empty.js.mock-http-headers
rename to content/test/data/workers/empty.js.mock-http-headers
diff --git a/content/test/data/service_worker/frame_factory.html b/content/test/data/workers/frame_factory.html
similarity index 100%
rename from content/test/data/service_worker/frame_factory.html
rename to content/test/data/workers/frame_factory.html
diff --git a/content/test/data/workers/service_worker_setup.html b/content/test/data/workers/service_worker_setup.html
index 5ead46f..bb334941 100644
--- a/content/test/data/workers/service_worker_setup.html
+++ b/content/test/data/workers/service_worker_setup.html
@@ -2,8 +2,9 @@
 <meta charset="utf-8">
 <title>register a service worker</title>
 <script>
-async function setup() {
-  await navigator.serviceWorker.register('service_worker.js');
+async function setup(script_file_arg) {
+  let script_file = script_file_arg || 'service_worker.js';
+  await navigator.serviceWorker.register(script_file);
   await navigator.serviceWorker.ready;
   return 'ok';
 }
diff --git a/content/test/data/service_worker/worker_with_import_and_fetch.js b/content/test/data/workers/worker_with_import_and_fetch.js
similarity index 100%
rename from content/test/data/service_worker/worker_with_import_and_fetch.js
rename to content/test/data/workers/worker_with_import_and_fetch.js
diff --git a/content/test/data/service_worker/worker_with_import_and_fetch_2.js b/content/test/data/workers/worker_with_import_and_fetch_2.js
similarity index 100%
rename from content/test/data/service_worker/worker_with_import_and_fetch_2.js
rename to content/test/data/workers/worker_with_import_and_fetch_2.js
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index d193d56..a847682 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -1,11 +1,11 @@
-# tags: [ android chromeos highsierra linux mac mojave win ]
+# tags: [ android chromeos highsierra linux mac mojave win win7 ]
 # tags: [ android-chromium android-webview-instrumentation debug ]
 # tags: [ skia-renderer no-skia-renderer ]
 # tags: [ use-vulkan no-use-vulkan ]
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x6821 intel intel-0xa2e intel-0x5912
-#         nvidia nvidia-0xfe9 qualcomm-adreno-(tm)-330 qualcomm-adreno-(tm)-418
-#         qualcomm-adreno-(tm)-420 qualcomm-adreno-(tm)-430
-#         qualcomm-adreno-(tm)-540
+#         nvidia nvidia-0xfe9 nvidia-0x1cb3 qualcomm-adreno-(tm)-330
+#         qualcomm-adreno-(tm)-418 qualcomm-adreno-(tm)-420
+#         qualcomm-adreno-(tm)-430 qualcomm-adreno-(tm)-540
 # ]
 # results: [ Failure RetryOnFailure Skip ]
 
@@ -151,9 +151,6 @@
 crbug.com/927107 [ android ] Pixel_CSS3DBlueBox [ Failure ]
 
 # Fail on Nexus 5, 5X, 6, 6P, 9 and Shield TV.
-crbug.com/925744 [ android ] Pixel_Video_MP4 [ Skip ]
-crbug.com/925744 [ android ] Pixel_Video_MP4_FourColors_Aspect_4x3 [ Skip ]
-crbug.com/925744 [ android ] Pixel_Video_MP4_FourColors_Rot_180 [ Skip ]
 crbug.com/925744 [ android ] Pixel_Video_MP4_FourColors_Rot_270 [ Skip ]
 crbug.com/925744 [ android ] Pixel_Video_MP4_FourColors_Rot_90 [ Skip ]
 crbug.com/925744 [ android ] Pixel_Video_VP9 [ Skip ]
@@ -219,3 +216,14 @@
 
 # Produces blank images on Intel HD 630 w/ Mesa 19.0.2
 crbug.com/976861 [ linux intel-0x5912 ] Pixel_OffscreenCanvasTransferToImageBitmap [ Skip ]
+
+# For whatever reason, these configurations are more prone to running into the
+# issue of producing valid images that haven't been seen for a long time, which
+# hits a known issue with Gold.
+crbug.com/983212 [ mac amd-0x679e ] Pixel_WebGLGreenTriangle_NonChromiumImage_AA_NoAlpha [ RetryOnFailure ]
+crbug.com/983212 [ mac amd-0x679e ] Pixel_WebGLGreenTriangle_NonChromiumImage_NoAA_Alpha [ RetryOnFailure ]
+crbug.com/983212 [ mac amd-0x679e ] Pixel_WebGLGreenTriangle_NonChromiumImage_NoAA_NoAlpha [ RetryOnFailure ]
+crbug.com/983212 [ mac amd-0x679e ] Pixel_WebGLGreenTriangle_NonChromiumImage_AA_Alpha [ RetryOnFailure ]
+crbug.com/983212 [ mac amd-0x679e ] Pixel_CSSFilterEffects_NoOverlays [ RetryOnFailure ]
+crbug.com/983212 [ win7 amd-0x6613 ] Pixel_CSS3DBlueBox [ RetryOnFailure ]
+crbug.com/983212 [ win7 nvidia-0x1cb3 ] Pixel_CSS3DBlueBox [ RetryOnFailure ]
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index 320fd6c2..3c8a8158 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -386,7 +386,7 @@
   // has already been created.
   num_did_start_navigation_called_++;
   RegisterTestThrottle(handle);
-  PrepareCompleteCallbackOnHandle(handle);
+  PrepareCompleteCallbackOnRequest();
 }
 
 void NavigationSimulatorImpl::RegisterTestThrottle(NavigationHandle* handle) {
@@ -459,7 +459,7 @@
   int previous_did_redirect_navigation_called =
       num_did_redirect_navigation_called_;
 
-  PrepareCompleteCallbackOnHandle(request_->navigation_handle());
+  PrepareCompleteCallbackOnRequest();
   NavigationRequest* request = frame_tree_node_->navigation_request();
   CHECK(request) << "Trying to redirect a navigation that does not go to the "
                     "network stack.";
@@ -523,7 +523,7 @@
     }
   }
 
-  PrepareCompleteCallbackOnHandle(request_->navigation_handle());
+  PrepareCompleteCallbackOnRequest();
   if (frame_tree_node_->navigation_request()) {
     static_cast<TestRenderFrameHost*>(frame_tree_node_->current_frame_host())
         ->PrepareForCommitDeprecatedForNavigationSimulator(
@@ -670,7 +670,7 @@
 
   state_ = FAILED;
 
-  PrepareCompleteCallbackOnHandle(handle);
+  PrepareCompleteCallbackOnRequest();
   CHECK(request_);
   TestNavigationURLLoader* url_loader =
       static_cast<TestNavigationURLLoader*>(request_->loader_for_testing());
@@ -980,7 +980,7 @@
 
   // Add a throttle to count NavigationThrottle calls count.
   RegisterTestThrottle(handle);
-  PrepareCompleteCallbackOnHandle(handle);
+  PrepareCompleteCallbackOnRequest();
 }
 
 void NavigationSimulatorImpl::DidRedirectNavigation(
@@ -1166,10 +1166,9 @@
     std::move(throttle_checks_complete_closure_).Run();
 }
 
-void NavigationSimulatorImpl::PrepareCompleteCallbackOnHandle(
-    NavigationHandleImpl* handle) {
+void NavigationSimulatorImpl::PrepareCompleteCallbackOnRequest() {
   last_throttle_check_result_.reset();
-  handle->set_complete_callback_for_testing(
+  request_->set_complete_callback_for_testing(
       base::BindOnce(&NavigationSimulatorImpl::OnThrottleChecksComplete,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index 6802ed4..4bade199 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -209,8 +209,8 @@
   void OnThrottleChecksComplete(NavigationThrottle::ThrottleCheckResult result);
 
   // Helper method to set the OnThrottleChecksComplete callback on the
-  // NavigationHandle.
-  void PrepareCompleteCallbackOnHandle(NavigationHandleImpl* handle);
+  // NavigationRequest.
+  void PrepareCompleteCallbackOnRequest();
 
   // Check if the navigation corresponds to a same-document navigation.
   // Only use on renderer-initiated navigations.
diff --git a/device/udev_linux/udev_unittest.cc b/device/udev_linux/udev_unittest.cc
index 244cd8e..80dd1c4 100644
--- a/device/udev_linux/udev_unittest.cc
+++ b/device/udev_linux/udev_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "device/udev_linux/udev.h"
+#include "device/udev_linux/udev_loader.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -18,4 +19,8 @@
   ASSERT_EQ("E-MU Systems,Inc.", UdevDecodeString("E-MU\\x20Systems\\x2cInc."));
 }
 
+TEST(UdevTest, Loader) {
+  ASSERT_NE(nullptr, UdevLoader::Get());
+}
+
 }  // namespace device
diff --git a/device/vr/windows/compositor_base.cc b/device/vr/windows/compositor_base.cc
index b2eafaf1..6eee1c3 100644
--- a/device/vr/windows/compositor_base.cc
+++ b/device/vr/windows/compositor_base.cc
@@ -491,6 +491,9 @@
       pending_frame_->frame_ready_time_ = base::TimeTicks::Now();
       if (!SubmitCompositedFrame()) {
         ExitPresent();
+        // ExitPresent() clears pending_frame_, so return here to avoid
+        // accessing it below.
+        return;
       }
     }
   }
diff --git a/docs/security/mojo.md b/docs/security/mojo.md
index ead3ae9..ae7d84b9 100644
--- a/docs/security/mojo.md
+++ b/docs/security/mojo.md
@@ -498,7 +498,7 @@
          .AssignIfValid(&alloc_size)) {
   // Safe: avoids allocating with a bogus size that overflowed to a smaller than
   // expected value.
-  mojo::ReportBadMessge("Invalid allocation size");
+  mojo::ReportBadMessage("Invalid allocation size");
 }
 
 Element* array = CreateArray(alloc_size);
diff --git a/docs/speed/apk_size_regressions.md b/docs/speed/apk_size_regressions.md
index 4b71107..cf0211b 100644
--- a/docs/speed/apk_size_regressions.md
+++ b/docs/speed/apk_size_regressions.md
@@ -21,6 +21,8 @@
  * Bisects [will not help you](https://bugs.chromium.org/p/chromium/issues/detail?id=678338).
  * For rolls, you can sometimes guess the commit(s) that caused the regression
    by looking at the `android-binary-size` trybot result for the roll commit.
+ * For V8 rolls, try checking the [V8 size graph](https://chromeperf.appspot.com/report?sid=59435a74c93b42599af4b02e2b3df765faef4685eb015f8aaaf2ecf7f4afb29c)
+   to see if any jumps correspond with a CL in the roll.
  * Otherwise, use [diagnose_bloat.py](https://chromium.googlesource.com/chromium/src/+/master/tools/binary_size/README.md#diagnose_bloat_py)
    in a [local Android checkout](https://chromium.googlesource.com/chromium/src/+/master/docs/android_build_instructions.md)
    to build all commits locally and find the culprit.
@@ -42,7 +44,7 @@
 
 * Check if the same increase happened in Monochrome.minimal.apks.
    * The goal is to ensure nothing creeps into webview unintentionally.
- 
+
 ## Step 2: File Bug or Silence Alert
 
 * If the commit message's `Binary-Size:` footer clearly justifies the size
@@ -60,7 +62,7 @@
 > Commit: **abc123abc123abc123abc123abc123abc123abcd**
 >
 > Link to size graph:
-> [https://chromeperf.appspot.com/report?sid=29a24f1d2b8b785551b26d945108889a5a5eed9a83848feb9f93ce8b58b1884d&num_points=10&rev=**$CRREV**](https://chromeperf.appspot.com/report?sid=29a24f1d2b8b785551b26d945108889a5a5eed9a83848feb9f93ce8b58b1884d&num_points=10&rev=480214)<br>
+> [https://chromeperf.appspot.com/report?sid=6269078068c45a41e23f5ee257da65d3f02da342849cdf3bde6aed0d5c61e450&num_points=10&rev=**$CRREV**](https://chromeperf.appspot.com/report?sid=6269078068c45a41e23f5ee257da65d3f02da342849cdf3bde6aed0d5c61e450&num_points=10&rev=480214)<br>
 > Link to trybot result:
 > [https://ci.chromium.org/p/chromium/builders/luci.chromium.try/android-binary-size/**$TRYJOB_NUMBER**](https://ci.chromium.org/p/chromium/builders/luci.chromium.try/android-binary-size/11111)
 >
@@ -163,7 +165,7 @@
 3) Why your commit is "worth" the size increase. For new features, feel free
    to link to a design doc (which presumably includes the motivation for adding
    the feature).
-   
+
 Close the bug as "Won't Fix".
 
 # For Binary Size Sheriffs
@@ -180,6 +182,6 @@
  * Check [alert page](https://chromeperf.appspot.com/alerts?sheriff=Binary%20Size%20Sheriff) regularly for new alerts.
  * Join [g/chrome-binary-size-alerts](https://goto.google.com/chrome-binary-size-alerts).
  * Deal with alerts as outlined above.
- 
+
 ## Step 3: Ping / Clear out Old Regression Bugs
  * https://bugs.chromium.org/p/chromium/issues/list?can=2&q=label%3DPerformance-Size+type%3DBug-Regression+resource_sizes
diff --git a/extensions/browser/api/declarative_net_request/ruleset_matcher.cc b/extensions/browser/api/declarative_net_request/ruleset_matcher.cc
index 780cae8..9c4d9de 100644
--- a/extensions/browser/api/declarative_net_request/ruleset_matcher.cc
+++ b/extensions/browser/api/declarative_net_request/ruleset_matcher.cc
@@ -37,9 +37,9 @@
   switch (type) {
     case content::ResourceType::kPrefetch:
     case content::ResourceType::kSubResource:
-    case content::ResourceType::kNavigationPreload:
       return flat_rule::ElementType_OTHER;
     case content::ResourceType::kMainFrame:
+    case content::ResourceType::kNavigationPreloadMainFrame:
       return flat_rule::ElementType_MAIN_FRAME;
     case content::ResourceType::kCspReport:
       return flat_rule::ElementType_CSP_REPORT;
@@ -59,6 +59,7 @@
     case content::ResourceType::kXhr:
       return flat_rule::ElementType_XMLHTTPREQUEST;
     case content::ResourceType::kSubFrame:
+    case content::ResourceType::kNavigationPreloadSubFrame:
       return flat_rule::ElementType_SUBDOCUMENT;
     case content::ResourceType::kPing:
       return flat_rule::ElementType_PING;
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc
index afcbdacb..f58f049 100644
--- a/extensions/browser/api/web_request/web_request_permissions.cc
+++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -249,14 +249,13 @@
 
   if (is_request_from_browser) {
     // Hide all non-navigation requests made by the browser. crbug.com/884932.
-    if (!request.is_navigation_request &&
-        request.type != content::ResourceType::kNavigationPreload) {
+    if (!request.is_navigation_request)
       return true;
-    }
 
     DCHECK(request.type == content::ResourceType::kMainFrame ||
            request.type == content::ResourceType::kSubFrame ||
-           request.type == content::ResourceType::kNavigationPreload);
+           request.type == content::ResourceType::kNavigationPreloadMainFrame ||
+           request.type == content::ResourceType::kNavigationPreloadSubFrame);
 
     // Hide sub-frame requests to clientsX.google.com.
     // TODO(crbug.com/890006): Determine if the code here can be cleaned up
diff --git a/extensions/browser/api/web_request/web_request_resource_type.cc b/extensions/browser/api/web_request/web_request_resource_type.cc
index 8a59ef2..d96632f 100644
--- a/extensions/browser/api/web_request/web_request_resource_type.cc
+++ b/extensions/browser/api/web_request/web_request_resource_type.cc
@@ -44,8 +44,10 @@
 WebRequestResourceType ToWebRequestResourceType(content::ResourceType type) {
   switch (type) {
     case content::ResourceType::kMainFrame:
+    case content::ResourceType::kNavigationPreloadMainFrame:
       return WebRequestResourceType::MAIN_FRAME;
     case content::ResourceType::kSubFrame:
+    case content::ResourceType::kNavigationPreloadSubFrame:
       return WebRequestResourceType::SUB_FRAME;
     case content::ResourceType::kStylesheet:
       return WebRequestResourceType::STYLESHEET;
@@ -78,8 +80,6 @@
       return WebRequestResourceType::CSP_REPORT;
     case content::ResourceType::kPluginResource:
       return WebRequestResourceType::OBJECT;
-    case content::ResourceType::kNavigationPreload:
-      return WebRequestResourceType::OTHER;
   }
   NOTREACHED();
   return WebRequestResourceType::OTHER;
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 2d9b502..16a1a00 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -1167,13 +1167,6 @@
                                                 std::move(withheld));
   UpdateOriginPermissions(*extension);
 
-  if (params.uses_default_policy_host_restrictions) {
-    extension->permissions_data()->SetUsesDefaultHostRestrictions();
-  } else {
-    extension->permissions_data()->SetPolicyHostRestrictions(
-        params.policy_blocked_hosts, params.policy_allowed_hosts);
-  }
-
   bindings_system_->OnExtensionPermissionsUpdated(params.extension_id);
   UpdateBindings(extension->id());
 }
diff --git a/google_apis/gaia/oauth2_token_service_delegate.h b/google_apis/gaia/oauth2_token_service_delegate.h
index 76fffa21..5f56ff2 100644
--- a/google_apis/gaia/oauth2_token_service_delegate.h
+++ b/google_apis/gaia/oauth2_token_service_delegate.h
@@ -132,14 +132,18 @@
   virtual bool FixRequestErrorIfPossible();
 
 #if defined(OS_IOS)
-  // Triggers platform specific implementation for IOS to add a given account
+  // Triggers platform specific implementation for iOS to reload all accounts
+  // from system.
+  virtual void ReloadAllAccountsFromSystem() {}
+
+  // Triggers platform specific implementation for iOS to add a given account
   // to the token service from a system account.
-  virtual void AddAccountFromSystem(const CoreAccountId& account_id) {}
+  virtual void ReloadAccountFromSystem(const CoreAccountId& account_id) {}
 #endif
 
-#if defined(OS_ANDROID) || defined(OS_IOS)
-  // Triggers platform specific implementation for Android and IOS to reload
-  // accounts from system.
+#if defined(OS_ANDROID)
+  // Triggers platform specific implementation for Android to reload accounts
+  // from system.
   virtual void ReloadAccountsFromSystem(
       const CoreAccountId& primary_account_id) {}
 #endif
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory.cc b/gpu/ipc/service/gpu_memory_buffer_factory.cc
index 46f0560e..edec577 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory.cc
@@ -44,4 +44,18 @@
 #endif
 }
 
+void GpuMemoryBufferFactory::CreateGpuMemoryBufferAsync(
+    gfx::GpuMemoryBufferId id,
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    int client_id,
+    SurfaceHandle surface_handle,
+    CreateGpuMemoryBufferAsyncCallback callback) {
+  // By default, we assume it's ok to allocate GMBs synchronously on the IO
+  // thread. However, subclasses can override this assumption.
+  std::move(callback).Run(CreateGpuMemoryBuffer(id, size, format, usage,
+                                                client_id, surface_handle));
+}
+
 }  // namespace gpu
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory.h b/gpu/ipc/service/gpu_memory_buffer_factory.h
index e603ff8..029ffb0 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory.h
+++ b/gpu/ipc/service/gpu_memory_buffer_factory.h
@@ -33,7 +33,9 @@
       viz::VulkanContextProvider* vulkan_context_provider);
 
   // Creates a new GPU memory buffer instance. A valid handle is returned on
-  // success. It can be called on any thread.
+  // success. This method is thread-safe but it should not be called on the IO
+  // thread as it can lead to deadlocks (see https://crbug.com/981721). Instead
+  // use the asynchronous version on the IO thread.
   virtual gfx::GpuMemoryBufferHandle CreateGpuMemoryBuffer(
       gfx::GpuMemoryBufferId id,
       const gfx::Size& size,
@@ -42,6 +44,20 @@
       int client_id,
       SurfaceHandle surface_handle) = 0;
 
+  // Same as above, but returns the result asynchrounously. Safe to use on the
+  // IO thread. |callback| will be called on the same thread that calls this
+  // method, and it might be called on the same call stack.
+  using CreateGpuMemoryBufferAsyncCallback =
+      base::OnceCallback<void(gfx::GpuMemoryBufferHandle)>;
+  virtual void CreateGpuMemoryBufferAsync(
+      gfx::GpuMemoryBufferId id,
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      gfx::BufferUsage usage,
+      int client_id,
+      SurfaceHandle surface_handle,
+      CreateGpuMemoryBufferAsyncCallback callback);
+
   // Destroys GPU memory buffer identified by |id|. It can be called on any
   // thread.
   virtual void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
index 70fb765..a86ac9a3 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
@@ -23,7 +23,7 @@
 namespace gpu {
 
 GpuMemoryBufferFactoryNativePixmap::GpuMemoryBufferFactoryNativePixmap()
-    : vulkan_context_provider_(nullptr) {}
+    : GpuMemoryBufferFactoryNativePixmap(nullptr) {}
 
 GpuMemoryBufferFactoryNativePixmap::GpuMemoryBufferFactoryNativePixmap(
     viz::VulkanContextProvider* vulkan_context_provider)
@@ -46,33 +46,37 @@
           ->GetSurfaceFactoryOzone()
           ->CreateNativePixmap(surface_handle, GetVulkanDevice(), size, format,
                                usage);
-  if (!pixmap.get()) {
-    DLOG(ERROR) << "Failed to create pixmap " << size.ToString() << ",  "
-                << gfx::BufferFormatToString(format) << ", usage "
-                << static_cast<int>(usage);
-    return gfx::GpuMemoryBufferHandle();
-  }
-
-  gfx::GpuMemoryBufferHandle new_handle;
-  new_handle.type = gfx::NATIVE_PIXMAP;
-  new_handle.id = id;
-  new_handle.native_pixmap_handle = pixmap->ExportHandle();
-
-  // TODO(reveman): Remove this once crbug.com/628334 has been fixed.
-  {
-    base::AutoLock lock(native_pixmaps_lock_);
-    NativePixmapMapKey key(id.id, client_id);
-    DCHECK(native_pixmaps_.find(key) == native_pixmaps_.end());
-    native_pixmaps_[key] = pixmap;
-  }
-
-  return new_handle;
+  return CreateGpuMemoryBufferFromNativePixmap(id, size, format, usage,
+                                               client_id, std::move(pixmap));
 #else
   NOTIMPLEMENTED();
   return gfx::GpuMemoryBufferHandle();
 #endif
 }
 
+void GpuMemoryBufferFactoryNativePixmap::CreateGpuMemoryBufferAsync(
+    gfx::GpuMemoryBufferId id,
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    int client_id,
+    SurfaceHandle surface_handle,
+    CreateGpuMemoryBufferAsyncCallback callback) {
+#if defined(USE_OZONE)
+  ui::OzonePlatform::GetInstance()
+      ->GetSurfaceFactoryOzone()
+      ->CreateNativePixmapAsync(
+          surface_handle, GetVulkanDevice(), size, format, usage,
+          base::BindOnce(
+              &GpuMemoryBufferFactoryNativePixmap::OnNativePixmapCreated, id,
+              size, format, usage, client_id, std::move(callback),
+              weak_factory_.GetWeakPtr()));
+#else
+  NOTIMPLEMENTED();
+  std::move(callback).Run(gfx::GpuMemoryBufferHandle());
+#endif
+}
+
 void GpuMemoryBufferFactoryNativePixmap::DestroyGpuMemoryBuffer(
     gfx::GpuMemoryBufferId id,
     int client_id) {
@@ -183,4 +187,53 @@
              : VK_NULL_HANDLE;
 }
 
+// static
+void GpuMemoryBufferFactoryNativePixmap::OnNativePixmapCreated(
+    gfx::GpuMemoryBufferId id,
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    int client_id,
+    CreateGpuMemoryBufferAsyncCallback callback,
+    base::WeakPtr<GpuMemoryBufferFactoryNativePixmap> weak_ptr,
+    scoped_refptr<gfx::NativePixmap> pixmap) {
+  if (weak_ptr) {
+    std::move(callback).Run(weak_ptr->CreateGpuMemoryBufferFromNativePixmap(
+        id, size, format, usage, client_id, pixmap));
+  } else {
+    std::move(callback).Run(gfx::GpuMemoryBufferHandle());
+  }
+}
+
+gfx::GpuMemoryBufferHandle
+GpuMemoryBufferFactoryNativePixmap::CreateGpuMemoryBufferFromNativePixmap(
+    gfx::GpuMemoryBufferId id,
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    int client_id,
+    scoped_refptr<gfx::NativePixmap> pixmap) {
+  if (!pixmap.get()) {
+    DLOG(ERROR) << "Failed to create pixmap " << size.ToString() << ",  "
+                << gfx::BufferFormatToString(format) << ", usage "
+                << static_cast<int>(usage);
+    return gfx::GpuMemoryBufferHandle();
+  }
+
+  gfx::GpuMemoryBufferHandle new_handle;
+  new_handle.type = gfx::NATIVE_PIXMAP;
+  new_handle.id = id;
+  new_handle.native_pixmap_handle = pixmap->ExportHandle();
+
+  // TODO(reveman): Remove this once crbug.com/628334 has been fixed.
+  {
+    base::AutoLock lock(native_pixmaps_lock_);
+    NativePixmapMapKey key(id.id, client_id);
+    DCHECK(native_pixmaps_.find(key) == native_pixmaps_.end());
+    native_pixmaps_[key] = pixmap;
+  }
+
+  return new_handle;
+}
+
 }  // namespace gpu
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h
index b3692aed..10aed05 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.h
@@ -41,6 +41,14 @@
       gfx::BufferUsage usage,
       int client_id,
       SurfaceHandle surface_handle) override;
+  void CreateGpuMemoryBufferAsync(
+      gfx::GpuMemoryBufferId id,
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      gfx::BufferUsage usage,
+      int client_id,
+      SurfaceHandle surface_handle,
+      CreateGpuMemoryBufferAsyncCallback callback) override;
   void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
                               int client_id) override;
   ImageFactory* AsImageFactory() override;
@@ -66,6 +74,24 @@
                                              scoped_refptr<gfx::NativePixmap>,
                                              NativePixmapMapKeyHash>;
 
+  static void OnNativePixmapCreated(
+      gfx::GpuMemoryBufferId id,
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      gfx::BufferUsage usage,
+      int client_id,
+      CreateGpuMemoryBufferAsyncCallback callback,
+      base::WeakPtr<GpuMemoryBufferFactoryNativePixmap> weak_ptr,
+      scoped_refptr<gfx::NativePixmap> pixmap);
+
+  gfx::GpuMemoryBufferHandle CreateGpuMemoryBufferFromNativePixmap(
+      gfx::GpuMemoryBufferId id,
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      gfx::BufferUsage usage,
+      int client_id,
+      scoped_refptr<gfx::NativePixmap> pixmap);
+
   VkDevice GetVulkanDevice();
 
   scoped_refptr<viz::VulkanContextProvider> vulkan_context_provider_;
@@ -73,6 +99,8 @@
   NativePixmapMap native_pixmaps_;
   base::Lock native_pixmaps_lock_;
 
+  base::WeakPtrFactory<GpuMemoryBufferFactoryNativePixmap> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(GpuMemoryBufferFactoryNativePixmap);
 };
 
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 87e1800..09ae1be3 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -2308,11 +2308,9 @@
       name: "GPU FYI Win x64 dEQP Builder"
       mixins: "win-gpu-fyi-ci"
     }
-    # Can't use builderless "win-gpu-fyi-ci" for perf bulder. crbug.com/977735
     builders {
       name: "GPU FYI XR Win Builder"
-      mixins: "win"
-      mixins: "gpu-fyi-ci"
+      mixins: "win-gpu-fyi-ci"
     }
     builders {
       name: "Win10 FYI Debug (NVIDIA)"
@@ -4408,6 +4406,10 @@
 
     builders {
       mixins: "win-optional-gpu-try"
+      name: "gpu-fyi-try-win-xr-builder"
+    }
+    builders {
+      mixins: "win-optional-gpu-try"
       name: "gpu-fyi-try-win7-amd-dbg"
     }
     builders {
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index e43da2d..8eadd0a 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -3114,7 +3114,6 @@
     category: "Linux"
   }
   builders {
-    name: "buildbot/chromium.gpu/Android Release (Nexus 5X)"
     name: "buildbucket/luci.chromium.ci/Android Release (Nexus 5X)"
     category: "Android"
   }
@@ -4450,6 +4449,9 @@
   refs: "refs/heads/master"
   manifest_name: "REVISION"
   builders {
+    name: "buildbucket/luci.chromium.try/gpu-fyi-try-win-xr-builder"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-win7-amd-dbg"
   }
   builders {
@@ -4788,6 +4790,9 @@
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-linux-nvidia-tsn"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/gpu-fyi-try-win-xr-builder"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-win7-amd-dbg"
   }
   builders {
diff --git a/ios/chrome/browser/payments/ios_payment_instrument_finder.h b/ios/chrome/browser/payments/ios_payment_instrument_finder.h
index d90170f..fc5bfa7 100644
--- a/ios/chrome/browser/payments/ios_payment_instrument_finder.h
+++ b/ios/chrome/browser/payments/ios_payment_instrument_finder.h
@@ -108,7 +108,8 @@
   // this manifest.
   void OnPaymentManifestDownloaded(const GURL& method,
                                    const GURL& method_url_after_redirects,
-                                   const std::string& content);
+                                   const std::string& content,
+                                   const std::string& error_message);
 
   // Parses a payment method manifest for its default applications and gets all
   // the valid ones. |input| is the json encoded payment method manifest to
@@ -127,7 +128,8 @@
       const GURL& method,
       const GURL& web_app_manifest_url,
       const GURL& web_app_manifest_url_after_redirects,
-      const std::string& content);
+      const std::string& content,
+      const std::string& error_message);
 
   // Parses a web app manifest for its name, icon, and universal link. |input|
   // is the json encoded web app manifest to parse. |web_app_manifest_url|
diff --git a/ios/chrome/browser/payments/ios_payment_instrument_finder.mm b/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
index c45cd80..53f50ec 100644
--- a/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
+++ b/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
@@ -126,7 +126,8 @@
 void IOSPaymentInstrumentFinder::OnPaymentManifestDownloaded(
     const GURL& method,
     const GURL& method_url_after_redirects,
-    const std::string& content) {
+    const std::string& content,
+    const std::string& error_message) {
   // If |content| is empty then the download failed.
   if (content.empty()) {
     OnPaymentInstrumentProcessed();
@@ -212,7 +213,8 @@
     const GURL& method,
     const GURL& web_app_manifest_url,
     const GURL& web_app_manifest_url_after_redirects,
-    const std::string& content) {
+    const std::string& content,
+    const std::string& error_message) {
   // If |content| is empty then the download failed.
   if (content.empty()) {
     OnPaymentInstrumentProcessed();
diff --git a/ios/chrome/browser/payments/ios_payment_instrument_finder_unittest.mm b/ios/chrome/browser/payments/ios_payment_instrument_finder_unittest.mm
index 743ef823..36905ede 100644
--- a/ios/chrome/browser/payments/ios_payment_instrument_finder_unittest.mm
+++ b/ios/chrome/browser/payments/ios_payment_instrument_finder_unittest.mm
@@ -142,7 +142,8 @@
     ios_payment_instrument_finder_->num_instruments_to_find_ = 1;
     GURL web_app_manifest_url("https://bobpay.xyz/bob/manifest.json");
     ios_payment_instrument_finder_->OnWebAppManifestDownloaded(
-        method, web_app_manifest_url, web_app_manifest_url, content);
+        method, web_app_manifest_url, web_app_manifest_url, content,
+        /*error_message=*/"");
   }
 
   void RunLoop() {
diff --git a/ios/chrome/browser/signin/authentication_service.h b/ios/chrome/browser/signin/authentication_service.h
index 695d3b774..45393ef 100644
--- a/ios/chrome/browser/signin/authentication_service.h
+++ b/ios/chrome/browser/signin/authentication_service.h
@@ -86,11 +86,13 @@
   virtual ChromeIdentity* GetAuthenticatedIdentity() const;
 
   // Signs |identity| in to Chrome with |hosted_domain| as its hosted domain,
-  // pauses sync and logs |identity| in to http://google.com. If |identity| has
-  // no hosted domain, |hosted_domain| should be empty.
+  // pauses sync and logs |identity| in to http://google.com.
   // Virtual for testing.
-  virtual void SignIn(ChromeIdentity* identity,
-                      const std::string& hosted_domain);
+  virtual void SignIn(ChromeIdentity* identity);
+
+  // Old and deprecated override of SignIn(ChromeIdentity* identity). Will be
+  // removed once all downstream code has been converted to use the new method.
+  void SignIn(ChromeIdentity* identity, const std::string&);
 
   // Signs the authenticated user out of Chrome.
   // Virtual for testing.
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 3a7a83f..59fb44b 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -292,9 +292,7 @@
       ->GetIdentityWithGaiaID(authenticated_gaia_id);
 }
 
-void AuthenticationService::SignIn(ChromeIdentity* identity,
-                                   const std::string& hosted_domain) {
-  DCHECK(!hosted_domain.empty());
+void AuthenticationService::SignIn(ChromeIdentity* identity) {
   DCHECK(ios::GetChromeBrowserProvider()
              ->GetChromeIdentityService()
              ->IsValidIdentity(identity));
@@ -312,12 +310,13 @@
       ->ReloadAllAccountsFromSystem();
 
   // Ensure that the account the user is trying to sign into has been loaded
-  // from the SSO library and that hosted_domain is set to the provided value.
+  // from the SSO library and that hosted_domain is set (should be the proper
+  // hosted domain or kNoHostedDomainFound that are both non-empty strings).
   const base::Optional<AccountInfo> account_info =
       identity_manager_->FindAccountInfoForAccountWithRefreshTokenByAccountId(
           account_id);
   CHECK(account_info.has_value());
-  CHECK_EQ(hosted_domain, account_info->hosted_domain);
+  CHECK(!account_info->hosted_domain.empty());
 
   // |PrimaryAccountManager::SetAuthenticatedAccountId| simply ignores the call
   // if there is already a signed in user. Check that there is no signed in
@@ -345,6 +344,11 @@
   breakpad_helper::SetCurrentlySignedIn(true);
 }
 
+void AuthenticationService::SignIn(ChromeIdentity* identity,
+                                   const std::string&) {
+  SignIn(identity);
+}
+
 void AuthenticationService::SignOut(
     signin_metrics::ProfileSignout signout_source,
     ProceduralBlock completion) {
diff --git a/ios/chrome/browser/signin/authentication_service_fake.h b/ios/chrome/browser/signin/authentication_service_fake.h
index 9361ac0..73247a0 100644
--- a/ios/chrome/browser/signin/authentication_service_fake.h
+++ b/ios/chrome/browser/signin/authentication_service_fake.h
@@ -26,8 +26,7 @@
 
   ~AuthenticationServiceFake() override;
 
-  void SignIn(ChromeIdentity* identity,
-              const std::string& hosted_domain) override;
+  void SignIn(ChromeIdentity* identity) override;
 
   void SignOut(signin_metrics::ProfileSignout signout_source,
                ProceduralBlock completion) override;
diff --git a/ios/chrome/browser/signin/authentication_service_fake.mm b/ios/chrome/browser/signin/authentication_service_fake.mm
index 8265a46c..58ee9b3 100644
--- a/ios/chrome/browser/signin/authentication_service_fake.mm
+++ b/ios/chrome/browser/signin/authentication_service_fake.mm
@@ -33,8 +33,7 @@
 
 AuthenticationServiceFake::~AuthenticationServiceFake() {}
 
-void AuthenticationServiceFake::SignIn(ChromeIdentity* identity,
-                                       const std::string& hosted_domain) {
+void AuthenticationServiceFake::SignIn(ChromeIdentity* identity) {
   // Needs to call PrepareForFirstSyncSetup to behave like
   // AuthenticationService.
   sync_setup_service_->PrepareForFirstSyncSetup();
diff --git a/ios/chrome/browser/signin/authentication_service_unittest.mm b/ios/chrome/browser/signin/authentication_service_unittest.mm
index 066b7956..5b768f1 100644
--- a/ios/chrome/browser/signin/authentication_service_unittest.mm
+++ b/ios/chrome/browser/signin/authentication_service_unittest.mm
@@ -237,7 +237,7 @@
 TEST_F(AuthenticationServiceTest, TestSignInAndGetAuthenticatedIdentity) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   EXPECT_NSEQ(identity(0),
               authentication_service()->GetAuthenticatedIdentity());
@@ -273,7 +273,7 @@
 
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   EXPECT_CALL(*sync_setup_service_mock(), HasFinishedInitialSetup())
       .WillOnce(Return(true));
@@ -293,7 +293,7 @@
 
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   EXPECT_CALL(*sync_setup_service_mock(), HasFinishedInitialSetup())
       .WillOnce(Invoke(
@@ -317,7 +317,7 @@
 
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   EXPECT_CALL(*sync_setup_service_mock(), HasFinishedInitialSetup())
       .WillOnce(Return(false));
@@ -332,7 +332,7 @@
 TEST_F(AuthenticationServiceTest, TestHandleForgottenIdentityNoPromptSignIn) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   // Set the authentication service as "In Foreground", remove identity and run
   // the loop.
@@ -351,7 +351,7 @@
 TEST_F(AuthenticationServiceTest, TestHandleForgottenIdentityPromptSignIn) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   // Set the authentication service as "In Background", remove identity and run
   // the loop.
@@ -373,7 +373,7 @@
 
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   // Store the accounts and get them back from the prefs. They should be the
   // same as the token service accounts.
@@ -401,7 +401,7 @@
        OnApplicationEnterForegroundReloadCredentials) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   identity_service()->AddIdentities(@[ @"foo3" ]);
 
@@ -463,7 +463,7 @@
 
 TEST_F(AuthenticationServiceTest, HaveAccountsNotChanged) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   identity_service()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged();
@@ -478,7 +478,7 @@
 
 TEST_F(AuthenticationServiceTest, HaveAccountsChanged) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   identity_service()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged();
@@ -495,7 +495,7 @@
 
 TEST_F(AuthenticationServiceTest, HaveAccountsChangedBackground) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   identity_service()->AddIdentities(@[ @"foo3" ]);
   FireIdentityListChanged();
@@ -513,7 +513,7 @@
 TEST_F(AuthenticationServiceTest, IsAuthenticatedBackground) {
   // Sign in.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   EXPECT_TRUE(authentication_service()->IsAuthenticated());
 
   // Remove the signed in identity while in background, and check that
@@ -542,7 +542,7 @@
 
   // Sign in user emails as account ids.
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   std::vector<std::string> accounts_in_prefs = GetAccountsInPrefs();
   ASSERT_EQ(2U, accounts_in_prefs.size());
   EXPECT_EQ("foo2@foo.com", accounts_in_prefs[0]);
@@ -582,7 +582,7 @@
   TestIdentityManagerObserver observer(identity_manager());
 
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   EXPECT_EQ(2, observer.refresh_token_available_count());
 
   NSDictionary* user_info = [NSDictionary dictionary];
@@ -611,7 +611,7 @@
   TestIdentityManagerObserver observer(identity_manager());
 
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
 
   NSDictionary* user_info = [NSDictionary dictionary];
   SetCachedMDMInfo(identity(0), user_info);
@@ -628,7 +628,7 @@
 // to MDM service when necessary.
 TEST_F(AuthenticationServiceTest, HandleMDMNotification) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   identity::UpdatePersistentErrorOfRefreshTokenForAccount(
@@ -664,7 +664,7 @@
 // the primary account is blocked.
 TEST_F(AuthenticationServiceTest, HandleMDMBlockedNotification) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   identity::UpdatePersistentErrorOfRefreshTokenForAccount(
@@ -722,7 +722,7 @@
 // corresponding error for the account.
 TEST_F(AuthenticationServiceTest, ShowMDMErrorDialog) {
   SetExpectationsForSignIn();
-  authentication_service()->SignIn(identity(0), kNoHostedDomainFound);
+  authentication_service()->SignIn(identity(0));
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
   identity::UpdatePersistentErrorOfRefreshTokenForAccount(
diff --git a/ios/chrome/browser/ui/activity_services/activity_service_controller_unittest.mm b/ios/chrome/browser/ui/activity_services/activity_service_controller_unittest.mm
index 6b7664a..dfcd836 100644
--- a/ios/chrome/browser/ui/activity_services/activity_service_controller_unittest.mm
+++ b/ios/chrome/browser/ui/activity_services/activity_service_controller_unittest.mm
@@ -34,6 +34,7 @@
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
+#import "ios/web/public/test/fakes/test_web_state.h"
 #include "ios/web/public/test/test_web_thread_bundle.h"
 #include "testing/gtest_mac.h"
 #include "testing/platform_test.h"
@@ -192,6 +193,13 @@
         [[UIViewController alloc] initWithNibName:nil bundle:nil];
     [[UIApplication sharedApplication] keyWindow].rootViewController =
         parentController_;
+    // Setting the |test_web_state_| to incognito to avoid using the snapshot
+    // genrator to create a thumbnail via the |thumbnail_generator_|.
+    test_web_state_.SetBrowserState(
+        chrome_browser_state_->GetOffTheRecordChromeBrowserState());
+    thumbnail_generator_ = [[ChromeActivityItemThumbnailGenerator alloc]
+        initWithWebState:&test_web_state_];
+
     shareData_ =
         [[ShareToData alloc] initWithShareURL:GURL("https://chromium.org")
                                    visibleURL:GURL("https://chromium.org")
@@ -200,7 +208,7 @@
                               isPagePrintable:YES
                              isPageSearchable:YES
                                     userAgent:web::UserAgentType::MOBILE
-                           thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                           thumbnailGenerator:thumbnail_generator_];
   }
 
   void TearDown() override {
@@ -208,10 +216,6 @@
     PlatformTest::TearDown();
   }
 
-  ThumbnailGeneratorBlock DummyThumbnailGeneratorBlock() {
-    return ^UIImage*(CGSize const& size) { return nil; };
-  }
-
   BOOL ArrayContainsImageSource(NSArray* array) {
     for (NSObject* item in array) {
       if ([item class] == [UIActivityImageSource class]) {
@@ -319,6 +323,8 @@
   ShareToData* shareData_;
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
   bookmarks::BookmarkModel* bookmark_model_;
+  ChromeActivityItemThumbnailGenerator* thumbnail_generator_;
+  web::TestWebState test_web_state_;
 };
 
 TEST_F(ActivityServiceControllerTest, PresentAndDismissController) {
@@ -364,7 +370,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::DESKTOP
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
   NSArray* items = [activityController activityItemsForData:data];
   NSString* findLoginAction =
       (NSString*)activity_services::kUTTypeAppExtensionFindLoginAction;
@@ -428,7 +434,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::DESKTOP
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
   NSArray* items = [activityController activityItemsForData:data];
   NSString* shareAction = @"com.apple.UIKit.activity.PostToFacebook";
   NSArray* urlItems =
@@ -532,7 +538,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
 
   NSArray* items =
       [activityController applicationActivitiesForData:data
@@ -551,7 +557,7 @@
          isPagePrintable:NO
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
                                              bookmarkModel:bookmark_model_
@@ -575,7 +581,7 @@
                             isPagePrintable:YES
                            isPageSearchable:YES
                                   userAgent:web::UserAgentType::MOBILE
-                         thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                         thumbnailGenerator:thumbnail_generator_];
 
   NSArray* items =
       [activityController applicationActivitiesForData:data
@@ -592,7 +598,7 @@
                                isPagePrintable:YES
                               isPageSearchable:YES
                                      userAgent:web::UserAgentType::MOBILE
-                            thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                            thumbnailGenerator:thumbnail_generator_];
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
                                              bookmarkModel:bookmark_model_
@@ -614,7 +620,7 @@
                             isPagePrintable:YES
                            isPageSearchable:YES
                                   userAgent:web::UserAgentType::NONE
-                         thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                         thumbnailGenerator:thumbnail_generator_];
 
   NSArray* items =
       [activityController applicationActivitiesForData:data
@@ -642,7 +648,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
                                              bookmarkModel:bookmark_model_
@@ -672,7 +678,7 @@
                             isPagePrintable:YES
                            isPageSearchable:YES
                                   userAgent:web::UserAgentType::MOBILE
-                         thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                         thumbnailGenerator:thumbnail_generator_];
   id mockDispatcher = OCMProtocolMock(@protocol(BrowserCommands));
   OCMExpect([mockDispatcher requestDesktopSite]);
   NSArray* items =
@@ -698,7 +704,7 @@
                                isPagePrintable:YES
                               isPageSearchable:YES
                                      userAgent:web::UserAgentType::DESKTOP
-                            thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                            thumbnailGenerator:thumbnail_generator_];
   mockDispatcher = OCMProtocolMock(@protocol(BrowserCommands));
   OCMExpect([mockDispatcher requestMobileSite]);
   items = [activityController applicationActivitiesForData:data
@@ -816,7 +822,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
 
   NSArray* items =
       [activityController applicationActivitiesForData:data
@@ -835,7 +841,7 @@
          isPagePrintable:YES
         isPageSearchable:NO
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
                                              bookmarkModel:bookmark_model_
@@ -859,7 +865,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
 
   NSArray* items =
       [activityController applicationActivitiesForData:data
@@ -882,7 +888,7 @@
          isPagePrintable:YES
         isPageSearchable:YES
                userAgent:web::UserAgentType::NONE
-      thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+      thumbnailGenerator:thumbnail_generator_];
 
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
@@ -900,7 +906,7 @@
                                isPagePrintable:YES
                               isPageSearchable:YES
                                      userAgent:web::UserAgentType::NONE
-                            thumbnailGenerator:DummyThumbnailGeneratorBlock()];
+                            thumbnailGenerator:thumbnail_generator_];
   items = [activityController applicationActivitiesForData:data
                                                 dispatcher:nil
                                              bookmarkModel:bookmark_model_
diff --git a/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.h b/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.h
index e6aa5ab..f90c723 100644
--- a/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.h
+++ b/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.h
@@ -33,7 +33,8 @@
 - (instancetype)initWithShareURL:(NSURL*)shareURL
               passwordManagerURL:(NSURL*)passwordManagerURL
                          subject:(NSString*)subject
-              thumbnailGenerator:(ThumbnailGeneratorBlock)thumbnailGenerator;
+              thumbnailGenerator:
+                  (ChromeActivityItemThumbnailGenerator*)thumbnailGenerator;
 
 @end
 
diff --git a/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.mm b/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.mm
index 42468f4..8529371 100644
--- a/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.mm
+++ b/ios/chrome/browser/ui/activity_services/chrome_activity_item_source.mm
@@ -52,7 +52,7 @@
 
 @interface UIActivityURLSource () {
   NSString* _subject;
-  ThumbnailGeneratorBlock _thumbnailGenerator;
+  ChromeActivityItemThumbnailGenerator* _thumbnailGenerator;
 }
 
 // URL to be shared with share extensions.
@@ -70,7 +70,8 @@
 - (instancetype)initWithShareURL:(NSURL*)shareURL
               passwordManagerURL:(NSURL*)passwordManagerURL
                          subject:(NSString*)subject
-              thumbnailGenerator:(ThumbnailGeneratorBlock)thumbnailGenerator {
+              thumbnailGenerator:
+                  (ChromeActivityItemThumbnailGenerator*)thumbnailGenerator {
   DCHECK(shareURL);
   DCHECK(passwordManagerURL);
   DCHECK(subject);
@@ -151,7 +152,7 @@
                 (UIActivityViewController*)activityViewController
      thumbnailImageForActivityType:(UIActivityType)activityType
                      suggestedSize:(CGSize)size {
-  return _thumbnailGenerator(size);
+  return [_thumbnailGenerator thumbnailWithSize:size];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h
index 5c4d7c3..c5a0eaae 100644
--- a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h
+++ b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.h
@@ -8,16 +8,24 @@
 #import <CoreGraphics/CoreGraphics.h>
 #import <UIKit/UIKit.h>
 
-@class Tab;
+namespace web {
+class WebState;
+}
 
-// Block returning a thumbnail at the specified size. May return nil.
-typedef UIImage* (^ThumbnailGeneratorBlock)(const CGSize&);
+// ChromeActivityItemThumbnailGenerator will be used to retrieve activity items
+// thumbnail given WebState.
+@interface ChromeActivityItemThumbnailGenerator : NSObject
 
-namespace activity_services {
+// Default initializer. |webState| must not be nullptr.
+- (instancetype)initWithWebState:(web::WebState*)webState
+    NS_DESIGNATED_INITIALIZER;
 
-// Returns a thumbnail generator for the tab |tab|. |tab| must not be nil.
-ThumbnailGeneratorBlock ThumbnailGeneratorForTab(Tab* tab);
+// ChromeActivityItemThumbnailGenerator must be created with a WebState.
+- (instancetype)init NS_UNAVAILABLE;
 
-}  // namespace activity_services
+// Returns a thumbnail at the specified size. May return nil.
+- (UIImage*)thumbnailWithSize:(const CGSize&)size;
+
+@end
 
 #endif  // IOS_CHROME_BROWSER_UI_ACTIVITY_SERVICES_CHROME_ACTIVITY_ITEM_THUMBNAIL_GENERATOR_H_
diff --git a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.mm b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.mm
index 9d936a9..e7e6285d 100644
--- a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.mm
+++ b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator.mm
@@ -6,38 +6,68 @@
 
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/web/public/web_state/web_state.h"
+#include "ios/web/public/web_state/web_state_observer.h"
+#import "ios/web/public/web_state/web_state_observer_bridge.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-namespace activity_services {
+@interface ChromeActivityItemThumbnailGenerator () <CRWWebStateObserver> {
+  // WebState to be used for generating the snapshot.
+  web::WebState* _webState;
+  // Bridges WebStateObserver methods to this object.
+  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
+}
+@end
 
-ThumbnailGeneratorBlock ThumbnailGeneratorForTab(Tab* tab) {
-  DCHECK(tab);
-  DCHECK(tab.webState);
-  // Do not generate thumbnails for incognito tabs.
-  if (tab.webState->GetBrowserState()->IsOffTheRecord()) {
-    return ^UIImage*(CGSize const& size) { return nil; };
-  } else {
-    __weak Tab* weakTab = tab;
-    return ^UIImage*(CGSize const& size) {
-      Tab* strongTab = weakTab;
-      if (!strongTab || !strongTab.webState)
-        return nil;
+@implementation ChromeActivityItemThumbnailGenerator
 
-      UIImage* snapshot = SnapshotTabHelper::FromWebState(strongTab.webState)
-                              ->GenerateSnapshotWithoutOverlays();
+#pragma mark - Initializers
 
-      if (!snapshot)
-        return nil;
-
-      return ResizeImage(snapshot, size, ProjectionMode::kAspectFillAlignTop,
-                         /*opaque=*/YES);
-    };
+- (instancetype)initWithWebState:(web::WebState*)webState {
+  DCHECK(webState);
+  self = [super init];
+  if (self) {
+    // Thumbnail shouldn't be generated for incognito tabs. So there is no need
+    // to observe the webState.
+    if (webState->GetBrowserState()->IsOffTheRecord())
+      return self;
+    _webState = webState;
+    _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
+    _webState->AddObserver(_webStateObserver.get());
   }
+  return self;
 }
 
-}  // namespace activity_services
+- (void)dealloc {
+  if (_webState)
+    _webState->RemoveObserver(_webStateObserver.get());
+}
+
+#pragma mark - Public methods
+
+- (UIImage*)thumbnailWithSize:(const CGSize&)size {
+  if (!_webState)
+    return nil;
+  UIImage* snapshot = SnapshotTabHelper::FromWebState(_webState)
+                          ->GenerateSnapshotWithoutOverlays();
+  if (!snapshot)
+    return nil;
+  return ResizeImage(snapshot, size, ProjectionMode::kAspectFillAlignTop,
+                     /*opaque=*/YES);
+}
+
+#pragma mark - Private methods
+#pragma mark - CRWWebStateObserver protocol
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+  DCHECK_EQ(_webState, webState);
+  _webState->RemoveObserver(_webStateObserver.get());
+  _webStateObserver.reset();
+  _webState = nullptr;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator_unittest.mm b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator_unittest.mm
index b4258ac5..6289d8ec 100644
--- a/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator_unittest.mm
+++ b/ios/chrome/browser/ui/activity_services/chrome_activity_item_thumbnail_generator_unittest.mm
@@ -8,10 +8,8 @@
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#include "ios/chrome/browser/tabs/tab.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
 #include "testing/platform_test.h"
-#import "third_party/ocmock/OCMock/OCMock.h"
 #include "ui/base/test/ios/ui_image_test_utils.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -34,15 +32,10 @@
     SnapshotTabHelper::FromWebState(&test_web_state_)->SetDelegate(delegate_);
   }
 
-  Tab* CreateMockTabForThumbnail(bool incognito) {
+  void SetIncognito(bool incognito) {
     test_web_state_.SetBrowserState(
         incognito ? chrome_browser_state_->GetOffTheRecordChromeBrowserState()
                   : chrome_browser_state_.get());
-
-    web::WebState* web_state = &test_web_state_;
-    id tab = [OCMockObject niceMockForClass:[Tab class]];
-    OCMStub([tab webState]).andReturn(web_state);
-    return tab;
   }
 
   FakeSnapshotGeneratorDelegate* delegate_ = nil;
@@ -51,26 +44,30 @@
   web::TestWebState test_web_state_;
 };
 
-TEST_F(ChromeActivityItemThumbnailGeneratorTest, ThumbnailForNonIncognitoTab) {
-  Tab* tab = CreateMockTabForThumbnail(/*incognito=*/false);
+TEST_F(ChromeActivityItemThumbnailGeneratorTest,
+       ThumbnailForNonIncognitoWebState) {
+  SetIncognito(/*incognito=*/false);
 
   CGSize size = CGSizeMake(50, 50);
-  ThumbnailGeneratorBlock generatorBlock =
-      activity_services::ThumbnailGeneratorForTab(tab);
-  EXPECT_TRUE(generatorBlock);
-  UIImage* thumbnail = generatorBlock(size);
+  ChromeActivityItemThumbnailGenerator* generator =
+      [[ChromeActivityItemThumbnailGenerator alloc]
+          initWithWebState:&test_web_state_];
+  EXPECT_TRUE(generator);
+  UIImage* thumbnail = [generator thumbnailWithSize:size];
   EXPECT_TRUE(thumbnail);
   EXPECT_TRUE(CGSizeEqualToSize(thumbnail.size, size));
 }
 
-TEST_F(ChromeActivityItemThumbnailGeneratorTest, NoThumbnailForIncognitoTab) {
-  Tab* tab = CreateMockTabForThumbnail(/*incognito=*/true);
+TEST_F(ChromeActivityItemThumbnailGeneratorTest,
+       NoThumbnailForIncognitoWebState) {
+  SetIncognito(/*incognito=*/true);
 
   CGSize size = CGSizeMake(50, 50);
-  ThumbnailGeneratorBlock generatorBlock =
-      activity_services::ThumbnailGeneratorForTab(tab);
-  EXPECT_TRUE(generatorBlock);
-  UIImage* thumbnail = generatorBlock(size);
+  ChromeActivityItemThumbnailGenerator* generator =
+      [[ChromeActivityItemThumbnailGenerator alloc]
+          initWithWebState:&test_web_state_];
+  EXPECT_TRUE(generator);
+  UIImage* thumbnail = [generator thumbnailWithSize:size];
   EXPECT_FALSE(thumbnail);
 }
 
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data.h b/ios/chrome/browser/ui/activity_services/share_to_data.h
index 1f439ec0..f94a617 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data.h
+++ b/ios/chrome/browser/ui/activity_services/share_to_data.h
@@ -21,7 +21,8 @@
        isPagePrintable:(BOOL)isPagePrintable
       isPageSearchable:(BOOL)isPageSearchable
              userAgent:(web::UserAgentType)userAgent
-    thumbnailGenerator:(ThumbnailGeneratorBlock)thumbnailGenerator;
+    thumbnailGenerator:
+        (ChromeActivityItemThumbnailGenerator*)thumbnailGenerator;
 
 // The URL to be shared with share extensions. This URL is the canonical URL of
 // the page.
@@ -44,7 +45,8 @@
 // Whether FindInPage can be enabled for this page.
 @property(nonatomic, readonly, assign) BOOL isPageSearchable;
 @property(nonatomic, readonly, assign) web::UserAgentType userAgent;
-@property(nonatomic, copy) ThumbnailGeneratorBlock thumbnailGenerator;
+@property(nonatomic, readonly)
+    ChromeActivityItemThumbnailGenerator* thumbnailGenerator;
 
 @end
 
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data.mm b/ios/chrome/browser/ui/activity_services/share_to_data.mm
index ff3b3fe3..888c3352 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data.mm
+++ b/ios/chrome/browser/ui/activity_services/share_to_data.mm
@@ -38,7 +38,8 @@
        isPagePrintable:(BOOL)isPagePrintable
       isPageSearchable:(BOOL)isPageSearchable
              userAgent:(web::UserAgentType)userAgent
-    thumbnailGenerator:(ThumbnailGeneratorBlock)thumbnailGenerator {
+    thumbnailGenerator:
+        (ChromeActivityItemThumbnailGenerator*)thumbnailGenerator {
   DCHECK(shareURL.is_valid());
   DCHECK(visibleURL.is_valid());
   DCHECK(title);
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data_builder.h b/ios/chrome/browser/ui/activity_services/share_to_data_builder.h
index 3d7dbb7..c0371c5 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data_builder.h
+++ b/ios/chrome/browser/ui/activity_services/share_to_data_builder.h
@@ -8,11 +8,11 @@
 class GURL;
 
 @class ShareToData;
-@class Tab;
 
 namespace web {
 class WebState;
 }
+
 namespace activity_services {
 
 // Returns a ShareToData object using data from |web_state|. |share_url| is the
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data_builder.mm b/ios/chrome/browser/ui/activity_services/share_to_data_builder.mm
index 186f9ce..4037e63 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data_builder.mm
+++ b/ios/chrome/browser/ui/activity_services/share_to_data_builder.mm
@@ -50,12 +50,10 @@
   }
 
   BOOL is_page_printable = [web_state->GetView() viewPrintFormatter] != nil;
-  Tab* tab = LegacyTabHelper::GetTabForWebState(web_state);
-  ThumbnailGeneratorBlock thumbnail_generator =
-      activity_services::ThumbnailGeneratorForTab(tab);
+  ChromeActivityItemThumbnailGenerator* thumbnail_generator =
+      [[ChromeActivityItemThumbnailGenerator alloc] initWithWebState:web_state];
   const GURL& finalURLToShare =
       !share_url.is_empty() ? share_url : web_state->GetVisibleURL();
-
   web::NavigationItem* visibleItem =
       web_state->GetNavigationManager()->GetVisibleItem();
   web::UserAgentType userAgent = web::UserAgentType::NONE;
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm b/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
index 7c943f6..f7e016932 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
+++ b/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/download/download_manager_tab_helper.h"
 #import "ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
 #import "ios/chrome/browser/ui/activity_services/share_to_data.h"
 #import "ios/testing/ocmock_complex_type_helper.h"
 #import "ios/web/public/test/fakes/test_navigation_manager.h"
@@ -61,8 +60,6 @@
                                          [[NSUUID UUID] UUIDString]);
     delegate_ = [[FakeSnapshotGeneratorDelegate alloc] init];
     SnapshotTabHelper::FromWebState(web_state_.get())->SetDelegate(delegate_);
-    LegacyTabHelper::CreateForWebState(web_state_.get());
-
     // Needed by the ShareToDataForWebState to get the tab title.
     DownloadManagerTabHelper::CreateForWebState(web_state_.get(),
                                                 /*delegate=*/nullptr);
@@ -102,7 +99,7 @@
 
   const CGSize size = CGSizeMake(40, 40);
   EXPECT_TRUE(UIImagesAreEqual(
-      actual_data.thumbnailGenerator(size),
+      [actual_data.thumbnailGenerator thumbnailWithSize:size],
       UIImageWithSizeAndSolidColor(size, [UIColor blueColor])));
 }
 
@@ -121,7 +118,7 @@
 
   const CGSize size = CGSizeMake(40, 40);
   EXPECT_TRUE(UIImagesAreEqual(
-      actual_data.thumbnailGenerator(size),
+      [actual_data.thumbnailGenerator thumbnailWithSize:size],
       UIImageWithSizeAndSolidColor(size, [UIColor blueColor])));
 }
 
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm b/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm
index 961e944..bc4f47a 100644
--- a/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm
@@ -177,9 +177,7 @@
       withHostedDomain:(NSString*)hostedDomain
         toBrowserState:(ios::ChromeBrowserState*)browserState {
   AuthenticationServiceFactory::GetForBrowserState(browserState)
-      ->SignIn(identity, [hostedDomain length] > 0
-                             ? base::SysNSStringToUTF8(hostedDomain)
-                             : kNoHostedDomainFound);
+      ->SignIn(identity);
 }
 
 - (void)signOutBrowserState:(ios::ChromeBrowserState*)browserState {
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow_unittest.mm b/ios/chrome/browser/ui/authentication/authentication_flow_unittest.mm
index 562ace8b..04ea3b8 100644
--- a/ios/chrome/browser/ui/authentication/authentication_flow_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_flow_unittest.mm
@@ -166,7 +166,7 @@
                        toBrowserState:browser_state_.get()];
 
   AuthenticationServiceFactory::GetForBrowserState(browser_state_.get())
-      ->SignIn(identity1_, kNoHostedDomainFound);
+      ->SignIn(identity1_);
   [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
 
   CheckSignInCompletion(true);
@@ -211,7 +211,7 @@
   [[performer_ expect] commitSyncForBrowserState:browser_state_.get()];
 
   AuthenticationServiceFactory::GetForBrowserState(browser_state_.get())
-      ->SignIn(identity2_, kNoHostedDomainFound);
+      ->SignIn(identity2_);
   [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
 
   CheckSignInCompletion(true);
diff --git a/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm b/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
index d947c8b..6a90ee64 100644
--- a/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/re_signin_infobar_delegate_unittest.mm
@@ -59,7 +59,7 @@
     AuthenticationService* authentication_service =
         AuthenticationServiceFactory::GetForBrowserState(
             chrome_browser_state_.get());
-    authentication_service->SignIn(chrome_identity, kNoHostedDomainFound);
+    authentication_service->SignIn(chrome_identity);
   }
 
   web::TestWebThreadBundle thread_bundle_;
diff --git a/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
index ac77ce2..1d0a5c7 100644
--- a/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller_unittest.mm
@@ -40,8 +40,7 @@
     identity_service->AddIdentities(
         @[ @"identity1", @"identity2", @"identity3" ]);
     auth_service_->SignIn(
-        [identity_service->GetAllIdentitiesSortedForDisplay() objectAtIndex:0],
-        kNoHostedDomainFound);
+        [identity_service->GetAllIdentitiesSortedForDisplay() objectAtIndex:0]);
   }
 
  protected:
diff --git a/ios/chrome/browser/ui/fancy_ui/BUILD.gn b/ios/chrome/browser/ui/fancy_ui/BUILD.gn
index bd2c82a..fe8377f 100644
--- a/ios/chrome/browser/ui/fancy_ui/BUILD.gn
+++ b/ios/chrome/browser/ui/fancy_ui/BUILD.gn
@@ -14,6 +14,7 @@
     "//base",
     "//base:i18n",
     "//ios/chrome/browser/ui/colors",
+    "//ios/chrome/common/colors",
   ]
   public_deps = [
     "//ios/third_party/material_components_ios",
diff --git a/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm b/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
index 48347e12..ad20782 100644
--- a/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
+++ b/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/fancy_ui/primary_action_button.h"
 
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -15,32 +17,67 @@
 - (id)initWithFrame:(CGRect)frame {
   self = [super initWithFrame:frame];
   if (self)
-    [self initializeStyling];
+    [self updateStyling];
   return self;
 }
 
 - (id)initWithCoder:(NSCoder*)aDecoder {
   self = [super initWithCoder:aDecoder];
   if (self)
-    [self initializeStyling];
+    [self updateStyling];
   return self;
 }
 
 - (void)awakeFromNib {
   [super awakeFromNib];
-  [self initializeStyling];
+  [self updateStyling];
 }
 
-- (void)initializeStyling {
+- (void)updateStyling {
   self.hasOpaqueBackground = YES;
-  self.underlyingColorHint = [UIColor whiteColor];
-  self.inkColor =
-      [[[MDCPalette cr_bluePalette] tint300] colorWithAlphaComponent:0.5f];
-  [self setBackgroundColor:[[MDCPalette cr_bluePalette] tint500]
-                  forState:UIControlStateNormal];
-  [self setBackgroundColor:[UIColor colorWithWhite:0.6f alpha:1.0f]
-                  forState:UIControlStateDisabled];
-  [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
+
+  UIColor* hintColor = UIColor.cr_systemBackgroundColor;
+  UIColor* inkColor = [UIColor colorWithWhite:1 alpha:0.2f];
+  UIColor* backgroundColor = [UIColor colorNamed:kTintColor];
+  UIColor* disabledColor = [UIColor colorNamed:kDisabledTintColor];
+  UIColor* titleColor = [UIColor colorNamed:kSolidButtonTextColor];
+
+#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
+  if (@available(iOS 13, *)) {
+    // As of iOS 13 Beta 3, MDCFlatButton has a bug updating it's colors
+    // automatically. Here the colors are resolved and passed instead.
+    // TODO(crbug.com/983224): Clean up this once the bug is fixed.
+    hintColor =
+        [hintColor resolvedColorWithTraitCollection:self.traitCollection];
+    inkColor = [inkColor resolvedColorWithTraitCollection:self.traitCollection];
+    backgroundColor =
+        [backgroundColor resolvedColorWithTraitCollection:self.traitCollection];
+    disabledColor =
+        [disabledColor resolvedColorWithTraitCollection:self.traitCollection];
+    titleColor =
+        [titleColor resolvedColorWithTraitCollection:self.traitCollection];
+  }
+#endif
+
+  self.underlyingColorHint = hintColor;
+  self.inkColor = inkColor;
+  [self setBackgroundColor:backgroundColor forState:UIControlStateNormal];
+  [self setBackgroundColor:disabledColor forState:UIControlStateDisabled];
+  [self setTitleColor:titleColor forState:UIControlStateNormal];
 }
 
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
+  if (@available(iOS 13, *)) {
+    if ([self.traitCollection
+            hasDifferentColorAppearanceComparedToTraitCollection:
+                previousTraitCollection]) {
+      // As of iOS 13 Beta 3, MDCFlatButton doesn't update it's colors
+      // automatically. This line forces it.
+      [self updateStyling];
+    }
+  }
+#endif
+}
 @end
diff --git a/ios/chrome/browser/ui/open_in/open_in_controller_egtest.mm b/ios/chrome/browser/ui/open_in/open_in_controller_egtest.mm
index 69b83fbb..b8c76c0 100644
--- a/ios/chrome/browser/ui/open_in/open_in_controller_egtest.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_controller_egtest.mm
@@ -48,6 +48,12 @@
 // Tests that open in button appears when opening a PDF, and that tapping on it
 // will open the activity view.
 - (void)testOpenIn {
+  // TODO(crbug.com/983135): The share menu displays in a popover on iPad, which
+  // causes this test to fail.
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_DISABLED(@"Disabled on iPad");
+  }
+
   // TODO(crbug.com/982845): A bug is causing the "Open in" toolbar to disappear
   // after any VC is presented fullscreen over the BVC.  The iOS 13 share menu
   // is presented fullscreen, but only when compiling with the iOS 12 SDK.
diff --git a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
index 8263608..bafc984 100644
--- a/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
+++ b/ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.mm
@@ -92,7 +92,7 @@
   ChromeIdentity* identity =
       [identityService->GetAllIdentitiesSortedForDisplay() objectAtIndex:0];
   AuthenticationServiceFactory::GetForBrowserState(chrome_browser_state_.get())
-      ->SignIn(identity, kNoHostedDomainFound);
+      ->SignIn(identity);
 }
 
 void PassphraseTableViewControllerTest::SetUpNavigationController(
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index c1f6950..3ea0d1ac 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -219,14 +219,10 @@
 @property(weak, nonatomic, readonly) CRWSessionController* sessionController;
 // The associated NavigationManagerImpl.
 @property(nonatomic, readonly) NavigationManagerImpl* navigationManagerImpl;
-// Whether the associated WebState has an opener.
-@property(nonatomic, readonly) BOOL hasOpener;
 // TODO(crbug.com/692871): Remove these functions and replace with more
 // appropriate NavigationItem getters.
 // Returns the navigation item for the current page.
 @property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem;
-// Returns the referrer for current navigation item. May be empty.
-@property(nonatomic, readonly) web::Referrer currentNavItemReferrer;
 
 // Returns the current URL of the web view, and sets |trustLevel| accordingly
 // based on the confidence in the verification.
@@ -280,8 +276,6 @@
 // Updates SSL status for the current navigation item based on the information
 // provided by web view.
 - (void)updateSSLStatusForCurrentNavigationItem;
-// Clears WebUI, if one exists.
-- (void)clearWebUI;
 
 @end
 
@@ -556,21 +550,12 @@
                            : nil;
 }
 
-- (BOOL)hasOpener {
-  return self.webStateImpl ? self.webStateImpl->HasOpener() : NO;
-}
-
 - (web::NavigationItemImpl*)currentNavItem {
   return self.navigationManagerImpl
              ? self.navigationManagerImpl->GetCurrentItemImpl()
              : nullptr;
 }
 
-- (web::Referrer)currentNavItemReferrer {
-  web::NavigationItem* currentItem = self.currentNavItem;
-  return currentItem ? currentItem->GetReferrer() : web::Referrer();
-}
-
 #pragma mark - ** Public Methods **
 
 #pragma mark - Header public methods
@@ -617,6 +602,7 @@
   _jsWindowErrorManager.reset();
   self.swipeRecognizerProvider = nil;
   [self.legacyNativeController close];
+  [self.requestController close];
 
   // Mark the destruction sequence has started, in case someone else holds a
   // strong reference and tries to continue using the tab.
@@ -687,67 +673,7 @@
   // cancelled.
   _userInteractionState.SetLastUserInteraction(nullptr);
   base::RecordAction(base::UserMetricsAction("Reload"));
-  GURL URL = self.currentNavItem->GetURL();
-  if ([self.legacyNativeController shouldLoadURLInNativeView:URL]) {
-    std::unique_ptr<web::NavigationContextImpl> navigationContext =
-        [_requestController
-            registerLoadRequestForURL:URL
-                             referrer:self.currentNavItemReferrer
-                           transition:ui::PageTransition::PAGE_TRANSITION_RELOAD
-               sameDocumentNavigation:NO
-                       hasUserGesture:YES
-                    rendererInitiated:rendererInitiated
-                placeholderNavigation:NO];
-    self.webStateImpl->OnNavigationStarted(navigationContext.get());
-    [self didStartLoading];
-    self.navigationManagerImpl->CommitPendingItem(
-        navigationContext->ReleaseItem());
-    [self.legacyNativeController reload];
-    navigationContext->SetHasCommitted(true);
-    self.webStateImpl->OnNavigationFinished(navigationContext.get());
-    [self loadCompleteWithSuccess:YES forContext:nullptr];
-  } else {
-    web::NavigationItem* transientItem =
-        self.navigationManagerImpl->GetTransientItem();
-    if (transientItem) {
-      // If there's a transient item, a reload is considered a new navigation to
-      // the transient item's URL (as on other platforms).
-      NavigationManager::WebLoadParams reloadParams(transientItem->GetURL());
-      reloadParams.transition_type = ui::PAGE_TRANSITION_RELOAD;
-      reloadParams.extra_headers =
-          [transientItem->GetHttpRequestHeaders() copy];
-      self.webState->GetNavigationManager()->LoadURLWithParams(reloadParams);
-    } else {
-      self.currentNavItem->SetTransitionType(
-          ui::PageTransition::PAGE_TRANSITION_RELOAD);
-      if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
-          !web::GetWebClient()->IsAppSpecificURL(
-              net::GURLWithNSURL(self.webView.URL))) {
-        // New navigation manager can delegate directly to WKWebView to reload
-        // for non-app-specific URLs. The necessary navigation states will be
-        // updated in WKNavigationDelegate callbacks.
-        WKNavigation* navigation = [self.webView reload];
-        [self.navigationHandler.navigationStates
-                 setState:web::WKNavigationState::REQUESTED
-            forNavigation:navigation];
-        std::unique_ptr<web::NavigationContextImpl> navigationContext =
-            [_requestController
-                registerLoadRequestForURL:URL
-                                 referrer:self.currentNavItemReferrer
-                               transition:ui::PageTransition::
-                                              PAGE_TRANSITION_RELOAD
-                   sameDocumentNavigation:NO
-                           hasUserGesture:YES
-                        rendererInitiated:rendererInitiated
-                    placeholderNavigation:NO];
-        [self.navigationHandler.navigationStates
-               setContext:std::move(navigationContext)
-            forNavigation:navigation];
-      } else {
-        [self loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated];
-      }
-    }
-  }
+  [_requestController reloadWithRendererInitiatedNavigation:rendererInitiated];
 }
 
 - (void)stopLoading {
@@ -792,50 +718,8 @@
 
   _currentURLLoadWasTrigerred = YES;
 
-  // Reset current WebUI if one exists.
-  [self clearWebUI];
-
-  // Abort any outstanding page load. This ensures the delegate gets informed
-  // about the outgoing page, and further messages from the page are suppressed.
-  if (self.navigationHandler.navigationState !=
-      web::WKNavigationState::FINISHED) {
-    [self.webView stopLoading];
-    [self.navigationHandler stopLoading];
-  }
-
-  self.webStateImpl->ClearTransientContent();
-
-  web::NavigationItem* item = self.currentNavItem;
-  const GURL currentURL = item ? item->GetURL() : GURL::EmptyGURL();
-  const bool isCurrentURLAppSpecific =
-      web::GetWebClient()->IsAppSpecificURL(currentURL);
-  // If it's a chrome URL, but not a native one, create the WebUI instance.
-  if (isCurrentURLAppSpecific &&
-      ![self.legacyNativeController shouldLoadURLInNativeView:currentURL]) {
-    if (!(item->GetTransitionType() & ui::PAGE_TRANSITION_TYPED ||
-          item->GetTransitionType() & ui::PAGE_TRANSITION_AUTO_BOOKMARK) &&
-        self.hasOpener) {
-      // WebUI URLs can not be opened by DOM to prevent cross-site scripting as
-      // they have increased power. WebUI URLs may only be opened when the user
-      // types in the URL or use bookmarks.
-      self.navigationManagerImpl->DiscardNonCommittedItems();
-      return;
-    } else {
-      [self createWebUIForURL:currentURL];
-    }
-  }
-
-  // Loading a new url, must check here if it's a native chrome URL and
-  // replace the appropriate view if so, or transition back to a web view from
-  // a native view.
-  if ([self.legacyNativeController shouldLoadURLInNativeView:currentURL]) {
-    [self.legacyNativeController
-        loadCurrentURLInNativeViewWithRendererInitiatedNavigation:
-            rendererInitiated];
-  } else if ([_requestController maybeLoadRequestForCurrentNavigationItem]) {
-    [self ensureWebViewCreated];
-    [_requestController loadRequestForCurrentNavigationItem];
-  }
+  [_requestController
+      loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated];
 }
 
 - (void)loadCurrentURLIfNecessary {
@@ -863,12 +747,6 @@
 
 // Loads the HTML into the page at the given URL. Only for testing purpose.
 - (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL {
-  // Web View should not be created for App Specific URLs.
-  if (!web::GetWebClient()->IsAppSpecificURL(URL)) {
-    [self ensureWebViewCreated];
-    DCHECK(self.webView) << "self.webView null while trying to load HTML";
-  }
-
   [_requestController loadHTML:HTML forURL:URL];
 }
 
@@ -1420,19 +1298,6 @@
   return NO;
 }
 
-#pragma mark - WebUI
-
-// Sets up WebUI for URL.
-- (void)createWebUIForURL:(const GURL&)URL {
-  // |CreateWebUI| will do nothing if |URL| is not a WebUI URL and then
-  // |HasWebUI| will return false.
-  self.webStateImpl->CreateWebUI(URL);
-}
-
-- (void)clearWebUI {
-  self.webStateImpl->ClearWebUI();
-}
-
 #pragma mark - CRWWebViewScrollViewProxyObserver
 
 - (void)webViewScrollViewDidZoom:
@@ -2184,7 +2049,7 @@
 
 - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
         createWebUIForURL:(const GURL&)URL {
-  [self createWebUIForURL:URL];
+  [_requestController createWebUIForURL:URL];
 }
 
 - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
@@ -2277,6 +2142,22 @@
   [self stopLoading];
 }
 
+- (void)webRequestControllerEnsureWebViewCreated:
+    (CRWWebRequestController*)requestController {
+  [self ensureWebViewCreated];
+}
+
+- (void)webRequestControllerDidStartLoading:
+    (CRWWebRequestController*)requestController {
+  [self didStartLoading];
+}
+
+- (void)webRequestController:(CRWWebRequestController*)requestController
+    didCompleteLoadWithSuccess:(BOOL)loadSuccess
+                    forContext:(web::NavigationContextImpl*)context {
+  [self loadCompleteWithSuccess:loadSuccess forContext:context];
+}
+
 - (void)webRequestControllerDisconnectScrollViewProxy:
     (CRWWebRequestController*)requestController {
   // Disable |allowsBackForwardNavigationGestures| during restore. Otherwise,
diff --git a/ios/web/web_state/ui/crw_web_request_controller.h b/ios/web/web_state/ui/crw_web_request_controller.h
index fec8ea1c..48c1b9b 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.h
+++ b/ios/web/web_state/ui/crw_web_request_controller.h
@@ -28,6 +28,20 @@
 - (void)webRequestControllerStopLoading:
     (CRWWebRequestController*)requestController;
 
+// The delegate will create a web view if it's not yet created.
+- (void)webRequestControllerEnsureWebViewCreated:
+    (CRWWebRequestController*)requestController;
+
+// The delegate is called when a page (native or web) has actually started
+// loading.
+- (void)webRequestControllerDidStartLoading:
+    (CRWWebRequestController*)requestController;
+
+// The delegate is called when a page is loaded.
+- (void)webRequestController:(CRWWebRequestController*)requestController
+    didCompleteLoadWithSuccess:(BOOL)loadSuccess
+                    forContext:(web::NavigationContextImpl*)context;
+
 // Asks proxy to disconnect scroll proxy if needed.
 - (void)webRequestControllerDisconnectScrollViewProxy:
     (CRWWebRequestController*)requestController;
@@ -61,7 +75,7 @@
 @end
 
 // Controller in charge of preparing and handling web requests for the delegate,
-// which should be |CrwWebController|.
+// which should be |CRWWebController|.
 @interface CRWWebRequestController : NSObject
 
 @property(nonatomic, weak) id<CRWWebRequestControllerDelegate> delegate;
@@ -82,12 +96,14 @@
 // state to finished and call |didFinishWithURL| with failure.
 - (BOOL)maybeLoadRequestForCurrentNavigationItem;
 
-// Loads request for the URL of the current navigation item. Subclasses may
-// choose to build a new NSURLRequest and call
-// |loadRequestForCurrentNavigationItem| on the underlying web view, or use
-// native web view navigation where possible (for example, going back and
-// forward through the history stack).
-- (void)loadRequestForCurrentNavigationItem;
+// Sets up WebUI for URL.
+- (void)createWebUIForURL:(const GURL&)URL;
+
+// Clears WebUI, if one exists.
+- (void)clearWebUI;
+
+// Loads the URL indicated by current session state.
+- (void)loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated;
 
 // Should be called by owner component after URL is finished loading and
 // self.navigationHandler.navigationState is set to FINISHED. |context| contains
@@ -128,6 +144,10 @@
 // document.
 - (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL;
 
+// Reloads web view. |rendererInitiated| is YES for renderer-initiated
+// navigation. |rendererInitiated| is NO for browser-initiated navigation.
+- (void)reloadWithRendererInitiatedNavigation:(BOOL)rendererInitiated;
+
 @end
 
 #endif  // IOS_WEB_WEB_STATE_UI_CRW_WEB_REQUEST_CONTROLLER_H_
diff --git a/ios/web/web_state/ui/crw_web_request_controller.mm b/ios/web/web_state/ui/crw_web_request_controller.mm
index 9cce397..7100b189 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.mm
+++ b/ios/web/web_state/ui/crw_web_request_controller.mm
@@ -69,6 +69,10 @@
 // Set to YES when [self close] is called.
 @property(nonatomic, assign) BOOL beingDestroyed;
 
+// Returns The CRWLegacyNativeContentController handler class from delegate.
+@property(nonatomic, readonly)
+    CRWLegacyNativeContentController* legacyNativeController;
+
 @end
 
 @implementation CRWWebRequestController
@@ -83,8 +87,474 @@
 
 - (void)close {
   self.beingDestroyed = YES;
+  _webState = nullptr;
 }
 
+- (void)loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated {
+  // Reset current WebUI if one exists.
+  [self clearWebUI];
+
+  // Abort any outstanding page load. This ensures the delegate gets informed
+  // about the outgoing page, and further messages from the page are suppressed.
+  if (self.navigationHandler.navigationState !=
+      web::WKNavigationState::FINISHED) {
+    [self.webView stopLoading];
+    [self.navigationHandler stopLoading];
+  }
+
+  self.webState->ClearTransientContent();
+
+  web::NavigationItem* item = self.currentNavItem;
+  const GURL currentURL = item ? item->GetURL() : GURL::EmptyGURL();
+  const bool isCurrentURLAppSpecific =
+      web::GetWebClient()->IsAppSpecificURL(currentURL);
+  // If it's a chrome URL, but not a native one, create the WebUI instance.
+  if (isCurrentURLAppSpecific &&
+      ![self.legacyNativeController shouldLoadURLInNativeView:currentURL]) {
+    if (!(item->GetTransitionType() & ui::PAGE_TRANSITION_TYPED ||
+          item->GetTransitionType() & ui::PAGE_TRANSITION_AUTO_BOOKMARK) &&
+        self.hasOpener) {
+      // WebUI URLs can not be opened by DOM to prevent cross-site scripting as
+      // they have increased power. WebUI URLs may only be opened when the user
+      // types in the URL or use bookmarks.
+      self.navigationManagerImpl->DiscardNonCommittedItems();
+      return;
+    } else {
+      [self createWebUIForURL:currentURL];
+    }
+  }
+
+  // Loading a new url, must check here if it's a native chrome URL and
+  // replace the appropriate view if so, or transition back to a web view from
+  // a native view.
+  if ([self.legacyNativeController shouldLoadURLInNativeView:currentURL]) {
+    [self.legacyNativeController
+        loadCurrentURLInNativeViewWithRendererInitiatedNavigation:
+            rendererInitiated];
+  } else if ([self maybeLoadRequestForCurrentNavigationItem]) {
+    [_delegate webRequestControllerEnsureWebViewCreated:self];
+    [self loadRequestForCurrentNavigationItem];
+  }
+}
+
+- (void)loadData:(NSData*)data
+        MIMEType:(NSString*)MIMEType
+          forURL:(const GURL&)URL {
+  [_delegate webRequestControllerStopLoading:self];
+  web::NavigationItemImpl* item =
+      self.navigationManagerImpl->GetLastCommittedItemImpl();
+  auto navigationContext = web::NavigationContextImpl::CreateNavigationContext(
+      self.webState, URL,
+      /*has_user_gesture=*/true, item->GetTransitionType(),
+      /*is_renderer_initiated=*/false);
+  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
+  navigationContext->SetNavigationItemUniqueID(item->GetUniqueID());
+
+  item->SetNavigationInitiationType(
+      web::NavigationInitiationType::BROWSER_INITIATED);
+  // The error_retry_state_machine may still be in the
+  // |kDisplayingWebErrorForFailedNavigation| from the navigation that is being
+  // replaced. As the navigation is now successful, the error can be cleared.
+  item->error_retry_state_machine().SetNoNavigationError();
+  // The load data call will replace the current navigation and the webView URL
+  // of the navigation will be replaced by |URL|. Set the URL of the
+  // navigationItem to keep them synced.
+  // Note: it is possible that the URL in item already match |url|. But item can
+  // also contain a placeholder URL intended to be replaced.
+  item->SetURL(URL);
+  navigationContext->SetMimeType(MIMEType);
+  if (item->GetUserAgentType() == web::UserAgentType::NONE &&
+      URLNeedsUserAgentType(URL)) {
+    item->SetUserAgentType(web::UserAgentType::MOBILE);
+  }
+
+  WKNavigation* navigation =
+      [self.webView loadData:data
+                       MIMEType:MIMEType
+          characterEncodingName:base::SysUTF8ToNSString(base::kCodepageUTF8)
+                        baseURL:net::NSURLWithGURL(URL)];
+
+  [self.navigationHandler.navigationStates
+         setContext:std::move(navigationContext)
+      forNavigation:navigation];
+  [self.navigationHandler.navigationStates
+           setState:web::WKNavigationState::REQUESTED
+      forNavigation:navigation];
+}
+
+- (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL {
+  // Web View should not be created for App Specific URLs.
+  if (!web::GetWebClient()->IsAppSpecificURL(URL)) {
+    [_delegate webRequestControllerEnsureWebViewCreated:self];
+    DCHECK(self.webView) << "self.webView null while trying to load HTML";
+  }
+
+  DCHECK(HTML.length);
+  // Remove the transient content view.
+  self.webState->ClearTransientContent();
+
+  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
+
+  WKNavigation* navigation =
+      [self.webView loadHTMLString:HTML baseURL:net::NSURLWithGURL(URL)];
+  [self.navigationHandler.navigationStates
+           setState:web::WKNavigationState::REQUESTED
+      forNavigation:navigation];
+  std::unique_ptr<web::NavigationContextImpl> context;
+  const ui::PageTransition loadHTMLTransition =
+      ui::PageTransition::PAGE_TRANSITION_TYPED;
+  if (self.webState->HasWebUI()) {
+    // WebUI uses |loadHTML:forURL:| to feed the content to web view. This
+    // should not be treated as a navigation, but WKNavigationDelegate callbacks
+    // still expect a valid context.
+    context = web::NavigationContextImpl::CreateNavigationContext(
+        self.webState, URL, /*has_user_gesture=*/true, loadHTMLTransition,
+        /*is_renderer_initiated=*/false);
+    context->SetNavigationItemUniqueID(self.currentNavItem->GetUniqueID());
+    // Transfer pending item ownership to NavigationContext.
+    // NavigationManager owns pending item after navigation is requested and
+    // until navigation context is created.
+    context->SetItem(self.navigationManagerImpl->ReleasePendingItem());
+  } else {
+    context = [self registerLoadRequestForURL:URL
+                                     referrer:web::Referrer()
+                                   transition:loadHTMLTransition
+                       sameDocumentNavigation:NO
+                               hasUserGesture:YES
+                            rendererInitiated:NO
+                        placeholderNavigation:NO];
+  }
+  context->SetLoadingHtmlString(true);
+  context->SetMimeType(@"text/html");
+  [self.navigationHandler.navigationStates setContext:std::move(context)
+                                        forNavigation:navigation];
+}
+
+- (void)reloadWithRendererInitiatedNavigation:(BOOL)rendererInitiated {
+  GURL URL = self.currentNavItem->GetURL();
+  if ([self.legacyNativeController shouldLoadURLInNativeView:URL]) {
+    std::unique_ptr<web::NavigationContextImpl> navigationContext = [self
+        registerLoadRequestForURL:URL
+                         referrer:self.currentNavItemReferrer
+                       transition:ui::PageTransition::PAGE_TRANSITION_RELOAD
+           sameDocumentNavigation:NO
+                   hasUserGesture:YES
+                rendererInitiated:rendererInitiated
+            placeholderNavigation:NO];
+    self.webState->OnNavigationStarted(navigationContext.get());
+    [_delegate webRequestControllerDidStartLoading:self];
+    self.navigationManagerImpl->CommitPendingItem(
+        navigationContext->ReleaseItem());
+    [self.legacyNativeController reload];
+    navigationContext->SetHasCommitted(true);
+    self.webState->OnNavigationFinished(navigationContext.get());
+    [_delegate webRequestController:self
+         didCompleteLoadWithSuccess:(BOOL)YES
+                         forContext:nullptr];
+  } else {
+    web::NavigationItem* transientItem =
+        self.navigationManagerImpl->GetTransientItem();
+    if (transientItem) {
+      // If there's a transient item, a reload is considered a new navigation to
+      // the transient item's URL (as on other platforms).
+      web::NavigationManager::WebLoadParams reloadParams(
+          transientItem->GetURL());
+      reloadParams.transition_type = ui::PAGE_TRANSITION_RELOAD;
+      reloadParams.extra_headers =
+          [transientItem->GetHttpRequestHeaders() copy];
+      self.webState->GetNavigationManager()->LoadURLWithParams(reloadParams);
+    } else {
+      self.currentNavItem->SetTransitionType(
+          ui::PageTransition::PAGE_TRANSITION_RELOAD);
+      if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
+          !web::GetWebClient()->IsAppSpecificURL(
+              net::GURLWithNSURL(self.webView.URL))) {
+        // New navigation manager can delegate directly to WKWebView to reload
+        // for non-app-specific URLs. The necessary navigation states will be
+        // updated in WKNavigationDelegate callbacks.
+        WKNavigation* navigation = [self.webView reload];
+        [self.navigationHandler.navigationStates
+                 setState:web::WKNavigationState::REQUESTED
+            forNavigation:navigation];
+        std::unique_ptr<web::NavigationContextImpl> navigationContext = [self
+            registerLoadRequestForURL:URL
+                             referrer:self.currentNavItemReferrer
+                           transition:ui::PageTransition::PAGE_TRANSITION_RELOAD
+               sameDocumentNavigation:NO
+                       hasUserGesture:YES
+                    rendererInitiated:rendererInitiated
+                placeholderNavigation:NO];
+        [self.navigationHandler.navigationStates
+               setContext:std::move(navigationContext)
+            forNavigation:navigation];
+      } else {
+        [self loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated];
+      }
+    }
+  }
+}
+
+- (std::unique_ptr<web::NavigationContextImpl>)
+    registerLoadRequestForURL:(const GURL&)URL
+       sameDocumentNavigation:(BOOL)sameDocumentNavigation
+               hasUserGesture:(BOOL)hasUserGesture
+            rendererInitiated:(BOOL)rendererInitiated
+        placeholderNavigation:(BOOL)placeholderNavigation {
+  // Get the navigation type from the last main frame load request, and try to
+  // map that to a PageTransition.
+  WKNavigationType navigationType =
+      self.navigationHandler.pendingNavigationInfo
+          ? self.navigationHandler.pendingNavigationInfo.navigationType
+          : WKNavigationTypeOther;
+  ui::PageTransition transition =
+      [self.navigationHandler pageTransitionFromNavigationType:navigationType];
+
+  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
+      navigationType == WKNavigationTypeBackForward &&
+      self.webView.backForwardList.currentItem) {
+    web::NavigationItem* currentItem = [[CRWNavigationItemHolder
+        holderForBackForwardListItem:self.webView.backForwardList.currentItem]
+        navigationItem];
+    if (currentItem) {
+      transition = ui::PageTransitionFromInt(transition |
+                                             currentItem->GetTransitionType());
+    }
+  }
+
+  // The referrer is not known yet, and will be updated later.
+  const web::Referrer emptyReferrer;
+  std::unique_ptr<web::NavigationContextImpl> context =
+      [self registerLoadRequestForURL:URL
+                             referrer:emptyReferrer
+                           transition:transition
+               sameDocumentNavigation:sameDocumentNavigation
+                       hasUserGesture:hasUserGesture
+                    rendererInitiated:rendererInitiated
+                placeholderNavigation:placeholderNavigation];
+  context->SetWKNavigationType(navigationType);
+  return context;
+}
+
+- (std::unique_ptr<web::NavigationContextImpl>)
+    registerLoadRequestForURL:(const GURL&)requestURL
+                     referrer:(const web::Referrer&)referrer
+                   transition:(ui::PageTransition)transition
+       sameDocumentNavigation:(BOOL)sameDocumentNavigation
+               hasUserGesture:(BOOL)hasUserGesture
+            rendererInitiated:(BOOL)rendererInitiated
+        placeholderNavigation:(BOOL)placeholderNavigation {
+  // Transfer time is registered so that further transitions within the time
+  // envelope are not also registered as links.
+  [_delegate
+      webRequestControllerUserInteractionState:self] -> ResetLastTransferTime();
+
+  // Add or update pending item before any WebStateObserver callbacks.
+  // See https://crbug.com/842151 for a scenario where this is important.
+  web::NavigationItem* item =
+      self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession();
+  if (item) {
+    // Update the existing pending entry.
+    // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
+    // Don't update if request is a placeholder entry because the pending item
+    // should have the original target URL.
+    // Don't update if pending URL has a different origin, because client
+    // redirects can not change the origin. It is possible to have more than one
+    // pending navigations, so the redirect does not necesserily belong to the
+    // pending navigation item.
+    if (!placeholderNavigation &&
+        item->GetURL().GetOrigin() == requestURL.GetOrigin()) {
+      self.navigationManagerImpl->UpdatePendingItemUrl(requestURL);
+    }
+  } else {
+    self.navigationManagerImpl->AddPendingItem(
+        requestURL, referrer, transition,
+        rendererInitiated ? web::NavigationInitiationType::RENDERER_INITIATED
+                          : web::NavigationInitiationType::BROWSER_INITIATED,
+        web::NavigationManager::UserAgentOverrideOption::INHERIT);
+    item =
+        self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession();
+  }
+
+  bool redirect = transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
+  if (!redirect) {
+    // Before changing navigation state, the delegate should be informed that
+    // any existing request is being cancelled before completion.
+    [self.navigationHandler loadCancelled];
+    DCHECK_EQ(web::WKNavigationState::FINISHED,
+              self.navigationHandler.navigationState);
+  }
+
+  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
+
+  // Record the state of outgoing web view. Do nothing if native controller
+  // exists, because in that case recordStateInHistory will record the state
+  // of incoming page as native controller is already inserted.
+  // TODO(crbug.com/811770) Don't record state under WKBasedNavigationManager
+  // because it may incorrectly clobber the incoming page if this is a
+  // back/forward navigation. WKWebView restores page scroll state for web view
+  // pages anyways so this only impacts user if WKWebView is deleted.
+  if (!redirect && ![self.legacyNativeController hasController] &&
+      !web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    [_delegate webRequestControllerRecordStateInHistory:self];
+  }
+
+  std::unique_ptr<web::NavigationContextImpl> context =
+      web::NavigationContextImpl::CreateNavigationContext(
+          self.webState, requestURL, hasUserGesture, transition,
+          rendererInitiated);
+  context->SetPlaceholderNavigation(placeholderNavigation);
+
+  // TODO(crbug.com/676129): LegacyNavigationManagerImpl::AddPendingItem does
+  // not create a pending item in case of reload. Remove this workaround once
+  // the bug is fixed or WKBasedNavigationManager is fully adopted.
+  if (!item) {
+    DCHECK(!web::GetWebClient()->IsSlimNavigationManagerEnabled());
+    item = self.navigationManagerImpl->GetLastCommittedItem();
+  }
+
+  context->SetNavigationItemUniqueID(item->GetUniqueID());
+  context->SetIsPost([self.navigationHandler isCurrentNavigationItemPOST]);
+  context->SetIsSameDocument(sameDocumentNavigation);
+
+  if (!IsWKInternalUrl(requestURL) && !placeholderNavigation) {
+    self.webState->SetIsLoading(true);
+  }
+
+  // WKWebView may have multiple pending items. Move pending item ownership from
+  // NavigationManager to NavigationContext. NavigationManager owns pending item
+  // after navigation was requested and until NavigationContext is created.
+  // No need to transfer the ownership for NativeContent URLs, because the
+  // load of NativeContent is synchronous. No need to transfer the ownership
+  // for WebUI navigations, because those navigation do not have access to
+  // NavigationContext.
+  if (![self.legacyNativeController
+          shouldLoadURLInNativeView:context->GetUrl()]) {
+    if (self.navigationManagerImpl->GetPendingItemIndex() == -1) {
+      context->SetItem(self.navigationManagerImpl->ReleasePendingItem());
+    }
+  }
+
+  return context;
+}
+
+- (void)didFinishWithURL:(const GURL&)currentURL
+             loadSuccess:(BOOL)loadSuccess
+                 context:(nullable const web::NavigationContextImpl*)context {
+  DCHECK_EQ(web::WKNavigationState::FINISHED,
+            self.navigationHandler.navigationState);
+
+  [_delegate webRequestControllerRestoreStateFromHistory:self];
+
+  // Placeholder and restore session URLs are implementation details so should
+  // not notify WebStateObservers. If |context| is nullptr, don't skip
+  // placeholder URLs because this may be the only opportunity to update
+  // |isLoading| for native view reload.
+
+  if (context && context->IsPlaceholderNavigation())
+    return;
+
+  if (context && IsRestoreSessionUrl(context->GetUrl()))
+    return;
+
+  if (IsRestoreSessionUrl(net::GURLWithNSURL(self.webView.URL)))
+    return;
+
+  if (context && context->IsLoadingErrorPage())
+    return;
+
+  if (!loadSuccess) {
+    // WebStateObserver callbacks will be called for load failure after
+    // loading placeholder URL.
+    return;
+  }
+
+  if (![self.navigationHandler.navigationStates
+              lastNavigationWithPendingItemInNavigationContext]) {
+    self.webState->SetIsLoading(false);
+  } else {
+    // There is another pending navigation, so the state is still loading.
+  }
+  self.webState->OnPageLoaded(currentURL, YES);
+}
+
+// Reports Navigation.IOSWKWebViewSlowFastBackForward UMA. No-op if pending
+// navigation is not back forward navigation.
+- (void)reportBackForwardNavigationTypeForFastNavigation:(BOOL)isFast {
+  web::NavigationManager* navigationManager = self.navigationManagerImpl;
+  int pendingIndex = navigationManager->GetPendingItemIndex();
+  if (pendingIndex == -1) {
+    // Pending navigation is not a back forward navigation.
+    return;
+  }
+
+  BOOL isBack = pendingIndex < navigationManager->GetLastCommittedItemIndex();
+  BackForwardNavigationType type = BackForwardNavigationType::FAST_BACK;
+  if (isBack) {
+    type = isFast ? BackForwardNavigationType::FAST_BACK
+                  : BackForwardNavigationType::SLOW_BACK;
+  } else {
+    type = isFast ? BackForwardNavigationType::FAST_FORWARD
+                  : BackForwardNavigationType::SLOW_FORWARD;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION(
+      "Navigation.IOSWKWebViewSlowFastBackForward", type,
+      BackForwardNavigationType::BACK_FORWARD_NAVIGATION_TYPE_COUNT);
+}
+
+#pragma mark Navigation and Session Information
+
+// Returns the associated NavigationManagerImpl.
+- (web::NavigationManagerImpl*)navigationManagerImpl {
+  return self.webState ? &(self.webState->GetNavigationManagerImpl()) : nil;
+}
+
+// Returns the navigation item for the current page.
+- (web::NavigationItemImpl*)currentNavItem {
+  return self.navigationManagerImpl
+             ? self.navigationManagerImpl->GetCurrentItemImpl()
+             : nullptr;
+}
+
+// Returns the current transition type.
+- (ui::PageTransition)currentTransition {
+  if (self.currentNavItem)
+    return self.currentNavItem->GetTransitionType();
+  else
+    return ui::PageTransitionFromInt(0);
+}
+
+// Returns the referrer for current navigation item. May be empty.
+- (web::Referrer)currentNavItemReferrer {
+  web::NavigationItem* currentItem = self.currentNavItem;
+  return currentItem ? currentItem->GetReferrer() : web::Referrer();
+}
+
+// The HTTP headers associated with the current navigation item. These are nil
+// unless the request was a POST.
+- (NSDictionary*)currentHTTPHeaders {
+  web::NavigationItem* currentItem = self.currentNavItem;
+  return currentItem ? currentItem->GetHttpRequestHeaders() : nil;
+}
+
+#pragma mark - WebUI
+
+- (void)createWebUIForURL:(const GURL&)URL {
+  // |CreateWebUI| will do nothing if |URL| is not a WebUI URL and then
+  // |HasWebUI| will return false.
+  self.webState->CreateWebUI(URL);
+}
+
+- (void)clearWebUI {
+  self.webState->ClearWebUI();
+}
+
+#pragma mark - Private methods
+
+// Checks if a load request of the current navigation item should proceed. If
+// this returns |YES|, caller should create a webView and call
+// |loadRequestForCurrentNavigationItem|. Otherwise this will set the request
+// state to finished and call |didFinishWithURL| with failure.
 - (BOOL)maybeLoadRequestForCurrentNavigationItem {
   web::NavigationItem* item = self.currentNavItem;
   GURL targetURL = item ? item->GetVirtualURL() : GURL::EmptyGURL();
@@ -104,6 +574,11 @@
   return YES;
 }
 
+// Loads request for the URL of the current navigation item. Subclasses may
+// choose to build a new NSURLRequest and call
+// |loadRequestForCurrentNavigationItem| on the underlying web view, or use
+// native web view navigation where possible (for example, going back and
+// forward through the history stack).
 - (void)loadRequestForCurrentNavigationItem {
   DCHECK(self.webView);
   DCHECK(self.currentNavItem);
@@ -292,303 +767,6 @@
       }));
 }
 
-- (void)loadData:(NSData*)data
-        MIMEType:(NSString*)MIMEType
-          forURL:(const GURL&)URL {
-  [_delegate webRequestControllerStopLoading:self];
-  web::NavigationItemImpl* item =
-      self.navigationManagerImpl->GetLastCommittedItemImpl();
-  auto navigationContext = web::NavigationContextImpl::CreateNavigationContext(
-      self.webState, URL,
-      /*has_user_gesture=*/true, item->GetTransitionType(),
-      /*is_renderer_initiated=*/false);
-  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
-  navigationContext->SetNavigationItemUniqueID(item->GetUniqueID());
-
-  item->SetNavigationInitiationType(
-      web::NavigationInitiationType::BROWSER_INITIATED);
-  // The error_retry_state_machine may still be in the
-  // |kDisplayingWebErrorForFailedNavigation| from the navigation that is being
-  // replaced. As the navigation is now successful, the error can be cleared.
-  item->error_retry_state_machine().SetNoNavigationError();
-  // The load data call will replace the current navigation and the webView URL
-  // of the navigation will be replaced by |URL|. Set the URL of the
-  // navigationItem to keep them synced.
-  // Note: it is possible that the URL in item already match |url|. But item can
-  // also contain a placeholder URL intended to be replaced.
-  item->SetURL(URL);
-  navigationContext->SetMimeType(MIMEType);
-  if (item->GetUserAgentType() == web::UserAgentType::NONE &&
-      URLNeedsUserAgentType(URL)) {
-    item->SetUserAgentType(web::UserAgentType::MOBILE);
-  }
-
-  WKNavigation* navigation =
-      [self.webView loadData:data
-                       MIMEType:MIMEType
-          characterEncodingName:base::SysUTF8ToNSString(base::kCodepageUTF8)
-                        baseURL:net::NSURLWithGURL(URL)];
-
-  [self.navigationHandler.navigationStates
-         setContext:std::move(navigationContext)
-      forNavigation:navigation];
-  [self.navigationHandler.navigationStates
-           setState:web::WKNavigationState::REQUESTED
-      forNavigation:navigation];
-}
-
-- (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL {
-  DCHECK(HTML.length);
-  // Remove the transient content view.
-  self.webState->ClearTransientContent();
-
-  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
-
-  WKNavigation* navigation =
-      [self.webView loadHTMLString:HTML baseURL:net::NSURLWithGURL(URL)];
-  [self.navigationHandler.navigationStates
-           setState:web::WKNavigationState::REQUESTED
-      forNavigation:navigation];
-  std::unique_ptr<web::NavigationContextImpl> context;
-  const ui::PageTransition loadHTMLTransition =
-      ui::PageTransition::PAGE_TRANSITION_TYPED;
-  if (self.webState->HasWebUI()) {
-    // WebUI uses |loadHTML:forURL:| to feed the content to web view. This
-    // should not be treated as a navigation, but WKNavigationDelegate callbacks
-    // still expect a valid context.
-    context = web::NavigationContextImpl::CreateNavigationContext(
-        self.webState, URL, /*has_user_gesture=*/true, loadHTMLTransition,
-        /*is_renderer_initiated=*/false);
-    context->SetNavigationItemUniqueID(self.currentNavItem->GetUniqueID());
-    // Transfer pending item ownership to NavigationContext.
-    // NavigationManager owns pending item after navigation is requested and
-    // until navigation context is created.
-    context->SetItem(self.navigationManagerImpl->ReleasePendingItem());
-  } else {
-    context = [self registerLoadRequestForURL:URL
-                                     referrer:web::Referrer()
-                                   transition:loadHTMLTransition
-                       sameDocumentNavigation:NO
-                               hasUserGesture:YES
-                            rendererInitiated:NO
-                        placeholderNavigation:NO];
-  }
-  context->SetLoadingHtmlString(true);
-  context->SetMimeType(@"text/html");
-  [self.navigationHandler.navigationStates setContext:std::move(context)
-                                        forNavigation:navigation];
-}
-
-- (std::unique_ptr<web::NavigationContextImpl>)
-    registerLoadRequestForURL:(const GURL&)URL
-       sameDocumentNavigation:(BOOL)sameDocumentNavigation
-               hasUserGesture:(BOOL)hasUserGesture
-            rendererInitiated:(BOOL)rendererInitiated
-        placeholderNavigation:(BOOL)placeholderNavigation {
-  // Get the navigation type from the last main frame load request, and try to
-  // map that to a PageTransition.
-  WKNavigationType navigationType =
-      self.navigationHandler.pendingNavigationInfo
-          ? self.navigationHandler.pendingNavigationInfo.navigationType
-          : WKNavigationTypeOther;
-  ui::PageTransition transition =
-      [self.navigationHandler pageTransitionFromNavigationType:navigationType];
-
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
-      navigationType == WKNavigationTypeBackForward &&
-      self.webView.backForwardList.currentItem) {
-    web::NavigationItem* currentItem = [[CRWNavigationItemHolder
-        holderForBackForwardListItem:self.webView.backForwardList.currentItem]
-        navigationItem];
-    if (currentItem) {
-      transition = ui::PageTransitionFromInt(transition |
-                                             currentItem->GetTransitionType());
-    }
-  }
-
-  // The referrer is not known yet, and will be updated later.
-  const web::Referrer emptyReferrer;
-  std::unique_ptr<web::NavigationContextImpl> context =
-      [self registerLoadRequestForURL:URL
-                             referrer:emptyReferrer
-                           transition:transition
-               sameDocumentNavigation:sameDocumentNavigation
-                       hasUserGesture:hasUserGesture
-                    rendererInitiated:rendererInitiated
-                placeholderNavigation:placeholderNavigation];
-  context->SetWKNavigationType(navigationType);
-  return context;
-}
-
-- (std::unique_ptr<web::NavigationContextImpl>)
-    registerLoadRequestForURL:(const GURL&)requestURL
-                     referrer:(const web::Referrer&)referrer
-                   transition:(ui::PageTransition)transition
-       sameDocumentNavigation:(BOOL)sameDocumentNavigation
-               hasUserGesture:(BOOL)hasUserGesture
-            rendererInitiated:(BOOL)rendererInitiated
-        placeholderNavigation:(BOOL)placeholderNavigation {
-  // Transfer time is registered so that further transitions within the time
-  // envelope are not also registered as links.
-  [_delegate
-      webRequestControllerUserInteractionState:self] -> ResetLastTransferTime();
-
-  // Add or update pending item before any WebStateObserver callbacks.
-  // See https://crbug.com/842151 for a scenario where this is important.
-  web::NavigationItem* item =
-      self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession();
-  if (item) {
-    // Update the existing pending entry.
-    // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
-    // Don't update if request is a placeholder entry because the pending item
-    // should have the original target URL.
-    // Don't update if pending URL has a different origin, because client
-    // redirects can not change the origin. It is possible to have more than one
-    // pending navigations, so the redirect does not necesserily belong to the
-    // pending navigation item.
-    if (!placeholderNavigation &&
-        item->GetURL().GetOrigin() == requestURL.GetOrigin()) {
-      self.navigationManagerImpl->UpdatePendingItemUrl(requestURL);
-    }
-  } else {
-    self.navigationManagerImpl->AddPendingItem(
-        requestURL, referrer, transition,
-        rendererInitiated ? web::NavigationInitiationType::RENDERER_INITIATED
-                          : web::NavigationInitiationType::BROWSER_INITIATED,
-        web::NavigationManager::UserAgentOverrideOption::INHERIT);
-    item =
-        self.navigationManagerImpl->GetPendingItemInCurrentOrRestoredSession();
-  }
-
-  bool redirect = transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
-  if (!redirect) {
-    // Before changing navigation state, the delegate should be informed that
-    // any existing request is being cancelled before completion.
-    [self.navigationHandler loadCancelled];
-    DCHECK_EQ(web::WKNavigationState::FINISHED,
-              self.navigationHandler.navigationState);
-  }
-
-  self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
-
-  // Record the state of outgoing web view. Do nothing if native controller
-  // exists, because in that case recordStateInHistory will record the state
-  // of incoming page as native controller is already inserted.
-  // TODO(crbug.com/811770) Don't record state under WKBasedNavigationManager
-  // because it may incorrectly clobber the incoming page if this is a
-  // back/forward navigation. WKWebView restores page scroll state for web view
-  // pages anyways so this only impacts user if WKWebView is deleted.
-  if (!redirect &&
-      ![[_delegate webRequestControllerLegacyNativeContentController:self]
-          hasController] &&
-      !web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
-    [_delegate webRequestControllerRecordStateInHistory:self];
-  }
-
-  std::unique_ptr<web::NavigationContextImpl> context =
-      web::NavigationContextImpl::CreateNavigationContext(
-          self.webState, requestURL, hasUserGesture, transition,
-          rendererInitiated);
-  context->SetPlaceholderNavigation(placeholderNavigation);
-
-  // TODO(crbug.com/676129): LegacyNavigationManagerImpl::AddPendingItem does
-  // not create a pending item in case of reload. Remove this workaround once
-  // the bug is fixed or WKBasedNavigationManager is fully adopted.
-  if (!item) {
-    DCHECK(!web::GetWebClient()->IsSlimNavigationManagerEnabled());
-    item = self.navigationManagerImpl->GetLastCommittedItem();
-  }
-
-  context->SetNavigationItemUniqueID(item->GetUniqueID());
-  context->SetIsPost([self.navigationHandler isCurrentNavigationItemPOST]);
-  context->SetIsSameDocument(sameDocumentNavigation);
-
-  if (!IsWKInternalUrl(requestURL) && !placeholderNavigation) {
-    self.webState->SetIsLoading(true);
-  }
-
-  // WKWebView may have multiple pending items. Move pending item ownership from
-  // NavigationManager to NavigationContext. NavigationManager owns pending item
-  // after navigation was requested and until NavigationContext is created.
-  // No need to transfer the ownership for NativeContent URLs, because the
-  // load of NativeContent is synchronous. No need to transfer the ownership
-  // for WebUI navigations, because those navigation do not have access to
-  // NavigationContext.
-  if (![[_delegate webRequestControllerLegacyNativeContentController:self]
-          shouldLoadURLInNativeView:context->GetUrl()]) {
-    if (self.navigationManagerImpl->GetPendingItemIndex() == -1) {
-      context->SetItem(self.navigationManagerImpl->ReleasePendingItem());
-    }
-  }
-
-  return context;
-}
-
-- (void)didFinishWithURL:(const GURL&)currentURL
-             loadSuccess:(BOOL)loadSuccess
-                 context:(nullable const web::NavigationContextImpl*)context {
-  DCHECK_EQ(web::WKNavigationState::FINISHED,
-            self.navigationHandler.navigationState);
-
-  [_delegate webRequestControllerRestoreStateFromHistory:self];
-
-  // Placeholder and restore session URLs are implementation details so should
-  // not notify WebStateObservers. If |context| is nullptr, don't skip
-  // placeholder URLs because this may be the only opportunity to update
-  // |isLoading| for native view reload.
-
-  if (context && context->IsPlaceholderNavigation())
-    return;
-
-  if (context && IsRestoreSessionUrl(context->GetUrl()))
-    return;
-
-  if (IsRestoreSessionUrl(net::GURLWithNSURL(self.webView.URL)))
-    return;
-
-  if (context && context->IsLoadingErrorPage())
-    return;
-
-  if (!loadSuccess) {
-    // WebStateObserver callbacks will be called for load failure after
-    // loading placeholder URL.
-    return;
-  }
-
-  if (![self.navigationHandler.navigationStates
-              lastNavigationWithPendingItemInNavigationContext]) {
-    self.webState->SetIsLoading(false);
-  } else {
-    // There is another pending navigation, so the state is still loading.
-  }
-  self.webState->OnPageLoaded(currentURL, YES);
-}
-
-// Reports Navigation.IOSWKWebViewSlowFastBackForward UMA. No-op if pending
-// navigation is not back forward navigation.
-- (void)reportBackForwardNavigationTypeForFastNavigation:(BOOL)isFast {
-  web::NavigationManager* navigationManager = self.navigationManagerImpl;
-  int pendingIndex = navigationManager->GetPendingItemIndex();
-  if (pendingIndex == -1) {
-    // Pending navigation is not a back forward navigation.
-    return;
-  }
-
-  BOOL isBack = pendingIndex < navigationManager->GetLastCommittedItemIndex();
-  BackForwardNavigationType type = BackForwardNavigationType::FAST_BACK;
-  if (isBack) {
-    type = isFast ? BackForwardNavigationType::FAST_BACK
-                  : BackForwardNavigationType::SLOW_BACK;
-  } else {
-    type = isFast ? BackForwardNavigationType::FAST_FORWARD
-                  : BackForwardNavigationType::SLOW_FORWARD;
-  }
-
-  UMA_HISTOGRAM_ENUMERATION(
-      "Navigation.IOSWKWebViewSlowFastBackForward", type,
-      BackForwardNavigationType::BACK_FORWARD_NAVIGATION_TYPE_COUNT);
-}
-
 // Returns a NSMutableURLRequest that represents the current NavigationItem.
 - (NSMutableURLRequest*)requestForCurrentNavigationItem {
   web::NavigationItem* item = self.currentNavItem;
@@ -630,42 +808,7 @@
          [list.backList indexOfObject:item] != NSNotFound;
 }
 
-#pragma mark Navigation and Session Information
-
-// Returns the associated NavigationManagerImpl.
-- (web::NavigationManagerImpl*)navigationManagerImpl {
-  return self.webState ? &(self.webState->GetNavigationManagerImpl()) : nil;
-}
-
-// Returns the navigation item for the current page.
-- (web::NavigationItemImpl*)currentNavItem {
-  return self.navigationManagerImpl
-             ? self.navigationManagerImpl->GetCurrentItemImpl()
-             : nullptr;
-}
-
-// Returns the current transition type.
-- (ui::PageTransition)currentTransition {
-  if (self.currentNavItem)
-    return self.currentNavItem->GetTransitionType();
-  else
-    return ui::PageTransitionFromInt(0);
-}
-
-// Returns the referrer for current navigation item. May be empty.
-- (web::Referrer)currentNavItemReferrer {
-  web::NavigationItem* currentItem = self.currentNavItem;
-  return currentItem ? currentItem->GetReferrer() : web::Referrer();
-}
-
-// The HTTP headers associated with the current navigation item. These are nil
-// unless the request was a POST.
-- (NSDictionary*)currentHTTPHeaders {
-  web::NavigationItem* currentItem = self.currentNavItem;
-  return currentItem ? currentItem->GetHttpRequestHeaders() : nil;
-}
-
-#pragma mark - Private methods
+#pragma mark - Private properties
 
 - (WKWebView*)webView {
   return [_delegate webRequestControllerWebView:self];
@@ -675,4 +818,13 @@
   return [_delegate webRequestControllerNavigationHandler:self];
 }
 
+- (CRWLegacyNativeContentController*)legacyNativeController {
+  return [_delegate webRequestControllerLegacyNativeContentController:self];
+}
+
+// Whether the associated WebState has an opener.
+- (BOOL)hasOpener {
+  return self.webState ? self.webState->HasOpener() : NO;
+}
+
 @end
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index b7ec367..be3b70c 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -796,7 +796,10 @@
   if (shared_state_.renderer && !renderer_ended_)
     return;
 
-  DCHECK_EQ(status_, PIPELINE_OK);
+  // Don't fire an ended event if we're already in an error state.
+  if (status_ != PIPELINE_OK)
+    return;
+
   main_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&PipelineImpl::OnEnded, weak_pipeline_));
 }
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 3034da0e..09b3f26 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -678,15 +678,6 @@
 
   ReportMetrics(load_type, loaded_url_, *frame_, media_log_.get());
 
-  // Report poster availability for SRC=.
-  if (load_type == kLoadTypeURL) {
-    if (preload_ == MultibufferDataSource::METADATA) {
-      UMA_HISTOGRAM_BOOLEAN("Media.SRC.PreloadMetaDataHasPoster", has_poster_);
-    } else if (preload_ == MultibufferDataSource::AUTO) {
-      UMA_HISTOGRAM_BOOLEAN("Media.SRC.PreloadAutoHasPoster", has_poster_);
-    }
-  }
-
   // Set subresource URL for crash reporting; will be truncated to 256 bytes.
   static base::debug::CrashKeyString* subresource_url =
       base::debug::AllocateCrashKeyString("subresource_url",
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 843d4f54..94b782eb 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -455,10 +455,7 @@
       media_log_(media_log),
       duration_(kNoTimestamp),
       user_specified_duration_(-1),
-      liveness_(DemuxerStream::LIVENESS_UNKNOWN),
-      detected_audio_track_count_(0),
-      detected_video_track_count_(0),
-      detected_text_track_count_(0) {
+      liveness_(DemuxerStream::LIVENESS_UNKNOWN) {
   DCHECK(open_cb_);
   DCHECK(encrypted_media_init_data_cb_);
   MEDIA_LOG(INFO, media_log_) << GetDisplayName();
@@ -1265,24 +1262,11 @@
       s->SetLiveness(params.liveness);
   }
 
-  detected_audio_track_count_ += params.detected_audio_track_count;
-  detected_video_track_count_ += params.detected_video_track_count;
-  detected_text_track_count_ += params.detected_text_track_count;
-
   // Wait until all streams have initialized.
   pending_source_init_ids_.erase(source_id);
   if (!pending_source_init_ids_.empty())
     return;
 
-  // Record detected track counts by type corresponding to an MSE playback.
-  // Counts are split into 50 buckets, capped into [0,100] range.
-  UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Audio",
-                           detected_audio_track_count_);
-  UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Video",
-                           detected_video_track_count_);
-  UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Text",
-                           detected_text_track_count_);
-
   for (const auto& s : video_streams_) {
     media_log_->RecordRapporWithSecurityOrigin(
         "Media.OriginUrl.MSE.VideoCodec." +
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index f1951f01..4b4ac61 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -523,11 +523,6 @@
   // in a shut down state, so reading from them will return EOS.
   std::vector<std::unique_ptr<ChunkDemuxerStream>> removed_streams_;
 
-  // Accumulate, by type, detected track counts across the SourceBuffers.
-  int detected_audio_track_count_;
-  int detected_video_track_count_;
-  int detected_text_track_count_;
-
   // Callback for reporting the number of bytes appended to this ChunkDemuxer.
   BytesReceivedCB bytes_received_cb_;
 
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 683a9c5a..7033eb4 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -133,16 +133,6 @@
       base::CustomHistogram::ArrayToCustomEnumRanges(kCommonAspectRatios100));
 }
 
-// Record detected track counts by type corresponding to a src= playback.
-// Counts are split into 50 buckets, capped into [0,100] range.
-static void RecordDetectedTrackTypeStats(int audio_count,
-                                         int video_count,
-                                         int text_count) {
-  UMA_HISTOGRAM_COUNTS_100("Media.DetectedTrackCount.Audio", audio_count);
-  UMA_HISTOGRAM_COUNTS_100("Media.DetectedTrackCount.Video", video_count);
-  UMA_HISTOGRAM_COUNTS_100("Media.DetectedTrackCount.Text", text_count);
-}
-
 // Record audio decoder config UMA stats corresponding to a src= playback.
 static void RecordAudioCodecStats(const AudioDecoderConfig& audio_config) {
   UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", audio_config.codec(),
@@ -1300,9 +1290,6 @@
   start_time_ = kInfiniteDuration;
 
   base::TimeDelta max_duration;
-  int detected_audio_track_count = 0;
-  int detected_video_track_count = 0;
-  int detected_text_track_count = 0;
   int supported_audio_track_count = 0;
   int supported_video_track_count = 0;
   bool has_opus_or_vorbis_audio = false;
@@ -1327,8 +1314,6 @@
         base::UmaHistogramSparse("Media.DetectedAudioCodecHash.Local",
                                  codec_hash);
       }
-
-      detected_audio_track_count++;
     } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
       // Log the codec detected, whether it is supported or not, and whether or
       // not we have already detected a supported codec in another stream.
@@ -1338,7 +1323,6 @@
         base::UmaHistogramSparse("Media.DetectedVideoCodecHash.Local",
                                  codec_hash);
       }
-      detected_video_track_count++;
 
 #if BUILDFLAG(ENABLE_HEVC_DEMUXING)
       if (codec_id == AV_CODEC_ID_HEVC) {
@@ -1355,7 +1339,6 @@
       }
 #endif
     } else if (codec_type == AVMEDIA_TYPE_SUBTITLE) {
-      detected_text_track_count++;
       stream->discard = AVDISCARD_ALL;
       continue;
     } else {
@@ -1474,10 +1457,6 @@
     streams_[i]->set_start_time(start_time);
   }
 
-  RecordDetectedTrackTypeStats(detected_audio_track_count,
-                               detected_video_track_count,
-                               detected_text_track_count);
-
   if (media_tracks->tracks().empty()) {
     MEDIA_LOG(ERROR, media_log_) << GetDisplayName()
                                  << ": no supported streams";
diff --git a/media/filters/stream_parser_factory.cc b/media/filters/stream_parser_factory.cc
index 2f29cef3..9e29421 100644
--- a/media/filters/stream_parser_factory.cc
+++ b/media/filters/stream_parser_factory.cc
@@ -524,8 +524,14 @@
 
   if (CheckTypeAndCodecs(type, codecs, media_log, &factory_function,
                          &audio_codecs, &video_codecs)) {
-    // Log the number of codecs specified, as well as the details on each one.
-    UMA_HISTOGRAM_COUNTS_100("Media.MSE.NumberOfTracks", codecs.size());
+    // Log the expected codecs.
+    // TODO(wolenetz): Relocate the logging to the parser configuration
+    // callback. This creation method is called in AddId(), and also in
+    // CanChangeType() and ChangeType(), so potentially overlogs codecs leading
+    // to disproportion versus actually parsed codec configurations from
+    // initialization segments. For this work and also recording when implicit
+    // codec switching occurs (without explicit ChangeType), see
+    // https://crbug.com/535738.
     for (size_t i = 0; i < audio_codecs.size(); ++i) {
       UMA_HISTOGRAM_ENUMERATION("Media.MSE.AudioCodec", audio_codecs[i],
                                 CodecInfo::HISTOGRAM_MAX + 1);
diff --git a/media/gpu/v4l2/v4l2_slice_video_decoder.cc b/media/gpu/v4l2/v4l2_slice_video_decoder.cc
index ba40afef..29d94acb 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decoder.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decoder.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/task/post_task.h"
 #include "media/base/scopedfd_helper.h"
+#include "media/base/video_util.h"
 #include "media/gpu/accelerated_video_decoder.h"
 #include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
 #include "media/gpu/gpu_video_decode_accelerator_helpers.h"
@@ -377,9 +378,9 @@
                                   base::BindOnce(std::move(init_cb), false));
     return;
   }
+  pixel_aspect_ratio_ = config.GetPixelAspectRatio();
   visible_rect_ = config.visible_rect();
-  natural_size_ = config.natural_size();
-  frame_pool_->SetFrameFormat(*frame_layout_, visible_rect_, natural_size_);
+  UpdateVideoFramePoolFormat();
 
   // Create Input/Output V4L2Queue
   input_queue_ = device_->GetQueue(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
@@ -479,6 +480,11 @@
   return true;
 }
 
+void V4L2SliceVideoDecoder::UpdateVideoFramePoolFormat() {
+  gfx::Size natural_size = GetNaturalSize(visible_rect_, pixel_aspect_ratio_);
+  frame_pool_->SetFrameFormat(*frame_layout_, visible_rect_, natural_size);
+}
+
 void V4L2SliceVideoDecoder::Reset(base::OnceClosure closure) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DVLOGF(3);
@@ -680,7 +686,8 @@
         scoped_refptr<V4L2DecodeSurface> surface = std::move(request.surface);
 
         DCHECK(surface->video_frame());
-        RunOutputCB(surface->video_frame(), request.timestamp);
+        RunOutputCB(surface->video_frame(), surface->visible_rect(),
+                    request.timestamp);
         break;
     }
   }
@@ -735,9 +742,14 @@
     return false;
   }
   frame_layout_ = VideoFrameLayout::Create(frame_layout_->format(), coded_size);
-  frame_pool_->SetFrameFormat(*frame_layout_, visible_rect_, natural_size_);
+  DCHECK(frame_layout_);
+
+  visible_rect_ = avd_->GetVisibleRect();
+  UpdateVideoFramePoolFormat();
 
   // Allocate new output buffers.
+  if (!output_queue_->DeallocateBuffers())
+    return false;
   size_t num_output_frames = avd_->GetRequiredNumOfPictures();
   DCHECK_GT(num_output_frames, 0u);
   if (output_queue_->AllocateBuffers(num_output_frames, V4L2_MEMORY_DMABUF) ==
@@ -868,14 +880,17 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
   DVLOGF(3);
 
+  if (visible_rect_ != visible_rect) {
+    visible_rect_ = visible_rect;
+    UpdateVideoFramePoolFormat();
+  }
+
   auto it = bitstream_id_to_timestamp_.find(bitstream_id);
   DCHECK(it != bitstream_id_to_timestamp_.end());
   base::TimeDelta timestamp = it->second;
   bitstream_id_to_timestamp_.erase(it);
 
-  // TODO(akahuang): Update visible_rect at the output frame.
   dec_surface->SetVisibleRect(visible_rect);
-
   output_request_queue_.push(
       OutputRequest::Surface(std::move(dec_surface), timestamp));
   PumpOutputSurfaces();
@@ -1057,14 +1072,20 @@
 }
 
 void V4L2SliceVideoDecoder::RunOutputCB(scoped_refptr<VideoFrame> frame,
+                                        const gfx::Rect& visible_rect,
                                         base::TimeDelta timestamp) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
   DVLOGF(4) << "timestamp: " << timestamp;
 
-  // The attribute of the frame is needed to update. Wrap the frame again.
-  if (frame->timestamp() != timestamp) {
+  // We need to update one or more attributes of the frame. Since we can't
+  // modify the attributes of the frame directly, we wrap the frame into a new
+  // frame with updated attributes. The old frame is bound to a destruction
+  // observer so it's not destroyed before the wrapped frame.
+  if (frame->visible_rect() != visible_rect ||
+      frame->timestamp() != timestamp) {
+    gfx::Size natural_size = GetNaturalSize(visible_rect, pixel_aspect_ratio_);
     scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
-        *frame, frame->format(), frame->visible_rect(), frame->natural_size());
+        *frame, frame->format(), visible_rect, natural_size);
     wrapped_frame->set_timestamp(timestamp);
     wrapped_frame->AddDestructionObserver(base::BindOnce(
         base::DoNothing::Once<scoped_refptr<VideoFrame>>(), std::move(frame)));
diff --git a/media/gpu/v4l2/v4l2_slice_video_decoder.h b/media/gpu/v4l2/v4l2_slice_video_decoder.h
index acb2d062..a024d99 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decoder.h
+++ b/media/gpu/v4l2/v4l2_slice_video_decoder.h
@@ -15,10 +15,12 @@
 #include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "base/threading/thread.h"
 #include "media/base/video_decoder.h"
+#include "media/base/video_frame_layout.h"
 #include "media/base/video_types.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/gpu/v4l2/v4l2_decode_surface_handler.h"
@@ -143,6 +145,8 @@
   uint32_t NegotiateOutputFormat();
   // Setup format for V4L2 output buffers.
   bool SetupOutputFormat(uint32_t output_format_fourcc);
+  // Update the format of frames in |frame_pool_|.
+  void UpdateVideoFramePoolFormat();
 
   // Destroy on decoder thread.
   void DestroyTask();
@@ -188,7 +192,9 @@
   // Get the next bitsream ID.
   int32_t GetNextBitstreamId();
   // Convert the frame and call the output callback.
-  void RunOutputCB(scoped_refptr<VideoFrame> frame, base::TimeDelta timestamp);
+  void RunOutputCB(scoped_refptr<VideoFrame> frame,
+                   const gfx::Rect& visible_rect,
+                   base::TimeDelta timestamp);
   // Call the decode callback and count the number of pending callbacks.
   void RunDecodeCB(DecodeCB cb, DecodeStatus status);
   // Change the state and check the state transition is valid.
@@ -220,7 +226,9 @@
   // Parameters for generating output VideoFrame.
   base::Optional<VideoFrameLayout> frame_layout_;
   gfx::Rect visible_rect_;
-  gfx::Size natural_size_;
+  // Ratio of natural_size to visible_rect of the output frame.
+  double pixel_aspect_ratio_;
+
   // Callbacks passed from Initialize().
   OutputCB output_cb_;
   // Callbacks of EOS buffer passed from Decode().
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index e5ad316..d3be4e9 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/video_frame.h"
+#include "media/base/video_util.h"
 #include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/format_utils.h"
@@ -231,7 +232,7 @@
   needs_bitstream_conversion_ = (config.codec() == kCodecH264);
 
   visible_rect_ = config.visible_rect();
-  natural_size_ = config.natural_size();
+  pixel_aspect_ratio_ = config.GetPixelAspectRatio();
   profile_ = profile;
 
   output_cb_ = std::move(output_cb);
@@ -459,12 +460,20 @@
 
 void VaapiVideoDecoder::SurfaceReady(const scoped_refptr<VASurface>& va_surface,
                                      int32_t /*buffer_id*/,
-                                     const gfx::Rect& /*visible_rect*/,
+                                     const gfx::Rect& visible_rect,
                                      const VideoColorSpace& /*color_space*/) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
   DCHECK_EQ(state_, State::kDecoding);
   DVLOGF(3);
 
+  // In some rare cases it's possible the frame's visible rectangle is different
+  // from what the decoder asked us to create in the last resolution change.
+  if (visible_rect_ != visible_rect) {
+    visible_rect_ = visible_rect;
+    gfx::Size natural_size = GetNaturalSize(visible_rect_, pixel_aspect_ratio_);
+    frame_pool_->SetFrameFormat(*frame_layout_, visible_rect_, natural_size);
+  }
+
   // Find the frame associated with the surface. We won't erase it from
   // |output_frames_| yet, as the decoder might still be using it for reference.
   DCHECK_EQ(output_frames_.count(va_surface->id()), 1u);
@@ -477,6 +486,21 @@
   DCHECK(video_frame);
   DVLOGF(4);
 
+  // We need to update one or more attributes of the frame. Since we can't
+  // modify the attributes of the frame directly, we wrap the frame into a new
+  // frame with updated attributes. The old frame is bound to a destruction
+  // observer so it's not destroyed before the wrapped frame.
+  if (video_frame->visible_rect() != visible_rect_) {
+    gfx::Size natural_size = GetNaturalSize(visible_rect_, pixel_aspect_ratio_);
+    scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
+        *video_frame, video_frame->format(), visible_rect_, natural_size);
+    wrapped_frame->AddDestructionObserver(
+        base::BindOnce(base::DoNothing::Once<scoped_refptr<VideoFrame>>(),
+                       std::move(video_frame)));
+
+    video_frame = std::move(wrapped_frame);
+  }
+
   scoped_refptr<VideoFrame> converted_frame =
       frame_converter_->ConvertFrame(video_frame);
   if (!converted_frame) {
@@ -499,14 +523,14 @@
   VLOGF(2);
 
   // TODO(hiroh): Handle profile changes.
-  // TODO(dstaessens): Update natural size after submitting crrev.com/c/1657871.
   visible_rect_ = decoder_->GetVisibleRect();
-  natural_size_ = visible_rect_.size();
+  gfx::Size natural_size = GetNaturalSize(visible_rect_, pixel_aspect_ratio_);
   gfx::Size pic_size = decoder_->GetPicSize();
   const VideoPixelFormat format =
       GfxBufferFormatToVideoPixelFormat(GetBufferFormat());
-  auto frame_layout = VideoFrameLayout::Create(format, pic_size);
-  frame_pool_->SetFrameFormat(*frame_layout, visible_rect_, natural_size_);
+  frame_layout_ = VideoFrameLayout::Create(format, pic_size);
+  DCHECK(frame_layout_);
+  frame_pool_->SetFrameFormat(*frame_layout_, visible_rect_, natural_size);
   frame_pool_->SetMaxNumFrames(decoder_->GetRequiredNumOfPictures());
 
   // All pending decode operations will be completed before triggering a
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h
index 94bbc02..0028f5c 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.h
+++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -17,10 +17,12 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/threading/thread.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_decoder.h"
+#include "media/base/video_frame_layout.h"
 #include "media/gpu/decode_surface_handler.h"
 #include "media/video/supported_video_decoder_config.h"
 #include "ui/gfx/geometry/rect.h"
@@ -165,8 +167,10 @@
   bool needs_bitstream_conversion_ = false;
 
   // Output frame properties.
+  base::Optional<VideoFrameLayout> frame_layout_;
   gfx::Rect visible_rect_;
-  gfx::Size natural_size_;
+  // Ratio of natural size to |visible_rect_| of the output frames.
+  double pixel_aspect_ratio_ = 0.0;
 
   // Video frame pool used to allocate and recycle video frames.
   std::unique_ptr<DmabufVideoFramePool> frame_pool_;
diff --git a/media/gpu/windows/d3d11_texture_selector.cc b/media/gpu/windows/d3d11_texture_selector.cc
index 3789d17..b248418 100644
--- a/media/gpu/windows/d3d11_texture_selector.cc
+++ b/media/gpu/windows/d3d11_texture_selector.cc
@@ -6,6 +6,8 @@
 
 #include <d3d11.h>
 
+#include "base/feature_list.h"
+#include "media/base/media_switches.h"
 #include "media/gpu/windows/d3d11_copying_texture_wrapper.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -27,35 +29,53 @@
   SetUpTextureDescriptor();
 }
 
+bool SupportsZeroCopy(const gpu::GpuPreferences& preferences,
+                      const gpu::GpuDriverBugWorkarounds& workarounds) {
+  if (!preferences.enable_zero_copy_dxgi_video)
+    return false;
+
+  if (!base::FeatureList::IsEnabled(kD3D11VideoDecoderIgnoreWorkarounds))
+    if (workarounds.disable_dxgi_zero_copy_video)
+      return false;
+
+  return true;
+}
+
 // static
 std::unique_ptr<TextureSelector> TextureSelector::Create(
+    const gpu::GpuPreferences& gpu_preferences,
+    const gpu::GpuDriverBugWorkarounds& workarounds,
     const VideoDecoderConfig& config) {
-  // TODO(tmathmeyer) use a CopyTextureSelector
-  if (config.profile() == VP9PROFILE_PROFILE2) {
-    return std::make_unique<TextureSelector>(
-        PIXEL_FORMAT_YUV420P10, DXGI_FORMAT_P010,
-        D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2, config.coded_size(),
-        config.is_encrypted(), false);
-  }
-
   bool supports_nv12_decode_swap_chain = base::FeatureList::IsEnabled(
       features::kDirectCompositionUseNV12DecodeSwapChain);
+  bool needs_texture_copy = !SupportsZeroCopy(gpu_preferences, workarounds);
 
-  if (config.profile() == VP9PROFILE_PROFILE0) {
-    return std::make_unique<TextureSelector>(
-        PIXEL_FORMAT_NV12, DXGI_FORMAT_NV12,
-        D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0, config.coded_size(),
-        config.is_encrypted(), supports_nv12_decode_swap_chain);
-  }
-
+  DXGI_FORMAT input_dxgi_format = DXGI_FORMAT_NV12;
+  DXGI_FORMAT output_dxgi_format = DXGI_FORMAT_NV12;
+  GUID decoder_guid = {};
   if (config.codec() == kCodecH264) {
-    return std::make_unique<TextureSelector>(
-        PIXEL_FORMAT_NV12, DXGI_FORMAT_NV12,
-        D3D11_DECODER_PROFILE_H264_VLD_NOFGT, config.coded_size(),
-        config.is_encrypted(), supports_nv12_decode_swap_chain);
+    decoder_guid = D3D11_DECODER_PROFILE_H264_VLD_NOFGT;
+  } else if (config.profile() == VP9PROFILE_PROFILE0) {
+    decoder_guid = D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0;
+  } else if (config.profile() == VP9PROFILE_PROFILE2) {
+    decoder_guid = D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2;
+    input_dxgi_format = DXGI_FORMAT_P010;
+  } else {
+    // TODO(tmathmeyer) support other profiles in the future.
+    return nullptr;
   }
 
-  return nullptr;
+  if ((input_dxgi_format != output_dxgi_format) || needs_texture_copy) {
+    return std::make_unique<CopyTextureSelector>(
+        PIXEL_FORMAT_NV12, input_dxgi_format, output_dxgi_format, decoder_guid,
+        config.coded_size(), config.is_encrypted(),
+        supports_nv12_decode_swap_chain);  // TODO(tmathmeyer) false always?
+  } else {
+    return std::make_unique<TextureSelector>(
+        PIXEL_FORMAT_NV12, output_dxgi_format, decoder_guid,
+        config.coded_size(), config.is_encrypted(),
+        supports_nv12_decode_swap_chain);
+  }
 }
 
 bool TextureSelector::SupportsDevice(
@@ -131,6 +151,7 @@
       D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
   texture_desc_.ArraySize = 1;
   texture_desc_.CPUAccessFlags = 0;
+  texture_desc_.Format = output_dxgifmt_;
 
   ComD3D11Texture2D out_texture = CreateOutputTexture(device, size);
   if (!out_texture)
diff --git a/media/gpu/windows/d3d11_texture_selector.h b/media/gpu/windows/d3d11_texture_selector.h
index f54d52f0..c828bf7 100644
--- a/media/gpu/windows/d3d11_texture_selector.h
+++ b/media/gpu/windows/d3d11_texture_selector.h
@@ -30,6 +30,8 @@
   virtual ~TextureSelector() = default;
 
   static std::unique_ptr<TextureSelector> Create(
+      const gpu::GpuPreferences& gpu_preferences,
+      const gpu::GpuDriverBugWorkarounds& workarounds,
       const VideoDecoderConfig& config);
 
   bool SupportsDevice(Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device);
@@ -67,17 +69,19 @@
 class MEDIA_GPU_EXPORT CopyTextureSelector : public TextureSelector {
  public:
   CopyTextureSelector(VideoPixelFormat pixfmt,
-                      DXGI_FORMAT dxgifmt,
+                      DXGI_FORMAT input_dxgifmt,
+                      DXGI_FORMAT output_dxgifmt,
                       GUID decoder_guid,
                       gfx::Size coded_size,
                       bool is_encrypted,
                       bool supports_swap_chain)
       : TextureSelector(pixfmt,
-                        dxgifmt,
+                        input_dxgifmt,
                         decoder_guid,
                         coded_size,
                         is_encrypted,
-                        supports_swap_chain) {}
+                        supports_swap_chain),
+        output_dxgifmt_(output_dxgifmt) {}
 
   std::unique_ptr<Texture2DWrapper> CreateTextureWrapper(
       ComD3D11Device device,
@@ -85,6 +89,9 @@
       ComD3D11DeviceContext,
       ComD3D11Texture2D input_texture,
       gfx::Size size) override;
+
+ private:
+  DXGI_FORMAT output_dxgifmt_;
 };
 
 }  // namespace media
diff --git a/media/gpu/windows/d3d11_texture_selector_unittest.cc b/media/gpu/windows/d3d11_texture_selector_unittest.cc
index a132094..2679128c 100644
--- a/media/gpu/windows/d3d11_texture_selector_unittest.cc
+++ b/media/gpu/windows/d3d11_texture_selector_unittest.cc
@@ -34,10 +34,20 @@
                          pattern));
     return result;
   }
+
+  std::unique_ptr<TextureSelector> CreateWithDefaultGPUInfo(
+      const VideoDecoderConfig& config,
+      bool zero_copy_enabled = true) {
+    gpu::GpuPreferences prefs;
+    prefs.enable_zero_copy_dxgi_video = zero_copy_enabled;
+    gpu::GpuDriverBugWorkarounds workarounds;
+    workarounds.disable_dxgi_zero_copy_video = false;
+    return TextureSelector::Create(prefs, workarounds, config);
+  }
 };
 
 TEST_F(D3D11TextureSelectorUnittest, VP9Profile0RightFormats) {
-  auto tex_sel = TextureSelector::Create(
+  auto tex_sel = CreateWithDefaultGPUInfo(
       CreateDecoderConfig(VP9PROFILE_PROFILE0, {0, 0}, false));
 
   EXPECT_EQ(tex_sel->DecoderGuid(), D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0);
@@ -45,8 +55,18 @@
   EXPECT_EQ(tex_sel->DecoderDescriptor()->OutputFormat, DXGI_FORMAT_NV12);
 }
 
+TEST_F(D3D11TextureSelectorUnittest, VP9Profile2RightFormats) {
+  auto tex_sel = CreateWithDefaultGPUInfo(
+      CreateDecoderConfig(VP9PROFILE_PROFILE2, {0, 0}, false), false);
+
+  EXPECT_EQ(tex_sel->DecoderGuid(),
+            D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2);
+  EXPECT_EQ(tex_sel->PixelFormat(), PIXEL_FORMAT_NV12);
+  EXPECT_EQ(tex_sel->DecoderDescriptor()->OutputFormat, DXGI_FORMAT_P010);
+}
+
 TEST_F(D3D11TextureSelectorUnittest, SupportsDeviceNoProfiles) {
-  auto tex_sel = TextureSelector::Create(
+  auto tex_sel = CreateWithDefaultGPUInfo(
       CreateDecoderConfig(VP9PROFILE_PROFILE0, {0, 0}, false));
 
   auto vd_mock = CreateD3D11Mock<D3D11VideoDeviceMock>();
@@ -58,7 +78,7 @@
 }
 
 TEST_F(D3D11TextureSelectorUnittest, SupportsDeviceWrongProfiles) {
-  auto tex_sel = TextureSelector::Create(
+  auto tex_sel = CreateWithDefaultGPUInfo(
       CreateDecoderConfig(VP9PROFILE_PROFILE0, {0, 0}, false));
 
   auto vd_mock = CreateD3D11Mock<D3D11VideoDeviceMock>();
@@ -78,7 +98,7 @@
 }
 
 TEST_F(D3D11TextureSelectorUnittest, SupportsDeviceCorrectProfile) {
-  auto tex_sel = TextureSelector::Create(
+  auto tex_sel = CreateWithDefaultGPUInfo(
       CreateDecoderConfig(VP9PROFILE_PROFILE0, {0, 0}, false));
 
   auto vd_mock = CreateD3D11Mock<D3D11VideoDeviceMock>();
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index 29cbc67..a14c0e1 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -6,6 +6,7 @@
 
 #include <d3d11_4.h>
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
@@ -243,7 +244,8 @@
     return;
   }
 
-  texture_selector_ = TextureSelector::Create(config);
+  texture_selector_ =
+      TextureSelector::Create(gpu_preferences_, gpu_workarounds_, config);
   if (!texture_selector_) {
     NotifyError("D3DD11: Config provided unsupported profile");
     return;
@@ -711,16 +713,16 @@
     GetD3D11DeviceCB get_d3d11_device_cb) {
   const std::string uma_name("Media.D3D11.WasVideoSupported");
 
-  // Must allow zero-copy of nv12 textures.
-  if (!gpu_preferences.enable_zero_copy_dxgi_video) {
-    UMA_HISTOGRAM_ENUMERATION(uma_name,
-                              NotSupportedReason::kZeroCopyNv12Required);
-    return {};
-  }
-
   // This workaround accounts for almost half of all startup results, and it's
   // unclear that it's relevant here.
   if (!base::FeatureList::IsEnabled(kD3D11VideoDecoderIgnoreWorkarounds)) {
+    // Must allow zero-copy of nv12 textures.
+    if (!gpu_preferences.enable_zero_copy_dxgi_video) {
+      UMA_HISTOGRAM_ENUMERATION(uma_name,
+                                NotSupportedReason::kZeroCopyNv12Required);
+      return {};
+    }
+
     if (gpu_workarounds.disable_dxgi_zero_copy_video) {
       UMA_HISTOGRAM_ENUMERATION(uma_name,
                                 NotSupportedReason::kZeroCopyVideoRequired);
diff --git a/media/gpu/windows/d3d11_video_decoder_unittest.cc b/media/gpu/windows/d3d11_video_decoder_unittest.cc
index b48e167..ba940fe 100644
--- a/media/gpu/windows/d3d11_video_decoder_unittest.cc
+++ b/media/gpu/windows/d3d11_video_decoder_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/win/scoped_com_initializer.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/media_log.h"
 #include "media/base/media_switches.h"
@@ -215,6 +216,7 @@
   Microsoft::WRL::ComPtr<D3D11VideoContextMock> mock_d3d11_video_context_;
 
   base::Optional<base::test::ScopedFeatureList> scoped_feature_list_;
+  base::win::ScopedCOMInitializer com_initializer_;
 };
 
 TEST_F(D3D11VideoDecoderTest, SupportsVP9Profile0WithDecoderEnabled) {
@@ -272,22 +274,6 @@
   InitializeDecoder(normal, kExpectFailure);
 }
 
-TEST_F(D3D11VideoDecoderTest, RequiresZeroCopyPreference) {
-  gpu_preferences_.enable_zero_copy_dxgi_video = false;
-  CreateDecoder();
-  InitializeDecoder(
-      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
-      kExpectFailure);
-}
-
-TEST_F(D3D11VideoDecoderTest, FailsIfZeroCopyWorkaround) {
-  gpu_workarounds_.disable_dxgi_zero_copy_video = true;
-  CreateDecoder();
-  InitializeDecoder(
-      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
-      kExpectFailure);
-}
-
 TEST_F(D3D11VideoDecoderTest, DoesNotSupportEncryptionWithoutFlag) {
   CreateDecoder();
   VideoDecoderConfig encrypted_config =
@@ -298,6 +284,40 @@
   InitializeDecoder(encrypted_config, kExpectFailure);
 }
 
+TEST_F(D3D11VideoDecoderTest, DoesNotSupportZeroCopyPreference) {
+  gpu_preferences_.enable_zero_copy_dxgi_video = false;
+  CreateDecoder();
+  InitializeDecoder(
+      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
+      kExpectFailure);
+}
+
+TEST_F(D3D11VideoDecoderTest, DoesNotSupportZeroCopyWorkaround) {
+  gpu_workarounds_.disable_dxgi_zero_copy_video = true;
+  CreateDecoder();
+  InitializeDecoder(
+      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
+      kExpectFailure);
+}
+
+TEST_F(D3D11VideoDecoderTest, SupportsZeroCopyPreferenceWithFlag) {
+  EnableFeature(kD3D11VideoDecoderIgnoreWorkarounds);
+  gpu_preferences_.enable_zero_copy_dxgi_video = false;
+  CreateDecoder();
+  InitializeDecoder(
+      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
+      kExpectSuccess);
+}
+
+TEST_F(D3D11VideoDecoderTest, SupportsZeroCopyWorkaroundWithFlag) {
+  EnableFeature(kD3D11VideoDecoderIgnoreWorkarounds);
+  gpu_workarounds_.disable_dxgi_zero_copy_video = true;
+  CreateDecoder();
+  InitializeDecoder(
+      TestVideoConfig::NormalCodecProfile(kCodecH264, H264PROFILE_MAIN),
+      kExpectSuccess);
+}
+
 TEST_F(D3D11VideoDecoderTest, DoesNotSupportEncryptionWithFlagOn11_0) {
   CreateDecoder();
   VideoDecoderConfig encrypted_config =
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index ac19233f..91449c52 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -307,6 +307,9 @@
       << mailbox_holder.texture_target;
 
   gpu::gles2::GLES2Interface* gl = context_provider->ContextGL();
+  gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
+  GLuint frame_texture =
+      gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
   unsigned source_texture = 0;
   gfx::ColorSpace color_space_for_skia;
   *wrapped_video_frame_texture =
@@ -314,9 +317,7 @@
   if (*wrapped_video_frame_texture) {
     // Fast path where we can avoid a copy, by having last_image_ directly wrap
     // the VideoFrame texture.
-    gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
-    source_texture =
-        gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
+    source_texture = frame_texture;
     color_space_for_skia = video_frame->ColorSpace();
   } else {
     // TODO(dcastagna): At the moment Skia doesn't support targets different
@@ -326,9 +327,9 @@
     gl->GenTextures(1, &source_texture);
     DCHECK(source_texture);
     gl->BindTexture(GL_TEXTURE_2D, source_texture);
-    PaintCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
-        gl, video_frame, GL_TEXTURE_2D, source_texture, GL_RGBA, GL_RGBA,
-        GL_UNSIGNED_BYTE, 0, false, false);
+    gl->CopyTextureCHROMIUM(frame_texture, 0, GL_TEXTURE_2D, source_texture, 0,
+                            GL_RGBA, GL_UNSIGNED_BYTE, false, false, false);
+    gl->DeleteTextures(1, &frame_texture);
   }
   GrGLTextureInfo source_texture_info;
   source_texture_info.fID = source_texture;
diff --git a/media/renderers/paint_canvas_video_renderer_unittest.cc b/media/renderers/paint_canvas_video_renderer_unittest.cc
index 35c5b757..e94736c 100644
--- a/media/renderers/paint_canvas_video_renderer_unittest.cc
+++ b/media/renderers/paint_canvas_video_renderer_unittest.cc
@@ -18,6 +18,7 @@
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/gles2_interface_stub.h"
 #include "gpu/command_buffer/common/capabilities.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/config/gpu_feature_info.h"
 #include "media/base/timestamp_constants.h"
 #include "media/base/video_frame.h"
@@ -66,6 +67,274 @@
       static_cast<uint8_t*>(external_memory), byte_size, timestamp);
 }
 
+// Destroys a list of shared images after a sync token is passed. Also runs
+// |callback|.
+static void DestroySharedImages(
+    scoped_refptr<viz::ContextProvider> context_provider,
+    std::vector<gpu::Mailbox> mailboxes,
+    base::OnceClosure callback,
+    const gpu::SyncToken& sync_token) {
+  auto* sii = context_provider->SharedImageInterface();
+  for (const auto& mailbox : mailboxes)
+    sii->DestroySharedImage(sync_token, mailbox);
+  std::move(callback).Run();
+}
+
+// Creates a video frame from a set of shared images with a common texture
+// target and sync token.
+static scoped_refptr<VideoFrame> CreateSharedImageFrame(
+    scoped_refptr<viz::ContextProvider> context_provider,
+    VideoPixelFormat format,
+    std::vector<gpu::Mailbox> mailboxes,
+    const gpu::SyncToken& sync_token,
+    GLenum texture_target,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    const gfx::Size& natural_size,
+    base::TimeDelta timestamp,
+    base::OnceClosure destroyed_callback) {
+  gpu::MailboxHolder mailboxes_for_frame[VideoFrame::kMaxPlanes] = {};
+  size_t i = 0;
+  for (const auto& mailbox : mailboxes) {
+    mailboxes_for_frame[i++] =
+        gpu::MailboxHolder(mailbox, sync_token, texture_target);
+  }
+  auto callback =
+      base::BindOnce(&DestroySharedImages, std::move(context_provider),
+                     std::move(mailboxes), std::move(destroyed_callback));
+  return VideoFrame::WrapNativeTextures(format, mailboxes_for_frame,
+                                        std::move(callback), coded_size,
+                                        visible_rect, natural_size, timestamp);
+}
+
+// Upload pixels to a shared image using GL.
+static void UploadPixels(gpu::gles2::GLES2Interface* gl,
+                         const gpu::Mailbox& mailbox,
+                         const gfx::Size& size,
+                         GLenum format,
+                         GLenum type,
+                         const uint8_t* data) {
+  GLuint texture = gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name);
+  gl->BeginSharedImageAccessDirectCHROMIUM(
+      texture, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+  gl->BindTexture(GL_TEXTURE_2D, texture);
+  gl->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), format,
+                    type, data);
+  gl->EndSharedImageAccessDirectCHROMIUM(texture);
+  gl->DeleteTextures(1, &texture);
+}
+
+// Creates a shared image backed frame in RGBA format, with colors on the shared
+// image mapped as follow.
+// Bk | R | G | Y
+// ---+---+---+---
+// Bl | M | C | W
+static scoped_refptr<VideoFrame> CreateSharedImageRGBAFrame(
+    scoped_refptr<viz::ContextProvider> context_provider,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    base::OnceClosure destroyed_callback) {
+  DCHECK_EQ(coded_size.width() % 4, 0);
+  DCHECK_EQ(coded_size.height() % 2, 0);
+  size_t pixels_size = coded_size.GetArea() * 4;
+  auto pixels = std::make_unique<uint8_t[]>(pixels_size);
+  size_t i = 0;
+  for (size_t block_y = 0; block_y < 2u; ++block_y) {
+    for (int y = 0; y < coded_size.height() / 2; ++y) {
+      for (size_t block_x = 0; block_x < 4u; ++block_x) {
+        for (int x = 0; x < coded_size.width() / 4; ++x) {
+          pixels[i++] = 0xffu * (block_x % 2);  // R
+          pixels[i++] = 0xffu * (block_x / 2);  // G
+          pixels[i++] = 0xffu * block_y;        // B
+          pixels[i++] = 0xffu;                  // A
+        }
+      }
+    }
+  }
+  DCHECK_EQ(i, pixels_size);
+
+  auto* sii = context_provider->SharedImageInterface();
+  gpu::Mailbox mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::RGBA_8888, coded_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  auto* gl = context_provider->ContextGL();
+  gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
+  UploadPixels(gl, mailbox, coded_size, GL_RGBA, GL_UNSIGNED_BYTE,
+               pixels.get());
+  gpu::SyncToken sync_token;
+  gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+
+  return CreateSharedImageFrame(
+      std::move(context_provider), VideoPixelFormat::PIXEL_FORMAT_ABGR,
+      {mailbox}, sync_token, GL_TEXTURE_2D, coded_size, visible_rect,
+      visible_rect.size(), base::TimeDelta::FromSeconds(1),
+      std::move(destroyed_callback));
+}
+
+static constexpr const uint8_t kYuvColors[8][3] = {
+    {0x00, 0x80, 0x80},  // Black
+    {0x4c, 0x54, 0xff},  // Red
+    {0x95, 0x2b, 0x15},  // Green
+    {0xe1, 0x00, 0x94},  // Yellow
+    {0x1d, 0xff, 0x6b},  // Blue
+    {0x69, 0xd3, 0xec},  // Magenta
+    {0xb3, 0xaa, 0x00},  // Cyan
+    {0xff, 0x80, 0x80},  // White
+};
+
+// Creates a shared image backed frame in I420 format, with colors mapped
+// exactly like CreateSharedImageRGBAFrame above, noting that subsamples may get
+// interpolated leading to inconsistent colors around the "seams".
+static scoped_refptr<VideoFrame> CreateSharedImageI420Frame(
+    scoped_refptr<viz::ContextProvider> context_provider,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    base::OnceClosure destroyed_callback) {
+  DCHECK_EQ(coded_size.width() % 8, 0);
+  DCHECK_EQ(coded_size.height() % 4, 0);
+  gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2);
+  size_t y_pixels_size = coded_size.GetArea();
+  size_t uv_pixels_size = uv_size.GetArea();
+  auto y_pixels = std::make_unique<uint8_t[]>(y_pixels_size);
+  auto u_pixels = std::make_unique<uint8_t[]>(uv_pixels_size);
+  auto v_pixels = std::make_unique<uint8_t[]>(uv_pixels_size);
+  size_t y_i = 0;
+  size_t uv_i = 0;
+  for (size_t block_y = 0; block_y < 2u; ++block_y) {
+    for (int y = 0; y < coded_size.height() / 2; ++y) {
+      for (size_t block_x = 0; block_x < 4u; ++block_x) {
+        size_t color_index = block_x + block_y * 4;
+        const uint8_t* yuv = kYuvColors[color_index];
+        for (int x = 0; x < coded_size.width() / 4; ++x) {
+          y_pixels[y_i++] = yuv[0];
+          if ((x % 2) && (y % 2)) {
+            u_pixels[uv_i] = yuv[1];
+            v_pixels[uv_i++] = yuv[2];
+          }
+        }
+      }
+    }
+  }
+  DCHECK_EQ(y_i, y_pixels_size);
+  DCHECK_EQ(uv_i, uv_pixels_size);
+
+  auto* sii = context_provider->SharedImageInterface();
+  gpu::Mailbox y_mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::LUMINANCE_8, coded_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  gpu::Mailbox u_mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::LUMINANCE_8, uv_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  gpu::Mailbox v_mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::LUMINANCE_8, uv_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  auto* gl = context_provider->ContextGL();
+  gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
+  UploadPixels(gl, y_mailbox, coded_size, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+               y_pixels.get());
+  UploadPixels(gl, u_mailbox, uv_size, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+               u_pixels.get());
+  UploadPixels(gl, v_mailbox, uv_size, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+               v_pixels.get());
+  gpu::SyncToken sync_token;
+  gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+
+  return CreateSharedImageFrame(
+      std::move(context_provider), VideoPixelFormat::PIXEL_FORMAT_I420,
+      {y_mailbox, u_mailbox, v_mailbox}, sync_token, GL_TEXTURE_2D, coded_size,
+      visible_rect, visible_rect.size(), base::TimeDelta::FromSeconds(1),
+      std::move(destroyed_callback));
+}
+
+// Creates a shared image backed frame in NV12 format, with colors mapped
+// exactly like CreateSharedImageRGBAFrame above.
+// This will return nullptr if the necessary extension is not available for NV12
+// support.
+static scoped_refptr<VideoFrame> CreateSharedImageNV12Frame(
+    scoped_refptr<viz::ContextProvider> context_provider,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    base::OnceClosure destroyed_callback) {
+  DCHECK_EQ(coded_size.width() % 8, 0);
+  DCHECK_EQ(coded_size.height() % 4, 0);
+  if (!context_provider->ContextCapabilities().texture_rg)
+    return {};
+  gfx::Size uv_size(coded_size.width() / 2, coded_size.height() / 2);
+  size_t y_pixels_size = coded_size.GetArea();
+  size_t uv_pixels_size = uv_size.GetArea() * 2;
+  auto y_pixels = std::make_unique<uint8_t[]>(y_pixels_size);
+  auto uv_pixels = std::make_unique<uint8_t[]>(uv_pixels_size);
+  size_t y_i = 0;
+  size_t uv_i = 0;
+  for (size_t block_y = 0; block_y < 2u; ++block_y) {
+    for (int y = 0; y < coded_size.height() / 2; ++y) {
+      for (size_t block_x = 0; block_x < 4u; ++block_x) {
+        size_t color_index = block_x + block_y * 4;
+        const uint8_t* yuv = kYuvColors[color_index];
+        for (int x = 0; x < coded_size.width() / 4; ++x) {
+          y_pixels[y_i++] = yuv[0];
+          if ((x % 2) && (y % 2)) {
+            uv_pixels[uv_i++] = yuv[1];
+            uv_pixels[uv_i++] = yuv[2];
+          }
+        }
+      }
+    }
+  }
+  DCHECK_EQ(y_i, y_pixels_size);
+  DCHECK_EQ(uv_i, uv_pixels_size);
+
+  auto* sii = context_provider->SharedImageInterface();
+  gpu::Mailbox y_mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::LUMINANCE_8, coded_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  gpu::Mailbox uv_mailbox =
+      sii->CreateSharedImage(viz::ResourceFormat::RG_88, uv_size,
+                             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+  auto* gl = context_provider->ContextGL();
+  gl->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());
+  UploadPixels(gl, y_mailbox, coded_size, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+               y_pixels.get());
+  UploadPixels(gl, uv_mailbox, uv_size, GL_RG, GL_UNSIGNED_BYTE,
+               uv_pixels.get());
+  gpu::SyncToken sync_token;
+  gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+
+  return CreateSharedImageFrame(
+      std::move(context_provider), VideoPixelFormat::PIXEL_FORMAT_NV12,
+      {y_mailbox, uv_mailbox}, sync_token, GL_TEXTURE_2D, coded_size,
+      visible_rect, visible_rect.size(), base::TimeDelta::FromSeconds(1),
+      std::move(destroyed_callback));
+}
+
+// Readback the contents of a RGBA texture into an array of RGBA values.
+static std::unique_ptr<uint8_t[]> ReadbackTexture(
+    gpu::gles2::GLES2Interface* gl,
+    GLuint texture,
+    const gfx::Size& size) {
+  size_t pixel_count = size.width() * size.height();
+  GLuint fbo = 0;
+  gl->GenFramebuffers(1, &fbo);
+  gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
+  gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                           texture, 0);
+  auto pixels = std::make_unique<uint8_t[]>(pixel_count * 4);
+  uint8_t* raw_pixels = pixels.get();
+  gl->ReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_BYTE,
+                 raw_pixels);
+  gl->DeleteFramebuffers(1, &fbo);
+  return pixels;
+}
+
+// Returns a functor that retrieves a SkColor for a given pixel, from raw RGBA
+// data.
+static auto ColorGetter(uint8_t* pixels, const gfx::Size& size) {
+  return [pixels, size](size_t x, size_t y) {
+    uint8_t* p = pixels + (size.width() * y + x) * 4;
+    return SkColorSetARGB(p[3], p[0], p[1], p[2]);
+  };
+}
+
 class PaintCanvasVideoRendererTest : public testing::Test {
  public:
   enum Color {
@@ -78,14 +347,6 @@
   PaintCanvasVideoRendererTest();
   ~PaintCanvasVideoRendererTest() override;
 
-  void SetUp() override { gl::GLSurfaceTestSupport::InitializeOneOff(); }
-
-  void TearDown() override {
-    renderer_.ResetCache();
-    viz::TestGpuServiceHolder::ResetInstance();
-    gl::GLSurfaceTestSupport::ShutdownGL();
-  }
-
   // Paints to |canvas| using |renderer_| without any frame data.
   void PaintWithoutFrame(cc::PaintCanvas* canvas);
 
@@ -795,21 +1056,167 @@
       2 /*xoffset*/, 1 /*yoffset*/, false /*flip_y*/, true);
 }
 
-TEST_F(PaintCanvasVideoRendererTest, CopyVideoFrameYUVDataToGLTexture) {
-  gl::DisableNullDrawGLBindings enable_pixels;
+// Fixture for tests that require a GL context.
+class PaintCanvasVideoRendererWithGLTest : public PaintCanvasVideoRendererTest {
+ public:
+  using GetColorCallback = base::RepeatingCallback<SkColor(int, int)>;
 
-  auto media_context = base::MakeRefCounted<viz::TestInProcessContextProvider>(
-      false /* enable_oop_rasterization */, false /* support_locking */);
-  gpu::ContextResult result = media_context->BindToCurrentThread();
-  ASSERT_EQ(result, gpu::ContextResult::kSuccess);
+  void SetUp() override {
+    gl::GLSurfaceTestSupport::InitializeOneOff();
+    enable_pixels_.emplace();
+    media_context_ = base::MakeRefCounted<viz::TestInProcessContextProvider>(
+        false /* enable_oop_rasterization */, false /* support_locking */);
+    gpu::ContextResult result = media_context_->BindToCurrentThread();
+    ASSERT_EQ(result, gpu::ContextResult::kSuccess);
 
-  auto destination_context =
-      base::MakeRefCounted<viz::TestInProcessContextProvider>(
-          false /* enable_oop_rasterization */, false /* support_locking */);
-  result = destination_context->BindToCurrentThread();
-  ASSERT_EQ(result, gpu::ContextResult::kSuccess);
+    destination_context_ =
+        base::MakeRefCounted<viz::TestInProcessContextProvider>(
+            false /* enable_oop_rasterization */, false /* support_locking */);
+    result = destination_context_->BindToCurrentThread();
+    ASSERT_EQ(result, gpu::ContextResult::kSuccess);
+  }
 
-  gpu::gles2::GLES2Interface* destination_gl = destination_context->ContextGL();
+  void TearDown() override {
+    renderer_.ResetCache();
+    destination_context_.reset();
+    media_context_.reset();
+    enable_pixels_.reset();
+    viz::TestGpuServiceHolder::ResetInstance();
+    gl::GLSurfaceTestSupport::ShutdownGL();
+  }
+
+  // Uses CopyVideoFrameTexturesToGLTexture to copy |frame| into a GL texture,
+  // reads back its contents, and runs |check_pixels| to validate it.
+  template <class CheckPixels>
+  void CopyVideoFrameTexturesAndCheckPixels(scoped_refptr<VideoFrame> frame,
+                                            CheckPixels check_pixels) {
+    auto* destination_gl = destination_context_->ContextGL();
+    DCHECK(destination_gl);
+    GLenum target = GL_TEXTURE_2D;
+    GLuint texture = 0;
+    destination_gl->GenTextures(1, &texture);
+    destination_gl->BindTexture(target, texture);
+
+    renderer_.CopyVideoFrameTexturesToGLTexture(
+        media_context_.get(), destination_gl, frame, target, texture, GL_RGBA,
+        GL_RGBA, GL_UNSIGNED_BYTE, 0, false /* premultiply_alpha */,
+        false /* flip_y */);
+
+    gfx::Size expected_size = frame->visible_rect().size();
+
+    std::unique_ptr<uint8_t[]> pixels =
+        ReadbackTexture(destination_gl, texture, expected_size);
+    destination_gl->DeleteTextures(1, &texture);
+
+    auto get_color = base::BindRepeating(
+        [](uint8_t* pixels, const gfx::Size& size, int x, int y) {
+          uint8_t* p = pixels + (size.width() * y + x) * 4;
+          return SkColorSetARGB(p[3], p[0], p[1], p[2]);
+        },
+        pixels.get(), expected_size);
+    check_pixels(get_color);
+  }
+
+  // Uses Copy to paint |frame| into a bitmap-backed canvas, then
+  // runs |check_pixels| to validate the contents of the canvas.
+  template <class CheckPixels>
+  void PaintVideoFrameAndCheckPixels(scoped_refptr<VideoFrame> frame,
+                                     CheckPixels check_pixels) {
+    gfx::Size expected_size = frame->visible_rect().size();
+    SkBitmap bitmap =
+        AllocBitmap(expected_size.width(), expected_size.height());
+    cc::SkiaPaintCanvas canvas(bitmap);
+    canvas.clear(SK_ColorGRAY);
+    renderer_.Copy(frame, &canvas, media_context_.get());
+
+    auto get_color = base::BindRepeating(
+        [](SkBitmap* bitmap, int x, int y) { return bitmap->getColor(x, y); },
+        &bitmap);
+    check_pixels(get_color);
+  }
+
+  // Creates a cropped RGBA VideoFrame. |closure| is run once the shared images
+  // backing the VideoFrame have been destroyed.
+  scoped_refptr<VideoFrame> CreateTestRGBAFrame(base::OnceClosure closure) {
+    return CreateSharedImageRGBAFrame(media_context_, gfx::Size(16, 8),
+                                      gfx::Rect(3, 3, 12, 4),
+                                      std::move(closure));
+  }
+
+  // Checks that the contents of a texture/canvas match the expectations for the
+  // cropped RGBA frame above. |get_color| is a callback that returns the actual
+  // color at a given pixel location.
+  static void CheckRGBAFramePixels(GetColorCallback get_color) {
+    EXPECT_EQ(SK_ColorBLACK, get_color.Run(0, 0));
+    EXPECT_EQ(SK_ColorRED, get_color.Run(1, 0));
+    EXPECT_EQ(SK_ColorRED, get_color.Run(4, 0));
+    EXPECT_EQ(SK_ColorGREEN, get_color.Run(5, 0));
+    EXPECT_EQ(SK_ColorYELLOW, get_color.Run(9, 0));
+    EXPECT_EQ(SK_ColorYELLOW, get_color.Run(11, 0));
+    EXPECT_EQ(SK_ColorBLUE, get_color.Run(0, 1));
+    EXPECT_EQ(SK_ColorBLUE, get_color.Run(0, 3));
+    EXPECT_EQ(SK_ColorMAGENTA, get_color.Run(1, 1));
+    EXPECT_EQ(SK_ColorMAGENTA, get_color.Run(4, 1));
+    EXPECT_EQ(SK_ColorMAGENTA, get_color.Run(1, 3));
+    EXPECT_EQ(SK_ColorMAGENTA, get_color.Run(4, 3));
+    EXPECT_EQ(SK_ColorCYAN, get_color.Run(5, 1));
+    EXPECT_EQ(SK_ColorCYAN, get_color.Run(5, 3));
+    EXPECT_EQ(SK_ColorWHITE, get_color.Run(9, 1));
+    EXPECT_EQ(SK_ColorWHITE, get_color.Run(11, 1));
+    EXPECT_EQ(SK_ColorWHITE, get_color.Run(9, 3));
+    EXPECT_EQ(SK_ColorWHITE, get_color.Run(11, 3));
+  }
+
+  // Creates a cropped I420 VideoFrame. |closure| is run once the shared images
+  // backing the VideoFrame have been destroyed.
+  scoped_refptr<VideoFrame> CreateTestI420Frame(base::OnceClosure closure) {
+    return CreateSharedImageI420Frame(media_context_, gfx::Size(16, 8),
+                                      gfx::Rect(2, 2, 12, 4),
+                                      std::move(closure));
+  }
+
+  // Checks that the contents of a texture/canvas match the expectations for the
+  // cropped I420 frame above. |get_color| is a callback that returns the actual
+  // color at a given pixel location.
+  static void CheckI420FramePixels(GetColorCallback get_color) {
+    // Avoid checking around the "seams" where subsamples may be interpolated.
+    EXPECT_EQ(SK_ColorBLACK, get_color.Run(0, 0));
+    EXPECT_EQ(SK_ColorRED, get_color.Run(3, 0));
+    EXPECT_EQ(SK_ColorRED, get_color.Run(4, 0));
+    EXPECT_EQ(SK_ColorGREEN, get_color.Run(7, 0));
+    EXPECT_EQ(SK_ColorGREEN, get_color.Run(8, 0));
+    EXPECT_EQ(SK_ColorYELLOW, get_color.Run(11, 0));
+    EXPECT_EQ(SK_ColorBLUE, get_color.Run(0, 3));
+    EXPECT_EQ(SK_ColorMAGENTA, get_color.Run(3, 3));
+    EXPECT_EQ(SK_ColorCYAN, get_color.Run(7, 3));
+    EXPECT_EQ(SK_ColorWHITE, get_color.Run(11, 3));
+  }
+
+  // Creates a cropped NV12 VideoFrame, or nullptr if the needed extension is
+  // not available. |closure| is run once the shared images backing the
+  // VideoFrame have been destroyed.
+  scoped_refptr<VideoFrame> CreateTestNV12Frame(base::OnceClosure closure) {
+    return CreateSharedImageNV12Frame(media_context_, gfx::Size(16, 8),
+                                      gfx::Rect(2, 2, 12, 4),
+                                      std::move(closure));
+  }
+
+  // Checks that the contents of a texture/canvas match the expectations for the
+  // cropped NV12 frame above. |get_color| is a callback that returns the actual
+  // color at a given pixel location. Note that the expectations are the same as
+  // for the I420 frame.
+  static void CheckNV12FramePixels(GetColorCallback get_color) {
+    CheckI420FramePixels(std::move(get_color));
+  }
+
+ protected:
+  base::Optional<gl::DisableNullDrawGLBindings> enable_pixels_;
+  scoped_refptr<viz::TestInProcessContextProvider> media_context_;
+  scoped_refptr<viz::TestInProcessContextProvider> destination_context_;
+};
+
+TEST_F(PaintCanvasVideoRendererWithGLTest, CopyVideoFrameYUVDataToGLTexture) {
+  auto* destination_gl = destination_context_->ContextGL();
   DCHECK(destination_gl);
   GLenum target = GL_TEXTURE_2D;
   GLuint texture = 0;
@@ -817,29 +1224,15 @@
   destination_gl->BindTexture(target, texture);
 
   renderer_.CopyVideoFrameYUVDataToGLTexture(
-      media_context.get(), destination_gl, *cropped_frame(), target, texture,
+      media_context_.get(), destination_gl, *cropped_frame(), target, texture,
       GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 0, false /* premultiply_alpha */,
       false /* flip_y */);
 
   gfx::Size expected_size = cropped_frame()->visible_rect().size();
-  size_t pixel_count = expected_size.width() * expected_size.height();
 
-  GLuint fbo = 0;
-  destination_gl->GenFramebuffers(1, &fbo);
-  destination_gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
-  destination_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-                                       GL_TEXTURE_2D, texture, 0);
-  auto pixels = std::make_unique<uint8_t[]>(pixel_count * 4);
-  uint8_t* raw_pixels = pixels.get();
-  destination_gl->ReadPixels(0, 0, expected_size.width(),
-                             expected_size.height(), GL_RGBA, GL_UNSIGNED_BYTE,
-                             raw_pixels);
-  destination_gl->DeleteFramebuffers(1, &fbo);
-
-  auto get_color = [raw_pixels, expected_size](size_t x, size_t y) {
-    uint8_t* p = raw_pixels + (expected_size.width() * y + x) * 4;
-    return SkColorSetARGB(p[3], p[0], p[1], p[2]);
-  };
+  std::unique_ptr<uint8_t[]> pixels =
+      ReadbackTexture(destination_gl, texture, expected_size);
+  auto get_color = ColorGetter(pixels.get(), expected_size);
 
   // Avoid checking around the seams.
   EXPECT_EQ(SK_ColorBLACK, get_color(0, 0));
@@ -853,21 +1246,9 @@
   destination_gl->DeleteTextures(1, &texture);
 }
 
-TEST_F(PaintCanvasVideoRendererTest, CopyVideoFrameYUVDataToGLTexture_FlipY) {
-  gl::DisableNullDrawGLBindings enable_pixels;
-
-  auto media_context = base::MakeRefCounted<viz::TestInProcessContextProvider>(
-      false /* enable_oop_rasterization */, false /* support_locking */);
-  gpu::ContextResult result = media_context->BindToCurrentThread();
-  ASSERT_EQ(result, gpu::ContextResult::kSuccess);
-
-  auto destination_context =
-      base::MakeRefCounted<viz::TestInProcessContextProvider>(
-          false /* enable_oop_rasterization */, false /* support_locking */);
-  result = destination_context->BindToCurrentThread();
-  ASSERT_EQ(result, gpu::ContextResult::kSuccess);
-
-  gpu::gles2::GLES2Interface* destination_gl = destination_context->ContextGL();
+TEST_F(PaintCanvasVideoRendererWithGLTest,
+       CopyVideoFrameYUVDataToGLTexture_FlipY) {
+  auto* destination_gl = destination_context_->ContextGL();
   DCHECK(destination_gl);
   GLenum target = GL_TEXTURE_2D;
   GLuint texture = 0;
@@ -875,29 +1256,15 @@
   destination_gl->BindTexture(target, texture);
 
   renderer_.CopyVideoFrameYUVDataToGLTexture(
-      media_context.get(), destination_gl, *cropped_frame(), target, texture,
+      media_context_.get(), destination_gl, *cropped_frame(), target, texture,
       GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 0, false /* premultiply_alpha */,
       true /* flip_y */);
 
   gfx::Size expected_size = cropped_frame()->visible_rect().size();
-  size_t pixel_count = expected_size.width() * expected_size.height();
 
-  GLuint fbo = 0;
-  destination_gl->GenFramebuffers(1, &fbo);
-  destination_gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
-  destination_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-                                       GL_TEXTURE_2D, texture, 0);
-  auto pixels = std::make_unique<uint8_t[]>(pixel_count * 4);
-  uint8_t* raw_pixels = pixels.get();
-  destination_gl->ReadPixels(0, 0, expected_size.width(),
-                             expected_size.height(), GL_RGBA, GL_UNSIGNED_BYTE,
-                             raw_pixels);
-  destination_gl->DeleteFramebuffers(1, &fbo);
-
-  auto get_color = [raw_pixels, expected_size](size_t x, size_t y) {
-    uint8_t* p = raw_pixels + (expected_size.width() * y + x) * 4;
-    return SkColorSetARGB(p[3], p[0], p[1], p[2]);
-  };
+  std::unique_ptr<uint8_t[]> pixels =
+      ReadbackTexture(destination_gl, texture, expected_size);
+  auto get_color = ColorGetter(pixels.get(), expected_size);
 
   // Avoid checking around the seams.
   EXPECT_EQ(SK_ColorBLACK, get_color(0, 5));
@@ -911,4 +1278,103 @@
   destination_gl->DeleteTextures(1, &texture);
 }
 
+// Checks that we correctly copy a RGBA shared image VideoFrame when using
+// CopyVideoFrameYUVDataToGLTexture, including correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest,
+       CopyVideoFrameTexturesToGLTextureRGBA) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestRGBAFrame(run_loop.QuitClosure());
+
+  CopyVideoFrameTexturesAndCheckPixels(frame, &CheckRGBAFramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly copy a RGBA shared image VideoFrame that needs read
+// lock fences, when using CopyVideoFrameYUVDataToGLTexture, including correct
+// cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest,
+       CopyVideoFrameTexturesToGLTextureRGBA_ReadLockFence) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestRGBAFrame(run_loop.QuitClosure());
+  frame->metadata()->SetBoolean(VideoFrameMetadata::READ_LOCK_FENCES_ENABLED,
+                                true);
+
+  CopyVideoFrameTexturesAndCheckPixels(frame, &CheckRGBAFramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly paint a RGBA shared image VideoFrame, including
+// correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest, PaintRGBA) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestRGBAFrame(run_loop.QuitClosure());
+
+  PaintVideoFrameAndCheckPixels(frame, &CheckRGBAFramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly copy an I420 shared image VideoFrame when using
+// CopyVideoFrameYUVDataToGLTexture, including correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest,
+       CopyVideoFrameTexturesToGLTextureI420) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestI420Frame(run_loop.QuitClosure());
+
+  CopyVideoFrameTexturesAndCheckPixels(frame, &CheckI420FramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly paint a I420 shared image VideoFrame, including
+// correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest, PaintI420) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestI420Frame(run_loop.QuitClosure());
+
+  PaintVideoFrameAndCheckPixels(frame, &CheckI420FramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly copy a NV12 shared image VideoFrame when using
+// CopyVideoFrameYUVDataToGLTexture, including correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest,
+       CopyVideoFrameTexturesToGLTextureNV12) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestNV12Frame(run_loop.QuitClosure());
+  if (!frame) {
+    LOG(ERROR) << "GL_EXT_texture_rg not supported, skipping NV12 test";
+    return;
+  }
+
+  CopyVideoFrameTexturesAndCheckPixels(frame, &CheckNV12FramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
+// Checks that we correctly paint a NV12 shared image VideoFrame, including
+// correct cropping.
+TEST_F(PaintCanvasVideoRendererWithGLTest, PaintNV12) {
+  base::RunLoop run_loop;
+  scoped_refptr<VideoFrame> frame = CreateTestNV12Frame(run_loop.QuitClosure());
+  if (!frame) {
+    LOG(ERROR) << "GL_EXT_texture_rg not supported, skipping NV12 test";
+    return;
+  }
+
+  PaintVideoFrameAndCheckPixels(frame, &CheckNV12FramePixels);
+
+  frame.reset();
+  run_loop.Run();
+}
+
 }  // namespace media
diff --git a/net/cert/internal/path_builder.cc b/net/cert/internal/path_builder.cc
index e81d485..c39f67c 100644
--- a/net/cert/internal/path_builder.cc
+++ b/net/cert/internal/path_builder.cc
@@ -9,6 +9,7 @@
 #include <unordered_set>
 
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "crypto/sha2.h"
 #include "net/base/net_errors.h"
@@ -55,6 +56,11 @@
   return s;
 }
 
+void RecordIterationCountHistogram(uint32_t iteration_count) {
+  base::UmaHistogramCounts10000("Net.CertVerifier.PathBuilderIterationCount",
+                                iteration_count);
+}
+
 // This structure describes a certificate and its trust level. Note that |cert|
 // may be null to indicate an "empty" entry.
 struct IssuerEntry {
@@ -591,6 +597,7 @@
       if (!deadline_.is_null() && base::TimeTicks::Now() > deadline_) {
         out_result_->exceeded_deadline = true;
       }
+      RecordIterationCountHistogram(iteration_count);
       return;
     }
 
@@ -612,6 +619,7 @@
     AddResultPath(std::move(result_path));
 
     if (path_is_good) {
+      RecordIterationCountHistogram(iteration_count);
       // Found a valid path, return immediately.
       // TODO(mattm): add debug/test mode that tries all possible paths.
       return;
diff --git a/net/cert/internal/path_builder_unittest.cc b/net/cert/internal/path_builder_unittest.cc
index 87840cd..1a954a7 100644
--- a/net/cert/internal/path_builder_unittest.cc
+++ b/net/cert/internal/path_builder_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/base_paths.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "net/cert/internal/cert_error_params.h"
 #include "net/cert/internal/cert_issuer_source_static.h"
 #include "net/cert/internal/common_cert_errors.h"
@@ -30,6 +31,7 @@
 namespace {
 
 using ::testing::_;
+using ::testing::ElementsAre;
 using ::testing::Invoke;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -450,12 +452,11 @@
 }
 
 TEST_F(PathBuilderMultiRootTest, TestIterationLimit) {
-  // Both D(D) and C(D) are trusted roots.
+  // D(D) is the trust root.
   TrustStoreInMemory trust_store;
   trust_store.AddTrustAnchor(d_by_d_);
-  trust_store.AddTrustAnchor(c_by_d_);
 
-  // Certs B(C), and C(D) are all supplied.
+  // Certs B(C) and C(D) are supplied.
   CertIssuerSourceStatic sync_certs;
   sync_certs.AddCert(b_by_c_);
   sync_certs.AddCert(c_by_d_);
@@ -481,10 +482,21 @@
       path_builder.SetIterationLimit(5);
     }
 
+    base::HistogramTester histogram_tester;
     path_builder.Run();
 
     EXPECT_EQ(!insufficient_limit, result.HasValidPath());
     EXPECT_EQ(insufficient_limit, result.exceeded_iteration_limit);
+
+    if (insufficient_limit) {
+      EXPECT_THAT(histogram_tester.GetAllSamples(
+                      "Net.CertVerifier.PathBuilderIterationCount"),
+                  ElementsAre(base::Bucket(/*sample=*/2, /*count=*/1)));
+    } else {
+      EXPECT_THAT(histogram_tester.GetAllSamples(
+                      "Net.CertVerifier.PathBuilderIterationCount"),
+                  ElementsAre(base::Bucket(/*sample=*/3, /*count=*/1)));
+    }
   }
 }
 
diff --git a/net/quic/OWNERS b/net/quic/OWNERS
index 1835f5b..6fd222f 100644
--- a/net/quic/OWNERS
+++ b/net/quic/OWNERS
@@ -1,3 +1,4 @@
+nharper@chromium.org
 rch@chromium.org
 zhongyi@chromium.org
 
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index 7b69a8c9..7d6bd3f 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -53,8 +53,10 @@
       "document_loader.h",
       "document_loader_impl.cc",
       "document_loader_impl.h",
-      "draw_utils.cc",
-      "draw_utils.h",
+      "draw_utils/coordinates.cc",
+      "draw_utils/coordinates.h",
+      "draw_utils/shadow.cc",
+      "draw_utils/shadow.h",
       "out_of_process_instance.cc",
       "out_of_process_instance.h",
       "paint_aggregator.cc",
@@ -150,6 +152,7 @@
     sources = [
       "chunk_stream_unittest.cc",
       "document_loader_impl_unittest.cc",
+      "draw_utils/coordinates_unittest.cc",
       "out_of_process_instance_unittest.cc",
       "pdf_transform_unittest.cc",
       "range_set_unittest.cc",
diff --git a/pdf/draw_utils/coordinates.cc b/pdf/draw_utils/coordinates.cc
new file mode 100644
index 0000000..d3ed045
--- /dev/null
+++ b/pdf/draw_utils/coordinates.cc
@@ -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.
+
+#include "pdf/draw_utils/coordinates.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "ppapi/cpp/point.h"
+
+namespace chrome_pdf {
+namespace draw_utils {
+
+pp::Rect GetScreenRect(const pp::Rect& rect,
+                       const pp::Point& position,
+                       double zoom) {
+  DCHECK_GT(zoom, 0);
+
+  int x = static_cast<int>(rect.x() * zoom - position.x());
+  int y = static_cast<int>(rect.y() * zoom - position.y());
+  int right = static_cast<int>(ceil(rect.right() * zoom - position.x()));
+  int bottom = static_cast<int>(ceil(rect.bottom() * zoom - position.y()));
+  return pp::Rect(x, y, right - x, bottom - y);
+}
+
+}  // namespace draw_utils
+}  // namespace chrome_pdf
diff --git a/pdf/draw_utils/coordinates.h b/pdf/draw_utils/coordinates.h
new file mode 100644
index 0000000..a11424f
--- /dev/null
+++ b/pdf/draw_utils/coordinates.h
@@ -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.
+
+#ifndef PDF_DRAW_UTILS_COORDINATES_H_
+#define PDF_DRAW_UTILS_COORDINATES_H_
+
+#include "ppapi/cpp/rect.h"
+
+namespace pp {
+class Point;
+}
+
+namespace chrome_pdf {
+namespace draw_utils {
+
+// Given |rect| in document coordinates, a |position| in screen coordinates, and
+// a |zoom| factor, returns the rectangle in screen coordinates (i.e. 0,0 is top
+// left corner of plugin area).
+// An empty |rect| will always result in an empty output rect.
+// For |zoom|, a value of 1 means 100%. |zoom| is never less than or equal to 0.
+pp::Rect GetScreenRect(const pp::Rect& rect,
+                       const pp::Point& position,
+                       double zoom);
+
+}  // namespace draw_utils
+}  // namespace chrome_pdf
+
+#endif  // PDF_DRAW_UTILS_COORDINATES_H_
diff --git a/pdf/draw_utils/coordinates_unittest.cc b/pdf/draw_utils/coordinates_unittest.cc
new file mode 100644
index 0000000..af4424a5
--- /dev/null
+++ b/pdf/draw_utils/coordinates_unittest.cc
@@ -0,0 +1,96 @@
+// 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 "pdf/draw_utils/coordinates.h"
+
+#include "ppapi/cpp/point.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_pdf {
+namespace draw_utils {
+
+TEST(CoordinateTest, GetScreenRect) {
+  pp::Rect screen_rect;
+  const pp::Rect rect(10, 20, 200, 300);
+
+  // Test various zooms with the position at the origin.
+  screen_rect = GetScreenRect(rect, {0, 0}, 1);
+  EXPECT_EQ(10, screen_rect.x());
+  EXPECT_EQ(20, screen_rect.y());
+  EXPECT_EQ(200, screen_rect.width());
+  EXPECT_EQ(300, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {0, 0}, 1.5);
+  EXPECT_EQ(15, screen_rect.x());
+  EXPECT_EQ(30, screen_rect.y());
+  EXPECT_EQ(300, screen_rect.width());
+  EXPECT_EQ(450, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {0, 0}, 0.5);
+  EXPECT_EQ(5, screen_rect.x());
+  EXPECT_EQ(10, screen_rect.y());
+  EXPECT_EQ(100, screen_rect.width());
+  EXPECT_EQ(150, screen_rect.height());
+
+  // Test various zooms with the position elsewhere.
+  screen_rect = GetScreenRect(rect, {400, 30}, 1);
+  EXPECT_EQ(-390, screen_rect.x());
+  EXPECT_EQ(-10, screen_rect.y());
+  EXPECT_EQ(200, screen_rect.width());
+  EXPECT_EQ(300, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {400, 30}, 1.5);
+  EXPECT_EQ(-385, screen_rect.x());
+  EXPECT_EQ(0, screen_rect.y());
+  EXPECT_EQ(300, screen_rect.width());
+  EXPECT_EQ(450, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {400, 30}, 0.5);
+  EXPECT_EQ(-395, screen_rect.x());
+  EXPECT_EQ(-20, screen_rect.y());
+  EXPECT_EQ(100, screen_rect.width());
+  EXPECT_EQ(150, screen_rect.height());
+
+  // Test various zooms with a negative position.
+  screen_rect = GetScreenRect(rect, {100, -50}, 1);
+  EXPECT_EQ(-90, screen_rect.x());
+  EXPECT_EQ(70, screen_rect.y());
+  EXPECT_EQ(200, screen_rect.width());
+  EXPECT_EQ(300, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {100, -50}, 1.5);
+  EXPECT_EQ(-85, screen_rect.x());
+  EXPECT_EQ(80, screen_rect.y());
+  EXPECT_EQ(300, screen_rect.width());
+  EXPECT_EQ(450, screen_rect.height());
+
+  screen_rect = GetScreenRect(rect, {100, -50}, 0.5);
+  EXPECT_EQ(-95, screen_rect.x());
+  EXPECT_EQ(60, screen_rect.y());
+  EXPECT_EQ(100, screen_rect.width());
+  EXPECT_EQ(150, screen_rect.height());
+
+  // Test an empty rect always outputs an empty rect.
+  const pp::Rect empty_rect;
+  screen_rect = GetScreenRect(empty_rect, {20, 500}, 1);
+  EXPECT_EQ(-20, screen_rect.x());
+  EXPECT_EQ(-500, screen_rect.y());
+  EXPECT_EQ(0, screen_rect.width());
+  EXPECT_EQ(0, screen_rect.height());
+
+  screen_rect = GetScreenRect(empty_rect, {20, 500}, 1.5);
+  EXPECT_EQ(-20, screen_rect.x());
+  EXPECT_EQ(-500, screen_rect.y());
+  EXPECT_EQ(0, screen_rect.width());
+  EXPECT_EQ(0, screen_rect.height());
+
+  screen_rect = GetScreenRect(empty_rect, {20, 500}, 0.5);
+  EXPECT_EQ(-20, screen_rect.x());
+  EXPECT_EQ(-500, screen_rect.y());
+  EXPECT_EQ(0, screen_rect.width());
+  EXPECT_EQ(0, screen_rect.height());
+}
+
+}  // namespace draw_utils
+}  // namespace chrome_pdf
diff --git a/pdf/draw_utils.cc b/pdf/draw_utils/shadow.cc
similarity index 95%
rename from pdf/draw_utils.cc
rename to pdf/draw_utils/shadow.cc
index adb2ceb..c17b757 100644
--- a/pdf/draw_utils.cc
+++ b/pdf/draw_utils/shadow.cc
@@ -2,18 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "pdf/draw_utils.h"
+#include "pdf/draw_utils/shadow.h"
 
 #include <math.h>
 #include <stddef.h>
-#include <stdint.h>
+
 #include <algorithm>
-#include <vector>
 
 #include "base/logging.h"
-#include "base/numerics/safe_math.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/rect.h"
 
 namespace chrome_pdf {
+namespace draw_utils {
+
+constexpr uint8_t kOpaqueAlpha = 0xFF;
+constexpr uint8_t kTransparentAlpha = 0x00;
 
 inline uint8_t GetBlue(const uint32_t& pixel) {
   return static_cast<uint8_t>(pixel & 0xFF);
@@ -169,4 +173,5 @@
   PaintShadow(image, rc.Intersect(clip_rc), shadow_rc, matrix);
 }
 
+}  // namespace draw_utils
 }  // namespace chrome_pdf
diff --git a/pdf/draw_utils.h b/pdf/draw_utils/shadow.h
similarity index 86%
rename from pdf/draw_utils.h
rename to pdf/draw_utils/shadow.h
index 93c769ee..91f0b168ab 100644
--- a/pdf/draw_utils.h
+++ b/pdf/draw_utils/shadow.h
@@ -2,20 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef PDF_DRAW_UTILS_H_
-#define PDF_DRAW_UTILS_H_
+#ifndef PDF_DRAW_UTILS_SHADOW_H_
+#define PDF_DRAW_UTILS_SHADOW_H_
 
 #include <stdint.h>
 
 #include <vector>
 
-#include "ppapi/cpp/image_data.h"
-#include "ppapi/cpp/rect.h"
+namespace pp {
+class ImageData;
+class Rect;
+}  // namespace pp
 
 namespace chrome_pdf {
-
-const uint8_t kOpaqueAlpha = 0xFF;
-const uint8_t kTransparentAlpha = 0x00;
+namespace draw_utils {
 
 // Shadow Matrix contains matrix for shadow rendering. To reduce amount of
 // calculations user may choose to cache matrix and reuse it if nothing changed.
@@ -52,6 +52,7 @@
                 const pp::Rect& clip_rc,
                 const ShadowMatrix& matrix);
 
+}  // namespace draw_utils
 }  // namespace chrome_pdf
 
-#endif  // PDF_DRAW_UTILS_H_
+#endif  // PDF_DRAW_UTILS_SHADOW_H_
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 1267d692..1533ceb 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -29,7 +29,8 @@
 #include "gin/public/gin_embedders.h"
 #include "gin/public/isolate_holder.h"
 #include "pdf/document_loader_impl.h"
-#include "pdf/draw_utils.h"
+#include "pdf/draw_utils/coordinates.h"
+#include "pdf/draw_utils/shadow.h"
 #include "pdf/pdf_transform.h"
 #include "pdf/pdfium/pdfium_api_string_buffer_adapter.h"
 #include "pdf/pdfium/pdfium_document.h"
@@ -3133,17 +3134,7 @@
 }
 
 pp::Rect PDFiumEngine::GetScreenRect(const pp::Rect& rect) const {
-  pp::Rect rv;
-  int right =
-      static_cast<int>(ceil(rect.right() * current_zoom_ - position_.x()));
-  int bottom =
-      static_cast<int>(ceil(rect.bottom() * current_zoom_ - position_.y()));
-
-  rv.set_x(static_cast<int>(rect.x() * current_zoom_ - position_.x()));
-  rv.set_y(static_cast<int>(rect.y() * current_zoom_ - position_.y()));
-  rv.set_width(right - rv.x());
-  rv.set_height(bottom - rv.y());
-  return rv;
+  return draw_utils::GetScreenRect(rect, position_, current_zoom_);
 }
 
 void PDFiumEngine::Highlight(void* buffer,
@@ -3367,8 +3358,8 @@
   depth = static_cast<uint32_t>(depth * 1.5) + 1;
 
   // We need to check depth only to verify our copy of shadow matrix is correct.
-  if (!page_shadow_.get() || page_shadow_->depth() != depth) {
-    page_shadow_ = std::make_unique<ShadowMatrix>(
+  if (!page_shadow_ || page_shadow_->depth() != depth) {
+    page_shadow_ = std::make_unique<draw_utils::ShadowMatrix>(
         depth, factor, client_->GetBackgroundColor());
   }
 
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 7d36979..6880eb5 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -38,7 +38,10 @@
 namespace chrome_pdf {
 
 class PDFiumDocument;
+
+namespace draw_utils {
 class ShadowMatrix;
+}
 
 class PDFiumEngine : public PDFEngine,
                      public DocumentLoader::Client,
@@ -394,9 +397,8 @@
   // Returns the currently visible rectangle in document coordinates.
   pp::Rect GetVisibleRect() const;
 
-  // Given a rectangle in document coordinates, returns the rectange into screen
-  // coordinates (i.e. 0,0 is top left corner of plugin area).  If it's not
-  // visible, an empty rectangle is returned.
+  // Given |rect| in document coordinates, returns the rectangle in screen
+  // coordinates. (i.e. 0,0 is top left corner of plugin area)
   pp::Rect GetScreenRect(const pp::Rect& rect) const;
 
   // Given an image |buffer| with |stride|, highlights |rect|.
@@ -649,7 +651,7 @@
   base::TimeDelta progressive_paint_timeout_;
 
   // Shadow matrix for generating the page shadow bitmap.
-  std::unique_ptr<ShadowMatrix> page_shadow_;
+  std::unique_ptr<draw_utils::ShadowMatrix> page_shadow_;
 
   // While true, the document try to be opened and parsed after download each
   // part. Else the document will be opened and parsed only on finish of
diff --git a/sandbox/win/src/target_interceptions.cc b/sandbox/win/src/target_interceptions.cc
index 7ea741c6..2dc2fd1 100644
--- a/sandbox/win/src/target_interceptions.cc
+++ b/sandbox/win/src/target_interceptions.cc
@@ -7,7 +7,6 @@
 #include "sandbox/win/src/interception_agent.h"
 #include "sandbox/win/src/sandbox_factory.h"
 #include "sandbox/win/src/sandbox_nt_util.h"
-#include "sandbox/win/src/target_services.h"
 
 namespace sandbox {
 
@@ -68,7 +67,6 @@
         if (ansi_module_name &&
             (g_nt._strnicmp(ansi_module_name, KERNEL32_DLL_NAME,
                             sizeof(KERNEL32_DLL_NAME)) == 0)) {
-          SandboxFactory::GetTargetServices()->GetState()->SetKernel32Loaded();
           s_state = kAfterKernel32;
         }
       } __except (EXCEPTION_EXECUTE_HANDLER) {
diff --git a/sandbox/win/src/target_services.cc b/sandbox/win/src/target_services.cc
index c946783..f35dc39d 100644
--- a/sandbox/win/src/target_services.cc
+++ b/sandbox/win/src/target_services.cc
@@ -215,37 +215,29 @@
   return true;
 }
 
-ProcessState::ProcessState() : process_state_(0), csrss_connected_(true) {}
-
-bool ProcessState::IsKernel32Loaded() const {
-  return process_state_ != 0;
-}
+ProcessState::ProcessState()
+    : process_state_(ProcessStateInternal::NONE), csrss_connected_(true) {}
 
 bool ProcessState::InitCalled() const {
-  return process_state_ > 1;
+  return process_state_ >= ProcessStateInternal::INIT_CALLED;
 }
 
 bool ProcessState::RevertedToSelf() const {
-  return process_state_ > 2;
+  return process_state_ >= ProcessStateInternal::REVERTED_TO_SELF;
 }
 
 bool ProcessState::IsCsrssConnected() const {
   return csrss_connected_;
 }
 
-void ProcessState::SetKernel32Loaded() {
-  if (!process_state_)
-    process_state_ = 1;
-}
-
 void ProcessState::SetInitCalled() {
-  if (process_state_ < 2)
-    process_state_ = 2;
+  if (process_state_ < ProcessStateInternal::INIT_CALLED)
+    process_state_ = ProcessStateInternal::INIT_CALLED;
 }
 
 void ProcessState::SetRevertedToSelf() {
-  if (process_state_ < 3)
-    process_state_ = 3;
+  if (process_state_ < ProcessStateInternal::REVERTED_TO_SELF)
+    process_state_ = ProcessStateInternal::REVERTED_TO_SELF;
 }
 
 void ProcessState::SetCsrssConnected(bool csrss_connected) {
diff --git a/sandbox/win/src/target_services.h b/sandbox/win/src/target_services.h
index a33c9201..e50b7fb 100644
--- a/sandbox/win/src/target_services.h
+++ b/sandbox/win/src/target_services.h
@@ -14,8 +14,6 @@
 class ProcessState {
  public:
   ProcessState();
-  // Returns true if kernel32.dll has been loaded.
-  bool IsKernel32Loaded() const;
   // Returns true if main has been called.
   bool InitCalled() const;
   // Returns true if LowerToken has been called.
@@ -23,13 +21,14 @@
   // Returns true if Csrss is connected.
   bool IsCsrssConnected() const;
   // Set the current state.
-  void SetKernel32Loaded();
   void SetInitCalled();
   void SetRevertedToSelf();
   void SetCsrssConnected(bool csrss_connected);
 
  private:
-  int process_state_;
+  enum class ProcessStateInternal { NONE = 0, INIT_CALLED, REVERTED_TO_SELF };
+
+  ProcessStateInternal process_state_;
   bool csrss_connected_;
   DISALLOW_COPY_AND_ASSIGN(ProcessState);
 };
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index 64d14fa..f0605c8 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -271,6 +271,8 @@
   }
 
   if (is_win) {
+    sources += [ "generic_sensor/platform_sensor_provider_winrt_unittest.cc" ]
+
     # Needed for "generic_sensor/platform_sensor_and_provider_unittest_win.cc"
     libs = [
       "propsys.lib",
diff --git a/services/device/generic_sensor/BUILD.gn b/services/device/generic_sensor/BUILD.gn
index fc50c56..9caa1e58 100644
--- a/services/device/generic_sensor/BUILD.gn
+++ b/services/device/generic_sensor/BUILD.gn
@@ -104,6 +104,11 @@
   }
 
   if (is_win) {
+    sources += [
+      "platform_sensor_provider_winrt.cc",
+      "platform_sensor_provider_winrt.h",
+    ]
+
     libs = [
       "portabledeviceguids.lib",
       "sensorsapi.lib",
diff --git a/services/device/generic_sensor/platform_sensor_provider.cc b/services/device/generic_sensor/platform_sensor_provider.cc
index aeb0f32..8b8c6916 100644
--- a/services/device/generic_sensor/platform_sensor_provider.cc
+++ b/services/device/generic_sensor/platform_sensor_provider.cc
@@ -9,7 +9,12 @@
 #elif defined(OS_ANDROID)
 #include "services/device/generic_sensor/platform_sensor_provider_android.h"
 #elif defined(OS_WIN)
+#include "base/feature_list.h"
+#include "base/win/windows_version.h"
+#include "build/build_config.h"
 #include "services/device/generic_sensor/platform_sensor_provider_win.h"
+#include "services/device/generic_sensor/platform_sensor_provider_winrt.h"
+#include "services/device/public/cpp/device_features.h"
 #elif defined(OS_LINUX) && defined(USE_UDEV)
 #include "services/device/generic_sensor/platform_sensor_provider_linux.h"
 #endif
@@ -23,7 +28,11 @@
 #elif defined(OS_ANDROID)
   return std::make_unique<PlatformSensorProviderAndroid>();
 #elif defined(OS_WIN)
-  return std::make_unique<PlatformSensorProviderWin>();
+  if (PlatformSensorProvider::UseWindowsWinrt()) {
+    return std::make_unique<PlatformSensorProviderWinrt>();
+  } else {
+    return std::make_unique<PlatformSensorProviderWin>();
+  }
 #elif defined(OS_LINUX) && defined(USE_UDEV)
   return std::make_unique<PlatformSensorProviderLinux>();
 #else
@@ -31,4 +40,17 @@
 #endif
 }
 
+#if defined(OS_WIN)
+// static
+bool PlatformSensorProvider::UseWindowsWinrt() {
+  // TODO: Windows version dependency should eventually be updated to
+  // a future version which supports WinRT sensor thresholding. Since
+  // this Windows version has yet to be released, Win10 is being
+  // provisionally used for testing. This also means sensors will
+  // stream if this implementation path is enabled.
+  return base::FeatureList::IsEnabled(features::kWinrtSensorsImplementation) &&
+         base::win::GetVersion() >= base::win::Version::WIN10;
+}
+#endif
+
 }  // namespace device
diff --git a/services/device/generic_sensor/platform_sensor_provider.h b/services/device/generic_sensor/platform_sensor_provider.h
index 525d81f..9e2c5e5 100644
--- a/services/device/generic_sensor/platform_sensor_provider.h
+++ b/services/device/generic_sensor/platform_sensor_provider.h
@@ -25,6 +25,10 @@
  protected:
   PlatformSensorProvider() = default;
 
+  // Determines if the ISensor or Windows.Devices.Sensors implementation
+  // should be used on Windows.
+  static bool UseWindowsWinrt();
+
   DISALLOW_COPY_AND_ASSIGN(PlatformSensorProvider);
 };
 
diff --git a/services/device/generic_sensor/platform_sensor_provider_winrt.cc b/services/device/generic_sensor/platform_sensor_provider_winrt.cc
new file mode 100644
index 0000000..2d3e5087
--- /dev/null
+++ b/services/device/generic_sensor/platform_sensor_provider_winrt.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 "services/device/generic_sensor/platform_sensor_provider_winrt.h"
+
+#include <comdef.h>
+
+#include "base/memory/singleton.h"
+
+namespace device {
+
+PlatformSensorProviderWinrt::PlatformSensorProviderWinrt() = default;
+
+PlatformSensorProviderWinrt::~PlatformSensorProviderWinrt() = default;
+
+void PlatformSensorProviderWinrt::CreateSensorInternal(
+    mojom::SensorType type,
+    SensorReadingSharedBuffer* reading_buffer,
+    const CreateSensorCallback& callback) {
+  callback.Run(nullptr);
+}
+
+}  // namespace device
\ No newline at end of file
diff --git a/services/device/generic_sensor/platform_sensor_provider_winrt.h b/services/device/generic_sensor/platform_sensor_provider_winrt.h
new file mode 100644
index 0000000..01a07b0
--- /dev/null
+++ b/services/device/generic_sensor/platform_sensor_provider_winrt.h
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_PROVIDER_WINRT_H_
+#define SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_PROVIDER_WINRT_H_
+
+#include "services/device/generic_sensor/platform_sensor_provider.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}  // namespace base
+
+namespace device {
+
+class PlatformSensorReaderWin;
+
+// Implementation of PlatformSensorProvider for Windows platform using the
+// Windows.Devices.Sensors WinRT API. PlatformSensorProviderWinrt is
+// responsible for the following tasks:
+// - Starts sensor thread and stops it when there are no active sensors.
+// - Creates sensor reader.
+// - Constructs PlatformSensorWin on IPC thread and returns it to requester.
+class PlatformSensorProviderWinrt final : public PlatformSensorProvider {
+ public:
+  PlatformSensorProviderWinrt();
+  ~PlatformSensorProviderWinrt() override;
+
+ protected:
+  // PlatformSensorProvider interface implementation.
+  void CreateSensorInternal(mojom::SensorType type,
+                            SensorReadingSharedBuffer* reading_buffer,
+                            const CreateSensorCallback& callback) override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<PlatformSensorProviderWinrt>;
+
+  void SensorReaderCreated(
+      mojom::SensorType type,
+      SensorReadingSharedBuffer* reading_buffer,
+      const CreateSensorCallback& callback,
+      std::unique_ptr<PlatformSensorReaderWin> sensor_reader);
+
+  PlatformSensorProviderWinrt(const PlatformSensorProviderWinrt&) = delete;
+  PlatformSensorProviderWinrt& operator=(const PlatformSensorProviderWinrt&) =
+      delete;
+};
+
+}  // namespace device
+
+#endif  // SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_PROVIDER_WINRT_H_
\ No newline at end of file
diff --git a/services/device/generic_sensor/platform_sensor_provider_winrt_unittest.cc b/services/device/generic_sensor/platform_sensor_provider_winrt_unittest.cc
new file mode 100644
index 0000000..27a79470
--- /dev/null
+++ b/services/device/generic_sensor/platform_sensor_provider_winrt_unittest.cc
@@ -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.
+
+#include "services/device/generic_sensor/platform_sensor_provider_winrt.h"
+
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+// Tests that PlatformSensorProviderWinrt can successfully be instantiated
+// and passes the correct result to the CreateSensor callback.
+TEST(PlatformSensorProviderTestWinrt, SensorCreationReturnCheck) {
+  base::test::ScopedTaskEnvironment scoped_task_environment;
+
+  auto provider = std::make_unique<PlatformSensorProviderWinrt>();
+
+  // CreateSensor is async so create a RunLoop to wait for completion.
+  base::RunLoop run_loop;
+
+  base::Callback<void(scoped_refptr<PlatformSensor> sensor)>
+      CreateSensorCallback =
+          base::BindLambdaForTesting([&](scoped_refptr<PlatformSensor> sensor) {
+            EXPECT_FALSE(sensor);
+            run_loop.Quit();
+          });
+
+  // PlatformSensorProviderWinrt is not implemented so it should always
+  // return nullptr.
+  provider->CreateSensor(mojom::SensorType::AMBIENT_LIGHT,
+                         CreateSensorCallback);
+  run_loop.Run();
+}
+
+}  // namespace device
\ No newline at end of file
diff --git a/services/device/generic_sensor/windows/README.md b/services/device/generic_sensor/windows/README.md
index 9207450..8772437 100644
--- a/services/device/generic_sensor/windows/README.md
+++ b/services/device/generic_sensor/windows/README.md
@@ -125,46 +125,7 @@
 
 ### 4.1 Support For Adapting Between ISensor and Windows.Devices.Sensors Sensor Implementations
 
-The `PlatformSensorProvider::GetInstance()` function decides which
-PlatformSensorProvider implementation to use. A new branch will be
-added for the Windows.Devices.Sensors path:
-
-```cpp
-PlatformSensorProvider* PlatformSensorProvider::GetInstance() {
-  if (g_provider_for_testing)
-    return g_provider_for_testing;
-#if defined(OS_MACOSX)
-  return PlatformSensorProviderMac::GetInstance();
-#elif defined(OS_ANDROID)
-  return PlatformSensorProviderAndroid::GetInstance();
-#elif defined(OS_WIN)
-  if (PlatformSensorProvider::UseWindowsWinrt()) {
-    // Windows.Devices.Sensors Windows implementation
-    return PlatformSensorProviderWinrt::GetInstance(); 
-  } else {
-    // ISensor Windows implementation
-    return PlatformSensorProviderWin::GetInstance(); 
-  }
-#elif defined(OS_LINUX) && defined(USE_UDEV)
-  return PlatformSensorProviderLinux::GetInstance();
-#else
-  return nullptr;
-#endif
-}
-```
-
-With `PlatformSensorProvider::UseWindowsWinrt()` being:
-
-```cpp
-bool PlatformSensorProvider::UseWindowsWinrt() {
-  return base::FeatureList::IsEnabled(kWinrtSensorsImplementation) &&
-      (base::win::GetVersion() >= base::win::Version::WIN10);
-}
-```
-
-The Windows.Devices.Sensors path is also decided on the
-`kWinrtSensorsImplementation` feature flag, which is explained further in
-section 5 below.
+Please refer to [platform_sensor_provider.cc](platform_sensor_provider.cc ).
 
 ### 4.2 Proposed Windows.Devices.Sensors Sensor Implementation
 
@@ -454,21 +415,6 @@
 The modernization changes will be broken down into several incremental
 changes to keep change lists to a reviewable size:
 
-#### Change list 1: Define the interface for PlatformSensorProviderWinrt and consume it
-
-- Feature Work:
-  - Create the PlatformSensorProviderWinrt header as defined in
-    Section 8.1.
-  - Create the Chromium feature flag as defined in Section 5 following
-    these [steps](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/how_to_add_your_feature_flag.md).
-  - Modify `PlatformSensorProvider::GetInstance()` to conditionally
-    create PlatformSensorProviderWinrt as defined in section 4.1.
-- Testing:
-  - Add a simple test to validate that PlatformSensorProviderWinrt
-    can be instantiated without errors.
-  - Run feature flag unit test as defined
-    [here](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/how_to_add_your_feature_flag.md).
-
 #### Change list 2: Implement PlatformSensorProviderWinrt
 
 - Feature Work:
diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
index c3d99f8..ea3a47c 100644
--- a/services/device/public/cpp/device_features.cc
+++ b/services/device/public/cpp/device_features.cc
@@ -18,5 +18,9 @@
 // (Generic Sensor and Device Orientation).
 const base::Feature kSensorContentSetting{"SensorContentSetting",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
+// Enables usage of the Windows.Devices.Sensors WinRT API for the sensor
+// backend instead of the ISensor API on Windows.
+const base::Feature kWinrtSensorsImplementation{
+    "WinrtSensorsImplementation", base::FEATURE_DISABLED_BY_DEFAULT};
 
 }  // namespace features
diff --git a/services/device/public/cpp/device_features.h b/services/device/public/cpp/device_features.h
index 6696ed63..1650f549 100644
--- a/services/device/public/cpp/device_features.h
+++ b/services/device/public/cpp/device_features.h
@@ -18,6 +18,7 @@
 DEVICE_FEATURES_EXPORT extern const base::Feature kGenericSensor;
 DEVICE_FEATURES_EXPORT extern const base::Feature kGenericSensorExtraClasses;
 DEVICE_FEATURES_EXPORT extern const base::Feature kSensorContentSetting;
+DEVICE_FEATURES_EXPORT extern const base::Feature kWinrtSensorsImplementation;
 
 }  // namespace features
 
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index c4c3a32..e774d58 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -66,6 +66,16 @@
 const base::Feature kRequestInitiatorSiteLock{"RequestInitiatorSiteLock",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
 
+// When kPauseBrowserInitiatedHeavyTrafficForP2P is enabled, then a subset of
+// the browser initiated traffic may be paused if there is at least one active
+// P2P connection and the network is estimated to be congested. This feature is
+// intended to throttle only the browser initiated traffic that is expected to
+// be heavy (has large request/response sizes) when real time content might be
+// streaming over an active P2P connection.
+const base::Feature kPauseBrowserInitiatedHeavyTrafficForP2P{
+    "PauseBrowserInitiatedHeavyTrafficForP2P",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool ShouldEnableOutOfBlinkCors() {
   // OOR-CORS requires NetworkService.
   if (!base::FeatureList::IsEnabled(features::kNetworkService))
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index c4fa3e8..2685828 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -31,6 +31,8 @@
 extern const base::Feature kFetchMetadataDestination;
 COMPONENT_EXPORT(NETWORK_CPP)
 extern const base::Feature kRequestInitiatorSiteLock;
+COMPONENT_EXPORT(NETWORK_CPP)
+extern const base::Feature kPauseBrowserInitiatedHeavyTrafficForP2P;
 
 COMPONENT_EXPORT(NETWORK_CPP) bool ShouldEnableOutOfBlinkCors();
 
diff --git a/services/network/resource_scheduler.cc b/services/network/resource_scheduler.cc
index 8359ac4..e1b24954 100644
--- a/services/network/resource_scheduler.cc
+++ b/services/network/resource_scheduler.cc
@@ -31,6 +31,7 @@
 #include "net/log/net_log.h"
 #include "net/nqe/effective_connection_type_observer.h"
 #include "net/nqe/network_quality_estimator.h"
+#include "net/nqe/peer_to_peer_connections_count_observer.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
 #include "services/network/public/cpp/features.h"
@@ -62,6 +63,7 @@
   REQUEST_REPRIORITIZED,
   LONG_QUEUED_REQUESTS_TIMER_FIRED,
   EFFECTIVE_CONNECTION_TYPE_CHANGED,
+  PEER_TO_PEER_CONNECTIONS_COUNT_CHANGED,
 };
 
 const char* RequestStartTriggerString(RequestStartTrigger trigger) {
@@ -84,6 +86,8 @@
       return "LONG_QUEUED_REQUESTS_TIMER_FIRED";
     case RequestStartTrigger::EFFECTIVE_CONNECTION_TYPE_CHANGED:
       return "EFFECTIVE_CONNECTION_TYPE_CHANGED";
+    case RequestStartTrigger::PEER_TO_PEER_CONNECTIONS_COUNT_CHANGED:
+      return "PEER_TO_PEER_CONNECTIONS_COUNT_CHANGED";
   }
 }
 
@@ -363,7 +367,9 @@
 }
 
 // Each client represents a tab.
-class ResourceScheduler::Client : public net::EffectiveConnectionTypeObserver {
+class ResourceScheduler::Client
+    : public net::EffectiveConnectionTypeObserver,
+      public net::PeerToPeerConnectionsCountObserver {
  public:
   Client(bool is_browser_client,
          net::NetworkQualityEstimator* network_quality_estimator,
@@ -383,13 +389,17 @@
           network_quality_estimator_->GetEffectiveConnectionType();
       UpdateParamsForNetworkQuality();
       network_quality_estimator_->AddEffectiveConnectionTypeObserver(this);
+      network_quality_estimator_->AddPeerToPeerConnectionsCountObserver(this);
     }
   }
 
   ~Client() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (network_quality_estimator_)
+    if (network_quality_estimator_) {
       network_quality_estimator_->RemoveEffectiveConnectionTypeObserver(this);
+      network_quality_estimator_->RemovePeerToPeerConnectionsCountObserver(
+          this);
+    }
   }
 
   void ScheduleRequest(const net::URLRequest& url_request,
@@ -525,6 +535,29 @@
         RequestStartTrigger::EFFECTIVE_CONNECTION_TYPE_CHANGED);
   }
 
+  // net::PeerToPeerConnectionsCountObserver:
+  void OnPeerToPeerConnectionsCountChange(uint32_t count) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (p2p_connections_count_ == count)
+      return;
+
+    p2p_connections_count_ = count;
+
+    if (p2p_connections_count_ > 0 &&
+        !p2p_connections_count_active_timestamp_.has_value()) {
+      p2p_connections_count_active_timestamp_ = base::TimeTicks::Now();
+    }
+
+    if (p2p_connections_count_ == 0 &&
+        p2p_connections_count_active_timestamp_.has_value()) {
+      p2p_connections_count_active_timestamp_ = base::nullopt;
+    }
+
+    LoadAnyStartablePendingRequests(
+        RequestStartTrigger::PEER_TO_PEER_CONNECTIONS_COUNT_CHANGED);
+  }
+
   // Records the metrics related to number of requests in flight.
   void RecordRequestCountMetrics() const {
     UMA_HISTOGRAM_COUNTS_100("ResourceScheduler.RequestsCount.All",
@@ -800,6 +833,77 @@
     request->Start(start_mode);
   }
 
+  // Returns true if |request| should be throttled to avoid network contention
+  // with active P2P connections.
+  bool ShouldThrottleBrowserInitiatedRequestDueToP2PConnections(
+      const ScheduledResourceRequestImpl& request) const {
+    DCHECK(is_browser_client_);
+
+    if (!base::FeatureList::IsEnabled(
+            features::kPauseBrowserInitiatedHeavyTrafficForP2P)) {
+      return false;
+    }
+
+    if (p2p_connections_count_ == 0)
+      return false;
+
+    // Only throttle when effective connection type is between Slow2G and 3G.
+    if (effective_connection_type_ <= net::EFFECTIVE_CONNECTION_TYPE_OFFLINE ||
+        effective_connection_type_ >= net::EFFECTIVE_CONNECTION_TYPE_4G) {
+      return false;
+    }
+
+    if (request.url_request()->priority() == net::HIGHEST)
+      return false;
+
+    base::TimeDelta time_since_p2p_connections_active =
+        tick_clock_->NowTicks() -
+        p2p_connections_count_active_timestamp_.value();
+
+    base::Optional<base::TimeDelta> max_wait_time_p2p_connections =
+        resource_scheduler_->resource_scheduler_params_manager_
+            .max_wait_time_p2p_connections();
+
+    if (time_since_p2p_connections_active >
+        max_wait_time_p2p_connections.value()) {
+      return false;
+    }
+
+    // Check other request specific constraints.
+    const net::NetworkTrafficAnnotationTag& traffic_annotation =
+        request.url_request()->traffic_annotation();
+    const int32_t unique_id_hash_code = traffic_annotation.unique_id_hash_code;
+
+    if (!resource_scheduler_->resource_scheduler_params_manager_
+             .CanThrottleNetworkTrafficAnnotationHash(unique_id_hash_code)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  // Records metrics on browser initiated requests. Called when the request is
+  // dispatched to the network.
+  void RecordMetricsForBrowserInitiatedRequestsOnNetworkDispatch(
+      const ScheduledResourceRequestImpl& request) const {
+    const net::NetworkTrafficAnnotationTag& traffic_annotation =
+        request.url_request()->traffic_annotation();
+    const int32_t unique_id_hash_code = traffic_annotation.unique_id_hash_code;
+
+    // Metrics are recorded only for browser initiated requests that are
+    // eligible for throttling.
+    if (!resource_scheduler_->resource_scheduler_params_manager_
+             .CanThrottleNetworkTrafficAnnotationHash(unique_id_hash_code)) {
+      return;
+    }
+
+    base::TimeDelta queuing_duration =
+        tick_clock_->NowTicks() - request.url_request()->creation_time();
+    UMA_HISTOGRAM_LONG_TIMES(
+        "ResourceScheduler.BrowserInitiatedHeavyRequest.QueuingDuration",
+        queuing_duration);
+  }
+
   // ShouldStartRequest is the main scheduling algorithm.
   //
   // Requests are evaluated on five attributes:
@@ -839,8 +943,13 @@
     if (!resource_scheduler_->enabled())
       return START_REQUEST;
 
-    // Currently, browser initiated requests are not throttled.
+    // Browser requests are treated differently since they are not user-facing.
     if (is_browser_client_) {
+      if (ShouldThrottleBrowserInitiatedRequestDueToP2PConnections(*request)) {
+        return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING;
+      }
+
+      RecordMetricsForBrowserInitiatedRequestsOnNetworkDispatch(*request);
       return START_REQUEST;
     }
 
@@ -1068,6 +1177,15 @@
   net::EffectiveConnectionType effective_connection_type_ =
       net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
 
+  // Current count of active peer to peer connections.
+  uint32_t p2p_connections_count_ = 0u;
+
+  // Earliest timestamp since when there is at least one active peer to peer
+  // connection. Set to current timestamp when |p2p_connections_count_|
+  // changes from 0 to a non-zero value. Reset to null when
+  // |p2p_connections_count_| becomes 0.
+  base::Optional<base::TimeTicks> p2p_connections_count_active_timestamp_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<ResourceScheduler::Client> weak_ptr_factory_{this};
diff --git a/services/network/resource_scheduler_params_manager.cc b/services/network/resource_scheduler_params_manager.cc
index 17dd66b..15dc7de 100644
--- a/services/network/resource_scheduler_params_manager.cc
+++ b/services/network/resource_scheduler_params_manager.cc
@@ -9,14 +9,52 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/optional.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "net/nqe/network_quality_estimator.h"
 #include "net/nqe/network_quality_estimator_params.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/features.h"
 
 namespace network {
 
 namespace {
 
+base::Optional<base::TimeDelta> GetMaxWaitTimeP2PConnections() {
+  if (!base::FeatureList::IsEnabled(
+          features::kPauseBrowserInitiatedHeavyTrafficForP2P)) {
+    return base::nullopt;
+  }
+
+  int max_wait_time_p2p_connections_in_minutes =
+      base::GetFieldTrialParamByFeatureAsInt(
+          features::kPauseBrowserInitiatedHeavyTrafficForP2P,
+          "max_wait_time_p2p_connections_in_minutes", 60);
+
+  return base::TimeDelta::FromMinutes(max_wait_time_p2p_connections_in_minutes);
+}
+
+std::set<int32_t> GetThrottledHashes() {
+  std::set<int32_t> throttled_hashes;
+
+  const std::string& throttled_traffic_annotation_tags =
+      base::GetFieldTrialParamValueByFeature(
+          features::kPauseBrowserInitiatedHeavyTrafficForP2P,
+          "throttled_traffic_annotation_tags");
+
+  const std::vector<std::string>& tokens =
+      base::SplitString(throttled_traffic_annotation_tags, ",",
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  for (const std::string& token : tokens) {
+    int int_token;
+    bool successful = base::StringToInt(token, &int_token);
+    if (!successful)
+      continue;
+    throttled_hashes.insert(int_token);
+  }
+  return throttled_hashes;
+}
+
 // The maximum number of delayable requests to allow to be in-flight at any
 // point in time (across all hosts).
 constexpr size_t kDefaultMaxNumDelayableRequestsPerClient = 10;
@@ -216,12 +254,17 @@
     const ParamsForNetworkQualityContainer&
         params_for_network_quality_container)
     : params_for_network_quality_container_(
-          params_for_network_quality_container) {}
+          params_for_network_quality_container),
+      max_wait_time_p2p_connections_(GetMaxWaitTimeP2PConnections()),
+      throttled_traffic_annotation_hashes_(GetThrottledHashes()) {}
 
 ResourceSchedulerParamsManager::ResourceSchedulerParamsManager(
     const ResourceSchedulerParamsManager& other)
     : params_for_network_quality_container_(
-          other.params_for_network_quality_container_) {}
+          other.params_for_network_quality_container_),
+      max_wait_time_p2p_connections_(other.max_wait_time_p2p_connections_),
+      throttled_traffic_annotation_hashes_(
+          other.throttled_traffic_annotation_hashes_) {}
 
 ResourceSchedulerParamsManager::~ResourceSchedulerParamsManager() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -239,4 +282,15 @@
                                  false, base::nullopt);
 }
 
+bool ResourceSchedulerParamsManager::CanThrottleNetworkTrafficAnnotationHash(
+    const int32_t unique_id_hash_code) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // TODO(tbansal): Replace this check by an algorithm that builds history
+  // locally, records exponential weighted moving average of request size per
+  // tag. Next, using this database, the algorithm throttles requests whose tag
+  // cause the most traffic.
+  return throttled_traffic_annotation_hashes_.find(unique_id_hash_code) !=
+         throttled_traffic_annotation_hashes_.end();
+}
+
 }  // namespace network
diff --git a/services/network/resource_scheduler_params_manager.h b/services/network/resource_scheduler_params_manager.h
index ab866e2..99ec21b 100644
--- a/services/network/resource_scheduler_params_manager.h
+++ b/services/network/resource_scheduler_params_manager.h
@@ -9,6 +9,8 @@
 #include <stdint.h>
 
 #include <map>
+#include <set>
+#include <unordered_set>
 
 #include "base/component_export.h"
 #include "base/optional.h"
@@ -80,12 +82,29 @@
     Reset(other.params_for_network_quality_container_);
   }
 
+  // Returns the maximum time for which the browser initiated traffic can be
+  // paused when there are active P2P connections.
+  const base::Optional<base::TimeDelta>& max_wait_time_p2p_connections() const {
+    return max_wait_time_p2p_connections_;
+  }
+
+  // Returns true if the browser initiated traffic with traffic annotation
+  // |unique_id_hash_code| can be paused when there are active P2P connections.
+  bool CanThrottleNetworkTrafficAnnotationHash(
+      const int32_t unique_id_hash_code) const;
+
  private:
   // The number of delayable requests in-flight for different ranges of the
   // network quality.
   ParamsForNetworkQualityContainer params_for_network_quality_container_;
 
+  const base::Optional<base::TimeDelta> max_wait_time_p2p_connections_;
+
+  const std::set<int32_t> throttled_traffic_annotation_hashes_;
+
   SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_ASSIGN(ResourceSchedulerParamsManager);
 };
 
 }  // namespace network
diff --git a/services/network/resource_scheduler_unittest.cc b/services/network/resource_scheduler_unittest.cc
index 9f30a37..2705168d 100644
--- a/services/network/resource_scheduler_unittest.cc
+++ b/services/network/resource_scheduler_unittest.cc
@@ -216,17 +216,19 @@
   std::unique_ptr<net::URLRequest> NewURLRequestWithChildAndRoute(
       const char* url,
       net::RequestPriority priority,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
       int child_id,
       int route_id) {
     std::unique_ptr<net::URLRequest> url_request(context_.CreateRequest(
-        GURL(url), priority, nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
+        GURL(url), priority, nullptr, traffic_annotation));
     return url_request;
   }
 
   std::unique_ptr<net::URLRequest> NewURLRequest(
       const char* url,
       net::RequestPriority priority) {
-    return NewURLRequestWithChildAndRoute(url, priority, kChildId, kRouteId);
+    return NewURLRequestWithChildAndRoute(
+        url, priority, TRAFFIC_ANNOTATION_FOR_TESTS, kChildId, kRouteId);
   }
 
   std::unique_ptr<TestRequest> NewRequestWithRoute(
@@ -241,7 +243,8 @@
       net::RequestPriority priority,
       int child_id,
       int route_id) {
-    return GetNewTestRequest(url, priority, child_id, route_id, true);
+    return GetNewTestRequest(url, priority, TRAFFIC_ANNOTATION_FOR_TESTS,
+                             child_id, route_id, true);
   }
 
   std::unique_ptr<TestRequest> NewRequest(const char* url,
@@ -263,6 +266,14 @@
                                        kBrowserRouteId);
   }
 
+  std::unique_ptr<TestRequest> NewBrowserRequestWithAnnotationTag(
+      const char* url,
+      net::RequestPriority priority,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+    return GetNewTestRequest(url, priority, traffic_annotation, kBrowserChildId,
+                             kBrowserRouteId, true);
+  }
+
   std::unique_ptr<TestRequest> NewSyncRequest(const char* url,
                                               net::RequestPriority priority) {
     return NewSyncRequestWithChildAndRoute(url, priority, kChildId, kRouteId);
@@ -280,16 +291,19 @@
       net::RequestPriority priority,
       int child_id,
       int route_id) {
-    return GetNewTestRequest(url, priority, child_id, route_id, false);
+    return GetNewTestRequest(url, priority, TRAFFIC_ANNOTATION_FOR_TESTS,
+                             child_id, route_id, false);
   }
 
-  std::unique_ptr<TestRequest> GetNewTestRequest(const char* url,
-                                                 net::RequestPriority priority,
-                                                 int child_id,
-                                                 int route_id,
-                                                 bool is_async) {
-    std::unique_ptr<net::URLRequest> url_request(
-        NewURLRequestWithChildAndRoute(url, priority, child_id, route_id));
+  std::unique_ptr<TestRequest> GetNewTestRequest(
+      const char* url,
+      net::RequestPriority priority,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      int child_id,
+      int route_id,
+      bool is_async) {
+    std::unique_ptr<net::URLRequest> url_request(NewURLRequestWithChildAndRoute(
+        url, priority, traffic_annotation, child_id, route_id));
     auto scheduled_request = scheduler_->ScheduleRequest(
         child_id, route_id, is_async, url_request.get());
     auto request = std::make_unique<TestRequest>(
@@ -731,6 +745,173 @@
   }
 }
 
+// Verify that browser requests are throttled by the resource scheduler only
+// when all the conditions are met.
+TEST_F(ResourceSchedulerTest,
+       LowerPriorityBrowserRequestsThrottleP2PConnections) {
+  const struct {
+    std::string test_case;
+    size_t p2p_active_connections;
+    net::EffectiveConnectionType effective_connection_type;
+    bool enable_pausing_behavior;
+    bool set_field_trial_param;
+    bool expected_browser_initiated_traffic_started;
+  } tests[] = {
+      {
+          "Field trial set",
+          1u,
+          net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+          true,
+          true,
+          false,
+      },
+      {
+          "Field trial not set",
+          1u,
+          net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+          false,
+          true,
+          true,
+      },
+      {
+          "Network fast",
+          1u,
+          net::EFFECTIVE_CONNECTION_TYPE_4G,
+          true,
+          true,
+          true,
+      },
+      {
+          "No active p2p connections",
+          0u,
+          net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+          true,
+          true,
+          true,
+      },
+      {
+          "Field trial param not set",
+          1u,
+          net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+          true,
+          false,
+          true,
+      },
+  };
+
+  for (const auto& test : tests) {
+    base::test::ScopedFeatureList scoped_feature_list;
+    if (test.enable_pausing_behavior) {
+      base::FieldTrialParams field_trial_params;
+      if (test.set_field_trial_param) {
+        field_trial_params["throttled_traffic_annotation_tags"] = "727528";
+      }
+      scoped_feature_list.InitAndEnableFeatureWithParameters(
+          features::kPauseBrowserInitiatedHeavyTrafficForP2P,
+          field_trial_params);
+    } else {
+      scoped_feature_list.InitAndDisableFeature(
+          features::kPauseBrowserInitiatedHeavyTrafficForP2P);
+    }
+    InitializeScheduler();
+
+    network_quality_estimator_
+        .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(
+            test.p2p_active_connections);
+    network_quality_estimator_.SetAndNotifyObserversOfEffectiveConnectionType(
+        test.effective_connection_type);
+
+    std::string url = "http://host/browser-initiatited";
+
+    net::NetworkTrafficAnnotationTag tag = net::DefineNetworkTrafficAnnotation(
+        "metrics_report_uma",
+        "Traffic annotation for unit, browser and other tests");
+    // (COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH(""));
+    std::unique_ptr<TestRequest> lows = (NewBrowserRequestWithAnnotationTag(
+        url.c_str(), net::LOWEST, tag));  //"metrics_report_uma"));
+    EXPECT_EQ(test.expected_browser_initiated_traffic_started, lows->started());
+  }
+}
+
+// Verify that browser requests that are currently queued are dispatched to the
+// network as soon as the active P2P connections count drops to 0.
+TEST_F(ResourceSchedulerTest, P2PConnectionWentAway) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  base::HistogramTester histogram_tester;
+  base::FieldTrialParams field_trial_params;
+  field_trial_params["throttled_traffic_annotation_tags"] = "727528";
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPauseBrowserInitiatedHeavyTrafficForP2P, field_trial_params);
+  InitializeScheduler();
+
+  network_quality_estimator_
+      .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(1u);
+  network_quality_estimator_.SetAndNotifyObserversOfEffectiveConnectionType(
+      net::EFFECTIVE_CONNECTION_TYPE_2G);
+
+  std::string url = "http://host/browser-initiatited";
+
+  net::NetworkTrafficAnnotationTag tag = net::DefineNetworkTrafficAnnotation(
+      "metrics_report_uma",
+      "Traffic annotation for unit, browser and other tests");
+  // (COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH(""));
+  std::unique_ptr<TestRequest> lows = (NewBrowserRequestWithAnnotationTag(
+      url.c_str(), net::LOWEST, tag));  //"metrics_report_uma"));
+  EXPECT_FALSE(lows->started());
+
+  network_quality_estimator_
+      .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(2u);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(lows->started());
+
+  network_quality_estimator_
+      .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(0u);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(lows->started());
+  histogram_tester.ExpectTotalCount(
+      "ResourceScheduler.BrowserInitiatedHeavyRequest.QueuingDuration", 1u);
+}
+
+// Verify that the previously queued browser requests are dispatched to the
+// network when the network quality becomes faster.
+TEST_F(ResourceSchedulerTest,
+       RequestThrottleOnlyOnSlowConnectionsWithP2PRequests) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList scoped_feature_list;
+  base::FieldTrialParams field_trial_params;
+  field_trial_params["throttled_traffic_annotation_tags"] = "727528";
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPauseBrowserInitiatedHeavyTrafficForP2P, field_trial_params);
+  InitializeScheduler();
+
+  network_quality_estimator_
+      .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(1u);
+  network_quality_estimator_.SetAndNotifyObserversOfEffectiveConnectionType(
+      net::EFFECTIVE_CONNECTION_TYPE_2G);
+
+  std::string url = "http://host/browser-initiatited";
+
+  net::NetworkTrafficAnnotationTag tag = net::DefineNetworkTrafficAnnotation(
+      "metrics_report_uma",
+      "Traffic annotation for unit, browser and other tests");
+  // (COMPUTE_NETWORK_TRAFFIC_ANNOTATION_ID_HASH(""));
+  std::unique_ptr<TestRequest> lows = (NewBrowserRequestWithAnnotationTag(
+      url.c_str(), net::LOWEST, tag));  //"metrics_report_uma"));
+  EXPECT_FALSE(lows->started());
+
+  network_quality_estimator_
+      .SetAndNotifyObserversOfP2PActiveConnectionsCountChange(2u);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(lows->started());
+
+  network_quality_estimator_.SetAndNotifyObserversOfEffectiveConnectionType(
+      net::EFFECTIVE_CONNECTION_TYPE_4G);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(lows->started());
+  histogram_tester.ExpectTotalCount(
+      "ResourceScheduler.BrowserInitiatedHeavyRequest.QueuingDuration", 1u);
+}
+
 TEST_F(ResourceSchedulerTest, ReprioritizedRequestGoesToBackOfQueue) {
   // Dummies to enforce scheduling.
   SetMaxDelayableRequests(1);
@@ -1432,7 +1613,8 @@
     another_scheduler.SetResourceSchedulerParamsManagerForTests(
         FixedParamsManager(1));
     std::unique_ptr<net::URLRequest> url_request(NewURLRequestWithChildAndRoute(
-        "http://host/another", net::LOWEST, kChildId, kRouteId));
+        "http://host/another", net::LOWEST, TRAFFIC_ANNOTATION_FOR_TESTS,
+        kChildId, kRouteId));
     auto scheduled_request = another_scheduler.ScheduleRequest(
         kChildId, kRouteId, true, url_request.get());
     auto another_request = std::make_unique<TestRequest>(
@@ -1787,7 +1969,8 @@
 
   for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) {
     EXPECT_EQ(i < max_low_priority_requests_allowed,
-              lows_singlehost[i]->started());
+              lows_singlehost[i]->started())
+        << " i=" << i;
   }
 }
 
diff --git a/services/network/sec_header_helpers.cc b/services/network/sec_header_helpers.cc
index bb55cce..7241385a 100644
--- a/services/network/sec_header_helpers.cc
+++ b/services/network/sec_header_helpers.cc
@@ -22,6 +22,11 @@
 
 bool IsSameSite(const url::Origin& initiator,
                 const url::Origin& target_origin) {
+  // Cross-scheme initiator should be considered cross-site (even if it's host
+  // is same-site with the target).  See also https://crbug.com/979257.
+  if (initiator.scheme() != target_origin.scheme())
+    return false;
+
   return net::registry_controlled_domains::SameDomainOrHost(
       initiator, target_origin,
       net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
diff --git a/services/tracing/perfetto/system_perfetto_unittest.cc b/services/tracing/perfetto/system_perfetto_unittest.cc
index 0e628c23..476b353c 100644
--- a/services/tracing/perfetto/system_perfetto_unittest.cc
+++ b/services/tracing/perfetto/system_perfetto_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <cerrno>
 #include <cstdlib>
 #include <memory>
 #include <string>
@@ -10,9 +11,12 @@
 
 #include "base/android/build_info.h"
 #include "base/bind.h"
+#include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/rand_util.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
@@ -25,6 +29,8 @@
 #include "services/tracing/public/cpp/tracing_features.h"
 #include "testing/gtest/include/gtest/gtest-death-test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/protos/perfetto/config/trace_config.pb.h"
+#include "third_party/perfetto/protos/perfetto/trace/trace.pb.h"
 
 namespace tracing {
 
@@ -34,6 +40,14 @@
     "org.chromium.chrome_integration_unittest";
 const char kPerfettoProducerName[] = "org.chromium.perfetto_producer.123";
 
+std::string RandomASCII(size_t length) {
+  std::string tmp;
+  for (size_t i = 0; i < length; ++i) {
+    tmp += base::RandInt('a', 'z');
+  }
+  return tmp;
+}
+
 class SystemPerfettoTest : public testing::Test {
  public:
   SystemPerfettoTest()
@@ -113,6 +127,50 @@
   PerfettoService* local_service() const { return perfetto_service_.get(); }
   void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
 
+  // Fork() + executes the perfetto cmdline client with the given args and
+  // returns true if we exited with a success otherwise |stderr_| is populated
+  // with the reason for the failure.
+  bool ExecPerfetto(std::initializer_list<std::string> args,
+                    std::string config) {
+    stderr_.clear();
+    base::CommandLine cmd(base::FilePath("/system/bin/perfetto"));
+    for (auto& arg : args) {
+      cmd.AppendArg(std::move(arg));
+    }
+    std::string config_path =
+        tmp_dir_.GetPath().Append(FILE_PATH_LITERAL("trace_config")).value();
+    config_path += RandomASCII(16);
+    cmd.AppendArgPath(base::FilePath(config_path));
+    base::File config_file(
+        base::FilePath(config_path),
+        base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    if (!config_file.IsValid()) {
+      stderr_ = "Tried to create ";
+      stderr_ += config_path;
+      stderr_ += " but failed with error ";
+      stderr_ += base::File::ErrorToString(config_file.error_details());
+      return false;
+    }
+    size_t written = config_file.Write(0, config.data(), config.size());
+    if (written != config.size()) {
+      stderr_ = base::StrCat({"Expected ", base::NumberToString(config.size()),
+                              " bytes written but actually wrote ",
+                              base::NumberToString(written)});
+      return false;
+    }
+    config_file.Close();
+
+    bool succeeded = base::GetAppOutputAndError(cmd, &stderr_);
+    if (!succeeded) {
+      stderr_ +=
+          " !!! end of |stderr_| this was generated by the commandline: ";
+      stderr_ += cmd.GetCommandLineString();
+    }
+    // This just cleans up the config file we generated above.
+    EXPECT_EQ(0, remove(config_path.c_str()));
+    return succeeded;
+  }
+
  protected:
   // |tmp_dir_| must be destroyed last. So must be declared first.
   base::ScopedTempDir tmp_dir_;
@@ -121,6 +179,7 @@
   std::unique_ptr<PerfettoService> perfetto_service_;
   std::vector<std::unique_ptr<TestDataSource>> data_sources_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
+  std::string stderr_;
 };
 
 TEST_F(SystemPerfettoTest, SystemTraceEndToEnd) {
@@ -153,6 +212,49 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
+TEST_F(SystemPerfettoTest, SystemTraceEndToEndRealService) {
+  if (base::android::BuildInfo::GetInstance()->sdk_int() <
+      base::android::SDK_VERSION_P) {
+    LOG(INFO)
+        << "Skipping SystemTraceEndToEndRealService test, this phone "
+        << "is pre P SDK, which means there is no 'real' service running.";
+    return;
+  }
+
+  perfetto::protos::TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
+  trace_config.add_data_sources()->mutable_config()->set_name(
+      data_sources_[0]->name());
+  trace_config.add_data_sources()->mutable_config()->set_name(
+      data_sources_[2]->name());
+  trace_config.set_duration_ms(100);
+
+  std::string path = "/data/misc/perfetto-traces/trace";
+  path += RandomASCII(16);
+  EXPECT_TRUE(
+      ExecPerfetto({"-o", path, "-c"}, trace_config.SerializeAsString()))
+      << "failed with stderr: \"" << stderr_ << "\"";
+
+  char* data = new char[1024 * 1024];
+  ASSERT_TRUE(data);
+  int num_bytes = base::ReadFile(base::FilePath(path), data, (1024 * 1024) - 1);
+  EXPECT_NE(num_bytes, -1);
+  std::string output(data, num_bytes);
+  delete[] data;
+
+  perfetto::protos::Trace trace;
+  EXPECT_TRUE(trace.ParseFromString(output));
+
+  int count = 0;
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_for_testing()) {
+      ++count;
+    }
+  }
+  EXPECT_EQ(1 + 7, count);
+  EXPECT_EQ(0, remove(path.c_str()));
+}
+
 TEST_F(SystemPerfettoTest, OneSystemSourceWithMultipleLocalSources) {
   auto system_service = CreateMockSystemService();
 
@@ -241,8 +343,7 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
-TEST_F(SystemPerfettoTest,
-       MultipleSystemSourceWithOneLocalSourcesLocalFirst) {
+TEST_F(SystemPerfettoTest, MultipleSystemSourceWithOneLocalSourcesLocalFirst) {
   auto system_service = CreateMockSystemService();
 
   base::RunLoop local_no_more_packets_runloop;
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.cc b/services/tracing/public/cpp/perfetto/perfetto_config.cc
index 5d31211..3323a72 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_config.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.cc
@@ -42,7 +42,22 @@
   if (size_limit == 0) {
     size_limit = 100 * 1024;
   }
-  perfetto_config.add_buffers()->set_size_kb(size_limit);
+  auto* buffer_config = perfetto_config.add_buffers();
+  buffer_config->set_size_kb(size_limit);
+  switch (chrome_config.GetTraceRecordMode()) {
+    case base::trace_event::RECORD_UNTIL_FULL:
+    case base::trace_event::RECORD_AS_MUCH_AS_POSSIBLE:
+      buffer_config->set_fill_policy(
+          perfetto::TraceConfig::BufferConfig::FillPolicy::DISCARD);
+      break;
+    case base::trace_event::RECORD_CONTINUOUSLY:
+      buffer_config->set_fill_policy(
+          perfetto::TraceConfig::BufferConfig::FillPolicy::RING_BUFFER);
+      break;
+    case base::trace_event::ECHO_TO_CONSOLE:
+      NOTREACHED();
+      break;
+  }
 
   // Perfetto uses clock_gettime for its internal snapshotting, which gets
   // blocked by the sandboxed and isn't needed for Chrome regardless.
diff --git a/services/tracing/public/cpp/trace_event_args_whitelist.cc b/services/tracing/public/cpp/trace_event_args_whitelist.cc
index 3d1de85..79fb67d 100644
--- a/services/tracing/public/cpp/trace_event_args_whitelist.cc
+++ b/services/tracing/public/cpp/trace_event_args_whitelist.cc
@@ -37,6 +37,9 @@
 const char* const kV8GCAllowedArgs[] = {"num_items", "num_tasks", nullptr};
 const char* const kTopLevelFlowAllowedArgs[] = {"task_queue_name", nullptr};
 const char* const kTopLevelIpcRunTaskAllowedArgs[] = {"ipc_hash", nullptr};
+const char* const kLifecyclesTaskPostedAllowedArgs[] = {
+    "task_queue_name", "time_since_disabled_ms", "ipc_hash", "location",
+    nullptr};
 
 const WhitelistEntry kEventArgsWhitelist[] = {
     {"__metadata", "thread_name", nullptr},
@@ -72,6 +75,8 @@
     {"ui", "UserEvent", nullptr},
     {TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), "SequenceManager::PostTask",
      kTopLevelFlowAllowedArgs},
+    {TRACE_DISABLED_BY_DEFAULT("lifecycles"), "task_posted_to_disabled_queue",
+     kLifecyclesTaskPostedAllowedArgs},
     {nullptr, nullptr, nullptr}};
 
 const char* kMetadataWhitelist[] = {"chrome-bitness",
diff --git a/sql/recover_module/btree.cc b/sql/recover_module/btree.cc
index 72ee3f1..57067ac 100644
--- a/sql/recover_module/btree.cc
+++ b/sql/recover_module/btree.cc
@@ -44,7 +44,7 @@
               "Move the destructor to the .cc file if it's non-trival");
 #endif  // !DCHECK_IS_ON()
 
-InnerPageDecoder::InnerPageDecoder(DatabasePageReader* db_reader)
+InnerPageDecoder::InnerPageDecoder(DatabasePageReader* db_reader) noexcept
     : page_id_(db_reader->page_id()),
       db_reader_(db_reader),
       cell_count_(ComputeCellCount(db_reader)),
@@ -135,7 +135,7 @@
               "Move the destructor to the .cc file if it's non-trival");
 #endif  // !DCHECK_IS_ON()
 
-LeafPageDecoder::LeafPageDecoder(DatabasePageReader* db_reader)
+LeafPageDecoder::LeafPageDecoder(DatabasePageReader* db_reader) noexcept
     : page_id_(db_reader->page_id()),
       db_reader_(db_reader),
       cell_count_(ComputeCellCount(db_reader)),
diff --git a/storage/browser/quota/quota_task.cc b/storage/browser/quota/quota_task.cc
index a937c4b..ff04875 100644
--- a/storage/browser/quota/quota_task.cc
+++ b/storage/browser/quota/quota_task.cc
@@ -22,7 +22,7 @@
 QuotaTask::~QuotaTask() = default;
 
 void QuotaTask::Start() {
-  DCHECK(observer_);
+  DCHECK(observer_ != nullptr);
   observer()->RegisterTask(this);
   Run();
 }
@@ -31,6 +31,7 @@
     : observer_(observer),
       original_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       delete_scheduled_(false) {
+  DCHECK(observer != nullptr);
 }
 
 void QuotaTask::CallCompleted() {
diff --git a/storage/browser/quota/quota_task.h b/storage/browser/quota/quota_task.h
index 11dcafd..09af2a7 100644
--- a/storage/browser/quota/quota_task.h
+++ b/storage/browser/quota/quota_task.h
@@ -45,17 +45,15 @@
   void DeleteSoon();
 
   QuotaTaskObserver* observer() const { return observer_; }
-  base::SingleThreadTaskRunner* original_task_runner() const {
-    return original_task_runner_.get();
-  }
 
  private:
   friend class base::DeleteHelper<QuotaTask>;
   friend class QuotaTaskObserver;
 
   void Abort();
+
   QuotaTaskObserver* observer_;
-  scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_;
+  const scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_;
   bool delete_scheduled_;
 };
 
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 4007415..0561e63 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -26012,6 +26012,50 @@
           ]
         },
         "test": "monochrome_public_test_ar_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "services_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ1A.190105.004",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "services_unittests"
       }
     ]
   }
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 202b82a..b44e1163 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -632,6 +632,36 @@
           "script": "//testing/trigger_scripts/chromeos_device_trigger.py"
         }
       }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "--browser=cros-chrome",
+          "--remote=variable_chromeos_device_hostname",
+          "--xvfb"
+        ],
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_perf_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_type": "kevin",
+              "os": "ChromeOS",
+              "pool": "chrome-cros-dut"
+            }
+          ],
+          "idempotent": false,
+          "shards": 2
+        },
+        "trigger_script": {
+          "script": "//testing/trigger_scripts/chromeos_device_trigger.py"
+        }
+      }
     ]
   },
   "linux-chromeos-dbg": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 506e54d7..ab71672 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -120,7 +120,8 @@
     "gtest_tests": [
       {
         "args": [
-          "--enable-features=VizDisplayCompositor,UseSkiaRenderer"
+          "--enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/skia_renderer.browser_tests.filter"
         ],
         "merge": {
           "args": [],
@@ -6252,36 +6253,7 @@
         "args": [
           "--browser=cros-chrome",
           "--remote=variable_chromeos_device_hostname",
-          "--xvfb"
-        ],
-        "isolate_name": "telemetry_perf_unittests",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "telemetry_perf_unittests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "device_type": "kevin",
-              "os": "ChromeOS",
-              "pool": "chrome-cros-dut"
-            }
-          ],
-          "idempotent": false,
-          "shards": 6
-        },
-        "trigger_script": {
-          "script": "//testing/trigger_scripts/chromeos_device_trigger.py"
-        }
-      },
-      {
-        "args": [
-          "--browser=cros-chrome",
-          "--remote=variable_chromeos_device_hostname",
-          "--jobs=1",
-          "--retry-limit=1"
+          "--jobs=1"
         ],
         "isolate_name": "telemetry_unittests",
         "merge": {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 6bb9205..667af1d 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -6951,6 +6951,7 @@
         "args": [
           "--use-vulkan=native",
           "--disable-vulkan-fallback-to-gl-for-testing",
+          "--enable-features=UseSkiaRenderer",
           "--test-launcher-filter-file=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 8c9d01bf..7386b228 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -36,6 +36,7 @@
   testonly = true
 
   data = [
+    "//testing/buildbot/filters/skia_renderer.browser_tests.filter",
     "//testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
     "//testing/buildbot/filters/webrtc_functional.browser_tests.filter",
     "//testing/buildbot/filters/navigation_loader_on_ui_browser_tests.filter",
diff --git a/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter b/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
index e24de01..36bfd86 100644
--- a/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
+++ b/testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter
@@ -1,11 +1,9 @@
 # crbug.com/963266: Android SkiaRenderer Vulkan content_browsertests failures
 -AccessibilityHitTestingBrowserTest.HitTestingWithPinchZoom
+-BrowserSideFlingBrowserTest.InertialGSEGetsBubbledFromOOPIF
 -BrowserSideFlingBrowserTest.InertialGSUBubblingStopsWhenParentCannotScroll
--DumpAccessibilityTreeTest.AccessibilityDfn/blink
--DumpAccessibilityTreeTest.AccessibilityOffscreen/blink
--DumpAccessibilityTreeTest.AccessibilityOffscreenScroll/blink
--DumpAccessibilityTreeTest.AccessibilityOffscreenSelect/blink
--DumpAccessibilityTreeTest.AccessibilityWindowCropsItems/blink
+-*CompositorImplLowEndBrowserTest.CompositorImplDropsResourcesOnBackground*
+-DumpAccessibilityTreeTest.*
 -File/MediaTest.VideoBearMp4/0
 -File/MediaTest.VideoTulipWebm/0
 -Http/MediaTest.VideoBearMp4/0
@@ -28,9 +26,11 @@
 -MSE_ExternalClearKey/EncryptedMediaTest.Playback_Encryption_CBCS_Video_CENC_Audio/0
 -MSE_ExternalClearKey/EncryptedMediaTest.Playback_Encryption_CENC/0
 -MSE_ExternalClearKey/EncryptedMediaTest.Playback_Encryption_CENC_Video_CBCS_Audio/0
+-NavigationBrowserTest.*
 -NavigationControllerBrowserTest.DontIgnoreBackAfterNavEntryLimit
 -NavigationControllerBrowserTest.ReloadWithUrlAnchor
 -NavigationControllerBrowserTest.ReloadWithUrlAnchorAndScroll
+-NavigationDownloadBrowserTest.StopLoadingAfterDroppedNavigation/0
 -OOPBrowserTest.Basic
 -P/CompositorImplBrowserTestRefreshRate.VideoPreference/0
 -SitePerProcessBrowserTest.ChildFrameCrashMetrics_ScrolledIntoViewAfterTabIsShown
@@ -40,6 +40,7 @@
 -SitePerProcessBrowserTest.ReloadHiddenTabWithCrashedSubframe
 -SitePerProcessBrowserTest.ScaledIframeRasterSize
 -SitePerProcessBrowserTest.ScrollOopifInPinchZoomedPage
+-SitePerProcessBrowserTest.SubframeReusesExistingProcess
 -SitePerProcessBrowserTest.SubframeVisibleAfterRenderViewBecomesSwappedOut
 -SitePerProcessBrowserTest.UnloadNestedPendingDeletion
 -SitePerProcessEmulatedTouchBrowserTest.EmulatedGestureScrollBubbles/0
@@ -69,15 +70,15 @@
 -TouchActionBrowserTest.PanXYMainThreadJanky/1
 -TouchActionBrowserTest.PanYMainThreadJanky/1
 -TouchSelectionControllerClientAndroidSiteIsolationTest.BasicSelectionIsolatedIframe
+-TracingControllerTest.ProcessesPresentInTrace
 -WebContentsImplBrowserTest.PopupWindowBrowserNavResumeLoad
 -WebContentsImplBrowserTest.SetPageFrozen
--WebRtcBrowserTest.CanSetupH264VideoCallOnSupportedDevice
+-WebRtcBrowserTest.*
 -WebRtcCaptureFromElementBrowserTest.CaptureFromCanvas2DHandlesContextLoss
 -WebRtcCaptureFromElementBrowserTest.VerifyCanvasCaptureOffscreenCanvasFrames
 -WebRtcCaptureFromElementBrowserTest.VerifyCanvasCaptureWebGLFrames
 -WebRtcCaptureFromElementBrowserTest.VerifyCanvasCapture2DFrames
 -WebRtcCaptureFromElementBrowserTest.VerifyCanvas2DCaptureColor
--WebRtcGetUserMediaBrowserTest.TestGetUserMediaAspectRatio1To1/0
--WebRtcGetUserMediaBrowserTest.TestGetUserMediaAspectRatio4To3/0
+-WebRtcGetUserMediaBrowserTest.*
 -WithOutOfBlinkCors/CrossSiteDocumentBlockingTest.BlockImages/0
 -WithoutOutOfBlinkCors/CrossSiteDocumentBlockingTest.BlockImages/0
diff --git a/testing/buildbot/filters/skia_renderer.browser_tests.filter b/testing/buildbot/filters/skia_renderer.browser_tests.filter
new file mode 100644
index 0000000..e2e76f9
--- /dev/null
+++ b/testing/buildbot/filters/skia_renderer.browser_tests.filter
@@ -0,0 +1,25 @@
+# These tests are all flaking after timing changes associated with
+# SkiaRenderer. We will need to determine the root causes, or update the tests
+# before beginning Finch trials. https://crbug.com/982933
+-BluetoothLowEnergyApiTest.*
+-BrowserActionApiTest.*
+-BrowserViewTest.*
+-ChromeMainTest.*
+-ContentSettingsWorkerModulesBrowserTest.*
+-ExtensionOverrideTest.*
+-InputImeApiTest.*
+-MediaRouterUIBrowserTest.*
+-ModernShowActionKey
+-NavigationPredictorBrowserTest.*
+-PaymentRequestCanMakePaymentMetricsTest.*
+-PlatformAppBrowserTest.*
+-PopupTrackerBrowserTest.*
+-RemoteDebuggingTest.*
+-SaveCardBubbleViewsFullFormBrowserTest.*
+-SecurityStateTabHelperTest.*
+-ServiceWorkerBasedBackgroundTest.*
+-ServiceWorkerTest.*
+-TabHoverCardBubbleViewBrowserTest.*
+-TranslateLanguageBrowserTest.*
+-WasmAppTest.*
+-WebDialogBrowserTest.*
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 71684fb..da28aa1 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -346,6 +346,21 @@
       'cros_browser_sanity_test': {},
     },
 
+    'chromeos_experimental_remote_device_isolated_tests': {
+      'telemetry_unittests': {
+        'args': [
+          '--browser=cros-chrome',
+          # The magic hostname that resolves to a CrOS device in the test lab.
+          '--remote=variable_chromeos_device_hostname',
+          '--jobs=1',
+        ],
+        'swarming': {
+          'idempotent': False,  # https://crbug.com/549140
+          'shards': 12,
+        },
+      },
+    },
+
     'chromeos_isolated_scripts': {
       'telemetry_perf_unittests': {
         'args': [
@@ -385,21 +400,7 @@
         ],
         'swarming': {
           'idempotent': False,  # https://crbug.com/549140
-          'shards': 6,
-        },
-      },
-      'telemetry_unittests': {
-        'args': [
-          '--browser=cros-chrome',
-          # The magic hostname that resolves to a CrOS device in the test lab.
-          '--remote=variable_chromeos_device_hostname',
-          '--jobs=1',
-          # TODO(crbug.com/866062): Use the default retry limit.
-          '--retry-limit=1',
-        ],
-        'swarming': {
-          'idempotent': False,  # https://crbug.com/549140
-          'shards': 12,
+          'shards': 2,
         },
       },
     },
@@ -2431,6 +2432,13 @@
       },
     },
 
+    # On some bots we don't have capacity to run all standard tests (for example
+    # Android Pie), however there are tracing integration tests we want to
+    # ensure are still working.
+    'chromium_tracing_gtests': {
+      'services_unittests': {},
+    },
+
     'chromium_web_tests_and_wpt_webdriver_isolated_scripts': {
       'webkit_layout_tests': {
         # layout test failures are retried 3 times when '--test-list' is not
@@ -3588,6 +3596,7 @@
         'args': [
           '--use-vulkan=native',
           '--disable-vulkan-fallback-to-gl-for-testing',
+          '--enable-features=UseSkiaRenderer',
           '--test-launcher-filter-file=../../testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter',
         ],
         'swarming': {
@@ -4485,6 +4494,7 @@
       'skia_renderer_browser_tests': {
         'args': [
           '--enable-features=VizDisplayCompositor,UseSkiaRenderer',
+          '--test-launcher-filter-file=../../testing/buildbot/filters/skia_renderer.browser_tests.filter',
         ],
         'swarming': {
           'shards': 10,
@@ -4785,6 +4795,7 @@
     'android_pie_gtests': [
       'android_ddready_vr_gtests',
       'android_ar_gtests',
+      'chromium_tracing_gtests',
       # No standard tests due to capacity, no Vega tests since it's currently
       # O only.
     ],
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 45f6389..55cc959c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -678,6 +678,7 @@
         },
         'test_suites': {
           'gtest_tests': 'chromeos_device_kevin_tests',
+          'isolated_scripts': 'chromeos_remote_device_isolated_tests',
         },
         'os_type': 'chromeos',
       },
@@ -1629,7 +1630,7 @@
           ],
         },
         'test_suites': {
-          'isolated_scripts': 'chromeos_remote_device_isolated_tests',
+          'isolated_scripts': 'chromeos_experimental_remote_device_isolated_tests',
         },
         'os_type': 'chromeos',
       },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 5e3c39d..0c089e13 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4833,29 +4833,6 @@
             ]
         }
     ],
-    "SkipPassthroughTouchEventQueueFilter": [
-        {
-            "platforms": [
-                "linux",
-                "windows",
-                "mac",
-                "android",
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledDiscreteEventsRendererProcess_20190507",
-                    "params": {
-                        "skip_filtering_process": "browser_and_renderer",
-                        "type": "discrete"
-                    },
-                    "enable_features": [
-                        "SkipTouchEventFilter"
-                    ]
-                }
-            ]
-        }
-    ],
     "SpellingServiceRestEndpoint": [
         {
             "platforms": [
diff --git a/third_party/android_crazy_linker/BUILD.gn b/third_party/android_crazy_linker/BUILD.gn
index d09d573..88bd711 100644
--- a/third_party/android_crazy_linker/BUILD.gn
+++ b/third_party/android_crazy_linker/BUILD.gn
@@ -9,6 +9,9 @@
 # NOTE: This file is included in Linux builds to build the
 # crazy_linker_zip_fuzzer target (see below). However, the rest of the
 # crazy linker doesn't build or work on Linux yet.
+#
+# NOTE: Instructions for testing the crazy linker are in
+# android_linker_testing.md.
 
 config("crazy_config") {
   include_dirs = [ "src/include" ]
diff --git a/third_party/android_crazy_linker/README.chromium b/third_party/android_crazy_linker/README.chromium
index 81cf96a..63106015 100644
--- a/third_party/android_crazy_linker/README.chromium
+++ b/third_party/android_crazy_linker/README.chromium
@@ -100,4 +100,6 @@
 
 - Improve FileDescriptor class.
 
-- Safer zip parsing code (avoid integer overflows and add range checks).
\ No newline at end of file
+- Safer zip parsing code (avoid integer overflows and add range checks).
+
+- Add a document about testing the crazy linker in a Chromium checkout
diff --git a/third_party/android_crazy_linker/src/README.TXT b/third_party/android_crazy_linker/src/README.TXT
index fc025eb..c08168ec 100644
--- a/third_party/android_crazy_linker/src/README.TXT
+++ b/third_party/android_crazy_linker/src/README.TXT
@@ -125,9 +125,6 @@
 Testing:
 --------
 
-  If you modify this code, check your changes by running the test suite using:
-
-    cd $NDK
-    tests/run-tests.sh crazy_linker
+  See android_linker_testing.md
 
   See DESIGN.TXT for an overview of the library's design.
diff --git a/third_party/android_crazy_linker/src/android_linker_testing.md b/third_party/android_crazy_linker/src/android_linker_testing.md
new file mode 100644
index 0000000..8c224c29
--- /dev/null
+++ b/third_party/android_crazy_linker/src/android_linker_testing.md
@@ -0,0 +1,56 @@
+# Testing the Android Crazy Linker
+
+The crazy linker is a custom dynamic linker used by Chrome on older Android
+versions where dynamic linking is not as advanced. It provides
+`android_dlopen_ext` functionality, RELRO sharing, compressed relocations, etc.
+
+For crazy reasons outlined in `linker/test_case.py` this linker cannot be tested
+using GTest or instrumentation test, hence it also carries a custom testing
+framework. The tests are not run as part of CQ, but it is still desirable to run
+them before landing changes in code locations listed below.
+
+Sorry.
+
+These instructions assume
+[Building Chromium for Android](android_build_instructions.md) as a
+prerequisite.
+
+## Code Locations
+
+The tested functionality is spread across these locations:
+```
+third_party/android_crazy_linker
+base/android/java/src/org/chromium/base/library_loader
+```
+
+The tests themselves are living mostly in these places:
+```
+build/android/pylib/linker/test_case.py
+content/shell/android
+```
+
+## Running native tests
+
+This will run both unittests and regression tests:
+```
+autoninja -C out/Release android_crazy_linker_tests
+out/Release/bin/run_android_crazy_linker_tests --unit-tests
+```
+
+Verbosity of the output can be increased by setting `CRAZY_DEBUG` to 1 in
+`crazy_linker_debug.h`.
+
+## Running Java Tests
+
+We recommend running these tests in Release mode, as there are known
+complications in testing with the component build. Setting `Linker.DEBUG` to
+`true` should also help increase verbosity of the output.
+```
+autoninja -C out/Release chromium_linker_test_apk
+out/Release/bin/run_chromium_linker_test_apk
+```
+
+## Fuzzer Tests
+
+There are also a few tests for fuzzing the ZIP parser. The instructions to run
+them are at the bottom of `third_party/android_crazy_linker/BUILD.gn`.
diff --git a/third_party/blink/public/mojom/idle/OWNERS b/third_party/blink/public/mojom/idle/OWNERS
index b0db262..14238e1 100644
--- a/third_party/blink/public/mojom/idle/OWNERS
+++ b/third_party/blink/public/mojom/idle/OWNERS
@@ -2,3 +2,5 @@
 
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# TEAM: fugu-dev@chromium.org
diff --git a/third_party/blink/public/web/web_settings.h b/third_party/blink/public/web/web_settings.h
index 09437c5d..9ee2dc11 100644
--- a/third_party/blink/public/web/web_settings.h
+++ b/third_party/blink/public/web/web_settings.h
@@ -254,6 +254,9 @@
   virtual void SetTextTrackTextColor(const WebString&) = 0;
   virtual void SetTextTrackTextShadow(const WebString&) = 0;
   virtual void SetTextTrackTextSize(const WebString&) = 0;
+  virtual void SetTextTrackWindowColor(const WebString&) = 0;
+  virtual void SetTextTrackWindowPadding(const WebString&) = 0;
+  virtual void SetTextTrackWindowRadius(const WebString&) = 0;
   virtual void SetThreadedScrollingEnabled(bool) = 0;
   virtual void SetTouchDragDropEnabled(bool) = 0;
   virtual void SetBarrelButtonForDragEnabled(bool) = 0;
diff --git a/third_party/blink/renderer/bindings/core/v8/isolated_world_csp.cc b/third_party/blink/renderer/bindings/core/v8/isolated_world_csp.cc
index 9404ad9..d5c0490 100644
--- a/third_party/blink/renderer/bindings/core/v8/isolated_world_csp.cc
+++ b/third_party/blink/renderer/bindings/core/v8/isolated_world_csp.cc
@@ -7,8 +7,10 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
+#include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -30,9 +32,11 @@
  public:
   IsolatedWorldCSPDelegate(Document& document,
                            scoped_refptr<SecurityOrigin> security_origin,
+                           int world_id,
                            bool apply_policy)
       : document_(&document),
         security_origin_(std::move(security_origin)),
+        world_id_(world_id),
         apply_policy_(apply_policy) {
     DCHECK(security_origin_);
   }
@@ -95,14 +99,17 @@
   }
 
   void DisableEval(const String& error_message) override {
-    // TODO(crbug.com/896041): Implement this.
-    NOTIMPLEMENTED();
+    if (!document_->GetFrame())
+      return;
+    document_->GetFrame()->GetScriptController().DisableEvalForIsolatedWorld(
+        world_id_, error_message);
   }
 
   void ReportBlockedScriptExecutionToInspector(
       const String& directive_text) override {
-    // TODO(crbug.com/896041): Figure out if this needs to be implemented.
-    NOTIMPLEMENTED();
+    // This allows users to set breakpoints in the Devtools for the case when
+    // script execution is blocked by CSP.
+    probe::ScriptExecutionBlockedByCSP(document_, directive_text);
   }
 
   void DidAddContentSecurityPolicies(
@@ -111,6 +118,7 @@
  private:
   const Member<Document> document_;
   const scoped_refptr<SecurityOrigin> security_origin_;
+  const int world_id_;
 
   // Whether the 'IsolatedWorldCSP' feature is enabled, and we are applying the
   // CSP provided by the isolated world.
@@ -172,7 +180,7 @@
 
   IsolatedWorldCSPDelegate* delegate =
       MakeGarbageCollected<IsolatedWorldCSPDelegate>(
-          document, std::move(self_origin), apply_policy);
+          document, std::move(self_origin), world_id, apply_policy);
   csp->BindToDelegate(*delegate);
 
   if (apply_policy) {
diff --git a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
index 59af62ec..50c06fdd 100644
--- a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/local_window_proxy.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/initialize_v8_extras_binding.h"
+#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -167,19 +168,30 @@
 
   SetupWindowPrototypeChain();
 
-  const SecurityOrigin* origin = nullptr;
-  if (world_->IsMainWorld()) {
-    // ActivityLogger for main world is updated within updateDocumentInternal().
-    UpdateDocumentInternal();
-    origin = GetFrame()->GetDocument()->GetSecurityOrigin();
-    // FIXME: Can this be removed when CSP moves to browser?
+  // Setup handling for eval checks for the context. Isolated worlds which don't
+  // specify their own CSPs are exempt from eval checks currently.
+  // TODO(crbug.com/982388): For other CSP checks, we use the main world CSP
+  // when an isolated world doesn't specify its own CSP. We should do the same
+  // here.
+  const bool evaluate_csp_for_eval =
+      world_->IsMainWorld() ||
+      (world_->IsIsolatedWorld() &&
+       IsolatedWorldCSP::Get().HasContentSecurityPolicy(world_->GetWorldId()));
+  if (evaluate_csp_for_eval) {
     ContentSecurityPolicy* csp =
-        GetFrame()->GetDocument()->GetContentSecurityPolicy();
+        GetFrame()->GetDocument()->GetContentSecurityPolicyForWorld();
     context->AllowCodeGenerationFromStrings(csp->AllowEval(
         nullptr, SecurityViolationReportingPolicy::kSuppressReporting,
         ContentSecurityPolicy::kWillNotThrowException, g_empty_string));
     context->SetErrorMessageForCodeGenerationFromStrings(
         V8String(GetIsolate(), csp->EvalDisabledErrorMessage()));
+  }
+
+  const SecurityOrigin* origin = nullptr;
+  if (world_->IsMainWorld()) {
+    // ActivityLogger for main world is updated within updateDocumentInternal().
+    UpdateDocumentInternal();
+    origin = GetFrame()->GetDocument()->GetSecurityOrigin();
   } else {
     UpdateActivityLogger();
     origin = world_->IsolatedWorldSecurityOrigin();
diff --git a/third_party/blink/renderer/bindings/core/v8/script_controller.cc b/third_party/blink/renderer/bindings/core/v8/script_controller.cc
index ad6fca3..9da29ec 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_controller.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_controller.cc
@@ -60,6 +60,7 @@
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
 #include "third_party/blink/renderer/core/loader/progress_tracker.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -153,23 +154,41 @@
 }
 
 void ScriptController::EnableEval() {
-  v8::HandleScope handle_scope(GetIsolate());
-  v8::Local<v8::Context> v8_context =
-      window_proxy_manager_->MainWorldProxyMaybeUninitialized()
-          ->ContextIfInitialized();
-  if (v8_context.IsEmpty())
-    return;
-  v8_context->AllowCodeGenerationFromStrings(true);
+  SetEvalForWorld(DOMWrapperWorld::MainWorld(), true /* allow_eval */,
+                  g_empty_string /* error_message */);
 }
 
 void ScriptController::DisableEval(const String& error_message) {
+  SetEvalForWorld(DOMWrapperWorld::MainWorld(), false /* allow_eval */,
+                  error_message);
+}
+
+void ScriptController::DisableEvalForIsolatedWorld(
+    int world_id,
+    const String& error_message) {
+  DCHECK(DOMWrapperWorld::IsIsolatedWorldId(world_id));
+  scoped_refptr<DOMWrapperWorld> world =
+      DOMWrapperWorld::EnsureIsolatedWorld(GetIsolate(), world_id);
+  SetEvalForWorld(*world, false /* allow_eval */, error_message);
+}
+
+void ScriptController::SetEvalForWorld(DOMWrapperWorld& world,
+                                       bool allow_eval,
+                                       const String& error_message) {
   v8::HandleScope handle_scope(GetIsolate());
-  v8::Local<v8::Context> v8_context =
-      window_proxy_manager_->MainWorldProxyMaybeUninitialized()
-          ->ContextIfInitialized();
+  LocalWindowProxy* proxy =
+      world.IsMainWorld()
+          ? window_proxy_manager_->MainWorldProxyMaybeUninitialized()
+          : WindowProxy(world);
+
+  v8::Local<v8::Context> v8_context = proxy->ContextIfInitialized();
   if (v8_context.IsEmpty())
     return;
-  v8_context->AllowCodeGenerationFromStrings(false);
+
+  v8_context->AllowCodeGenerationFromStrings(allow_eval);
+  if (allow_eval)
+    return;
+
   v8_context->SetErrorMessageForCodeGenerationFromStrings(
       V8String(GetIsolate(), error_message));
 }
diff --git a/third_party/blink/renderer/bindings/core/v8/script_controller.h b/third_party/blink/renderer/bindings/core/v8/script_controller.h
index 0a6422c9..0f0c9db 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_controller.h
+++ b/third_party/blink/renderer/bindings/core/v8/script_controller.h
@@ -120,8 +120,13 @@
   scoped_refptr<DOMWrapperWorld> CreateNewInspectorIsolatedWorld(
       const String& world_name);
 
+  // Disables eval for the main world.
   void DisableEval(const String& error_message);
 
+  // Disables eval for the given isolated |world_id|. This initializes the
+  // window proxy for the isolated world, if it's not yet initialized.
+  void DisableEvalForIsolatedWorld(int world_id, const String& error_message);
+
   TextPosition EventHandlerPosition() const;
 
   void ClearWindowProxy();
@@ -143,6 +148,12 @@
   }
   void EnableEval();
 
+  // Sets whether eval is enabled for the context corresponding to the given
+  // |world|. |error_message| is used only when |allow_eval| is false.
+  void SetEvalForWorld(DOMWrapperWorld& world,
+                       bool allow_eval,
+                       const String& error_message);
+
   v8::Local<v8::Value> EvaluateScriptInMainWorld(const ScriptSourceCode&,
                                                  const KURL& base_url,
                                                  SanitizeScriptErrors,
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc b/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
index c25dd97..2cdc9f97 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_embedder_graph_builder.cc
@@ -665,6 +665,9 @@
 void EmbedderGraphBuilder::BuildEmbedderGraphCallback(v8::Isolate* isolate,
                                                       v8::EmbedderGraph* graph,
                                                       void*) {
+  // Synchronize with concurrent sweepers before taking a snapshot.
+  ThreadState::Current()->CompleteSweep();
+
   NodeBuilder node_builder(graph);
   V8EmbedderGraphBuilder builder(isolate, graph, &node_builder);
   builder.BuildEmbedderGraph();
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index d4fcac3..e53efa8 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -33,6 +33,7 @@
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
+#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
 #include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
 #include "third_party/blink/renderer/bindings/core/v8/rejected_promises.h"
 #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h"
@@ -357,8 +358,16 @@
   if (ExecutionContext* execution_context = ToExecutionContext(context)) {
     DCHECK(execution_context->IsDocument() ||
            execution_context->IsMainThreadWorkletGlobalScope());
+
+    v8::Context::Scope scope(context);
+
+    // Note this callback is only triggered for contexts which have eval
+    // disabled. Hence we don't need to handle the case of isolated world
+    // contexts with no CSP specified. (They should be exempt from the page CSP.
+    // See crbug.com/982388.)
+
     if (ContentSecurityPolicy* policy =
-            execution_context->GetContentSecurityPolicy()) {
+            execution_context->GetContentSecurityPolicyForWorld()) {
       v8::String::Value source_str(context->GetIsolate(), source);
       UChar snippet[ContentSecurityPolicy::kMaxSampleLength + 1];
       size_t len = std::min((sizeof(snippet) / sizeof(UChar)) - 1,
diff --git a/third_party/blink/renderer/core/animation/css_resolution_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_resolution_interpolation_type.cc
index 3b8b3afbf..14756aa 100644
--- a/third_party/blink/renderer/core/animation/css_resolution_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_resolution_interpolation_type.cc
@@ -20,8 +20,7 @@
     const StyleResolverState*,
     ConversionCheckers&) const {
   auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value);
-  if (!primitive_value ||
-      !CSSPrimitiveValue::IsResolution(primitive_value->TypeWithCalcResolved()))
+  if (!primitive_value || !primitive_value->IsResolution())
     return nullptr;
   return InterpolationValue(std::make_unique<InterpolableNumber>(
       primitive_value->ComputeDotsPerPixel()));
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index ff74c1a0..ad173432 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -145,8 +145,7 @@
 CSSMathExpressionNumericLiteral::CSSMathExpressionNumericLiteral(
     CSSNumericLiteralValue* value,
     bool is_integer)
-    : CSSMathExpressionNode(UnitCategory(value->TypeWithCalcResolved()),
-                            is_integer),
+    : CSSMathExpressionNode(UnitCategory(value->GetType()), is_integer),
       value_(value) {}
 
 bool CSSMathExpressionNumericLiteral::IsZero() const {
@@ -233,7 +232,7 @@
 
 CSSPrimitiveValue::UnitType CSSMathExpressionNumericLiteral::ResolvedUnitType()
     const {
-  return value_->TypeWithCalcResolved();
+  return value_->GetType();
 }
 
 void CSSMathExpressionNumericLiteral::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/css/css_math_function_value.cc b/third_party/blink/renderer/core/css/css_math_function_value.cc
index 8fac2b7..bc8148e5 100644
--- a/third_party/blink/renderer/core/css/css_math_function_value.cc
+++ b/third_party/blink/renderer/core/css/css_math_function_value.cc
@@ -150,4 +150,10 @@
   return expression_->IsZero();
 }
 
+bool CSSMathFunctionValue::IsPx() const {
+  // TODO(crbug.com/979895): This is the result of refactoring, which might be
+  // an existing bug. Fix it if necessary.
+  return Category() == kCalcLength;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_math_function_value.h b/third_party/blink/renderer/core/css/css_math_function_value.h
index c4d8093..2b7ad773 100644
--- a/third_party/blink/renderer/core/css/css_math_function_value.h
+++ b/third_party/blink/renderer/core/css/css_math_function_value.h
@@ -34,6 +34,14 @@
   bool MayHaveRelativeUnit() const;
 
   CalculationCategory Category() const { return expression_->Category(); }
+  bool IsAngle() const { return Category() == kCalcAngle; }
+  bool IsLength() const { return Category() == kCalcLength; }
+  bool IsNumber() const { return Category() == kCalcNumber; }
+  bool IsPercentage() const { return Category() == kCalcPercent; }
+  bool IsTime() const { return Category() == kCalcTime; }
+
+  bool IsPx() const;
+
   bool IsInt() const { return expression_->IsInteger(); }
   bool IsNegative() const { return expression_->DoubleValue() < 0; }
   ValueRange PermittedValueRange() const {
diff --git a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
index 0adefe1..3b3cb93 100644
--- a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
+++ b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
@@ -104,6 +104,11 @@
   }
 }
 
+double CSSNumericLiteralValue::ComputeDotsPerPixel() const {
+  DCHECK(IsResolution());
+  return DoubleValue() * ConversionToCanonicalUnitsScaleFactor(GetType());
+}
+
 double CSSNumericLiteralValue::ComputeLengthPx(
     const CSSToLengthConversionData& conversion_data) const {
   return conversion_data.ZoomedComputedPixels(num_, GetType());
diff --git a/third_party/blink/renderer/core/css/css_numeric_literal_value.h b/third_party/blink/renderer/core/css/css_numeric_literal_value.h
index cb1f0055..625875d 100644
--- a/third_party/blink/renderer/core/css/css_numeric_literal_value.h
+++ b/third_party/blink/renderer/core/css/css_numeric_literal_value.h
@@ -22,6 +22,7 @@
     return static_cast<UnitType>(numeric_literal_unit_type_);
   }
 
+  bool IsAngle() const { return CSSPrimitiveValue::IsAngle(GetType()); }
   bool IsFontRelativeLength() const {
     return GetType() == UnitType::kQuirkyEms || GetType() == UnitType::kEms ||
            GetType() == UnitType::kExs || GetType() == UnitType::kRems ||
@@ -31,6 +32,14 @@
   bool IsViewportPercentageLength() const {
     return CSSPrimitiveValue::IsViewportPercentageLength(GetType());
   }
+  bool IsLength() const { return CSSPrimitiveValue::IsLength(GetType()); }
+  bool IsPx() const { return GetType() == UnitType::kPixels; }
+  bool IsNumber() const {
+    return GetType() == UnitType::kNumber || GetType() == UnitType::kInteger;
+  }
+  bool IsInteger() const { return GetType() == UnitType::kInteger; }
+  bool IsPercentage() const { return GetType() == UnitType::kPercentage; }
+  bool IsTime() const { return CSSPrimitiveValue::IsTime(GetType()); }
   bool IsResolution() const {
     return CSSPrimitiveValue::IsResolution(GetType());
   }
@@ -41,6 +50,7 @@
   double DoubleValue() const { return num_; }
   double ComputeSeconds() const;
   double ComputeDegrees() const;
+  double ComputeDotsPerPixel() const;
 
   double ComputeLengthPx(
       const CSSToLengthConversionData& conversion_data) const;
diff --git a/third_party/blink/renderer/core/css/css_primitive_value.cc b/third_party/blink/renderer/core/css/css_primitive_value.cc
index 344ac2f..28542b4 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value.cc
+++ b/third_party/blink/renderer/core/css/css_primitive_value.cc
@@ -126,6 +126,48 @@
   return IsNumericLiteralValue() && To<CSSNumericLiteralValue>(this)->IsFlex();
 }
 
+bool CSSPrimitiveValue::IsAngle() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsAngle();
+  return To<CSSMathFunctionValue>(this)->IsAngle();
+}
+
+bool CSSPrimitiveValue::IsLength() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsLength();
+  return To<CSSMathFunctionValue>(this)->IsLength();
+}
+
+bool CSSPrimitiveValue::IsPx() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsPx();
+  return To<CSSMathFunctionValue>(this)->IsPx();
+}
+
+bool CSSPrimitiveValue::IsNumber() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsNumber();
+  return To<CSSMathFunctionValue>(this)->IsNumber();
+}
+
+bool CSSPrimitiveValue::IsInteger() const {
+  // TODO(style-dev): Support integer math functions properly.
+  return IsNumericLiteralValue() &&
+         To<CSSNumericLiteralValue>(this)->IsInteger();
+}
+
+bool CSSPrimitiveValue::IsPercentage() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsPercentage();
+  return To<CSSMathFunctionValue>(this)->IsPercentage();
+}
+
+bool CSSPrimitiveValue::IsTime() const {
+  if (IsNumericLiteralValue())
+    return To<CSSNumericLiteralValue>(this)->IsTime();
+  return To<CSSMathFunctionValue>(this)->IsTime();
+}
+
 CSSPrimitiveValue::CSSPrimitiveValue(ClassType class_type)
     : CSSValue(class_type) {}
 
@@ -174,9 +216,10 @@
 }
 
 double CSSPrimitiveValue::ComputeDotsPerPixel() const {
-  UnitType current_type = TypeWithCalcResolved();
-  DCHECK(IsResolution(current_type));
-  return GetDoubleValue() * ConversionToCanonicalUnitsScaleFactor(current_type);
+  // TODO(style-dev): Either support math functions on resolutions; or provide a
+  // justification for not supporting it.
+  DCHECK(IsNumericLiteralValue());
+  return To<CSSNumericLiteralValue>(this)->ComputeDotsPerPixel();
 }
 
 template <>
diff --git a/third_party/blink/renderer/core/css/css_primitive_value.h b/third_party/blink/renderer/core/css/css_primitive_value.h
index 4162932..e5b0c226 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value.h
+++ b/third_party/blink/renderer/core/css/css_primitive_value.h
@@ -159,7 +159,7 @@
     return unit == UnitType::kDegrees || unit == UnitType::kRadians ||
            unit == UnitType::kGradians || unit == UnitType::kTurns;
   }
-  bool IsAngle() const { return IsAngle(TypeWithCalcResolved()); }
+  bool IsAngle() const;
   bool IsFontRelativeLength() const;
   static bool IsViewportPercentageLength(UnitType type) {
     return type >= UnitType::kViewportWidth && type <= UnitType::kViewportMax;
@@ -173,19 +173,15 @@
            type == UnitType::kExs || type == UnitType::kRems ||
            type == UnitType::kChs || IsViewportPercentageLength(type);
   }
-  bool IsLength() const { return IsLength(TypeWithCalcResolved()); }
-  bool IsNumber() const {
-    return TypeWithCalcResolved() == UnitType::kNumber ||
-           TypeWithCalcResolved() == UnitType::kInteger;
-  }
-  bool IsPercentage() const {
-    return TypeWithCalcResolved() == UnitType::kPercentage;
-  }
-  bool IsPx() const { return TypeWithCalcResolved() == UnitType::kPixels; }
+  bool IsLength() const;
+  bool IsNumber() const;
+  bool IsInteger() const;
+  bool IsPercentage() const;
+  bool IsPx() const;
   static bool IsTime(UnitType unit) {
     return unit == UnitType::kSeconds || unit == UnitType::kMilliseconds;
   }
-  bool IsTime() const { return IsTime(TypeWithCalcResolved()); }
+  bool IsTime() const;
   static bool IsFrequency(UnitType unit) {
     return unit == UnitType::kHertz || unit == UnitType::kKilohertz;
   }
diff --git a/third_party/blink/renderer/core/css/media_query_exp.cc b/third_party/blink/renderer/core/css/media_query_exp.cc
index 695792e..735ba846 100644
--- a/third_party/blink/renderer/core/css/media_query_exp.cc
+++ b/third_party/blink/renderer/core/css/media_query_exp.cc
@@ -29,6 +29,8 @@
 
 #include "third_party/blink/renderer/core/css/media_query_exp.h"
 
+#include "third_party/blink/renderer/core/css/css_math_expression_node.h"
+#include "third_party/blink/renderer/core/css/css_math_function_value.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css/parser/css_property_parser_helpers.h"
 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
@@ -117,13 +119,7 @@
 
 static inline bool FeatureWithValidDensity(const String& media_feature,
                                            const CSSPrimitiveValue* value) {
-  if ((value->TypeWithCalcResolved() !=
-           CSSPrimitiveValue::UnitType::kDotsPerPixel &&
-       value->TypeWithCalcResolved() !=
-           CSSPrimitiveValue::UnitType::kDotsPerInch &&
-       value->TypeWithCalcResolved() !=
-           CSSPrimitiveValue::UnitType::kDotsPerCentimeter) ||
-      value->GetDoubleValue() <= 0)
+  if (!value->IsResolution() || value->GetDoubleValue() <= 0)
     return false;
 
   return media_feature == kResolutionMediaFeature ||
@@ -147,7 +143,7 @@
 
 static inline bool FeatureWithPositiveInteger(const String& media_feature,
                                               const CSSPrimitiveValue* value) {
-  if (value->TypeWithCalcResolved() != CSSPrimitiveValue::UnitType::kInteger)
+  if (!value->IsInteger())
     return false;
   return FeatureExpectingPositiveInteger(media_feature);
 }
@@ -165,7 +161,7 @@
 
 static inline bool FeatureWithZeroOrOne(const String& media_feature,
                                         const CSSPrimitiveValue* value) {
-  if (value->TypeWithCalcResolved() != CSSPrimitiveValue::UnitType::kInteger ||
+  if (!value->IsInteger() ||
       !(value->GetDoubleValue() == 1 || !value->GetDoubleValue()))
     return false;
 
@@ -272,9 +268,7 @@
   // Create value for media query expression that must have 1 or more values.
   if (value) {
     if (FeatureWithAspectRatio(lower_media_feature)) {
-      if (value->TypeWithCalcResolved() !=
-              CSSPrimitiveValue::UnitType::kInteger ||
-          value->GetDoubleValue() == 0)
+      if (!value->IsInteger() || value->GetDoubleValue() == 0)
         return Invalid();
       if (!css_property_parser_helpers::ConsumeSlashIncludingWhitespace(range))
         return Invalid();
@@ -291,11 +285,25 @@
                FeatureWithPositiveInteger(lower_media_feature, value) ||
                FeatureWithPositiveNumber(lower_media_feature, value) ||
                FeatureWithZeroOrOne(lower_media_feature, value)) {
-      exp_value.value = value->GetDoubleValue();
-      if (value->IsNumber())
-        exp_value.unit = CSSPrimitiveValue::UnitType::kNumber;
-      else
-        exp_value.unit = value->TypeWithCalcResolved();
+      if (!value->IsLength() || !value->IsMathFunctionValue()) {
+        exp_value.value = value->GetDoubleValue();
+        if (value->IsNumber())
+          exp_value.unit = CSSPrimitiveValue::UnitType::kNumber;
+        else
+          exp_value.unit = value->TypeWithCalcResolved();
+      } else {
+        const auto* math_value = To<CSSMathFunctionValue>(value);
+        CSSPrimitiveValue::UnitType expression_unit =
+            math_value->ExpressionNode()->ResolvedUnitType();
+        if (expression_unit == CSSPrimitiveValue::UnitType::kUnknown) {
+          // TODO(crbug.com/982542): Support math expressions involving type
+          // conversions properly. For example, calc(10px + 1em).
+          return Invalid();
+        } else {
+          exp_value.value = math_value->DoubleValue();
+          exp_value.unit = expression_unit;
+        }
+      }
       exp_value.is_value = true;
     } else {
       return Invalid();
diff --git a/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc b/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
index ac74d974..af56e06 100644
--- a/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
+++ b/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
@@ -202,7 +202,7 @@
   }
 
   const CSSMathFunctionValue* Value() const { return calc_value_; }
-  CSSPrimitiveValue* ConsumeValue() {
+  CSSMathFunctionValue* ConsumeValue() {
     if (!calc_value_)
       return nullptr;
     source_range_ = range_;
@@ -450,8 +450,12 @@
 namespace {
 
 bool IsNonZeroUserUnitsValue(const CSSPrimitiveValue* value) {
-  return value &&
-         value->TypeWithCalcResolved() ==
+  // TODO(crbug.com/979895): This is the result of a refactoring, which might
+  // have revealed an existing bug in handling user units in math functions. Fix
+  // it if necessary.
+  const auto* numeric_literal = DynamicTo<CSSNumericLiteralValue>(value);
+  return numeric_literal &&
+         numeric_literal->GetType() ==
              CSSPrimitiveValue::UnitType::kUserUnits &&
          value->GetDoubleValue() != 0;
 }
@@ -511,14 +515,14 @@
   if (const CSSMathFunctionValue* calculation = calc_parser.Value()) {
     if (calculation->Category() != kCalcAngle)
       return nullptr;
-    if (CSSPrimitiveValue* result = calc_parser.ConsumeValue()) {
-      if (result->GetDoubleValue() < minimum_value) {
-        return CSSNumericLiteralValue::Create(minimum_value,
-                                              result->TypeWithCalcResolved());
+    if (CSSMathFunctionValue* result = calc_parser.ConsumeValue()) {
+      if (result->ComputeDegrees() < minimum_value) {
+        return CSSNumericLiteralValue::Create(
+            minimum_value, CSSPrimitiveValue::UnitType::kDegrees);
       }
-      if (result->GetDoubleValue() > maximum_value) {
-        return CSSNumericLiteralValue::Create(maximum_value,
-                                              result->TypeWithCalcResolved());
+      if (result->ComputeDegrees() > maximum_value) {
+        return CSSNumericLiteralValue::Create(
+            maximum_value, CSSPrimitiveValue::UnitType::kDegrees);
       }
       return result;
     }
diff --git a/third_party/blink/renderer/core/css/resolver/transform_builder.cc b/third_party/blink/renderer/core/css/resolver/transform_builder.cc
index 7fffb5b..d2bfb95 100644
--- a/third_party/blink/renderer/core/css/resolver/transform_builder.cc
+++ b/third_party/blink/renderer/core/css/resolver/transform_builder.cc
@@ -33,6 +33,7 @@
 #include "third_party/blink/renderer/core/css/css_function_value.h"
 #include "third_party/blink/renderer/core/css/css_math_expression_node.h"
 #include "third_party/blink/renderer/core/css/css_math_function_value.h"
+#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/matrix_3d_transform_operation.h"
@@ -109,14 +110,14 @@
 
     for (const CSSValue* item : *transform_value) {
       const auto& primitive_value = To<CSSPrimitiveValue>(*item);
-      if (primitive_value.IsCalculated() &&
-          To<CSSMathFunctionValue>(primitive_value).MayHaveRelativeUnit()) {
-        return true;
-      }
-
-      if (CSSPrimitiveValue::IsRelativeUnit(
-              primitive_value.TypeWithCalcResolved())) {
-        return true;
+      if (primitive_value.IsCalculated()) {
+        if (To<CSSMathFunctionValue>(primitive_value).MayHaveRelativeUnit())
+          return true;
+      } else {
+        CSSPrimitiveValue::UnitType unit_type =
+            To<CSSNumericLiteralValue>(primitive_value).GetType();
+        if (CSSPrimitiveValue::IsRelativeUnit(unit_type))
+          return true;
       }
     }
   }
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 8ac2664..2944b05 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -729,8 +729,16 @@
 
   // FinishingPrinting denotes that the non-printing layout state is being
   // restored.
-  enum PrintingState { kNotPrinting, kPrinting, kFinishingPrinting };
+  enum PrintingState {
+    kNotPrinting,
+    kBeforePrinting,
+    kPrinting,
+    kFinishingPrinting
+  };
   bool Printing() const { return printing_ == kPrinting; }
+  bool BeforePrintingOrPrinting() const {
+    return printing_ == kPrinting || printing_ == kBeforePrinting;
+  }
   bool FinishingOrIsPrinting() {
     return printing_ == kPrinting || printing_ == kFinishingPrinting;
   }
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index ce27f74..500e277 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1224,47 +1224,6 @@
   return static_cast<const T*>(node);
 }
 
-template <typename T>
-inline T* ToElementOrNull(Node& node) {
-  return IsElementOfType<const T>(node) ? static_cast<T*>(&node) : nullptr;
-}
-template <typename T>
-inline T* ToElementOrNull(Node* node) {
-  return (node && IsElementOfType<const T>(*node)) ? static_cast<T*>(node)
-                                                   : nullptr;
-}
-template <typename T>
-inline const T* ToElementOrNull(const Node& node) {
-  return IsElementOfType<const T>(node) ? static_cast<const T*>(&node)
-                                        : nullptr;
-}
-template <typename T>
-inline const T* ToElementOrNull(const Node* node) {
-  return (node && IsElementOfType<const T>(*node)) ? static_cast<const T*>(node)
-                                                   : nullptr;
-}
-
-template <typename T>
-inline T& ToElementOrDie(Node& node) {
-  CHECK(IsElementOfType<const T>(node));
-  return static_cast<T&>(node);
-}
-template <typename T>
-inline T* ToElementOrDie(Node* node) {
-  CHECK(!node || IsElementOfType<const T>(*node));
-  return static_cast<T*>(node);
-}
-template <typename T>
-inline const T& ToElementOrDie(const Node& node) {
-  CHECK(IsElementOfType<const T>(node));
-  return static_cast<const T&>(node);
-}
-template <typename T>
-inline const T* ToElementOrDie(const Node* node) {
-  CHECK(!node || IsElementOfType<const T>(*node));
-  return static_cast<const T*>(node);
-}
-
 inline bool IsDisabledFormControl(const Node* node) {
   auto* element = DynamicTo<Element>(node);
   return element && element->IsDisabledFormControl();
diff --git a/third_party/blink/renderer/core/editing/commands/apply_style_command.cc b/third_party/blink/renderer/core/editing/commands/apply_style_command.cc
index 68df523..d4d67a3 100644
--- a/third_party/blink/renderer/core/editing/commands/apply_style_command.cc
+++ b/third_party/blink/renderer/core/editing/commands/apply_style_command.cc
@@ -2050,7 +2050,7 @@
     return 0;
 
   // TODO(yosin): We should have printer for |CSSPrimitiveValue::UnitType|.
-  DCHECK(value->TypeWithCalcResolved() == CSSPrimitiveValue::UnitType::kPixels);
+  DCHECK(value->IsPx());
   return value->GetFloatValue();
 }
 
diff --git a/third_party/blink/renderer/core/editing/editing_style.cc b/third_party/blink/renderer/core/editing/editing_style.cc
index 6312c1f..f8d9d45 100644
--- a/third_party/blink/renderer/core/editing/editing_style.cc
+++ b/third_party/blink/renderer/core/editing/editing_style.cc
@@ -1877,25 +1877,32 @@
                                bool is_monospace_font,
                                LegacyFontSizeMode mode) {
   if (const auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value)) {
-    CSSPrimitiveValue::LengthUnitType length_type;
-    if (CSSPrimitiveValue::UnitTypeToLengthUnitType(
-            primitive_value->TypeWithCalcResolved(), length_type) &&
-        length_type == CSSPrimitiveValue::kUnitTypePixels) {
-      double conversion =
-          CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
-              primitive_value->TypeWithCalcResolved());
-      int pixel_font_size =
-          clampTo<int>(primitive_value->GetDoubleValue() * conversion);
-      int legacy_font_size = FontSizeFunctions::LegacyFontSize(
-          document, pixel_font_size, is_monospace_font);
-      // Use legacy font size only if pixel value matches exactly to that of
-      // legacy font size.
-      if (mode == kAlwaysUseLegacyFontSize ||
-          FontSizeFunctions::FontSizeForKeyword(
-              document, legacy_font_size, is_monospace_font) == pixel_font_size)
-        return legacy_font_size;
+    if (primitive_value->IsLength()) {
+      // TODO(crbug.com/979895): This doesn't seem to be handle math functions
+      // correctly. This is the result of a refactoring, and may have revealed
+      // an existing bug. Fix it if necessary.
+      CSSPrimitiveValue::UnitType length_unit =
+          primitive_value->IsNumericLiteralValue()
+              ? To<CSSNumericLiteralValue>(primitive_value)->GetType()
+              : CSSPrimitiveValue::UnitType::kPixels;
+      if (!CSSPrimitiveValue::IsRelativeUnit(length_unit)) {
+        double conversion =
+            CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
+                length_unit);
+        int pixel_font_size =
+            clampTo<int>(primitive_value->GetDoubleValue() * conversion);
+        int legacy_font_size = FontSizeFunctions::LegacyFontSize(
+            document, pixel_font_size, is_monospace_font);
+        // Use legacy font size only if pixel value matches exactly to that of
+        // legacy font size.
+        if (mode == kAlwaysUseLegacyFontSize ||
+            FontSizeFunctions::FontSizeForKeyword(document, legacy_font_size,
+                                                  is_monospace_font) ==
+                pixel_font_size)
+          return legacy_font_size;
 
-      return 0;
+        return 0;
+      }
     }
   }
 
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.cc b/third_party/blink/renderer/core/exported/web_settings_impl.cc
index ed21f4e..6b068d1 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.cc
@@ -377,6 +377,18 @@
   settings_->SetTextTrackTextSize(size);
 }
 
+void WebSettingsImpl::SetTextTrackWindowColor(const WebString& color) {
+  settings_->SetTextTrackWindowColor(color);
+}
+
+void WebSettingsImpl::SetTextTrackWindowPadding(const WebString& padding) {
+  settings_->SetTextTrackWindowPadding(padding);
+}
+
+void WebSettingsImpl::SetTextTrackWindowRadius(const WebString& radius) {
+  settings_->SetTextTrackWindowRadius(radius);
+}
+
 void WebSettingsImpl::SetDNSPrefetchingEnabled(bool enabled) {
   settings_->SetDNSPrefetchingEnabled(enabled);
 }
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.h b/third_party/blink/renderer/core/exported/web_settings_impl.h
index 60113db0..91457bfa 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.h
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.h
@@ -175,6 +175,9 @@
   void SetTextTrackTextColor(const WebString&) override;
   void SetTextTrackTextShadow(const WebString&) override;
   void SetTextTrackTextSize(const WebString&) override;
+  void SetTextTrackWindowColor(const WebString&) override;
+  void SetTextTrackWindowPadding(const WebString&) override;
+  void SetTextTrackWindowRadius(const WebString&) override;
   void SetThreadedScrollingEnabled(bool) override;
   void SetTouchDragDropEnabled(bool) override;
   void SetBarrelButtonForDragEnabled(bool) override;
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.cc b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
index 03ae36bed..37a92dbf 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
@@ -164,8 +164,7 @@
 }
 
 void RootFrameViewport::UpdateScrollAnimator() {
-  GetScrollAnimator().SetCurrentOffset(
-      ToFloatSize(ScrollOffsetFromScrollAnimators()));
+  GetScrollAnimator().SetCurrentOffset(ScrollOffsetFromScrollAnimators());
 }
 
 ScrollOffset RootFrameViewport::ScrollOffsetFromScrollAnimators() const {
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index c92364f..196cdaf4 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -771,6 +771,18 @@
       name: "textTrackTextSize",
       type: "String",
     },
+    {
+      name: "textTrackWindowColor",
+      type: "String",
+    },
+    {
+      name: "textTrackWindowPadding",
+      type: "String",
+    },
+    {
+      name: "textTrackWindowRadius",
+      type: "String",
+    },
 
     // Margin for title-safe placement of cues with overscan, gives top and bottom margin size as
     // percentage of video element height (for horizontal text) into which cues will not be placed.
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index d953864..6addbfc 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -1432,6 +1432,7 @@
   is_in_printing_ = true;
 #endif
 
+  GetFrame()->GetDocument()->SetPrinting(Document::kBeforePrinting);
   DispatchPrintEventRecursively(event_type_names::kBeforeprint);
 }
 
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index fc9270fd..1cbf13b 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -773,6 +773,27 @@
   context_->PaintRenderingResultsToCanvas(kFrontBuffer);
   if (HasResourceProvider()) {
     if (!context.ContextDisabled()) {
+      // For 2D Canvas, there are two ways of render Canvas for printing:
+      // display list or image snapshot. Display list allows better PDF printing
+      // and we prefer this method.
+      // Here are the requirements for display list to be used:
+      //    1. We must have had a full repaint of the Canvas after beginprint
+      //       event has been fired. Otherwise, we don't have a PaintRecord.
+      //    2. CSS property 'image-rendering' must not be 'pixelated'.
+
+      // display list rendering: we replay the last full PaintRecord, if Canvas
+      // has been redraw since beginprint happened.
+      if (IsPrinting() && !Is3d() && canvas2d_bridge_) {
+        canvas2d_bridge_->FlushRecording();
+        if (canvas2d_bridge_->getLastRecord()) {
+          const ComputedStyle* style = GetComputedStyle();
+          if (style && style->ImageRendering() != EImageRendering::kPixelated) {
+            context.Canvas()->drawPicture(canvas2d_bridge_->getLastRecord());
+            return;
+          }
+        }
+      }
+      // or image snapshot rendering: grab a snapshot and raster it.
       SkBlendMode composite_operator =
           !context_ || context_->CreationAttributes().alpha
               ? SkBlendMode::kSrcOver
@@ -801,6 +822,10 @@
     context_->MarkLayerComposited();
 }
 
+bool HTMLCanvasElement::IsPrinting() const {
+  return GetDocument().BeforePrintingOrPrinting();
+}
+
 void HTMLCanvasElement::SetSurfaceSize(const IntSize& size) {
   size_ = size;
   did_fail_to_create_resource_provider_ = false;
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
index 375f60a..675071b 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
@@ -223,6 +223,7 @@
   bool LowLatencyEnabled() const override { return !!frame_dispatcher_; }
   CanvasResourceProvider* GetOrCreateCanvasResourceProvider(
       AccelerationHint hint) override;
+  bool IsPrinting() const override;
 
   void DisableAcceleration(std::unique_ptr<Canvas2DLayerBridge>
                                unaccelerated_bridge_used_for_testing = nullptr);
diff --git a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
index 890a17a..251a4be 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
+++ b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
@@ -548,8 +548,8 @@
 }
 
 inline Element* DateTimeEditElement::FieldsWrapperElement() const {
-  DCHECK(firstChild());
-  return ToElementOrDie(firstChild());
+  CHECK(!firstChild() || IsA<Element>(firstChild()));
+  return To<Element>(firstChild());
 }
 
 void DateTimeEditElement::AddField(DateTimeFieldElement* field) {
diff --git a/third_party/blink/renderer/core/html/forms/file_input_type.cc b/third_party/blink/renderer/core/html/forms/file_input_type.cc
index b1095de..50e65c4 100644
--- a/third_party/blink/renderer/core/html/forms/file_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/file_input_type.cc
@@ -323,16 +323,20 @@
 
 void FileInputType::DisabledAttributeChanged() {
   DCHECK(IsShadowHost(GetElement()));
+  CHECK(!GetElement().UserAgentShadowRoot()->firstChild() ||
+        IsA<Element>(GetElement().UserAgentShadowRoot()->firstChild()));
   if (Element* button =
-          ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild()))
+          To<Element>(GetElement().UserAgentShadowRoot()->firstChild()))
     button->SetBooleanAttribute(kDisabledAttr,
                                 GetElement().IsDisabledFormControl());
 }
 
 void FileInputType::MultipleAttributeChanged() {
   DCHECK(IsShadowHost(GetElement()));
+  CHECK(!GetElement().UserAgentShadowRoot()->firstChild() ||
+        IsA<Element>(GetElement().UserAgentShadowRoot()->firstChild()));
   if (Element* button =
-          ToElementOrDie(GetElement().UserAgentShadowRoot()->firstChild()))
+          To<Element>(GetElement().UserAgentShadowRoot()->firstChild()))
     button->setAttribute(
         kValueAttr,
         AtomicString(GetLocale().QueryString(
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
index 72e2abe..c5d86ab0 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -336,6 +336,9 @@
 }
 
 bool HTMLFormControlElement::IsKeyboardFocusable() const {
+  if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled())
+    return HTMLElement::IsKeyboardFocusable();
+
   // Skip tabIndex check in a parent class.
   return IsFocusable();
 }
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index 7f0fba4..a06d37c 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -757,32 +757,7 @@
     GetImageLoader().UpdateFromElement(behavior, referrer_policy_);
   }
 
-  ImageResourceContent* image_content = GetImageLoader().GetContent();
-  // Images such as data: uri's can return immediately and may already have
-  // errored out.
-  bool image_has_loaded = image_content && !image_content->IsLoading() &&
-                          !image_content->ErrorOccurred();
-  bool image_still_loading =
-      !image_has_loaded && GetImageLoader().HasPendingActivity() &&
-      !GetImageLoader().HasPendingError() && !ImageSourceURL().IsEmpty();
-  bool image_has_image = image_content && image_content->HasImage();
-  bool image_is_document = GetImageLoader().IsLoadingImageDocument() &&
-                           image_content && !image_content->ErrorOccurred();
-
-  // Icky special case for deferred images:
-  // A deferred image is not loading, does have pending activity, does not
-  // have an error, but it does have an ImageResourceContent associated
-  // with it, so imageHasLoaded will be true even though the image hasn't
-  // actually loaded. Fixing the definition of imageHasLoaded isn't
-  // sufficient, because a deferred image does have pending activity, does not
-  // have a pending error, and does have a source URL, so if imageHasLoaded
-  // was correct, imageStillLoading would become wrong.
-  //
-  // Instead of dealing with that, there's a separate check that the
-  // ImageResourceContent has non-null image data associated with it, which
-  // isn't folded into imageHasLoaded above.
-  if ((image_has_loaded && image_has_image) || image_still_loading ||
-      image_is_document)
+  if (GetImageLoader().ImageIsPotentiallyAvailable())
     EnsurePrimaryContent();
   else
     EnsureCollapsedOrFallbackContent();
diff --git a/third_party/blink/renderer/core/html/track/text_track_container.cc b/third_party/blink/renderer/core/html/track/text_track_container.cc
index 872db89..5fe6743 100644
--- a/third_party/blink/renderer/core/html/track/text_track_container.cc
+++ b/third_party/blink/renderer/core/html/track/text_track_container.cc
@@ -66,21 +66,42 @@
 
 }  // namespace
 
-TextTrackContainer::TextTrackContainer(Document& document)
-    : HTMLDivElement(document), default_font_size_(0) {}
-
 TextTrackContainer::TextTrackContainer(HTMLMediaElement& media_element)
-    : TextTrackContainer(media_element.GetDocument()) {
+    : HTMLDivElement(media_element.GetDocument()),
+      media_element_(&media_element),
+      default_font_size_(0) {
   SetShadowPseudoId(AtomicString("-webkit-media-text-track-container"));
-  if (IsHTMLVideoElement(media_element))
-    ObserveSizeChanges(media_element);
+  if (IsHTMLVideoElement(*media_element_))
+    ObserveSizeChanges(*media_element_);
 }
 
 void TextTrackContainer::Trace(Visitor* visitor) {
+  visitor->Trace(media_element_);
   visitor->Trace(video_size_observer_);
   HTMLDivElement::Trace(visitor);
 }
 
+Node::InsertionNotificationRequest TextTrackContainer::InsertedInto(
+    ContainerNode& root) {
+  if (!video_size_observer_ && media_element_->isConnected() &&
+      IsHTMLVideoElement(*media_element_)) {
+    ObserveSizeChanges(*media_element_);
+  }
+
+  return HTMLDivElement::InsertedInto(root);
+}
+
+void TextTrackContainer::RemovedFrom(ContainerNode& insertion_point) {
+  DCHECK(!media_element_->isConnected());
+
+  HTMLDivElement::RemovedFrom(insertion_point);
+
+  if (video_size_observer_) {
+    video_size_observer_->disconnect();
+    video_size_observer_.Clear();
+  }
+}
+
 LayoutObject* TextTrackContainer::CreateLayoutObject(const ComputedStyle&,
                                                      LegacyLayout) {
   // TODO(mstensho): Should use LayoutObjectFactory to create the right type of
diff --git a/third_party/blink/renderer/core/html/track/text_track_container.h b/third_party/blink/renderer/core/html/track/text_track_container.h
index 5735a05..190f2474 100644
--- a/third_party/blink/renderer/core/html/track/text_track_container.h
+++ b/third_party/blink/renderer/core/html/track/text_track_container.h
@@ -39,9 +39,12 @@
 
 class TextTrackContainer final : public HTMLDivElement {
  public:
-  TextTrackContainer(Document&);
   TextTrackContainer(HTMLMediaElement&);
 
+  // Node override.
+  Node::InsertionNotificationRequest InsertedInto(ContainerNode&) override;
+  void RemovedFrom(ContainerNode&) override;
+
   // Runs the "rules for updating the text track rendering". The
   // ExposingControls enum is used in the WebVTT processing model to reset the
   // layout when the media controls become visible, to avoid overlapping them.
@@ -60,6 +63,7 @@
 
   LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
 
+  Member<HTMLMediaElement> media_element_;
   Member<ResizeObserver> video_size_observer_;
   float default_font_size_;
 };
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index f3140d13..04897fe 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -1129,6 +1129,13 @@
   SetInlineStylePropertyIfNotEmpty(*cue_background_box_,
                                    CSSPropertyID::kFontSize,
                                    settings->GetTextTrackTextSize());
+  SetInlineStylePropertyIfNotEmpty(*display_tree_,
+                                   CSSPropertyID::kBackgroundColor,
+                                   settings->GetTextTrackWindowColor());
+  SetInlineStylePropertyIfNotEmpty(*display_tree_, CSSPropertyID::kPadding,
+                                   settings->GetTextTrackWindowPadding());
+  SetInlineStylePropertyIfNotEmpty(*display_tree_, CSSPropertyID::kBorderRadius,
+                                   settings->GetTextTrackWindowRadius());
 }
 
 ExecutionContext* VTTCue::GetExecutionContext() const {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 4a482c1..6d2cb10 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3583,14 +3583,12 @@
   if (RuntimeEnabledFeatures::ElementTimingEnabled(&GetDocument())) {
     LocalDOMWindow* window = GetDocument().domWindow();
     if (window) {
-      ImageElementTiming::From(*window).NotifyBackgroundImageRemoved(this,
-                                                                     image);
+      ImageElementTiming::From(*window).NotifyImageRemoved(this, image);
     }
   }
   if (RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled()) {
     if (LocalFrameView* frame_view = GetFrameView()) {
-      frame_view->GetPaintTimingDetector().NotifyBackgroundImageRemoved(*this,
-                                                                        image);
+      frame_view->GetPaintTimingDetector().NotifyImageRemoved(*this, image);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/layout/layout_vtt_cue.cc b/third_party/blink/renderer/core/layout/layout_vtt_cue.cc
index 722895a6a..b94fee62 100644
--- a/third_party/blink/renderer/core/layout/layout_vtt_cue.cc
+++ b/third_party/blink/renderer/core/layout/layout_vtt_cue.cc
@@ -137,7 +137,7 @@
 }
 
 IntRect CueBoundingBox(const LayoutBox& cue_box) {
-  return ContentBoxRelativeToAncestor(cue_box, *cue_box.ContainingBlock());
+  return PaddingBoxRelativeToAncestor(cue_box, *cue_box.ContainingBlock());
 }
 
 bool SnapToLinesLayouter::IsOutside(const IntRect& title_area) const {
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index ebfacde5..2f53751 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -384,6 +384,32 @@
   return !HasPendingActivity() || referrer_policy != last_referrer_policy_;
 }
 
+bool ImageLoader::ImageIsPotentiallyAvailable() const {
+  bool image_has_loaded = image_content_ && !image_content_->IsLoading() &&
+                          !image_content_->ErrorOccurred();
+  bool image_still_loading = !image_has_loaded && HasPendingActivity() &&
+                             !HasPendingError() &&
+                             !element_->ImageSourceURL().IsEmpty();
+  bool image_has_image = image_content_ && image_content_->HasImage();
+  bool image_is_document = loading_image_document_ && image_content_ &&
+                           !image_content_->ErrorOccurred();
+
+  // Icky special case for deferred images:
+  // A deferred image is not loading, does have pending activity, does not
+  // have an error, but it does have an ImageResourceContent associated
+  // with it, so |image_has_loaded| will be true even though the image hasn't
+  // actually loaded. Fixing the definition of |image_has_loaded| isn't
+  // sufficient, because a deferred image does have pending activity, does not
+  // have a pending error, and does have a source URL, so if |image_has_loaded|
+  // was correct, |image_still_loading| would become wrong.
+  //
+  // Instead of dealing with that, there's a separate check that the
+  // ImageResourceContent has non-null image data associated with it, which
+  // isn't folded into |image_has_loaded| above.
+  return (image_has_loaded && image_has_image) || image_still_loading ||
+         image_is_document;
+}
+
 void ImageLoader::ClearImage() {
   SetImageWithoutConsideringPendingLoadEvent(nullptr);
 }
@@ -667,6 +693,13 @@
       new_image_content == old_image_content) {
     ToLayoutImage(element_->GetLayoutObject())->IntrinsicSizeChanged();
   } else {
+    // Loading didn't start (loading of images was disabled). We show fallback
+    // contents here, while we don't dispatch an 'error' event etc., because
+    // spec-wise the image remains in the "Unavailable" state.
+    if (new_image_content &&
+        new_image_content->GetContentStatus() == ResourceStatus::kNotStarted)
+      NoImageResourceToLoad();
+
     if (pending_load_event_.IsActive())
       pending_load_event_.Cancel();
 
diff --git a/third_party/blink/renderer/core/loader/image_loader.h b/third_party/blink/renderer/core/loader/image_loader.h
index 30aee841..ffea35f 100644
--- a/third_party/blink/renderer/core/loader/image_loader.h
+++ b/third_party/blink/renderer/core/loader/image_loader.h
@@ -95,6 +95,11 @@
       network::mojom::ReferrerPolicy referrer_policy =
           network::mojom::ReferrerPolicy::kDefault) const;
 
+  // Returns true if a the owner of this loader should consider the image being
+  // loaded as "potentially available", i.e that it may eventually become
+  // available.
+  bool ImageIsPotentiallyAvailable() const;
+
   // Cancels pending load events, and doesn't dispatch new ones.
   // Note: ClearImage/SetImage.*() are not a simple setter.
   // Check the implementation to see what they do.
@@ -113,7 +118,6 @@
   // Otherwise:
   //   Normal loading via ResourceFetcher/ResourceLoader.
   //   |image_resource_for_image_document_| is null.
-  bool IsLoadingImageDocument() { return loading_image_document_; }
   void SetLoadingImageDocument() { loading_image_document_ = true; }
   ImageResource* ImageResourceForImageDocument() const {
     return image_resource_for_image_document_;
diff --git a/third_party/blink/renderer/core/page/print_context_test.cc b/third_party/blink/renderer/core/page/print_context_test.cc
index 6487b77f..b14acfddf 100644
--- a/third_party/blink/renderer/core/page/print_context_test.cc
+++ b/third_party/blink/renderer/core/page/print_context_test.cc
@@ -6,8 +6,10 @@
 
 #include <memory>
 
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/events/before_print_event.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
@@ -24,6 +26,8 @@
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 
+using testing::_;
+
 namespace blink {
 
 const int kPageWidth = 800;
@@ -60,6 +64,22 @@
     return recorded_operations_;
   }
 
+  MOCK_METHOD2(onDrawRect, void(const SkRect&, const SkPaint&));
+  MOCK_METHOD1(DrawPicture, void(const SkPicture*));
+  MOCK_METHOD1(OnDrawPicture, void(const SkPicture*));
+  MOCK_METHOD3(OnDrawPicture,
+               void(const SkPicture*, const SkMatrix*, const SkPaint*));
+  MOCK_METHOD3(DrawPicture,
+               void(const SkPicture*, const SkMatrix*, const SkPaint*));
+  MOCK_METHOD4(onDrawImage,
+               void(const SkImage*, SkScalar, SkScalar, const SkPaint*));
+  MOCK_METHOD5(onDrawImageRect,
+               void(const SkImage*,
+                    const SkRect*,
+                    const SkRect&,
+                    const SkPaint*,
+                    SrcRectConstraint));
+
  private:
   Vector<Operation> recorded_operations_;
 };
@@ -84,8 +104,11 @@
     GetDocument().body()->SetInnerHTMLFromString(body_content);
   }
 
-  void PrintSinglePage(MockPageContextCanvas& canvas) {
+  void PrintSinglePage(SkCanvas& canvas) {
     IntRect page_rect(0, 0, kPageWidth, kPageHeight);
+    GetDocument().SetPrinting(Document::kBeforePrinting);
+    Event* event = MakeGarbageCollected<BeforePrintEvent>();
+    GetPrintContext().GetFrame()->DomWindow()->DispatchEvent(*event);
     GetPrintContext().BeginPrintMode(page_rect.Width(), page_rect.Height());
     UpdateAllLifecyclePhasesForTest();
     PaintRecordBuilder builder;
@@ -396,6 +419,46 @@
   EXPECT_EQ(node->OffsetWidth(), 800);
 }
 
+TEST_P(PrintContextTest, Canvas2DBeforePrint) {
+  MockPageContextCanvas canvas;
+  SetBodyInnerHTML("<canvas id='c' width=100 height=100></canvas>");
+  GetDocument().GetSettings()->SetScriptEnabled(true);
+  Element* const script_element =
+      GetDocument().CreateRawElement(html_names::kScriptTag);
+  script_element->setTextContent(
+      "window.addEventListener('beforeprint', (ev) => {"
+      "const ctx = document.getElementById('c').getContext('2d');"
+      "ctx.fillRect(0, 0, 10, 10);"
+      "ctx.fillRect(50, 50, 10, 10);"
+      "});");
+  GetDocument().body()->AppendChild(script_element);
+
+  EXPECT_CALL(canvas, onDrawRect(_, _)).Times(testing::AtLeast(2));
+
+  PrintSinglePage(canvas);
+}
+
+TEST_P(PrintContextTest, Canvas2DPixelated) {
+  MockPageContextCanvas canvas;
+  SetBodyInnerHTML(
+      "<canvas id='c' style='image-rendering: pixelated' "
+      "width=100 height=100></canvas>");
+  GetDocument().GetSettings()->SetScriptEnabled(true);
+  Element* const script_element =
+      GetDocument().CreateRawElement(html_names::kScriptTag);
+  script_element->setTextContent(
+      "window.addEventListener('beforeprint', (ev) => {"
+      "const ctx = document.getElementById('c').getContext('2d');"
+      "ctx.fillRect(0, 0, 10, 10);"
+      "ctx.fillRect(50, 50, 10, 10);"
+      "});");
+  GetDocument().body()->AppendChild(script_element);
+
+  EXPECT_CALL(canvas, onDrawImageRect(_, _, _, _, _));
+
+  PrintSinglePage(canvas);
+}
+
 // This tests that we don't resize or re-layout subframes in printed content.
 // TODO(weili): This test fails when the iframe isn't the root scroller - e.g.
 // Adding ScopedImplicitRootScrollerForTest disabler(false);
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.cc b/third_party/blink/renderer/core/paint/image_element_timing.cc
index abac00e2..d580430 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/image_element_timing.cc
@@ -226,9 +226,8 @@
   images_notified_.erase(image);
 }
 
-void ImageElementTiming::NotifyBackgroundImageRemoved(
-    const LayoutObject* layout_object,
-    const ImageResourceContent* image) {
+void ImageElementTiming::NotifyImageRemoved(const LayoutObject* layout_object,
+                                            const ImageResourceContent* image) {
   background_images_notified_.erase(std::make_pair(layout_object, image));
 }
 
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.h b/third_party/blink/renderer/core/paint/image_element_timing.h
index 82cc5bb..04de9035 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.h
+++ b/third_party/blink/renderer/core/paint/image_element_timing.h
@@ -56,8 +56,8 @@
   // Called when the LayoutImage will be destroyed.
   void NotifyWillBeDestroyed(const LayoutObject*);
 
-  void NotifyBackgroundImageRemoved(const LayoutObject*,
-                                    const ImageResourceContent* image);
+  void NotifyImageRemoved(const LayoutObject*,
+                          const ImageResourceContent* image);
 
   void Trace(blink::Visitor*) override;
 
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
index de8180f..0b563ed 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
@@ -63,8 +63,8 @@
   DCHECK(b);
   if (a->first_size != b->first_size)
     return a->first_size > b->first_size;
-  // This make sure that two different nodes with the same |first_size| wouldn't
-  // be merged in the set.
+  // This make sure that two different |ImageRecord|s with the same |first_size|
+  // wouldn't be merged in the |size_ordered_set_|.
   return a->insertion_index < b->insertion_index;
 }
 
@@ -166,15 +166,15 @@
   records_manager_.RemoveInvisibleRecordIfNeeded(node_id);
 }
 
-void ImagePaintTimingDetector::NotifyBackgroundImageRemoved(
+void ImagePaintTimingDetector::NotifyImageRemoved(
     DOMNodeId node_id,
     const ImageResourceContent* cached_image) {
   if (!is_recording_)
     return;
-  BackgroundImageId background_image_id = std::make_pair(node_id, cached_image);
-  if (!records_manager_.IsRecordedVisibleNode(background_image_id))
+  RecordId record_id = std::make_pair(node_id, cached_image);
+  if (!records_manager_.IsRecordedVisibleImage(record_id))
     return;
-  records_manager_.RemoveVisibleRecord(background_image_id);
+  records_manager_.RemoveVisibleRecord(record_id);
   need_update_timing_at_frame_end_ = true;
 }
 
@@ -207,14 +207,14 @@
     return;
   // The callback is safe from race-condition only when running on main-thread.
   DCHECK(ThreadState::Current()->IsMainThread());
-  records_manager_.AssignPaintTimeToRegisteredQueuedNodes(
+  records_manager_.AssignPaintTimeToRegisteredQueuedRecords(
       timestamp, last_queued_frame_index);
   UpdateCandidate();
   num_pending_swap_callbacks_--;
   DCHECK_GE(num_pending_swap_callbacks_, 0);
 }
 
-void ImageRecordsManager::AssignPaintTimeToRegisteredQueuedNodes(
+void ImageRecordsManager::AssignPaintTimeToRegisteredQueuedRecords(
     const base::TimeTicks& timestamp,
     unsigned last_queued_frame_index) {
   // TODO(crbug.com/971419): should guarantee the queue not empty.
@@ -231,7 +231,7 @@
   }
 }
 
-void ImagePaintTimingDetector::RecordBackgroundImage(
+void ImagePaintTimingDetector::RecordImage(
     const LayoutObject& object,
     const IntSize& intrinsic_size,
     const ImageResourceContent& cached_image,
@@ -241,22 +241,21 @@
     return;
   DOMNodeId node_id = DOMNodeIds::IdForNode(node);
   DCHECK_NE(node_id, kInvalidDOMNodeId);
-  if (records_manager_.IsRecordedInvisibleNode(node_id))
+  if (records_manager_.IsRecordedInvisibleImage(node_id))
     return;
 
-  BackgroundImageId background_image_id =
-      std::make_pair(node_id, &cached_image);
-  bool is_recored_visible_node =
-      records_manager_.IsRecordedVisibleNode(background_image_id);
-  if (is_recored_visible_node &&
-      !records_manager_.WasVisibleNodeLoaded(background_image_id) &&
+  RecordId record_id = std::make_pair(node_id, &cached_image);
+  bool is_recored_visible_image =
+      records_manager_.IsRecordedVisibleImage(record_id);
+  if (is_recored_visible_image &&
+      !records_manager_.IsVisibleImageLoaded(record_id) &&
       cached_image.IsLoaded()) {
-    records_manager_.OnImageLoaded(background_image_id, frame_index_);
+    records_manager_.OnImageLoaded(record_id, frame_index_);
     need_update_timing_at_frame_end_ = true;
     return;
   }
 
-  if (is_recored_visible_node || !is_recording_)
+  if (is_recored_visible_image || !is_recording_)
     return;
   IntRect visual_rect = object.FragmentsVisualRectBoundingBox();
   // Before the image resource starts loading, <img> has no size info. We wait
@@ -272,14 +271,11 @@
       rect_size, intrinsic_size.Area(), visual_rect.Size().Area());
 
   if (rect_size == 0) {
-    // Each invisible background image is tracked by its node id. In other
-    // words, when a node is deemed as invisible, all of the background images
-    // are deemed as invisible.
-    records_manager_.RecordInvisibleNode(node_id);
+    records_manager_.RecordInvisible(node_id);
   } else {
-    records_manager_.RecordVisibleNode(background_image_id, rect_size);
+    records_manager_.RecordVisible(record_id, rect_size);
     if (cached_image.IsLoaded()) {
-      records_manager_.OnImageLoaded(background_image_id, frame_index_);
+      records_manager_.OnImageLoaded(record_id, frame_index_);
       need_update_timing_at_frame_end_ = true;
     }
   }
@@ -288,10 +284,9 @@
 ImageRecordsManager::ImageRecordsManager()
     : size_ordered_set_(&LargeImageFirst) {}
 
-void ImageRecordsManager::OnImageLoaded(
-    const BackgroundImageId& background_image_id,
-    unsigned current_frame_index) {
-  base::WeakPtr<ImageRecord> record = FindVisibleRecord(background_image_id);
+void ImageRecordsManager::OnImageLoaded(const RecordId& record_id,
+                                        unsigned current_frame_index) {
+  base::WeakPtr<ImageRecord> record = FindVisibleRecord(record_id);
   OnImageLoadedInternal(record, current_frame_index);
 }
 
@@ -302,13 +297,12 @@
   QueueToMeasurePaintTime(record, current_frame_index);
 }
 
-void ImageRecordsManager::RecordVisibleNode(
-    const BackgroundImageId& background_image_id,
-    const uint64_t& visual_size) {
-  std::unique_ptr<ImageRecord> record = CreateImageRecord(
-      background_image_id.first, background_image_id.second, visual_size);
+void ImageRecordsManager::RecordVisible(const RecordId& record_id,
+                                        const uint64_t& visual_size) {
+  std::unique_ptr<ImageRecord> record =
+      CreateImageRecord(record_id.first, record_id.second, visual_size);
   size_ordered_set_.insert(record->AsWeakPtr());
-  visible_background_image_map_.insert(background_image_id, std::move(record));
+  visible_images_.insert(record_id, std::move(record));
 }
 
 std::unique_ptr<ImageRecord> ImageRecordsManager::CreateImageRecord(
@@ -322,7 +316,7 @@
 }
 
 ImageRecord* ImageRecordsManager::FindLargestPaintCandidate() const {
-  DCHECK_EQ(visible_background_image_map_.size(), size_ordered_set_.size());
+  DCHECK_EQ(visible_images_.size(), size_ordered_set_.size());
   if (size_ordered_set_.size() == 0)
     return nullptr;
   return size_ordered_set_.begin()->get();
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector.h b/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
index 526d38b3..ebf4cc8 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
@@ -52,7 +52,7 @@
   bool loaded = false;
 };
 
-typedef std::pair<DOMNodeId, const ImageResourceContent*> BackgroundImageId;
+typedef std::pair<DOMNodeId, const ImageResourceContent*> RecordId;
 
 // |ImageRecordsManager| is the manager of all of the images that Largest Image
 // Paint cares about. Note that an image does not necessarily correspond to a
@@ -71,43 +71,36 @@
   ImageRecordsManager();
   ImageRecord* FindLargestPaintCandidate() const;
 
-  bool AreAllVisibleNodesDetached() const;
-
   inline void RemoveInvisibleRecordIfNeeded(
       const DOMNodeId& invisible_node_id) {
-    invisible_node_ids_.erase(invisible_node_id);
+    invisible_images_.erase(invisible_node_id);
   }
-  inline void RemoveVisibleRecord(
-      const BackgroundImageId& background_image_id) {
+
+  inline void RemoveVisibleRecord(const RecordId& record_id) {
     base::WeakPtr<ImageRecord> record =
-        visible_background_image_map_.find(background_image_id)
-            ->value->AsWeakPtr();
+        visible_images_.find(record_id)->value->AsWeakPtr();
     size_ordered_set_.erase(record);
-    visible_background_image_map_.erase(background_image_id);
+    visible_images_.erase(record_id);
     // Leave out |images_queued_for_paint_time_| intentionally because the null
-    // record will be removed in |AssignPaintTimeToRegisteredQueuedNodes|.
+    // record will be removed in |AssignPaintTimeToRegisteredQueuedRecords|.
   }
 
-  inline void RecordInvisibleNode(const DOMNodeId& node_id) {
-    invisible_node_ids_.insert(node_id);
+  inline void RecordInvisible(const DOMNodeId& node_id) {
+    invisible_images_.insert(node_id);
   }
-  void RecordVisibleNode(const BackgroundImageId& background_image_id,
-                         const uint64_t& visual_size);
-  size_t CountInvisibleNodes() const { return invisible_node_ids_.size(); }
-  bool IsRecordedVisibleNode(
-      const BackgroundImageId& background_image_id) const {
-    return visible_background_image_map_.Contains(background_image_id);
+  void RecordVisible(const RecordId& record_id, const uint64_t& visual_size);
+  bool IsRecordedVisibleImage(const RecordId& record_id) const {
+    return visible_images_.Contains(record_id);
   }
-  bool IsRecordedInvisibleNode(const DOMNodeId& node_id) const {
-    return invisible_node_ids_.Contains(node_id);
+  bool IsRecordedInvisibleImage(const DOMNodeId& node_id) const {
+    return invisible_images_.Contains(node_id);
   }
 
-  inline bool WasVisibleNodeLoaded(
-      const BackgroundImageId& background_image_id) const {
-    DCHECK(visible_background_image_map_.Contains(background_image_id));
-    return visible_background_image_map_.at(background_image_id)->loaded;
+  inline bool IsVisibleImageLoaded(const RecordId& record_id) const {
+    DCHECK(visible_images_.Contains(record_id));
+    return visible_images_.at(record_id)->loaded;
   }
-  void OnImageLoaded(const BackgroundImageId&, unsigned current_frame_index);
+  void OnImageLoaded(const RecordId&, unsigned current_frame_index);
   void OnImageLoadedInternal(base::WeakPtr<ImageRecord>&,
                              unsigned current_frame_index);
 
@@ -128,8 +121,9 @@
     DCHECK(last_registered_frame_index <= LastQueuedFrameIndex());
     return last_registered_frame_index < LastQueuedFrameIndex();
   }
-  void AssignPaintTimeToRegisteredQueuedNodes(const base::TimeTicks&,
-                                              unsigned last_queued_frame_index);
+  void AssignPaintTimeToRegisteredQueuedRecords(
+      const base::TimeTicks&,
+      unsigned last_queued_frame_index);
   inline unsigned LastQueuedFrameIndex() const {
     DCHECK(images_queued_for_paint_time_.back());
     return images_queued_for_paint_time_.back()->frame_index;
@@ -138,10 +132,9 @@
  private:
   // Find the image record of an visible image.
   inline base::WeakPtr<ImageRecord> FindVisibleRecord(
-      const BackgroundImageId& background_image_id) const {
-    DCHECK(visible_background_image_map_.Contains(background_image_id));
-    return visible_background_image_map_.find(background_image_id)
-        ->value->AsWeakPtr();
+      const RecordId& record_id) const {
+    DCHECK(visible_images_.Contains(record_id));
+    return visible_images_.find(record_id)->value->AsWeakPtr();
   }
   std::unique_ptr<ImageRecord> CreateImageRecord(
       const DOMNodeId&,
@@ -156,9 +149,8 @@
     record->loaded = true;
   }
 
-  HashMap<BackgroundImageId, std::unique_ptr<ImageRecord>>
-      visible_background_image_map_;
-  HashSet<DOMNodeId> invisible_node_ids_;
+  HashMap<RecordId, std::unique_ptr<ImageRecord>> visible_images_;
+  HashSet<DOMNodeId> invisible_images_;
 
   // This stores the image records, which are ordered by size.
   ImageRecordSet size_ordered_set_;
@@ -206,7 +198,7 @@
       const PropertyTreeState& current_paint_chunk_properties);
   void OnPaintFinished();
   void LayoutObjectWillBeDestroyed(const LayoutObject&);
-  void NotifyBackgroundImageRemoved(DOMNodeId, const ImageResourceContent*);
+  void NotifyImageRemoved(DOMNodeId, const ImageResourceContent*);
   // After the method being called, the detector stops to record new entries and
   // node removal. But it still observe the loading status. In other words, if
   // an image is recorded before stopping recording, and finish loading after
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
index 440f1ff5..3d93238 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
@@ -99,22 +99,22 @@
   size_t CountVisibleImageRecords() {
     return GetPaintTimingDetector()
         .GetImagePaintTimingDetector()
-        ->records_manager_.visible_background_image_map_.size();
+        ->records_manager_.visible_images_.size();
   }
 
   size_t CountInvisibleRecords() {
     return GetPaintTimingDetector()
         .GetImagePaintTimingDetector()
-        ->records_manager_.invisible_node_ids_.size();
+        ->records_manager_.invisible_images_.size();
   }
 
   size_t ContainerTotalSize() {
     return GetPaintTimingDetector()
                .GetImagePaintTimingDetector()
-               ->records_manager_.invisible_node_ids_.size() +
+               ->records_manager_.invisible_images_.size() +
            GetPaintTimingDetector()
                .GetImagePaintTimingDetector()
-               ->records_manager_.visible_background_image_map_.size() +
+               ->records_manager_.visible_images_.size() +
            GetPaintTimingDetector()
                .GetImagePaintTimingDetector()
                ->records_manager_.size_ordered_set_.size() +
@@ -126,7 +126,7 @@
   size_t CountChildFrameRecords() {
     return GetChildPaintTimingDetector()
         .GetImagePaintTimingDetector()
-        ->records_manager_.visible_background_image_map_.size();
+        ->records_manager_.visible_images_.size();
   }
 
   size_t CountRankingSetRecords() {
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 5395153..9bad6af 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -2471,6 +2471,8 @@
   float zoom = GetLayoutObject().StyleRef().EffectiveZoom();
   if (zoom != 1)
     reference_box.Scale(1 / zoom);
+  if (!ResourceInfo() || ResourceInfo()->FilterReferenceBox() != reference_box)
+    GetLayoutObject().SetNeedsPaintPropertyUpdate();
   EnsureResourceInfo().SetFilterReferenceBox(reference_box);
 }
 
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 97384366..9862b2b 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
@@ -1002,13 +1002,6 @@
             state.backdrop_filter_bounds =
                 properties_->Effect()->BackdropFilterBounds();
           }
-          // With BGPT disabled, UpdateFilterReferenceBox gets called from
-          // CompositedLayerMapping::UpdateGraphicsLayerGeometry, but only
-          // for composited layers.
-          if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
-              layer->GetCompositingState() != kPaintsIntoOwnBacking) {
-            layer->UpdateFilterReferenceBox();
-          }
           layer->UpdateCompositorFilterOperationsForBackdropFilter(
               state.backdrop_filter, &state.backdrop_filter_bounds);
           layer->ClearBackdropFilterOnEffectNodeDirty();
@@ -2290,6 +2283,17 @@
 }
 
 void FragmentPaintPropertyTreeBuilder::SetNeedsPaintPropertyUpdateIfNeeded() {
+  if (object_.HasLayer()) {
+    PaintLayer* layer = ToLayoutBoxModelObject(object_).Layer();
+    // With BGPT disabled, UpdateFilterReferenceBox gets called from
+    // CompositedLayerMapping::UpdateGraphicsLayerGeometry, but only
+    // for composited layers.
+    if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+        layer->GetCompositingState() != kPaintsIntoOwnBacking) {
+      layer->UpdateFilterReferenceBox();
+    }
+  }
+
   if (!object_.IsBox())
     return;
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index 11b515d0..bc9bb00 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -1725,4 +1725,25 @@
   EXPECT_FALSE(PaintPropertiesForElement("fixed"));
 }
 
+TEST_P(PaintPropertyTreeUpdateTest, InlineFilterReferenceBoxChange) {
+  SetBodyInnerHTML(R"HTML(
+    <div id="spacer" style="display: inline-block; height: 20px"></div>
+    <br>
+    <span id="span" style="filter: blur(1px); font-size: 20px">SPAN</span>
+  )HTML");
+
+  const auto* properties = PaintPropertiesForElement("span");
+  ASSERT_TRUE(properties);
+  ASSERT_TRUE(properties->Filter());
+  EXPECT_EQ(FloatPoint(0, 20),
+            properties->Filter()->Filter().ReferenceBox().Location());
+
+  GetDocument().getElementById("spacer")->setAttribute(
+      html_names::kStyleAttr, "display: inline-block; height: 100px");
+  UpdateAllLifecyclePhasesForTest();
+  ASSERT_EQ(properties, PaintPropertiesForElement("span"));
+  EXPECT_EQ(FloatPoint(0, 100),
+            properties->Filter()->Filter().ReferenceBox().Location());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_timing_detector.cc b/third_party/blink/renderer/core/paint/paint_timing_detector.cc
index d462f117..530f5ad 100644
--- a/third_party/blink/renderer/core/paint/paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/paint_timing_detector.cc
@@ -94,7 +94,7 @@
     return;
   if (!IsBackgroundImageContentful(*object, *image))
     return;
-  detector.GetImagePaintTimingDetector()->RecordBackgroundImage(
+  detector.GetImagePaintTimingDetector()->RecordImage(
       *object, image->Size(), *cached_image, current_paint_chunk_properties);
 }
 
@@ -112,7 +112,7 @@
   PaintTimingDetector& detector = frame_view->GetPaintTimingDetector();
   if (!detector.GetImagePaintTimingDetector())
     return;
-  detector.GetImagePaintTimingDetector()->RecordBackgroundImage(
+  detector.GetImagePaintTimingDetector()->RecordImage(
       object, intrinsic_size, *cached_image, current_paint_chunk_properties);
 }
 
@@ -125,15 +125,14 @@
     image_paint_timing_detector_->LayoutObjectWillBeDestroyed(object);
 }
 
-void PaintTimingDetector::NotifyBackgroundImageRemoved(
+void PaintTimingDetector::NotifyImageRemoved(
     const LayoutObject& object,
     const ImageResourceContent* cached_image) {
   DOMNodeId node_id = DOMNodeIds::ExistingIdForNode(object.GetNode());
   if (node_id == kInvalidDOMNodeId)
     return;
   if (image_paint_timing_detector_) {
-    image_paint_timing_detector_->NotifyBackgroundImageRemoved(node_id,
-                                                               cached_image);
+    image_paint_timing_detector_->NotifyImageRemoved(node_id, cached_image);
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/paint_timing_detector.h b/third_party/blink/renderer/core/paint/paint_timing_detector.h
index 19639a3..ee061d7 100644
--- a/third_party/blink/renderer/core/paint/paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/paint_timing_detector.h
@@ -50,8 +50,7 @@
   inline static void NotifyTextPaint(const IntRect& text_visual_rect);
 
   void LayoutObjectWillBeDestroyed(const LayoutObject&);
-  void NotifyBackgroundImageRemoved(const LayoutObject&,
-                                    const ImageResourceContent*);
+  void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
   void NotifyPaintFinished();
   void NotifyInputEvent(WebInputEvent::Type);
   bool NeedToNotifyInputOrScroll() const;
diff --git a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
index 281dabf..207e32b8 100644
--- a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
+++ b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
@@ -81,6 +81,22 @@
       null;
   }
 
+  set action(val) {
+    const previousAction = this.action;
+    if (val !== null) {
+      if (!isElement(val)) {
+        throw new TypeError('Invalid argument: must be type Element');
+      }
+
+      val.setAttribute('slot', 'action');
+      this.insertBefore(val, previousAction);
+    }
+
+    if (previousAction !== null) {
+      previousAction.remove();
+    }
+  }
+
   show({duration = DEFAULT_DURATION} = {}) {
     this.setAttribute('open', '');
     clearTimeout(this.#timeoutID);
@@ -124,7 +140,9 @@
   const toast = new StdToastElement(message);
 
   const {action, ...showOptions} = options;
-  if (action !== undefined) {
+  if (isElement(action)) {
+    toast.action = action;
+  } else if (action !== undefined) {
     const actionButton = document.createElement('button');
 
     // Unlike String(), this performs the desired JavaScript ToString operation.
@@ -140,3 +158,14 @@
 
   return toast;
 }
+
+const idGetter =
+  Object.getOwnPropertyDescriptor(Element.prototype, 'id').get;
+function isElement(value) {
+  try {
+    idGetter.call(value);
+    return true;
+  } catch {
+    return false;
+  }
+}
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
index 3f6fd62..f41918bc 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
@@ -413,6 +413,7 @@
     return;
 
   SMILTime earliest_fire_time = UpdateAnimations(elapsed, seek_to_time);
+  ApplyAnimations(elapsed);
   if (!CanScheduleFrame(earliest_fire_time))
     return;
   double delay_time = earliest_fire_time.Value() - elapsed;
@@ -429,7 +430,8 @@
   // scheduledAnimations during this critical section. Similarly, any elements
   // removed will unschedule themselves, so this will catch modification of
   // animationsToApply.
-  prevent_scheduled_animations_changes_ = true;
+  base::AutoReset<bool> no_scheduled_animations_change_scope(
+      &prevent_scheduled_animations_changes_, true);
 #endif
 
   if (document_order_indexes_dirty_)
@@ -445,8 +447,7 @@
     attribute_map.RemoveAll(invalid_keys);
   }
 
-  HeapVector<ScheduledVector> active_sandwiches;
-  active_sandwiches.ReserveInitialCapacity(scheduled_animations_.size());
+  active_sandwiches_.ReserveCapacity(scheduled_animations_.size());
   for (auto& attribute_entry : scheduled_animations_) {
     AttributeMap& attribute_map = attribute_entry.value;
     for (auto& entry : attribute_map) {
@@ -478,20 +479,12 @@
       }
 
       if (!sandwich.IsEmpty()) {
-        active_sandwiches.push_back(sandwich);
+        active_sandwiches_.push_back(sandwich);
       }
     }
   }
 
-  if (active_sandwiches.IsEmpty()) {
-#if DCHECK_IS_ON()
-    prevent_scheduled_animations_changes_ = false;
-#endif
-    return earliest_fire_time;
-  }
-
-  ScheduledVector animations_to_apply;
-  for (auto& sandwich : active_sandwiches) {
+  for (auto& sandwich : active_sandwiches_) {
     if (seek_to_time) {
       for (auto& animation : sandwich) {
         animation->TriggerPendingEvents(elapsed);
@@ -525,6 +518,19 @@
       if (next_fire_time.IsFinite())
         earliest_fire_time = std::min(next_fire_time, earliest_fire_time);
     }
+  }
+  return earliest_fire_time;
+}
+
+void SMILTimeContainer::ApplyAnimations(double elapsed) {
+#if DCHECK_IS_ON()
+  prevent_scheduled_animations_changes_ = true;
+#endif
+  ScheduledVector animations_to_apply;
+  for (auto& sandwich : active_sandwiches_) {
+    if (sandwich.IsEmpty()) {
+      continue;
+    }
 
     // Results are accumulated to the first animation that animates and
     // contributes to a particular element/attribute pair.
@@ -553,12 +559,13 @@
 
     animations_to_apply.push_back(result_element);
   }
+  active_sandwiches_.Shrink(0);
 
   if (animations_to_apply.IsEmpty()) {
 #if DCHECK_IS_ON()
     prevent_scheduled_animations_changes_ = false;
 #endif
-    return earliest_fire_time;
+    return;
   }
 
   UseCounter::Count(&GetDocument(), WebFeature::kSVGSMILAnimationAppliedEffect);
@@ -588,7 +595,7 @@
       }
     }
   }
-  return earliest_fire_time;
+  return;
 }
 
 void SMILTimeContainer::AdvanceFrameForTesting() {
@@ -599,6 +606,7 @@
 void SMILTimeContainer::Trace(blink::Visitor* visitor) {
   visitor->Trace(scheduled_animations_);
   visitor->Trace(owner_svg_element_);
+  visitor->Trace(active_sandwiches_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.h b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
index 24a9ca84..ea02d8b 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.h
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
@@ -112,6 +112,7 @@
   void UpdateAnimationsAndScheduleFrameIfNeeded(double elapsed,
                                                 bool seek_to_time = false);
   SMILTime UpdateAnimations(double elapsed, bool seek_to_time);
+  void ApplyAnimations(double elapsed);
   void ServiceOnNextFrame();
   void ScheduleWakeUp(double delay_time, FrameSchedulingState);
   bool HasPendingSynchronization() const;
@@ -137,6 +138,7 @@
   TaskRunnerTimer<SMILTimeContainer> animation_policy_once_timer_;
 
   AnimationsMap scheduled_animations_;
+  HeapVector<ScheduledVector> active_sandwiches_;
 
   Member<SVGSVGElement> owner_svg_element_;
 
diff --git a/third_party/blink/renderer/core/svg/svg_animated_length.cc b/third_party/blink/renderer/core/svg/svg_animated_length.cc
index 6179957..72f7c26 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_length.cc
+++ b/third_party/blink/renderer/core/svg/svg_animated_length.cc
@@ -39,9 +39,13 @@
       SVGAnimatedProperty<SVGLength>::AttributeChanged(value);
 
   if (SVGLength::NegativeValuesForbiddenForAnimatedLengthAttribute(
-          AttributeName()) &&
-      BaseValue()->ValueInSpecifiedUnits() < 0)
-    parse_status = SVGParseStatus::kNegativeValue;
+          AttributeName())) {
+    // TODO(crbug.com/982425): Pass |kValueRangeNonNegative| to property parser
+    // to handle range checking on math functions correctly, and also to avoid
+    // this ad hoc range checking.
+    if (BaseValue()->IsNegativeNumericLiteral())
+      parse_status = SVGParseStatus::kNegativeValue;
+  }
 
   return parse_status;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_length.cc b/third_party/blink/renderer/core/svg/svg_length.cc
index 9bd69733..4b28bdbc 100644
--- a/third_party/blink/renderer/core/svg/svg_length.cc
+++ b/third_party/blink/renderer/core/svg/svg_length.cc
@@ -360,4 +360,10 @@
   value_ = CreateInitialCSSValue(static_cast<Initial>(initial_value));
 }
 
+bool SVGLength::IsNegativeNumericLiteral() const {
+  if (!value_->IsNumericLiteralValue())
+    return false;
+  return value_->GetDoubleValue() < 0;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_length.h b/third_party/blink/renderer/core/svg/svg_length.h
index f63aa8c..719d474 100644
--- a/third_party/blink/renderer/core/svg/svg_length.h
+++ b/third_party/blink/renderer/core/svg/svg_length.h
@@ -114,6 +114,7 @@
   bool IsFontRelative() const { return value_->IsFontRelativeLength(); }
   bool IsCalculated() const { return value_->IsCalculated(); }
 
+  bool IsNegativeNumericLiteral() const;
   bool IsZero() const { return value_->GetFloatValue() == 0; }
 
   static SVGLengthMode LengthModeForAnimatedLengthAttribute(
diff --git a/third_party/blink/renderer/core/timing/performance_mark.cc b/third_party/blink/renderer/core/timing/performance_mark.cc
index 1464e99..8e52412 100644
--- a/third_party/blink/renderer/core/timing/performance_mark.cc
+++ b/third_party/blink/renderer/core/timing/performance_mark.cc
@@ -11,6 +11,8 @@
 #include "third_party/blink/renderer/core/timing/performance.h"
 #include "third_party/blink/renderer/core/timing/performance_mark_options.h"
 #include "third_party/blink/renderer/core/timing/performance_user_timing.h"
+#include "third_party/blink/renderer/core/timing/worker_global_scope_performance.h"
+#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
 
 namespace blink {
 
@@ -49,15 +51,23 @@
                                          const AtomicString& mark_name,
                                          PerformanceMarkOptions* mark_options,
                                          ExceptionState& exception_state) {
-  LocalDOMWindow* window = LocalDOMWindow::From(script_state);
-  if (!window)
-    return nullptr;
-  Performance* performance = DOMWindowPerformance::performance(*window);
-  if (!performance)
-    return nullptr;
+  Performance* performance = nullptr;
+  if (LocalDOMWindow* window = LocalDOMWindow::From(script_state)) {
+    performance = DOMWindowPerformance::performance(*window);
+    DCHECK(performance);
+  } else if (auto* scope = DynamicTo<WorkerGlobalScope>(
+                 ExecutionContext::From(script_state))) {
+    performance = WorkerGlobalScopePerformance::performance(*scope);
+    DCHECK(performance);
+  }
 
-  return performance->GetUserTiming().CreatePerformanceMark(
-      script_state, mark_name, mark_options, exception_state);
+  if (performance) {
+    return performance->GetUserTiming().CreatePerformanceMark(
+        script_state, mark_name, mark_options, exception_state);
+  }
+  exception_state.ThrowTypeError(
+      "PerformanceMark: no 'worker' or 'window' in current context.");
+  return nullptr;
 }
 
 AtomicString PerformanceMark::entryType() const {
diff --git a/third_party/blink/renderer/devtools/front_end/audits/lighthouse/report-generator.js b/third_party/blink/renderer/devtools/front_end/audits/lighthouse/report-generator.js
index 01257be..cec8f79 100644
--- a/third_party/blink/renderer/devtools/front_end/audits/lighthouse/report-generator.js
+++ b/third_party/blink/renderer/devtools/front_end/audits/lighthouse/report-generator.js
@@ -18,10 +18,18 @@
 const cachedResources = Runtime.cachedResources;
 
 module.exports = {
-  REPORT_CSS: cachedResources['audits/lighthouse/report.css'],
-  REPORT_JAVASCRIPT: cachedResources['audits/lighthouse/report.js'],
-  REPORT_TEMPLATE: cachedResources['audits/lighthouse/template.html'],
-  REPORT_TEMPLATES: cachedResources['audits/lighthouse/templates.html'],
+  get REPORT_CSS() {
+    return cachedResources['audits/lighthouse/report.css'];
+  },
+  get REPORT_JAVASCRIPT() {
+    return cachedResources['audits/lighthouse/report.js'];
+  },
+  get REPORT_TEMPLATE() {
+    return cachedResources['audits/lighthouse/template.html'];
+  },
+  get REPORT_TEMPLATES() {
+    return cachedResources['audits/lighthouse/templates.html'];
+  },
 };
 
 },{}],1:[function(require,module,exports){
diff --git a/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js b/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
index a534245..f0e40db6 100644
--- a/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
+++ b/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
@@ -105,7 +105,7 @@
    */
   _renderFileSystem(fileSystem) {
     const fileSystemPath = fileSystem.path();
-    const lastIndexOfSlash = fileSystemPath.lastIndexOf(Host.isWin() ? '\\' : '/');
+    const lastIndexOfSlash = fileSystemPath.lastIndexOf('/');
     const folderName = fileSystemPath.substr(lastIndexOfSlash + 1);
 
     const element = createElementWithClass('div', 'file-system-container');
diff --git a/third_party/blink/renderer/devtools/scripts/check_localizable_resources.js b/third_party/blink/renderer/devtools/scripts/check_localizable_resources.js
index 525bc05..bec1895 100644
--- a/third_party/blink/renderer/devtools/scripts/check_localizable_resources.js
+++ b/third_party/blink/renderer/devtools/scripts/check_localizable_resources.js
@@ -44,8 +44,9 @@
 
 async function getErrors(frontendStrings, IDSkeys) {
   const toAddError = await checkLocalizedStrings.getAndReportResourcesToAdd(frontendStrings, IDSkeys);
+  const toModifyError = checkLocalizedStrings.getAndReportIDSKeysToModify();
   const toRemoveError = checkLocalizedStrings.getAndReportResourcesToRemove(frontendStrings, IDSkeys);
-  let error = `${toAddError ? toAddError : ''}${toRemoveError ? toRemoveError : ''}`;
+  let error = `${toAddError || ''}${toModifyError || ''}${toRemoveError || ''}`;
 
   if (error === '') {
     console.log('DevTools localizable resources checker passed.');
@@ -61,9 +62,10 @@
   const keysToAddToGRD = checkLocalizedStrings.getDifference(IDSkeys, frontendStrings);
   const keysToRemoveFromGRD = checkLocalizedStrings.getDifference(frontendStrings, IDSkeys);
   const resourceAdded = await addResourcesToGRDP(keysToAddToGRD, keysToRemoveFromGRD);
+  const resourceModified = await modifyResourcesInGRDP();
   const resourceRemoved = await removeResourcesFromGRDP(keysToRemoveFromGRD);
 
-  if (!resourceAdded && !resourceRemoved) {
+  if (!resourceAdded && !resourceRemoved && !resourceModified) {
     console.log('DevTools localizable resources checker passed.');
     return;
   }
@@ -78,27 +80,30 @@
 
 // Return true if any resources are added
 async function addResourcesToGRDP(keysToAddToGRD, keysToRemoveFromGRD) {
+  function mapGRDPFilePathToStrings(keysToAddToGRD, keysToRemoveFromGRD) {
+    const grdpFilePathToStrings = new Map();
+    // Get the grdp files that need to be modified
+    for (const [key, stringObj] of keysToAddToGRD) {
+      if (!grdpFilePathToStrings.has(stringObj.grdpPath))
+        grdpFilePathToStrings.set(stringObj.grdpPath, []);
+
+      // Add the IDS key to stringObj so we have access to it later
+      stringObj.ids = key;
+      // If the same key is to be removed, this is likely a string copy
+      // to another folder. Keep the description.
+      if (keysToRemoveFromGRD.has(key))
+        stringObj.description = keysToRemoveFromGRD.get(key).description;
+      grdpFilePathToStrings.get(stringObj.grdpPath).push(stringObj);
+    }
+    return grdpFilePathToStrings;
+  }
+
   if (keysToAddToGRD.size === 0)
     return false;
 
   // Map grdp file path to strings to be added to that file so that we only need to
   // modify every grdp file once
-  const grdpFilePathToStrings = new Map();
-
-  // Get the grdp files that need to be modified
-  for (const [key, stringObj] of keysToAddToGRD) {
-    if (!grdpFilePathToStrings.has(stringObj.grdpPath))
-      grdpFilePathToStrings.set(stringObj.grdpPath, []);
-
-    // Add the IDS key to stringObj so we have access to it later
-    stringObj.ids = key;
-    // If the same key is to be removed, this is likely a string copy
-    // to another folder. Keep the description.
-    if (keysToRemoveFromGRD.has(key))
-      stringObj.description = keysToRemoveFromGRD.get(key).description;
-    grdpFilePathToStrings.get(stringObj.grdpPath).push(stringObj);
-  }
-
+  const grdpFilePathToStrings = mapGRDPFilePathToStrings(keysToAddToGRD, keysToRemoveFromGRD);
   const promises = [];
 
   const grdpFilePathsToAdd = [];
@@ -188,28 +193,44 @@
   return writeFileAsync(localizationUtils.GRD_PATH, newGrdFileContent);
 }
 
+// Return true if any resources are updated
+async function modifyResourcesInGRDP() {
+  const messagesToModify = checkLocalizedStrings.getIDSKeysToModify();
+  if (messagesToModify.size === 0)
+    return false;
+
+  const grdpToMessages = mapGRDPFilePathToMessages(messagesToModify);
+  const promises = [];
+  for (const [grdpPath, messages] of grdpToMessages) {
+    let fileContent = await localizationUtils.parseFileContent(grdpPath);
+    for (const message of messages) {
+      const idsRegex = new RegExp(`name="${message.actualIDSKey}"`);
+      fileContent = fileContent.replace(idsRegex, `name="${message.ids}"`);
+    }
+    promises.push(writeFileAsync(grdpPath, fileContent));
+  }
+  await Promise.all(promises);
+  return true;
+}
+
 // Return true if any resources are removed
 async function removeResourcesFromGRDP(keysToRemoveFromGRD) {
+  function lineContainsIDS(line, messages) {
+    return messages.some(message => line.includes(message.ids));
+  }
+
   if (keysToRemoveFromGRD.size === 0)
     return false;
 
-  // Map grdp file path to message IDS keys to remove
-  const grdpFilePathToKeys = new Map();
-  for (const [key, messageObj] of keysToRemoveFromGRD) {
-    if (!grdpFilePathToKeys.has(messageObj.grdpPath))
-      grdpFilePathToKeys.set(messageObj.grdpPath, []);
-
-    grdpFilePathToKeys.get(messageObj.grdpPath).push(key);
-  }
-
+  const grdpToMessages = mapGRDPFilePathToMessages(keysToRemoveFromGRD);
   const promises = [];
-  for (const [grdpFilePath, listOfKeys] of grdpFilePathToKeys) {
+  for (const [grdpFilePath, messages] of grdpToMessages) {
     let newGrdpFileContent = '';
     const grdpFileContent = await localizationUtils.parseFileContent(grdpFilePath);
     const grdpFileLines = grdpFileContent.split('\n');
 
     for (let i = 0; i < grdpFileLines.length; i++) {
-      if (!lineContainsIDS(grdpFileLines[i], listOfKeys)) {
+      if (!lineContainsIDS(grdpFileLines[i], messages)) {
         newGrdpFileContent += grdpFileLines[i];
         if (i < grdpFileLines.length - 1)
           newGrdpFileContent += '\n';
@@ -226,6 +247,17 @@
   return true;
 }
 
-function lineContainsIDS(line, listOfIDS) {
-  return listOfIDS.some(ids => line.includes(ids));
+// Given a map from IDS key to a list of messages, return a map
+// from grdp file path to a list of messages with a new property
+// `ids` set to the key.
+function mapGRDPFilePathToMessages(keyToMessages) {
+  const grdpFilePathToMessages = new Map();
+  for (const [ids, message] of keyToMessages) {
+    if (!grdpFilePathToMessages.has(message.grdpPath))
+      grdpFilePathToMessages.set(message.grdpPath, []);
+
+    message.ids = ids;
+    grdpFilePathToMessages.get(message.grdpPath).push(message);
+  }
+  return grdpFilePathToMessages;
 }
diff --git a/third_party/blink/renderer/devtools/scripts/localization_utils/check_localized_strings.js b/third_party/blink/renderer/devtools/scripts/localization_utils/check_localized_strings.js
index 0a912dc9..32e1dde3 100644
--- a/third_party/blink/renderer/devtools/scripts/localization_utils/check_localized_strings.js
+++ b/third_party/blink/renderer/devtools/scripts/localization_utils/check_localized_strings.js
@@ -42,6 +42,7 @@
 // Format
 // {
 //   IDS_KEY => {
+//     actualIDSKey: string,  // the IDS key in the message tag
 //     description: string,
 //     filepath: string,
 //     location: {
@@ -297,31 +298,33 @@
   }
 
   // Example:
-  //  <message name="IDS_*" desc="Description of this message">
+  //  <message name="IDS_DEVTOOLS_md5_hash" desc="Description of this message">
   //      Message text here with optional placeholders <ph name="phname">$1s</ph>
   //  </message>
   // match[0]: the entire '<message>...</message>' block.
-  // match[1]: 'Description of this message'
-  // match[2]: '     Message text here with optional placeholders <ph name="phname">$1s</ph>\n  '
-  const messageRegex = new RegExp('<message[^>]*desc="([^"]*)"[^>]*>\s*\n(.*?)<\/message>', 'gms');
+  // match[1]: 'IDS_DEVTOOLS_md5_hash'
+  // match[2]: 'Description of this message'
+  // match[3]: '     Message text here with optional placeholders <ph name="phname">$1s</ph>\n  '
+  const messageRegex = new RegExp('<message[^>]*name="([^"]*)"[^>]*desc="([^"]*)"[^>]*>\s*\n(.*?)<\/message>', 'gms');
   let match;
   while ((match = messageRegex.exec(fileContent)) !== null) {
     const line = lineNumberOfIndex(fileContent, match.index);
-    const description = match[1];
-    let message = match[2];
+    const actualIDSKey = match[1];
+    const description = match[2];
+    let message = match[3];
     message = convertToFrontendPlaceholders(message.trim());
     message = stripWhitespacePadding(message);
     message = localizationUtils.sanitizeStringIntoFrontendFormat(message);
 
     const ids = localizationUtils.getIDSKey(message);
-    IDSkeys.set(ids, {grdpPath: filePath, location: {start: {line}, end: {line}}, description});
+    IDSkeys.set(ids, {actualIDSKey, grdpPath: filePath, location: {start: {line}, end: {line}}, description});
   }
 }
 
 /**
  * The following functions compare frontend localizable strings
- * with grdp <message>s and report error of resources to add or
- * remove.
+ * with grdp <message>s and report error of resources to add,
+ * remove or modify.
  */
 async function getAndReportResourcesToAdd(frontendStrings, IDSkeys) {
   const keysToAddToGRD = getDifference(IDSkeys, frontendStrings);
@@ -363,6 +366,22 @@
   return errorStr;
 }
 
+function getAndReportIDSKeysToModify() {
+  const messagesToModify = getIDSKeysToModify();
+  if (messagesToModify.size === 0)
+    return;
+
+  let errorStr = '\nThe following GRD/GRDP message(s) do not have the correct IDS key.\n';
+  errorStr += 'Please update the key(s) by changing the "name" value.\n\n';
+
+  for (const [expectedIDSKey, message] of messagesToModify) {
+    errorStr += `${localizationUtils.getRelativeFilePathFromSrc(message.grdpPath)}${
+        localizationUtils.getLocationMessage(message.location)}:\n`;
+    errorStr += `${message.actualIDSKey} --> ${expectedIDSKey}\n\n`;
+  }
+  return errorStr;
+}
+
 /**
  * Output a Map containing sorted entries that are in @comparison but not @reference,
  * or entries that are in both but belong to different grdp files.
@@ -376,11 +395,22 @@
   return new Map(difference.sort());
 }
 
+function getIDSKeysToModify() {
+  const messagesToModify = new Map();
+  for (const [expectedIDSKey, message] of IDSkeys) {
+    if (expectedIDSKey !== message.actualIDSKey)
+      messagesToModify.set(expectedIDSKey, message);
+  }
+  return messagesToModify;
+}
+
 module.exports = {
   frontendStrings,
   IDSkeys,
   parseLocalizableResourceMaps,
+  getAndReportIDSKeysToModify,
   getAndReportResourcesToAdd,
   getAndReportResourcesToRemove,
-  getDifference
+  getDifference,
+  getIDSKeysToModify
 };
diff --git a/third_party/blink/renderer/modules/accessibility/ax_selection.cc b/third_party/blink/renderer/modules/accessibility/ax_selection.cc
index bdf32836..827dfd3 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_selection.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_selection.cc
@@ -120,15 +120,6 @@
 AXSelection AXSelection::FromCurrentSelection(
     const Document& document,
     const AXSelectionBehavior selection_behavior) {
-  // Previously, retrieving the selection would cause the layout to become
-  // clean, because we were using a deprecated function for retrieving the
-  // selection from the DOM tree,
-  // FrameSelection::ComputeVisibleSelectionInDOMTreeDeprecated. The layout
-  // should not be dirty in the first place, but somehow it is. While we are
-  // investigating the reasons behind this, the workaround is to restore the
-  // previous behavior by forcing the layout to  clean.
-  // TODO(nektar): Remove the following line at the earliest opportunity.
-  const_cast<Document&>(document).UpdateStyleAndLayout();
   const LocalFrame* frame = document.GetFrame();
   if (!frame)
     return {};
diff --git a/third_party/blink/renderer/modules/credentialmanager/authenticator_selection_criteria.idl b/third_party/blink/renderer/modules/credentialmanager/authenticator_selection_criteria.idl
index 7efb40a0..59f6982 100644
--- a/third_party/blink/renderer/modules/credentialmanager/authenticator_selection_criteria.idl
+++ b/third_party/blink/renderer/modules/credentialmanager/authenticator_selection_criteria.idl
@@ -22,5 +22,5 @@
 dictionary AuthenticatorSelectionCriteria {
   AuthenticatorAttachment authenticatorAttachment;
   boolean requireResidentKey = false;
-  UserVerificationRequirement userVerification = "preferred";
+  UserVerificationRequirement userVerification;
 };
diff --git a/third_party/blink/renderer/modules/credentialmanager/credential_manager_type_converters.cc b/third_party/blink/renderer/modules/credentialmanager/credential_manager_type_converters.cc
index 530c4421..4a5fac5 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credential_manager_type_converters.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/credential_manager_type_converters.cc
@@ -293,8 +293,11 @@
   mojo_criteria->authenticator_attachment =
       ConvertTo<AuthenticatorAttachment>(criteria->authenticatorAttachment());
   mojo_criteria->require_resident_key = criteria->requireResidentKey();
-  mojo_criteria->user_verification =
-      ConvertTo<UserVerificationRequirement>(criteria->userVerification());
+  mojo_criteria->user_verification = UserVerificationRequirement::PREFERRED;
+  if (criteria->hasUserVerification()) {
+    mojo_criteria->user_verification =
+        ConvertTo<UserVerificationRequirement>(criteria->userVerification());
+  }
   return mojo_criteria;
 }
 
@@ -553,8 +556,11 @@
     }
   }
 
-  mojo_options->user_verification =
-      ConvertTo<UserVerificationRequirement>(options->userVerification());
+  mojo_options->user_verification = UserVerificationRequirement::PREFERRED;
+  if (options->hasUserVerification()) {
+    mojo_options->user_verification =
+        ConvertTo<UserVerificationRequirement>(options->userVerification());
+  }
 
   if (options->hasExtensions()) {
     auto* extensions = options->extensions();
diff --git a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
index e676d55..4b5b647 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
@@ -16,6 +16,7 @@
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/frame.h"
+#include "third_party/blink/renderer/core/frame/frame_console.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/page/frame_tree.h"
@@ -536,6 +537,18 @@
       }
     }
 
+    if (!options->publicKey()->hasUserVerification()) {
+      resolver->GetFrame()->Console().AddMessage(ConsoleMessage::Create(
+          mojom::ConsoleMessageSource::kJavaScript,
+          mojom::ConsoleMessageLevel::kWarning,
+          "publicKey.userVerification was not set to any value in Web "
+          "Authentication navigator.credentials.get() call. This defaults to "
+          "'preferred', which is probably not what you want. If in doubt, set "
+          "to 'discouraged'. See "
+          "https://chromium.googlesource.com/chromium/src/+/master/content/"
+          "browser/webauth/uv_preferred.md for details."));
+    }
+
     if (options->hasSignal()) {
       if (options->signal()->aborted()) {
         resolver->Reject(MakeGarbageCollected<DOMException>(
@@ -735,6 +748,21 @@
           WTF::Bind(&Abort, WTF::Passed(WrapPersistent(script_state))));
     }
 
+    if (options->publicKey()->hasAuthenticatorSelection() &&
+        !options->publicKey()
+             ->authenticatorSelection()
+             ->hasUserVerification()) {
+      resolver->GetFrame()->Console().AddMessage(ConsoleMessage::Create(
+          mojom::ConsoleMessageSource::kJavaScript,
+          mojom::ConsoleMessageLevel::kWarning,
+          "publicKey.authenticatorSelection.userVerification was not set to "
+          "any value in Web Authentication navigator.credentials.create() "
+          "call. This defaults to 'preferred', which is probably not what you "
+          "want. If in doubt, set to 'discouraged'. See "
+          "https://chromium.googlesource.com/chromium/src/+/master/content/"
+          "browser/webauth/uv_preferred.md for details"));
+    }
+
     auto mojo_options =
         MojoPublicKeyCredentialCreationOptions::From(options->publicKey());
     if (!mojo_options) {
diff --git a/third_party/blink/renderer/modules/credentialmanager/public_key_credential_request_options.idl b/third_party/blink/renderer/modules/credentialmanager/public_key_credential_request_options.idl
index d5fe5025..041dcb58 100644
--- a/third_party/blink/renderer/modules/credentialmanager/public_key_credential_request_options.idl
+++ b/third_party/blink/renderer/modules/credentialmanager/public_key_credential_request_options.idl
@@ -9,6 +9,6 @@
     unsigned long timeout;
     USVString rpId;
     sequence<PublicKeyCredentialDescriptor> allowCredentials = [];
-    UserVerificationRequirement userVerification = "preferred";
+    UserVerificationRequirement userVerification;
     AuthenticationExtensionsClientInputs extensions;
 };
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 0d5844e..2455817 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -127,6 +127,8 @@
 static bool IsLayoutClean(Document* document) {
   if (!document || !document->View())
     return false;
+  if (document->NeedsLayoutTreeUpdate())
+    return false;
   if (document->View()->NeedsLayout())
     return false;
   DocumentLifecycle::LifecycleState state = document->Lifecycle().GetState();
diff --git a/third_party/blink/renderer/modules/idle/OWNERS b/third_party/blink/renderer/modules/idle/OWNERS
index 5a67a327..bb1b52f 100644
--- a/third_party/blink/renderer/modules/idle/OWNERS
+++ b/third_party/blink/renderer/modules/idle/OWNERS
@@ -1 +1,3 @@
 file://content/browser/idle/OWNERS
+
+# TEAM: fugu-dev@chromium.org
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
index b9a11484..c0b2f16 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
@@ -50,16 +50,6 @@
   kMax = 3
 };
 
-// These values are used for histograms. Do not reorder.
-enum class LockResultMetrics {
-  kAlreadyLocked = 0,  // Frame already has a lock.
-  kPortrait = 1,       // Locked to portrait.
-  kLandscape = 2,      // Locked to landscape.
-
-  // Keep at the end.
-  kMax = 3
-};
-
 void RecordMetadataAvailability(MetadataAvailabilityMetrics metrics) {
   DEFINE_STATIC_LOCAL(
       EnumerationHistogram, metadata_histogram,
@@ -68,13 +58,6 @@
   metadata_histogram.Count(static_cast<int>(metrics));
 }
 
-void RecordLockResult(LockResultMetrics metrics) {
-  DEFINE_STATIC_LOCAL(EnumerationHistogram, lock_result_histogram,
-                      ("Media.Video.FullscreenOrientationLock.LockResult",
-                       static_cast<int>(LockResultMetrics::kMax)));
-  lock_result_histogram.Count(static_cast<int>(metrics));
-}
-
 void RecordAutoRotateEnabled(bool enabled) {
   DEFINE_STATIC_LOCAL(
       BooleanHistogram, auto_rotate_histogram,
@@ -144,21 +127,14 @@
 
   auto* controller =
       ScreenOrientationController::From(*GetDocument().GetFrame());
-  if (controller->MaybeHasActiveLock()) {
-    RecordLockResult(LockResultMetrics::kAlreadyLocked);
+  if (controller->MaybeHasActiveLock())
     return;
-  }
 
   locked_orientation_ = ComputeOrientationLock();
   DCHECK_NE(locked_orientation_, kWebScreenOrientationLockDefault);
   controller->lock(locked_orientation_,
                    std::make_unique<DummyScreenOrientationCallback>());
 
-  if (locked_orientation_ == kWebScreenOrientationLockLandscape)
-    RecordLockResult(LockResultMetrics::kLandscape);
-  else
-    RecordLockResult(LockResultMetrics::kPortrait);
-
   MaybeListenToDeviceOrientation();
 }
 
diff --git a/third_party/blink/renderer/modules/media_controls/resources/mediaControls.css b/third_party/blink/renderer/modules/media_controls/resources/mediaControls.css
index 01a5fbe..26cbd81 100644
--- a/third_party/blink/renderer/modules/media_controls/resources/mediaControls.css
+++ b/third_party/blink/renderer/modules/media_controls/resources/mediaControls.css
@@ -1226,7 +1226,6 @@
     transition: top 433ms linear;
 }
 
-
 video::-webkit-media-text-track-display {
     position: absolute;
     overflow: hidden;
diff --git a/third_party/blink/renderer/modules/sms/OWNERS b/third_party/blink/renderer/modules/sms/OWNERS
index ad9d6f7..39f063f 100644
--- a/third_party/blink/renderer/modules/sms/OWNERS
+++ b/third_party/blink/renderer/modules/sms/OWNERS
@@ -1 +1,4 @@
 file://content/browser/sms/OWNERS
+
+# COMPONENT: Blink>SMS
+# TEAM: fugu-dev@chromium.org
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
index 225e4d0..77c2ce3 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc
@@ -266,7 +266,7 @@
 void OfflineAudioDestinationHandler::NotifySuspend(size_t frame) {
   DCHECK(IsMainThread());
 
-  if (Context() && Context()->GetExecutionContext())
+  if (!IsExecutionContextDestroyed() && Context())
     Context()->ResolveSuspendOnMainThread(frame);
 }
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_buffer.cc b/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
index 2df498d..d49b4fe 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
@@ -41,9 +41,7 @@
   SetObject(buffer);
 }
 
-WebGLBuffer::~WebGLBuffer() {
-  RunDestructor();
-}
+WebGLBuffer::~WebGLBuffer() = default;
 
 void WebGLBuffer::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   gl->DeleteBuffers(1, &object_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
index 0746836d..d25b582 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
@@ -219,9 +219,7 @@
   ctx->ContextGL()->GenFramebuffers(1, &object_);
 }
 
-WebGLFramebuffer::~WebGLFramebuffer() {
-  RunDestructor();
-}
+WebGLFramebuffer::~WebGLFramebuffer() = default;
 
 void WebGLFramebuffer::SetAttachmentForBoundFramebuffer(GLenum target,
                                                         GLenum attachment,
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.cc b/third_party/blink/renderer/modules/webgl/webgl_object.cc
index b3db68f..094b25c 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.cc
@@ -76,10 +76,10 @@
   DeleteObject(nullptr);
 }
 
-void WebGLObject::RunDestructor() {
+void WebGLObject::Dispose() {
   DCHECK(!destruction_in_progress_);
-  // This boilerplate destructor is sufficient for all subclasses, as long
-  // as they implement deleteObjectImpl properly, and don't try to touch
+  // This boilerplate pre-finalizer is sufficient for all subclasses, as long
+  // as they implement DeleteObjectImpl properly, and don't try to touch
   // other objects on the Oilpan heap if the destructor's been entered.
   destruction_in_progress_ = true;
   DetachAndDeleteObject();
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.h b/third_party/blink/renderer/modules/webgl/webgl_object.h
index 1ac871c..a595ece 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.h
@@ -57,13 +57,19 @@
 }
 
 class WebGLObject : public ScriptWrappable {
+  // WebGLObjects are pre-finalized, and the WebGLRenderingContextBase
+  // is specifically not. This is done in order to allow WebGLObjects to
+  // refer back to their owning context in their destructor to delete their
+  // resources if they are GC'd before the context is.
+  USING_PRE_FINALIZER(WebGLObject, Dispose);
+
  public:
   // We can't call virtual functions like deleteObjectImpl in this class's
   // destructor; doing so results in a pure virtual function call. Further,
   // making this destructor non-virtual is complicated with respect to
   // Oilpan tracing. Therefore this destructor is declared virtual, but is
   // empty, and the code that would have gone into its body is called by
-  // subclasses via runDestructor().
+  // subclasses via Dispose().
   ~WebGLObject() override;
 
   // deleteObject may not always delete the OpenGL resource.  For programs and
@@ -84,12 +90,6 @@
                         const WebGLRenderingContextBase*) const = 0;
   virtual bool HasObject() const = 0;
 
-  // WebGLObjects are eagerly finalized, and the WebGLRenderingContextBase
-  // is specifically not. This is done in order to allow WebGLObjects to
-  // refer back to their owning context in their destructor to delete their
-  // resources if they are GC'd before the context is.
-  EAGERLY_FINALIZE();
-
  protected:
   explicit WebGLObject(WebGLRenderingContextBase*);
 
@@ -111,10 +111,9 @@
 
   virtual gpu::gles2::GLES2Interface* GetAGLInterface() const = 0;
 
-  // Used by leaf subclasses to run the destruction sequence -- what would
-  // be in the destructor of the base class, if it could be. Must be called
-  // no more than once.
-  void RunDestructor();
+  // Runs the pre-finalization sequence -- what would be in the destructor
+  // of the base class, if it could be. Must be called no more than once.
+  void Dispose();
 
   // Indicates to subclasses that the destructor is being run.
   bool DestructionInProgress() const;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_program.cc b/third_party/blink/renderer/modules/webgl/webgl_program.cc
index 026b3e6..48372fe0 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_program.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_program.cc
@@ -46,9 +46,7 @@
   SetObject(ctx->ContextGL()->CreateProgram());
 }
 
-WebGLProgram::~WebGLProgram() {
-  RunDestructor();
-}
+WebGLProgram::~WebGLProgram() = default;
 
 void WebGLProgram::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   gl->DeleteProgram(object_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_query.cc b/third_party/blink/renderer/modules/webgl/webgl_query.cc
index ed12cbe..6b533e6e 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_query.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_query.cc
@@ -33,9 +33,7 @@
   SetObject(query);
 }
 
-WebGLQuery::~WebGLQuery() {
-  RunDestructor();
-}
+WebGLQuery::~WebGLQuery() = default;
 
 void WebGLQuery::SetTarget(GLenum target) {
   DCHECK(Object());
diff --git a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
index 09203b8..cecfa2b 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
@@ -45,9 +45,7 @@
   SetObject(rbo);
 }
 
-WebGLRenderbuffer::~WebGLRenderbuffer() {
-  RunDestructor();
-}
+WebGLRenderbuffer::~WebGLRenderbuffer() = default;
 
 void WebGLRenderbuffer::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   gl->DeleteRenderbuffers(1, &object_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sampler.cc b/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
index 962aa16..24d45af 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
@@ -20,9 +20,7 @@
   SetObject(sampler);
 }
 
-WebGLSampler::~WebGLSampler() {
-  RunDestructor();
-}
+WebGLSampler::~WebGLSampler() = default;
 
 void WebGLSampler::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   gl->DeleteSamplers(1, &object_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shader.cc b/third_party/blink/renderer/modules/webgl/webgl_shader.cc
index c9fea1f..50f4417 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_shader.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_shader.cc
@@ -39,9 +39,7 @@
   SetObject(ctx->ContextGL()->CreateShader(type));
 }
 
-WebGLShader::~WebGLShader() {
-  RunDestructor();
-}
+WebGLShader::~WebGLShader() = default;
 
 void WebGLShader::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   gl->DeleteShader(object_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sync.cc b/third_party/blink/renderer/modules/webgl/webgl_sync.cc
index b10625391..01ec4dd 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sync.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_sync.cc
@@ -28,9 +28,7 @@
   ScheduleAllowCacheUpdate();
 }
 
-WebGLSync::~WebGLSync() {
-  RunDestructor();
-}
+WebGLSync::~WebGLSync() = default;
 
 void WebGLSync::UpdateCache(gpu::gles2::GLES2Interface* gl) {
   if (sync_status_ == GL_SIGNALED) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_texture.cc b/third_party/blink/renderer/modules/webgl/webgl_texture.cc
index 6f2647a2..6b2dc57 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_texture.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_texture.cc
@@ -41,9 +41,7 @@
   SetObject(texture);
 }
 
-WebGLTexture::~WebGLTexture() {
-  RunDestructor();
-}
+WebGLTexture::~WebGLTexture() = default;
 
 void WebGLTexture::SetTarget(GLenum target) {
   if (!Object())
diff --git a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
index cbb0b5e..344dc2d 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
@@ -27,9 +27,7 @@
   Context()->ContextGL()->GenQueriesEXT(1, &query_id_);
 }
 
-WebGLTimerQueryEXT::~WebGLTimerQueryEXT() {
-  RunDestructor();
-}
+WebGLTimerQueryEXT::~WebGLTimerQueryEXT() = default;
 
 void WebGLTimerQueryEXT::ResetCachedResult() {
   can_update_availability_ = false;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
index 907695b..7203b64 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
@@ -40,9 +40,7 @@
   }
 }
 
-WebGLTransformFeedback::~WebGLTransformFeedback() {
-  RunDestructor();
-}
+WebGLTransformFeedback::~WebGLTransformFeedback() = default;
 
 void WebGLTransformFeedback::DispatchDetached(gpu::gles2::GLES2Interface* gl) {
   for (WebGLBuffer* buffer : bound_indexed_transform_feedback_buffers_) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
index 6113578..2fec1e6 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
@@ -32,9 +32,7 @@
   }
 }
 
-WebGLVertexArrayObjectBase::~WebGLVertexArrayObjectBase() {
-  RunDestructor();
-}
+WebGLVertexArrayObjectBase::~WebGLVertexArrayObjectBase() = default;
 
 void WebGLVertexArrayObjectBase::DispatchDetached(
     gpu::gles2::GLES2Interface* gl) {
diff --git a/third_party/blink/renderer/platform/geometry/double_point.cc b/third_party/blink/renderer/platform/geometry/double_point.cc
index 3ffbe33..53e3844 100644
--- a/third_party/blink/renderer/platform/geometry/double_point.cc
+++ b/third_party/blink/renderer/platform/geometry/double_point.cc
@@ -5,12 +5,15 @@
 #include "third_party/blink/renderer/platform/geometry/double_point.h"
 
 #include <algorithm>
-#include "third_party/blink/renderer/platform/geometry/float_size.h"
-#include "third_party/blink/renderer/platform/geometry/layout_point.h"
+#include "third_party/blink/renderer/platform/wtf/math_extras.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
+DoublePoint::operator FloatPoint() const {
+  return FloatPoint(clampTo<float>(x_), clampTo<float>(y_));
+}
+
 DoublePoint DoublePoint::ExpandedTo(const DoublePoint& other) const {
   return DoublePoint(std::max(x_, other.x_), std::max(y_, other.y_));
 }
diff --git a/third_party/blink/renderer/platform/geometry/double_point.h b/third_party/blink/renderer/platform/geometry/double_point.h
index 3b16da26..bb874d1 100644
--- a/third_party/blink/renderer/platform/geometry/double_point.h
+++ b/third_party/blink/renderer/platform/geometry/double_point.h
@@ -14,8 +14,6 @@
 
 namespace blink {
 
-class LayoutPoint;
-
 class PLATFORM_EXPORT DoublePoint {
   DISALLOW_NEW();
 
@@ -33,7 +31,7 @@
   constexpr explicit DoublePoint(const DoubleSize& size)
       : x_(size.Width()), y_(size.Height()) {}
 
-  constexpr explicit operator FloatPoint() const { return FloatPoint(x_, y_); }
+  explicit operator FloatPoint() const;
 
   static constexpr DoublePoint Zero() { return DoublePoint(); }
 
@@ -123,10 +121,6 @@
   return IntPoint(clampTo<int>(floor(p.X())), clampTo<int>(floor(p.Y())));
 }
 
-constexpr FloatPoint ToFloatPoint(const DoublePoint& a) {
-  return FloatPoint(a.X(), a.Y());
-}
-
 constexpr DoubleSize ToDoubleSize(const DoublePoint& a) {
   return DoubleSize(a.X(), a.Y());
 }
diff --git a/third_party/blink/renderer/platform/geometry/double_size.h b/third_party/blink/renderer/platform/geometry/double_size.h
index b9cfd6a7..b31f181 100644
--- a/third_party/blink/renderer/platform/geometry/double_size.h
+++ b/third_party/blink/renderer/platform/geometry/double_size.h
@@ -104,10 +104,6 @@
   return IntSize(clampTo<int>(ceil(p.Width())), clampTo<int>(ceil(p.Height())));
 }
 
-constexpr FloatSize ToFloatSize(const DoubleSize& p) {
-  return FloatSize(p.Width(), p.Height());
-}
-
 PLATFORM_EXPORT std::ostream& operator<<(std::ostream&, const DoubleSize&);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
index 6b887046..76bdeca 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
@@ -69,11 +69,14 @@
       snapshot_state_(kInitialSnapshotState),
       resource_host_(nullptr),
       random_generator_((uint32_t)base::RandUint64()),
-      bernoulli_distribution_(kRasterMetricProbability) {
+      bernoulli_distribution_(kRasterMetricProbability),
+      last_recording_(nullptr) {
   // Used by browser tests to detect the use of a Canvas2DLayerBridge.
   TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation",
                        TRACE_EVENT_SCOPE_GLOBAL);
-  StartRecording();
+  if (is_deferral_enabled_) {
+    StartRecording();
+  }
   // Clear the background transparent or opaque. Similar code at
   // CanvasResourceProvider::Clear().
   if (IsValid()) {
@@ -546,8 +549,11 @@
 
   {  // Make a new scope so that PaintRecord gets deleted and that gets timed
     cc::PaintCanvas* canvas = ResourceProvider()->Canvas();
-    sk_sp<PaintRecord> recording = recorder_->finishRecordingAsPicture();
-    canvas->drawPicture(recording);
+    last_recording_ = recorder_->finishRecordingAsPicture();
+    canvas->drawPicture(last_recording_);
+    if (!resource_host_ || !resource_host_->IsPrinting()) {
+      last_recording_ = nullptr;
+    }
     ResourceProvider()->FlushSkia();
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
index 0eccc46..b9231df 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
@@ -169,6 +169,9 @@
   CanvasResourceProvider* ResourceProvider() const;
   void FlushRecording();
 
+  PaintRecorder* getRecorder() { return recorder_.get(); }
+  sk_sp<cc::PaintRecord> getLastRecord() { return last_recording_; }
+
  private:
   friend class Canvas2DLayerBridgeTest;
   friend class CanvasRenderingContext2DTest;
@@ -230,6 +233,8 @@
   std::bernoulli_distribution bernoulli_distribution_;
   Deque<RasterTimer> pending_raster_timers_;
 
+  sk_sp<cc::PaintRecord> last_recording_;
+
   base::WeakPtrFactory<Canvas2DLayerBridge> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(Canvas2DLayerBridge);
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index dbb7f45..efd53ab 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -685,14 +685,13 @@
       is_overlay_candidate_(is_overlay_candidate),
       size_(size),
       is_origin_top_left_(is_origin_top_left),
-      texture_target_(
-          is_overlay_candidate_
-              ? gpu::GetBufferTextureTarget(
-                    gfx::BufferUsage::SCANOUT,
-                    BufferFormat(ColorParams().TransferableResourceFormat()),
-                    context_provider_wrapper_->ContextProvider()
-                        ->GetCapabilities())
-              : GL_TEXTURE_2D),
+      texture_target_(is_overlay_candidate_
+                          ? gpu::GetBufferTextureTarget(
+                                gfx::BufferUsage::SCANOUT,
+                                ColorParams().GetBufferFormat(),
+                                context_provider_wrapper_->ContextProvider()
+                                    ->GetCapabilities())
+                          : GL_TEXTURE_2D),
       owning_thread_id_(Thread::Current()->ThreadId()),
       owning_thread_task_runner_(Thread::Current()->GetTaskRunner()) {
   if (!context_provider_wrapper_)
@@ -709,7 +708,7 @@
     flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
 
   auto shared_image_mailbox = shared_image_interface->CreateSharedImage(
-      ColorParams().TransferableResourceFormat(), gfx::Size(size),
+      viz::GetResourceFormat(ColorParams().GetBufferFormat()), gfx::Size(size),
       ColorParams().GetStorageGfxColorSpace(), flags);
 
   // Wait for the mailbox to be ready to be used.
@@ -830,18 +829,12 @@
   // Initialize GLFilter first so that the generated sync token includes this
   // update.
   SetGLFilterIfNeeded();
-  GetSyncToken();
 
   // TODO(khushalsagar): This is for consistency with MailboxTextureHolder
   // transfer path. Its unclear why the verification can not be deferred until
   // the resource needs to be transferred cross-process.
-  if (!owning_thread_data().sync_token.verified_flush()) {
-    int8_t* token_data = owning_thread_data().sync_token.GetData();
-    auto* gl = ContextGL();
-    gl->ShallowFlushCHROMIUM();
-    gl->VerifySyncTokensCHROMIUM(&token_data, 1);
-    owning_thread_data().sync_token.SetVerifyFlush();
-  }
+  owning_thread_data().mailbox_sync_mode = kVerifiedSyncToken;
+  GetSyncToken();
 }
 
 scoped_refptr<StaticBitmapImage> CanvasResourceSharedImage::Bitmap() {
@@ -880,6 +873,12 @@
   SkImageInfo image_info = SkImageInfo::Make(
       Size().Width(), Size().Height(), ColorParams().GetSkColorType(),
       ColorParams().GetSkAlphaType(), ColorParams().GetSkColorSpace());
+
+  // If its cross thread, then the sync token was already verified. If not, then
+  // it doesn't need to be verified.
+  if (!is_cross_thread())
+    owning_thread_data().mailbox_sync_mode = kUnverifiedSyncToken;
+
   image = AcceleratedStaticBitmapImage::CreateFromCanvasMailbox(
       mailbox(), is_cross_thread() ? sync_token() : GetSyncToken(),
       texture_id_for_image, image_info, texture_target_,
@@ -920,20 +919,34 @@
 }
 
 const gpu::SyncToken CanvasResourceSharedImage::GetSyncToken() {
-  if (mailbox_needs_new_sync_token()) {
-    DCHECK(!is_cross_thread());
+  if (is_cross_thread()) {
+    // Sync token should be generated at Transfer time, which must always be
+    // called before cross-thread usage. And since we don't allow writes on
+    // another thread, the sync token generated at Transfer time shouldn't
+    // have been invalidated.
+    DCHECK(!mailbox_needs_new_sync_token());
+    DCHECK(sync_token().verified_flush());
 
+    return sync_token();
+  }
+
+  if (mailbox_needs_new_sync_token()) {
     auto* gl = ContextGL();
     DCHECK(gl);  // caller should already have early exited if !gl.
-    if (owning_thread_data().mailbox_sync_mode == kVerifiedSyncToken) {
-      gl->GenSyncTokenCHROMIUM(owning_thread_data().sync_token.GetData());
-      DCHECK(owning_thread_data().sync_token.verified_flush());
-    } else {
-      gl->GenUnverifiedSyncTokenCHROMIUM(
-          owning_thread_data().sync_token.GetData());
-    }
+    gl->GenUnverifiedSyncTokenCHROMIUM(
+        owning_thread_data().sync_token.GetData());
     owning_thread_data().mailbox_needs_new_sync_token = false;
   }
+
+  if (owning_thread_data().mailbox_sync_mode == kVerifiedSyncToken &&
+      !owning_thread_data().sync_token.verified_flush()) {
+    int8_t* token_data = owning_thread_data().sync_token.GetData();
+    auto* gl = ContextGL();
+    gl->ShallowFlushCHROMIUM();
+    gl->VerifySyncTokensCHROMIUM(&token_data, 1);
+    owning_thread_data().sync_token.SetVerifyFlush();
+  }
+
   return sync_token();
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
index 239e25f..9c1e69a6 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
@@ -40,6 +40,8 @@
 
   virtual void DiscardResourceProvider();
 
+  virtual bool IsPrinting() const { return false; }
+
  private:
   std::unique_ptr<CanvasResourceProvider> resource_provider_;
 };
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index b7d88c0..4d5d2d37f 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -563,30 +563,30 @@
     if (IsGpuContextLost())
       return;
 
-    // If |resource_| is still being used by the compositor we need to create
-    // a new resource or reuse a previously recycled one. This class holds one
-    // reference so we check if there is more than one.
-    if (!resource_->HasOneRef() || resource()->is_lost()) {
+    if (DoCopyOnWrite()) {
       DCHECK(!current_resource_has_write_access_)
           << "Write access must be released before sharing the resource";
 
-      auto* old_resource =
-          static_cast<CanvasResourceSharedImage*>(resource_.get());
+      auto old_resource = std::move(resource_);
+      auto* old_resource_shared_image =
+          static_cast<CanvasResourceSharedImage*>(old_resource.get());
       resource_ = NewOrRecycledResource();
+      DCHECK(resource_);
+
       EnsureWriteAccess();
       if (surface_) {
         // Take read access to the outgoing resource for the skia copy below.
-        if (!old_resource->has_read_access()) {
+        if (!old_resource_shared_image->has_read_access()) {
           ContextGL()->BeginSharedImageAccessDirectCHROMIUM(
-              old_resource->GetTextureIdForBackendTexture(),
+              old_resource_shared_image->GetTextureIdForBackendTexture(),
               GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
         }
         surface_->replaceBackendTexture(CreateGrTextureForResource(),
                                         GetGrSurfaceOrigin());
         surface_->flush();
-        if (!old_resource->has_read_access()) {
+        if (!old_resource_shared_image->has_read_access()) {
           ContextGL()->EndSharedImageAccessDirectCHROMIUM(
-              old_resource->GetTextureIdForBackendTexture());
+              old_resource_shared_image->GetTextureIdForBackendTexture());
         }
       }
     }
@@ -595,6 +595,24 @@
     resource()->WillDraw();
   }
 
+  bool DoCopyOnWrite() {
+    // If the resource was lost, we can not use it for writes again.
+    if (resource()->is_lost())
+      return true;
+
+    // We have the only ref to the resource which implies there are no active
+    // readers.
+    if (resource_->HasOneRef())
+      return false;
+
+    // Its possible to have deferred work in skia which uses this resource. Try
+    // flushing once to see if that releases the read refs. We can avoid a copy
+    // by queuing this work before writing to this resource.
+    surface_->flush();
+
+    return !resource_->HasOneRef();
+  }
+
   sk_sp<SkSurface> CreateSkSurface() const override {
     TRACE_EVENT0("blink", "CanvasResourceProviderSharedImage::CreateSkSurface");
     if (IsGpuContextLost())
diff --git a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
index 7cbf81a..243b9a1 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
+++ b/third_party/blink/renderer/platform/graphics/compositor_filter_operations.cc
@@ -117,7 +117,8 @@
 }
 
 String CompositorFilterOperations::ToString() const {
-  return filter_operations_.ToString().c_str();
+  return String(filter_operations_.ToString().c_str()) + " at " +
+         reference_box_.ToString();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
index 0a2ae5a9..3622f7f6 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
@@ -111,10 +111,6 @@
   void AddObserver(WebMediaStreamObserver*);
   void RemoveObserver(WebMediaStreamObserver*);
 
-  // |m_extraData| may hold pointers to GC objects, and it may touch them in
-  // destruction.  So this class is eagerly finalized to finalize |m_extraData|
-  // promptly.
-  EAGERLY_FINALIZE();
   void Trace(blink::Visitor*);
 
  private:
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
index 639c7b4..32f2f5a 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.cc
@@ -166,6 +166,12 @@
   visitor->Trace(observers_);
 }
 
+void MediaStreamSource::Dispose() {
+  audio_consumers_.clear();
+  platform_source_.reset();
+  constraints_.Reset();
+}
+
 STATIC_ASSERT_ENUM(WebMediaStreamSource::kTypeAudio,
                    MediaStreamSource::kTypeAudio);
 STATIC_ASSERT_ENUM(WebMediaStreamSource::kTypeVideo,
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.h b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
index 7cbeae6..fecbcc2 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
@@ -51,6 +51,8 @@
 
 class PLATFORM_EXPORT MediaStreamSource final
     : public GarbageCollectedFinalized<MediaStreamSource> {
+  USING_PRE_FINALIZER(MediaStreamSource, Dispose);
+
  public:
   class PLATFORM_EXPORT Observer : public GarbageCollectedMixin {
    public:
@@ -119,12 +121,10 @@
     return audio_consumers_;
   }
 
-  // |m_extraData| may hold pointers to GC objects, and it may touch them in
-  // destruction.  So this class is eagerly finalized to finalize |m_extraData|
-  // promptly.
-  EAGERLY_FINALIZE();
   void Trace(blink::Visitor*);
 
+  void Dispose();
+
  private:
   String id_;
   StreamType type_;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 3e1af82..4af60a1 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -477,7 +477,7 @@
     },
     {
       name: "DiscardInputToMovingIframes",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "DisplayCutoutAPI",
diff --git a/third_party/blink/renderer/platform/scheduler/common/dummy_schedulers.cc b/third_party/blink/renderer/platform/scheduler/common/dummy_schedulers.cc
index 9c366e27..02127048 100644
--- a/third_party/blink/renderer/platform/scheduler/common/dummy_schedulers.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/dummy_schedulers.cc
@@ -33,6 +33,7 @@
   bool IsFrameVisible() const override { return true; }
   bool IsPageVisible() const override { return true; }
   void SetPaused(bool) override {}
+  void SetShouldReportPostedTasksWhenDisabled(bool) override {}
   void SetCrossOrigin(bool) override {}
   bool IsCrossOrigin() const override { return false; }
   void SetIsAdFrame() override {}
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index e281441..163238f 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -780,6 +780,17 @@
   UpdatePolicy();
 }
 
+void FrameSchedulerImpl::SetShouldReportPostedTasksWhenDisabled(
+    bool should_report) {
+  // Forward this to all the task queues associated with this frame.
+  for (const auto& task_queue_and_voter :
+       frame_task_queue_controller_->GetAllTaskQueuesAndVoters()) {
+    auto* task_queue = task_queue_and_voter.first;
+    if (task_queue->CanBeFrozen())
+      task_queue->SetShouldReportPostedTasksWhenDisabled(should_report);
+  }
+}
+
 void FrameSchedulerImpl::SetPageFrozenForTracing(bool frozen) {
   page_frozen_for_tracing_ = frozen;
 }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h
index 30514c8..fbc2b9ed 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h
@@ -86,6 +86,7 @@
   bool IsAudioPlaying() const;
 
   void SetPaused(bool frame_paused) override;
+  void SetShouldReportPostedTasksWhenDisabled(bool should_report) override;
 
   void SetCrossOrigin(bool cross_origin) override;
   bool IsCrossOrigin() const override;
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.cc
index 5d122d3..b334baf8 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.cc
@@ -257,8 +257,10 @@
   if (is_frozen_ == frozen)
     return;
   is_frozen_ = frozen;
-  for (FrameSchedulerImpl* frame_scheduler : frame_schedulers_)
+  for (FrameSchedulerImpl* frame_scheduler : frame_schedulers_) {
     frame_scheduler->SetPageFrozenForTracing(frozen);
+    frame_scheduler->SetShouldReportPostedTasksWhenDisabled(frozen);
+  }
   if (notification_policy ==
       PageSchedulerImpl::NotificationPolicy::kNotifyFrames)
     NotifyFrames();
diff --git a/third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h b/third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h
index c7c7ece..a32a0d2 100644
--- a/third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h
+++ b/third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h
@@ -77,6 +77,10 @@
   // allowed to run on a suspended frame.
   virtual void SetPaused(bool) = 0;
 
+  // Sets whether or not this frame should report (via tracing) tasks that are
+  // posted to it.
+  virtual void SetShouldReportPostedTasksWhenDisabled(bool) = 0;
+
   // Set whether this frame is cross origin w.r.t. the top level frame. Cross
   // origin frames may use a different scheduling policy from same origin
   // frames.
diff --git a/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.cc b/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.cc
index 630cb12b..105b406 100644
--- a/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.cc
+++ b/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.cc
@@ -113,4 +113,8 @@
   visitor->Trace(web_speech_synthesizer_client_);
 }
 
+void PlatformSpeechSynthesizer::Dispose() {
+  web_speech_synthesizer_.reset();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.h b/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.h
index 0739d16..4b4821ac 100644
--- a/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.h
+++ b/third_party/blink/renderer/platform/speech/platform_speech_synthesizer.h
@@ -62,6 +62,18 @@
 
 class PLATFORM_EXPORT PlatformSpeechSynthesizer
     : public GarbageCollectedFinalized<PlatformSpeechSynthesizer> {
+  // Pre-finalization is required to promptly release the owned
+  // WebSpeechSynthesizer.
+  //
+  // If not and delayed until lazily swept, web_speech_synthesizer_client_ may
+  // end up being lazily swept first (i.e., before this
+  // PlatformSpeechSynthesizer), leaving web_speech_synthesizer_ with a
+  // dangling pointer to a finalized object -- WebSpeechSynthesizer embedder
+  // implementations calling notification methods in the other directions by
+  // way of web_speech_synthesizer_client_. Eagerly releasing
+  // WebSpeechSynthesizer prevents such unsafe accesses.
+  USING_PRE_FINALIZER(PlatformSpeechSynthesizer, Dispose);
+
  public:
   static PlatformSpeechSynthesizer* Create(PlatformSpeechSynthesizerClient*);
 
@@ -81,19 +93,10 @@
 
   void SetVoiceList(Vector<scoped_refptr<PlatformSpeechSynthesisVoice>>&);
 
-  // Eager finalization is required to promptly release the owned
-  // WebSpeechSynthesizer.
-  //
-  // If not and delayed until lazily swept, m_webSpeechSynthesizerClient may end
-  // up being lazily swept first (i.e., before this PlatformSpeechSynthesizer),
-  // leaving m_webSpeechSynthesizer with a dangling pointer to a finalized
-  // object -- WebSpeechSynthesizer embedder implementations calling
-  // notification methods in the other directions by way of
-  // m_webSpeechSynthesizerClient. Eagerly releasing WebSpeechSynthesizer
-  // prevents such unsafe accesses.
-  EAGERLY_FINALIZE();
   virtual void Trace(blink::Visitor*);
 
+  void Dispose();
+
  protected:
   virtual void InitializeVoiceList();
 
diff --git a/third_party/blink/tools/blinkpy/third_party/README.chromium b/third_party/blink/tools/blinkpy/third_party/README.chromium
index 773092d..f09295f 100644
--- a/third_party/blink/tools/blinkpy/third_party/README.chromium
+++ b/third_party/blink/tools/blinkpy/third_party/README.chromium
@@ -32,7 +32,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: 7edf9eabfae3550a4dd2a48625e68b02a734de15
+Version: 3fb0150bb0a53b5a6630e8eda7f43bf75d8a6bbe
 License: LICENSES FOR W3C TEST SUITES (http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html)
 License File: wpt/wpt/LICENSE.md
 Security Critical: no
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh b/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
index ffc40e2..e49dd713 100755
--- a/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/checkout.sh
@@ -9,7 +9,7 @@
 
 TARGET_DIR=$DIR/wpt
 REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
-WPT_HEAD=7edf9eabfae3550a4dd2a48625e68b02a734de15
+WPT_HEAD=3fb0150bb0a53b5a6630e8eda7f43bf75d8a6bbe
 
 function clone {
   # Remove existing repo if already exists.
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
index 5b156b1..834e5c5 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/lint.py
@@ -146,6 +146,13 @@
     return []
 
 
+def check_file_type(repo_root, path):
+    # type: (str, str) -> List[rules.Error]
+    if os.path.islink(path):
+        return [rules.FileType.error(path, (path, "symlink"))]
+    return []
+
+
 def check_worker_collision(repo_root, path):
     # type: (str, str) -> List[rules.Error]
     endings = [(".any.html", ".any.js"),
@@ -913,7 +920,8 @@
                 logger.info(line)
     return sum(itervalues(error_count))
 
-path_lints = [check_path_length, check_worker_collision, check_ahem_copy, check_gitignore_file]
+path_lints = [check_file_type, check_path_length, check_worker_collision, check_ahem_copy,
+              check_gitignore_file]
 all_paths_lints = [check_css_globally_unique]
 file_lints = [check_regexp_line, check_parsed, check_python_ast, check_script_metadata]
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
index 685bac34..9b78f349 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/lint/rules.py
@@ -57,6 +57,11 @@
     description = "/%s longer than maximum path length (%d > 150)"
 
 
+class FileType(Rule):
+    name = "FILE TYPE"
+    description = "/%s is an unsupported file type (%s)"
+
+
 class WorkerCollision(Rule):
     name = "WORKER COLLISION"
     description = ("path ends with %s which collides with generated tests "
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
index b5ebeed6..616f95e 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/manifest.py
@@ -2,7 +2,7 @@
 import json
 import os
 from collections import MutableMapping, defaultdict
-from six import iteritems, iterkeys, itervalues, string_types
+from six import iteritems, iterkeys, itervalues, string_types, binary_type, text_type
 
 from . import vcs
 from .item import (ConformanceCheckerTest, ManifestItem, ManualTest, RefTest, RefTestNode, Stub,
@@ -323,7 +323,7 @@
 
         for source_file, update in tree:
             if not update:
-                assert isinstance(source_file, (bytes, str))
+                assert isinstance(source_file, (binary_type, text_type))
                 rel_path = source_file  # type: Text
                 seen_files.add(rel_path)
                 assert rel_path in path_hash
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
index c810be12..d87e804c 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/manifest/vcs.py
@@ -61,24 +61,11 @@
         # type: () -> Set[bytes]
         """get a set of files which have changed between HEAD and working copy"""
         assert self.git is not None
-
-        changes = set()  # type: Set[bytes]
-
-        cmd = [b"status", b"-z", b"--ignore-submodules=all"]
-        data = self.git(*cmd)  # type: bytes
-
-        in_rename = False
-        for line in data.split(b"\0")[:-1]:
-            if in_rename:
-                changes.add(line)
-                in_rename = False
-            else:
-                status = line[:2]
-                if b"R" in status or b"C" in status:
-                    in_rename = True
-                changes.add(line[3:])
-
-        return changes
+        # note that git runs the command with tests_root as the cwd, which may
+        # not be the root of the git repo (e.g., within a browser repo)
+        cmd = [b"diff-index", b"--relative", b"--no-renames", b"--name-only", b"-z", b"HEAD"]
+        data = self.git(*cmd)
+        return set(data.split(b"\0"))
 
     def hash_cache(self):
         # type: () -> Dict[bytes, Optional[bytes]]
@@ -90,7 +77,9 @@
         if self.git is None:
             return hash_cache
 
-        cmd = [b"ls-tree", b"-r", b"-z", b"HEAD"]
+        # note that git runs the command with tests_root as the cwd, which may
+        # not be the root of the git repo (e.g., within a browser repo)
+        cmd = ["ls-tree", "-r", "-z", "HEAD"]
         local_changes = self._local_changes()
         for result in self.git(*cmd).split(b"\0")[:-1]:  # type: bytes
             data, rel_path = result.rsplit(b"\t", 1)
@@ -168,7 +157,10 @@
         try:
             if not rebuild:
                 with open(self.path, 'r') as f:
-                    data = json.load(f)
+                    try:
+                        data = json.load(f)
+                    except ValueError:
+                        pass
                 data = self.check_valid(data)
         except IOError:
             pass
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/serve/serve.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/serve/serve.py
index 1fc61747..77675dbe 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/serve/serve.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/serve/serve.py
@@ -8,6 +8,7 @@
 import logging
 import os
 import platform
+import signal
 import socket
 import sys
 import threading
@@ -574,13 +575,11 @@
 
 
 class WebSocketDaemon(object):
-    def __init__(self, host, port, doc_root, handlers_root, log_level, bind_address,
-                 ssl_config):
+    def __init__(self, host, port, doc_root, handlers_root, bind_address, ssl_config):
         self.host = host
         cmd_args = ["-p", port,
                     "-d", doc_root,
-                    "-w", handlers_root,
-                    "--log-level", log_level]
+                    "-w", handlers_root]
 
         if ssl_config is not None:
             # This is usually done through pywebsocket.main, however we're
@@ -604,17 +603,6 @@
         opts, args = pywebsocket._parse_args_and_config(cmd_args)
         opts.cgi_directories = []
         opts.is_executable_method = None
-
-        # Logging needs to be configured both before and after reloading,
-        # because some modules store loggers as global variables.
-        pywebsocket._configure_logging(opts)
-        # Ensure that when we start this in a new process we have the global
-        # lock in the logging module unlocked.
-        reload_module(logging)
-        release_mozlog_lock()
-        pywebsocket._configure_logging(opts)
-        # DO NOT LOG BEFORE THIS LINE.
-
         self.server = pywebsocket.WebSocketServer(opts)
         ports = [item[0].getsockname()[1] for item in self.server._sockets]
         assert all(item == ports[0] for item in ports)
@@ -661,21 +649,27 @@
 
 
 def start_ws_server(host, port, paths, routes, bind_address, config, **kwargs):
+    # Ensure that when we start this in a new process we have the global lock
+    # in the logging module unlocked
+    reload_module(logging)
+    release_mozlog_lock()
     return WebSocketDaemon(host,
                            str(port),
                            repo_root,
                            config.paths["ws_doc_root"],
-                           config.log_level.lower(),
                            bind_address,
                            ssl_config=None)
 
 
 def start_wss_server(host, port, paths, routes, bind_address, config, **kwargs):
+    # Ensure that when we start this in a new process we have the global lock
+    # in the logging module unlocked
+    reload_module(logging)
+    release_mozlog_lock()
     return WebSocketDaemon(host,
                            str(port),
                            repo_root,
                            config.paths["ws_doc_root"],
-                           config.log_level.lower(),
                            bind_address,
                            config.ssl_config)
 
@@ -841,11 +835,19 @@
 
 
 def run(**kwargs):
+    received_signal = threading.Event()
+
     with build_config(os.path.join(repo_root, "config.json"),
                       **kwargs) as config:
         global logger
         logger = config.logger
         set_logger(logger)
+        # Configure the root logger to cover third-party libraries.
+        logging.getLogger().setLevel(config.log_level)
+
+        def handle_signal(signum, frame):
+            logger.debug("Received signal %s. Shutting down.", signum)
+            received_signal.set()
 
         bind_address = config["bind_address"]
 
@@ -868,20 +870,19 @@
 
         with stash.StashServer(stash_address, authkey=str(uuid.uuid4())):
             servers = start(config, build_routes(config["aliases"]), **kwargs)
+            signal.signal(signal.SIGTERM, handle_signal)
+            signal.signal(signal.SIGINT, handle_signal)
 
-            try:
-                while all(item.is_alive() for item in iter_procs(servers)):
-                    for item in iter_procs(servers):
-                        item.join(1)
-                exited = [item for item in iter_procs(servers) if not item.is_alive()]
-                subject = "subprocess" if len(exited) == 1 else "subprocesses"
-
-                logger.info("%s %s exited:" % (len(exited), subject))
-
+            while all(item.is_alive() for item in iter_procs(servers)) and not received_signal.is_set():
                 for item in iter_procs(servers):
-                    logger.info("Status of %s:\t%s" % (item.name, "running" if item.is_alive() else "not running"))
-            except KeyboardInterrupt:
-                logger.info("Shutting down")
+                    item.join(1)
+            exited = [item for item in iter_procs(servers) if not item.is_alive()]
+            subject = "subprocess" if len(exited) == 1 else "subprocesses"
+
+            logger.info("%s %s exited:" % (len(exited), subject))
+
+            for item in iter_procs(servers):
+                logger.info("Status of %s:\t%s" % (item.name, "running" if item.is_alive() else "not running"))
 
 
 def main():
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/webdriver/webdriver/client.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/webdriver/webdriver/client.py
index 52ebc95..0bddd7b 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/webdriver/webdriver/client.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/webdriver/webdriver/client.py
@@ -218,7 +218,12 @@
                         ``ActionSequence.dict``.
         """
         body = {"actions": [] if actions is None else actions}
-        return self.session.send_session_command("POST", "actions", body)
+        actions = self.session.send_session_command("POST", "actions", body)
+        """WebDriver window should be set to the top level window when wptrunner
+        processes the next event.
+        """
+        self.session.switch_frame(None)
+        return actions
 
     @command
     def release(self):
@@ -308,8 +313,11 @@
         self.session = session
 
     @command
-    def css(self, selector, all=True):
-        return self._find_element("css selector", selector, all)
+    def css(self, element_selector, all=True, frame="window"):
+        if (frame != "window"):
+            self.session.switch_frame(frame)
+        elements = self._find_element("css selector", element_selector, all)
+        return elements
 
     def _find_element(self, strategy, selector, all):
         route = "elements" if all else "element"
@@ -413,7 +421,7 @@
         if self.session_id is not None:
             return
 
-        body = {}
+        body = {"capabilities": {}}
 
         if self.requested_capabilities is not None:
             body["capabilities"] = self.requested_capabilities
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/browser.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/browser.py
index 1b883b9..23e91a7 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/browser.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/browser.py
@@ -3,6 +3,7 @@
 import re
 import shutil
 import stat
+import errno
 import subprocess
 import tempfile
 import urlparse
@@ -17,6 +18,24 @@
 uname = platform.uname()
 
 
+def _get_fileversion(binary, logger=None):
+    command = "(Get-Item '%s').VersionInfo.FileVersion" % binary.replace("'", "''")
+    try:
+        return call("powershell.exe", command).strip()
+    except (subprocess.CalledProcessError, OSError):
+        if logger is not None:
+            logger.warning("Failed to call %s in PowerShell" % command)
+        return None
+
+
+def handle_remove_readonly(func, path, exc):
+    excvalue = exc[1]
+    if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
+        os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  # 0777
+        func(path)
+    else:
+        raise
+
 class Browser(object):
     __metaclass__ = ABCMeta
 
@@ -108,7 +127,7 @@
             ("linux", "x86"): "linux",
             ("linux", "x86_64"): "linux64",
             ("win", "x86"): "win",
-            ("win", "x86_64"): "win64",
+            ("win", "AMD64"): "win64",
             ("macos", "x86_64"): "osx",
         }
         os_key = (self.platform, uname[4])
@@ -149,7 +168,7 @@
 
         installer_path = os.path.join(dest, filename)
 
-        with open(installer_path, "w") as f:
+        with open(installer_path, "wb") as f:
             f.write(resp.content)
 
         try:
@@ -197,6 +216,14 @@
         path = os.path.join(venv_path, "browsers", channel)
         binary = self.find_binary_path(path, channel)
 
+        if not binary and self.platform == "win":
+            winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Mozilla Firefox"),
+                        os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Mozilla Firefox")]
+            for winpath in winpaths:
+                binary = self.find_binary_path(winpath, channel)
+                if binary is not None:
+                    break
+
         if not binary and self.platform == "macos":
             macpaths = ["/Applications/Firefox Nightly.app/Contents/MacOS",
                         os.path.expanduser("~/Applications/Firefox Nightly.app/Contents/MacOS"),
@@ -215,7 +242,7 @@
         path = find_executable("certutil")
         if path is None:
             return None
-        if os.path.splitdrive(path)[1].split(os.path.sep) == ["", "Windows", "system32", "certutil.exe"]:
+        if os.path.splitdrive(os.path.normcase(path))[1].split(os.path.sep) == ["", "windows", "system32", "certutil.exe"]:
             return None
         return path
 
@@ -418,7 +445,8 @@
             return "/usr/bin/google-chrome"
         if uname[0] == "Darwin":
             return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
-        # TODO Windows?
+        if uname[0] == "Windows":
+            return os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe")
         self.logger.warning("Unable to find the browser binary.")
         return None
 
@@ -521,19 +549,19 @@
 
     def version(self, binary=None, webdriver_binary=None):
         binary = binary or self.binary
-        if uname[0] != "Windows":
-            try:
-                version_string = call(binary, "--version").strip()
-            except subprocess.CalledProcessError:
-                self.logger.warning("Failed to call %s" % binary)
-                return None
-            m = re.match(r"(?:Google Chrome|Chromium) (.*)", version_string)
-            if not m:
-                self.logger.warning("Failed to extract version from: %s" % version_string)
-                return None
-            return m.group(1)
-        self.logger.warning("Unable to extract version from binary on Windows.")
-        return None
+        if uname[0] == "Windows":
+            return _get_fileversion(binary, self.logger)
+
+        try:
+            version_string = call(binary, "--version").strip()
+        except subprocess.CalledProcessError:
+            self.logger.warning("Failed to call %s" % binary)
+            return None
+        m = re.match(r"(?:Google Chrome|Chromium) (.*)", version_string)
+        if not m:
+            self.logger.warning("Failed to extract version from: %s" % version_string)
+            return None
+        return m.group(1)
 
 
 class ChromeAndroid(Browser):
@@ -644,6 +672,7 @@
         "Darwin": "macos"
     }.get(uname[0])
     product = "edgechromium"
+    edgedriver_name = "msedgedriver"
     requirements = "requirements_edge_chromium.txt"
 
     def install(self, dest=None, channel=None):
@@ -678,22 +707,40 @@
         return find_executable("msedgedriver")
 
     def install_webdriver(self, dest=None, channel=None, browser_binary=None):
-        if self.platform == "win":
-            raise ValueError("Only Windows platform is currently supported")
+        if self.platform != "win" and self.platform != "macos":
+            raise ValueError("Only Windows and Mac platforms are currently supported")
 
         if dest is None:
             dest = os.pwd
 
-        platform = "x64" if uname[4] == "x86_64" else "x86"
-        url = "https://az813057.vo.msecnd.net/webdriver/msedgedriver_%s/msedgedriver.exe" % platform
+        if channel is None:
+            version_url = "https://msedgedriver.azureedge.net/LATEST_DEV"
+        else:
+            version_url = "https://msedgedriver.azureedge.net/LATEST_%s" % channel.upper()
+        version = get(version_url).text.strip()
+
+        if self.platform == "macos":
+            bits = "mac64"
+            edgedriver_path = os.path.join(dest, self.edgedriver_name)
+        else:
+            bits = "win64" if uname[4] == "x86_64" else "win32"
+            edgedriver_path = os.path.join(dest, "%s.exe" % self.edgedriver_name)
+        url = "https://msedgedriver.azureedge.net/%s/edgedriver_%s.zip" % (version, bits)
+
+        # cleanup existing Edge driver files to avoid access_denied errors when unzipping
+        if os.path.isfile(edgedriver_path):
+            # remove read-only attribute
+            os.chmod(edgedriver_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  # 0777
+            os.remove(edgedriver_path)
+            driver_notes_path = os.path.join(dest, "Driver_notes")
+            if os.path.isdir(driver_notes_path):
+                shutil.rmtree(driver_notes_path, ignore_errors=False, onerror=handle_remove_readonly)
 
         self.logger.info("Downloading MSEdgeDriver from %s" % url)
-        resp = get(url)
-        installer_path = os.path.join(dest, "msedgedriver.exe")
-        with open(installer_path, "wb") as f:
-            f.write(resp.content)
-
-        return find_executable("msedgedriver", dest)
+        unzip(get(url).raw, dest)
+        if os.path.isfile(edgedriver_path):
+            self.logger.info("Successfully downloaded MSEdgeDriver to %s" % edgedriver_path)
+        return find_executable(self.edgedriver_name, dest)
 
     def version(self, binary=None, webdriver_binary=None):
         if binary is None:
@@ -711,12 +758,7 @@
             return m.group(1)
         else:
             if binary is not None:
-                command = "(Get-Item '%s').VersionInfo.FileVersion" % binary
-                try:
-                    return call("powershell.exe", command).strip()
-                except (subprocess.CalledProcessError, OSError):
-                    self.logger.warning("Failed to call %s in PowerShell" % command)
-                    return None
+                return _get_fileversion(binary, self.logger)
             self.logger.warning("Failed to find Edge binary.")
             return None
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/install.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/install.py
index be6bf81..b107752 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/install.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/install.py
@@ -6,6 +6,7 @@
 latest_channels = {
     'firefox': 'nightly',
     'chrome': 'dev',
+    'edgechromium': 'dev',
     'safari': 'preview',
     'servo': 'nightly'
 }
@@ -18,6 +19,7 @@
     'dev': latest_channels,
     'preview': latest_channels,
     'experimental': latest_channels,
+    'canary': 'canary',
 }
 
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/run.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/run.py
index 0b306aee..282dd29 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/run.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/run.py
@@ -338,15 +338,23 @@
     browser_cls = browser.EdgeChromium
 
     def setup_kwargs(self, kwargs):
+        browser_channel = kwargs["browser_channel"]
+        if kwargs["binary"] is None:
+            binary = self.browser.find_binary(channel=browser_channel)
+            if binary:
+                kwargs["binary"] = self.browser.find_binary()
+            else:
+                raise WptrunError("Unable to locate Edge binary")
         if kwargs["webdriver_binary"] is None:
             webdriver_binary = self.browser.find_webdriver()
 
-            if webdriver_binary is None:
+            # Install browser if none are found or if it's found in venv path
+            if webdriver_binary is None or webdriver_binary in self.venv.bin_path:
                 install = self.prompt_install("msedgedriver")
 
                 if install:
                     logger.info("Downloading msedgedriver")
-                    webdriver_binary = self.browser.install_webdriver(dest=self.venv.bin_path)
+                    webdriver_binary = self.browser.install_webdriver(dest=self.venv.bin_path, channel=browser_channel)
             else:
                 logger.info("Using webdriver binary %s" % webdriver_binary)
 
@@ -354,6 +362,9 @@
                 kwargs["webdriver_binary"] = webdriver_binary
             else:
                 raise WptrunError("Unable to locate or install msedgedriver binary")
+        if browser_channel == "dev":
+            logger.info("Automatically turning on experimental features for Edge Dev")
+            kwargs["binary_args"].append("--enable-experimental-web-platform-features")
 
 
 class Edge(BrowserSetup):
@@ -576,7 +587,8 @@
             kwargs["browser_channel"] = channel
         else:
             logger.info("Valid channels for %s not known; using argument unmodified" % kwargs["product"])
-    del kwargs["channel"]
+            kwargs["browser_channel"] = kwargs["channel"]
+        del kwargs["channel"]
 
     if install_browser:
         logger.info("Installing browser")
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
index e0f51d5..a894c9c 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wpt/virtualenv.py
@@ -34,6 +34,11 @@
     def exists(self):
         return os.path.isdir(self.path)
 
+    @property
+    def broken_link(self):
+        python_link = os.path.join(self.path, ".Python")
+        return os.path.lexists(python_link) and not os.path.exists(python_link)
+
     def create(self):
         if os.path.exists(self.path):
             shutil.rmtree(self.path)
@@ -88,7 +93,7 @@
         execfile(path, {"__file__": path})  # noqa: F821
 
     def start(self):
-        if not self.exists:
+        if not self.exists or self.broken_link:
             self.create()
         self.activate()
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/pipes.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/pipes.py
index d1eb724..ceb34cc 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/pipes.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/pipes.py
@@ -342,23 +342,25 @@
       A dictionary of parts of the request URL. Valid keys are
       'server, 'scheme', 'host', 'hostname', 'port', 'path' and 'query'.
       'server' is scheme://host:port, 'host' is hostname:port, and query
-       includes the leading '?', but other delimiters are omitted.
+      includes the leading '?', but other delimiters are omitted.
     headers
       A dictionary of HTTP headers in the request.
     header_or_default(header, default)
       The value of an HTTP header, or a default value if it is absent.
-      For example:
+      For example::
 
         {{header_or_default(X-Test, test-header-absent)}}
+
     GET
       A dictionary of query parameters supplied with the request.
     uuid()
       A pesudo-random UUID suitable for usage with stash
     file_hash(algorithm, filepath)
       The cryptographic hash of a file. Supported algorithms: md5, sha1,
-      sha224, sha256, sha384, and sha512. For example:
+      sha224, sha256, sha384, and sha512. For example::
 
         {{file_hash(md5, dom/interfaces.html)}}
+
     fs_path(filepath)
       The absolute path to a file inside the wpt document root
 
@@ -369,16 +371,15 @@
       {{domains[www]}} => www.localhost
       {{ports[http][1]}} => 81
 
+    It is also possible to assign a value to a variable name, which must start
+    with the $ character, using the ":" syntax e.g.::
 
-    It is also possible to assign a value to a variable name, which must start with
-    the $ character, using the ":" syntax e.g.
-
-    {{$id:uuid()}}
+      {{$id:uuid()}}
 
     Later substitutions in the same file may then refer to the variable
-    by name e.g.
+    by name e.g.::
 
-    {{$id}}
+      {{$id}}
     """
     content = resolve_content(response)
 
diff --git a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/response.py b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/response.py
index 6a241039..3a60c2ba 100644
--- a/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/response.py
+++ b/third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/wptserve/wptserve/response.py
@@ -179,11 +179,13 @@
         If any part of the content is a function, this will be called
         and the resulting value (if any) returned.
 
-        :param read_file: - boolean controlling the behaviour when content
-        is a file handle. When set to False the handle will be returned directly
-        allowing the file to be passed to the output in small chunks. When set to
-        True, the entire content of the file will be returned as a string facilitating
-        non-streaming operations like template substitution.
+        :param read_file: boolean controlling the behaviour when content is a
+                          file handle. When set to False the handle will be
+                          returned directly allowing the file to be passed to
+                          the output in small chunks. When set to True, the
+                          entire content of the file will be returned as a
+                          string facilitating non-streaming operations like
+                          template substitution.
         """
         if isinstance(self.content, binary_type):
             yield self.content
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer b/third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer
index 362c2640..c0ccb3ca 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer
+++ b/third_party/blink/web_tests/FlagExpectations/enable-features=UseSkiaRenderer
@@ -3,3 +3,5 @@
 crbug.com/954328 css3/filters/effect-blur-hw.html [ Failure ]
 crbug.com/954328 media/video-layer-crash.html [ Failure ]
 crbug.com/954328 transforms/3d/point-mapping/3d-point-mapping-deep.html [ Failure ]
+
+crbug.com/918155 compositing/overflow/scrollbar-layer-placement-negative-z-index-child.html [ Failure ]
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization b/third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization
index d814edd8..f1b0771 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization
+++ b/third_party/blink/web_tests/FlagExpectations/enable-gpu-rasterization
@@ -88,6 +88,7 @@
 # Tests fail even with a fuzzy pixel diff. They require a re-baseline.
 crbug.com/954328 compositing/fixed-background-after-style-recalc.html [ Failure ]
 crbug.com/954328 compositing/overflow/mask-with-filter.html [ Failure ]
+crbug.com/954328 compositing/overflow/scrollbar-layer-placement-negative-z-index-child.html [ Failure ]
 crbug.com/954328 css3/blending/background-blend-mode-crossfade-image-gradient.html [ Failure ]
 crbug.com/954328 css3/blending/background-blend-mode-default-value.html [ Failure ]
 crbug.com/954328 css3/blending/background-blend-mode-gradient-gradient.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e60318e..03a3241 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5169,6 +5169,10 @@
 crbug.com/783301 external/wpt/imagecapture/MediaStreamTrack-getSettings.html [ Pass Failure ]
 crbug.com/783301 external/wpt/imagecapture/MediaStreamTrack-getCapabilities.html [ Pass Failure ]
 
+# Cookie Store API
+crbug.com/827231 external/wpt/cookie-store/document_cookie.tentative.html [ Pass Failure ]
+crbug.com/827231 external/wpt/cookie-store/document_cookie.tentative.https.html [ Pass Failure ]
+
 # Failing SameSite cookies tests
 crbug.com/843945 external/wpt/cookies/samesite/form-get-blank-reload.html [ Failure ]
 crbug.com/843945 external/wpt/cookies/samesite/form-post-blank-reload.html [ Failure ]
@@ -6238,20 +6242,6 @@
 # TODO(crbug.com/980588): reenable once WPT is fixed
 crbug.com/980588 external/wpt/screen-orientation/lock-unlock-check.html [ Pass Failure ]
 
-# TODO (michaelludwig) - Rebaseline for Skia roll
-crbug.com/981879 virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png.html [ Pass Failure ]
-crbug.com/981879 virtual/gpu-rasterization/images/color-profile-background-image-cross-fade.html [ Pass Failure ]
-crbug.com/981879 virtual/gpu-rasterization/images/cross-fade-background-size.html [ Pass Failure ]
-
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-background-image-cover.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-background-image-repeat.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-background-image-space.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-image-canvas-pattern.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-image-canvas.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-mask-image-svg.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile.html [ Pass Failure ]
-crbug.com/982289 virtual/gpu-rasterization/images/color-profile-image.html [ Pass Failure ]
-
 # Sheriff 2019-07-11
 crbug.com/982290 [ Win ] http/tests/devtools/elements/styles-4/styles-do-not-detach-sourcemap-on-edits.js [ Pass Timeout ]
 
diff --git a/third_party/blink/web_tests/external/wpt/css/mediaqueries/mq-calc-006.html b/third_party/blink/web_tests/external/wpt/css/mediaqueries/mq-calc-006.html
new file mode 100644
index 0000000..989ee42
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/mediaqueries/mq-calc-006.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel="author" title="Xiaocheng Hu" href="xiaochengh@chromium.org">
+<link rel="help" href="http://www.w3.org/TR/css3-values/#calc-notation">
+<link rel="help" href="http://www.w3.org/TR/css3-mediaqueries/">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="The 'in' unit used in calc is not mistaken as 'px'.">
+<style>
+p { font-size: 16px; }
+#target {
+  width: 100px;
+  height: 100px;
+  background-color: green;
+}
+@media (min-width: calc(100in)) {
+  /* Should not be selected */
+  #target { background-color: red }
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id=target></div>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.sub.html
new file mode 100644
index 0000000..0c5caa7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.sub.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/fetch/sec-metadata/resources/helper.js></script>
+<script>
+  // http -> https should see `Sec-Fetch-Site: cross-site`.
+  // This is a regression test for
+  // https://github.com/w3c/webappsec-fetch-metadata/issues/34
+  promise_test(t => {
+    assert_equals(location.protocol, "http:");
+    return fetch("https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/echo-as-json.py")
+        .then(r => r.json())
+        .then(j => {
+          assert_header_equals(j, {
+            "dest": "empty",
+            "site": "cross-site",
+            "user": "",
+            "mode": "cors",
+          });
+        });
+  }, "http->https fetch (cross-scheme => cross-site)");
+
+  // http -> http should see no `Sec-Fetch-Site`.
+  promise_test(t => {
+    assert_equals(location.protocol, "http:");
+    return fetch("resources/echo-as-json.py")
+        .then(r => r.json())
+        .then(j => {
+          assert_header_equals(j, {
+            "dest": "",
+            "site": "",
+            "user": "",
+            "mode": "",
+          });
+        });
+  }, "http->http fetch (non-trustworthy destination => no sec-metadata)");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.sub.html
index eab2d3f..2bc2581 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.sub.html
@@ -60,4 +60,23 @@
 
     document.body.appendChild(i);
   }, "Non-secure cross-site iframe => No headers.");
+
+  async_test(t => {
+    let i = document.createElement('iframe');
+    i.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
+    window.addEventListener('message', t.step_func(e => {
+      if (e.source != i.contentWindow)
+        return;
+
+      assert_header_equals(e.data, {
+        "dest": "nested-document",
+        "site": "cross-site",
+        "user": "",
+        "mode": "nested-navigate",
+      });
+      t.done();
+    }));
+
+    document.body.appendChild(i);
+  }, "Secure, cross-site (cross-scheme, same-host) iframe");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-https-downgrade-upgrade.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-https-downgrade-upgrade.tentative.sub.html
index 3f8726d..cede671 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-https-downgrade-upgrade.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-https-downgrade-upgrade.tentative.sub.html
@@ -19,7 +19,7 @@
     <div id="fontTest">Downgraded then upgraded font</div>
     <script>
   let nonce = token();
-  let expected = { "dest": "", "site": "same-site", "user": "", "mode": "" };
+  let expected = { "dest": "", "site": "cross-site", "user": "", "mode": "" };
 
   // Validate various scenarios handle a request that redirects from https => http
   // correctly and avoids disclosure of any Sec- headers.
@@ -52,7 +52,7 @@
            // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
            // that `image.py` encodes data.
            "dest": undefined,
-           "site": "same-site",
+           "site": "cross-site",
            "user": undefined,
            "mode": undefined,
          });
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/redirect-http-upgrade.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/redirect-http-upgrade.tentative.sub.html
index fe55cda..4f173d9 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/redirect-http-upgrade.tentative.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/redirect-http-upgrade.tentative.sub.html
@@ -19,7 +19,7 @@
     <div id="fontTest">Upgraded font</div>
     <script>
   let nonce = token();
-  let expected = { "dest": "", "site": "same-site", "user": "", "mode": "" };
+  let expected = { "dest": "", "site": "cross-site", "user": "", "mode": "" };
 
   // Validate various scenarios handle a request that redirects from http => https correctly and add the proper Sec- headers.
   RunCommonRedirectTests("Http upgrade", upgradeRedirectTo, expected);
@@ -51,7 +51,7 @@
            // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
            // that `image.py` encodes data.
            "dest": undefined,
-           "site": "same-site",
+           "site": "cross-site",
            "user": undefined,
            "mode": undefined,
          });
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/actions.html b/third_party/blink/web_tests/external/wpt/std-toast/actions.html
index 018ce21..d07e5fd3 100644
--- a/third_party/blink/web_tests/external/wpt/std-toast/actions.html
+++ b/third_party/blink/web_tests/external/wpt/std-toast/actions.html
@@ -102,4 +102,89 @@
 
     assert_equals(actionButton, null);
 }, 'passing non-string (undefined) as action option does not create an action button');
+
+testToastElement((toast) => {
+    const actionButton = document.createElement('button');
+    actionButton.textContent = 'action';
+    toast.action = actionButton;
+
+    assertActionButtonOnToast(actionButton, toast);
+}, 'setting the action on an actionless toast inserts the element into the slot');
+
+testActionToast((toast, action) => {
+    const actionButton = document.createElement('button');
+    actionButton.textContent = 'replacement';
+    toast.action = actionButton;
+
+    assert_false(document.contains(action));
+    assertActionButtonOnToast(actionButton, toast);
+}, 'resetting the action on an action toast changes the action element');
+
+testToastElement((toast) => {
+    const text = document.createTextNode('some text');
+    assert_throws(new TypeError(), () => {
+        toast.action = text;
+    });
+}, 'setting the action to an invalid type (Text node) throws an error');
+
+testToastElement((toast) => {
+    const text = 'some text';
+    assert_throws(new TypeError(), () => {
+        toast.action = text;
+    });
+}, 'setting the action to an invalid type (string) throws an error');
+
+test(() => {
+    const actionButton = document.createElement('button');
+    actionButton.textContent = 'action';
+    const toast = showToast('Message', {action: actionButton});
+
+    assertActionButtonOnToast(actionButton, toast);
+}, 'showToast can take an Element as the action parameter');
+
+testActionToast((toast, action) => {
+    toast.action = null;
+
+    assert_not_equals(toast.action, action);
+    assert_equals(toast.querySelector('button'), null);
+}, 'setting toast.action to null removes the action from the toast');
+
+testActionToast((toast, action) => {
+    const wrongAction = document.createElement('button');
+    wrongAction.textContent = 'wrong';
+    wrongAction.setAttribute('slot', 'action');
+    toast.appendChild(wrongAction);
+
+    const correctAction = document.createElement('button');
+    correctAction.textContent = 'correct';
+    toast.action = correctAction;
+
+    assertActionButtonOnToast(correctAction, toast);
+}, 'resetting toast.action on a toast with multiple actions slotted sets properly');
+
+test(() => {
+    try {
+        Object.defineProperty(Element, Symbol.hasInstance, {
+            value: () => true,
+            configurable: true
+        });
+
+        const fakeElement = {};
+        const toast = showToast('Message');
+        assert_throws(new TypeError(), () => toast.action = fakeElement);
+    } finally {
+        delete Element[Symbol.hasInstance];
+    }
+}, 'spoofing element instance will not register as element to action setter');
+
+test(() => {
+    const iframe = document.createElement('iframe');
+    document.body.append(iframe);
+    iframe.contentDocument.body.innerHTML = '<div></div>';
+    const elementFromAnotherFrame = iframe.contentDocument.querySelector('div');
+
+    // Should not throw:
+    const toast = showToast('Message');
+    toast.action = elementFromAnotherFrame;
+}, 'element from iframe instance will pass correctly to action without throwing an error');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.any.js b/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.any.js
new file mode 100644
index 0000000..ef9c403d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.any.js
@@ -0,0 +1,40 @@
+// META: script=resources/user-timing-helper.js
+
+test(()=>{
+  const entry = new PerformanceMark("name");
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark"});
+}, "Mark entry can be created by 'new PerformanceMark(string)'.");
+
+test(()=>{
+  const entry = new PerformanceMark("name", {});
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark"});
+}, "Mark entry can be created by 'new PerformanceMark(string, {})'.");
+
+test(()=>{
+  const entry = new PerformanceMark("name", {startTime: 1});
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark", startTime: 1});
+}, "Mark entry can be created by 'new PerformanceMark(string, {startTime})'.");
+
+test(()=>{
+  const entry = new PerformanceMark("name", {detail: {info: "abc"}});
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark", detail: {info: "abc"}});
+}, "Mark entry can be created by 'new PerformanceMark(string, {detail})'.");
+
+test(()=>{
+  const entry =
+      new PerformanceMark("name", {startTime: 1, detail: {info: "abc"}});
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark", startTime: 1, detail: {info: "abc"}});
+}, "Mark entry can be created by " +
+   "'new PerformanceMark(string, {startTime, detail})'.");
+
+test(()=>{
+  const entry = new PerformanceMark("name");
+  assert_true(entry instanceof PerformanceMark);
+  checkEntry(entry, {name: "name", entryType: "mark"});
+  assert_equals(performance.getEntriesByName("name").length, 0);
+}, "Using new PerformanceMark() shouldn't add the entry to performance timeline.");
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.html b/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.html
deleted file mode 100644
index 47c9a64..0000000
--- a/third_party/blink/web_tests/external/wpt/user-timing/mark-entry-constructor.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/user-timing-helper.js"></script>
-<title>User Timing L3: create mark entry by constructor</title>
-<h1>User Timing L3: create mark entry by constructor</h1>
-<p>
-User Timing L3: Mark entry can be created by using constructor."
-</p>
-<script>
-  test(()=>{
-    const entry = new PerformanceMark("name");
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark"});
-  }, "Mark entry can be created by 'new PerformanceMark(string)'.");
-
-  test(()=>{
-    const entry = new PerformanceMark("name", {});
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark"});
-  }, "Mark entry can be created by 'new PerformanceMark(string, {})'.");
-
-  test(()=>{
-    const entry = new PerformanceMark("name", {startTime: 1});
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark", startTime: 1});
-  }, "Mark entry can be created by 'new PerformanceMark(string, {startTime})'.");
-
-  test(()=>{
-    const entry = new PerformanceMark("name", {detail: {info: "abc"}});
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark", detail: {info: "abc"}});
-  }, "Mark entry can be created by 'new PerformanceMark(string, {detail})'.");
-
-  test(()=>{
-    const entry =
-        new PerformanceMark("name", {startTime: 1, detail: {info: "abc"}});
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark", startTime: 1, detail: {info: "abc"}});
-  }, "Mark entry can be created by " +
-     "'new PerformanceMark(string, {startTime, detail})'.");
-
-  test(()=>{
-    const entry = new PerformanceMark("name");
-    assert_true(entry instanceof PerformanceMark);
-    checkEntry(entry, {name: "name", entryType: "mark"});
-    assert_equals(performance.getEntriesByName("name").length, 0);
-  }, "Using new PerformanceMark() shouldn't add the entry to performance timeline.");
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.any.js b/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.any.js
new file mode 100644
index 0000000..3207d18
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.any.js
@@ -0,0 +1,15 @@
+test(function() {
+  assert_throws(new TypeError(), function() { self.performance.mark("mark1", 123); }, "Number passed as a dict argument should cause type-error.")
+}, "Number should be rejected as the mark-options.")
+
+test(function() {
+  assert_throws(new TypeError(), function() { self.performance.mark("mark1", NaN); }, "NaN passed as a dict argument should cause type-error.")
+}, "NaN should be rejected as the mark-options.")
+
+test(function() {
+  assert_throws(new TypeError(), function() { self.performance.mark("mark1", Infinity); }, "Infinity passed as a dict argument should cause type-error.")
+}, "Infinity should be rejected as the mark-options.")
+
+test(function() {
+  assert_throws(new TypeError(), function() { self.performance.mark("mark1", "string"); }, "String passed as a dict argument should cause type-error.")
+}, "String should be rejected as the mark-options.")
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.html b/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.html
deleted file mode 100644
index c182a39..0000000
--- a/third_party/blink/web_tests/external/wpt/user-timing/mark-errors.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>UserTimingL3: errors are thrown when mark() is called incorrectly.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h1>UserTimingL3: Mark</h1>
-<script>
-  test(function() {
-    assert_throws(new TypeError(), function() { self.performance.mark("mark1", 123); }, "Number passed as a dict argument should cause type-error.")
-  }, "Number should be rejected as the mark-options.")
-
-  test(function() {
-    assert_throws(new TypeError(), function() { self.performance.mark("mark1", NaN); }, "NaN passed as a dict argument should cause type-error.")
-  }, "NaN should be rejected as the mark-options.")
-
-  test(function() {
-    assert_throws(new TypeError(), function() { self.performance.mark("mark1", Infinity); }, "Infinity passed as a dict argument should cause type-error.")
-  }, "Infinity should be rejected as the mark-options.")
-
-  test(function() {
-    assert_throws(new TypeError(), function() { self.performance.mark("mark1", "string"); }, "String passed as a dict argument should cause type-error.")
-  }, "String should be rejected as the mark-options.")
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-l3.html b/third_party/blink/web_tests/external/wpt/user-timing/mark-l3.any.js
similarity index 84%
rename from third_party/blink/web_tests/external/wpt/user-timing/mark-l3.html
rename to third_party/blink/web_tests/external/wpt/user-timing/mark-l3.any.js
index bb4b22510..407a5c8b 100644
--- a/third_party/blink/web_tests/external/wpt/user-timing/mark-l3.html
+++ b/third_party/blink/web_tests/external/wpt/user-timing/mark-l3.any.js
@@ -1,11 +1,6 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>User Timing L3: mark</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/user-timing-helper.js"></script>
-<script>
-  async_test(function (t) {
+// META: script=resources/user-timing-helper.js
+
+async_test(function (t) {
     let mark_entries = [];
     const expected_entries =
         [{ entryType: "mark", name: "mark1", detail: null},
@@ -41,5 +36,4 @@
     returned_entries.push(self.performance.mark("mark8", {startTime: 234.56}));
     returned_entries.push(self.performance.mark("mark9", {detail: {count: 3}, startTime: 345.67}));
     checkEntries(returned_entries, expected_entries);
-  }, "mark entries' detail and startTime are customizable.");
-</script>
+}, "mark entries' detail and startTime are customizable.");
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.any.js b/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.any.js
new file mode 100644
index 0000000..fa45388
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.any.js
@@ -0,0 +1,37 @@
+async_test(function (t) {
+  self.performance.clearMeasures();
+  const measure = self.performance.measure("measure1");
+  assert_true(measure instanceof PerformanceMeasure);
+  t.done();
+}, "L3: performance.measure(name) should return an entry.");
+
+async_test(function (t) {
+  self.performance.clearMeasures();
+  const measure = self.performance.measure("measure2",
+      { startTime: 12, endTime:23 });
+  assert_true(measure instanceof PerformanceMeasure);
+  t.done();
+}, "L3: performance.measure(name, param1) should return an entry.");
+
+async_test(function (t) {
+  self.performance.clearMeasures();
+  self.performance.mark("1");
+  self.performance.mark("2");
+  const measure = self.performance.measure("measure3", "1", "2");
+  assert_true(measure instanceof PerformanceMeasure);
+  t.done();
+}, "L3: performance.measure(name, param1, param2) should return an entry.");
+
+async_test(function (t) {
+  self.performance.clearMarks();
+  const mark = self.performance.mark("mark1");
+  assert_true(mark instanceof PerformanceMark);
+  t.done();
+}, "L3: performance.mark(name) should return an entry.");
+
+async_test(function (t) {
+  self.performance.clearMarks();
+  const mark = self.performance.mark("mark2", { startTime: 34 });
+  assert_true(mark instanceof PerformanceMark);
+  t.done();
+}, "L3: performance.mark(name, param) should return an entry.");
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.html b/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.html
deleted file mode 100644
index d2d8cc3c..0000000
--- a/third_party/blink/web_tests/external/wpt/user-timing/mark-measure-return-objects.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>UserTiming L3: mark/measure methods return objects.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<p>Custom User Timing: L3 API returns a mark/measure object</p>
-<div id="log"></div>
-<script>
-  async_test(function (t) {
-    self.performance.clearMeasures();
-    const measure = self.performance.measure("measure1");
-    assert_true(measure instanceof PerformanceMeasure);
-    t.done();
-  }, "L3: performance.measure(name) should return an entry.");
-
-  async_test(function (t) {
-    self.performance.clearMeasures();
-    const measure = self.performance.measure("measure2",
-        { startTime: 12, endTime:23 });
-    assert_true(measure instanceof PerformanceMeasure);
-    t.done();
-  }, "L3: performance.measure(name, param1) should return an entry.");
-
-  async_test(function (t) {
-    self.performance.clearMeasures();
-    self.performance.mark("1");
-    self.performance.mark("2");
-    const measure = self.performance.measure("measure3", "1", "2");
-    assert_true(measure instanceof PerformanceMeasure);
-    t.done();
-  }, "L3: performance.measure(name, param1, param2) should return an entry.");
-
-  async_test(function (t) {
-    self.performance.clearMarks();
-    const mark = self.performance.mark("mark1");
-    assert_true(mark instanceof PerformanceMark);
-    t.done();
-  }, "L3: performance.mark(name) should return an entry.");
-
-  async_test(function (t) {
-    self.performance.clearMarks();
-    const mark = self.performance.mark("mark2", { startTime: 34 });
-    assert_true(mark instanceof PerformanceMark);
-    t.done();
-  }, "L3: performance.mark(name, param) should return an entry.");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.any.js b/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.any.js
new file mode 100644
index 0000000..24c27c4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.any.js
@@ -0,0 +1,35 @@
+// META: script=resources/user-timing-helper.js
+
+function endTime(entry) {
+  return entry.startTime + entry.duration;
+}
+
+test(function() {
+  performance.clearMarks();
+  performance.clearMeasures();
+  const markEntry = performance.mark("mark", {startTime: 123});
+  const measureEntry = performance.measure("A", undefined, "mark");
+  assert_equals(measureEntry.startTime, 0);
+  assert_equals(endTime(measureEntry), markEntry.startTime);
+}, "When the end mark is given and the start is unprovided, the end time of the measure entry should be the end mark's time, the start time should be 0.");
+
+test(function() {
+  performance.clearMarks();
+  performance.clearMeasures();
+  const markEntry = performance.mark("mark", {startTime: 123});
+  const endMin = performance.now();
+  const measureEntry = performance.measure("A", "mark", undefined);
+  const endMax = performance.now();
+  assert_equals(measureEntry.startTime, markEntry.startTime);
+  assert_greater_than_equal(endTime(measureEntry), endMin);
+  assert_greater_than_equal(endMax, endTime(measureEntry));
+}, "When the start mark is given and the end is unprovided, the start time of the measure entry should be the start mark's time, the end should be now.");
+
+test(function() {
+  performance.clearMarks();
+  performance.clearMeasures();
+  const markEntry = performance.mark("mark", {startTime: 123});
+  const measureEntry = performance.measure("A", "mark", "mark");
+  assert_equals(endTime(measureEntry), markEntry.startTime);
+  assert_equals(measureEntry.startTime, markEntry.startTime);
+}, "When start and end mark are both given, the start time and end time of the measure entry should be the the marks' time, repectively");
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.html b/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.html
deleted file mode 100644
index 0e8dacf..0000000
--- a/third_party/blink/web_tests/external/wpt/user-timing/measure-l3.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>UserTiming L3: Measure basic usage</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-  function endTime(entry) {
-    return entry.startTime + entry.duration;
-  }
-
-  test(function() {
-    performance.clearMarks();
-    performance.clearMeasures();
-    const markEntry = performance.mark("mark", {startTime: 123});
-    const measureEntry = performance.measure("A", undefined, "mark");
-    assert_equals(measureEntry.startTime, 0);
-    assert_equals(endTime(measureEntry), markEntry.startTime);
-  }, "When the end mark is given and the start is unprovided, the end time of the measure entry should be the end mark's time, the start time should be 0.");
-
-  test(function() {
-    performance.clearMarks();
-    performance.clearMeasures();
-    const markEntry = performance.mark("mark", {startTime: 123});
-    const endMin = performance.now();
-    const measureEntry = performance.measure("A", "mark", undefined);
-    const endMax = performance.now();
-    assert_equals(measureEntry.startTime, markEntry.startTime);
-    assert_greater_than_equal(endTime(measureEntry), endMin);
-    assert_greater_than_equal(endMax, endTime(measureEntry));
-  }, "When the start mark is given and the end is unprovided, the start time of the measure entry should be the start mark's time, the end should be now.");
-
-  test(function() {
-    performance.clearMarks();
-    performance.clearMeasures();
-    const markEntry = performance.mark("mark", {startTime: 123});
-    const measureEntry = performance.measure("A", "mark", "mark");
-    assert_equals(endTime(measureEntry), markEntry.startTime);
-    assert_equals(measureEntry.startTime, markEntry.startTime);
-  }, "When start and end mark are both given, the start time and end time of the measure entry should be the the marks' time, repectively");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.html b/third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.any.js
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.html
rename to third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.any.js
index 8ba7b9f..99a2fe4d7 100644
--- a/third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.html
+++ b/third_party/blink/web_tests/external/wpt/user-timing/measure-with-dict.any.js
@@ -1,16 +1,11 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>User Timing L3: measure is customizable</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/user-timing-helper.js"></script>
-<script>
-  function cleanupPerformanceTimeline() {
+// META: script=resources/user-timing-helper.js
+
+function cleanupPerformanceTimeline() {
     performance.clearMarks();
     performance.clearMeasures();
-  }
+}
 
-  async_test(function (t) {
+async_test(function (t) {
     this.add_cleanup(cleanupPerformanceTimeline);
     let measureEntries = [];
     const timeStamp1 = 784.4;
@@ -91,9 +86,9 @@
     returnedEntries.push(self.performance.measure("measure20", undefined, 'mark1'));
     returnedEntries.push(self.performance.measure("measure21", { invalidDict:1 }, 'mark1'));
     checkEntries(returnedEntries, expectedEntries);
-  }, "measure entries' detail and start/end are customizable");
+}, "measure entries' detail and start/end are customizable");
 
-  test(function() {
+test(function() {
     this.add_cleanup(cleanupPerformanceTimeline);
     assert_throws(new TypeError(), function() {
       self.performance.measure("optionsAndNumberEnd", {'start': 2}, 12);
@@ -107,5 +102,5 @@
     assert_throws(new TypeError(), function() {
       self.performance.measure("negativeEndInOptions", {'end': -1});
     }, "measure cannot have a negative time stamp for end.");
-  }, "measure should throw a TypeError when passed an invalid argument combination");
-</script>
+}, "measure should throw a TypeError when passed an invalid argument combination");
+
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.any.js b/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.any.js
new file mode 100644
index 0000000..55cbd924
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.any.js
@@ -0,0 +1,64 @@
+test(function() {
+  performance.clearMarks();
+  const detail = { randomInfo: 123 }
+  const markEntry = new PerformanceMark("A", { detail });
+  assert_equals(markEntry.detail.randomInfo, detail.randomInfo);
+  assert_not_equals(markEntry.detail, detail);
+}, "The detail property in the mark constructor should be structured-clone.");
+
+test(function() {
+  performance.clearMarks();
+  const detail = { randomInfo: 123 }
+  const markEntry = performance.mark("A", { detail });
+  assert_not_equals(markEntry.detail, detail);
+}, "The detail property in the mark method should be structured-clone.");
+
+test(function() {
+  performance.clearMarks();
+  const markEntry = performance.mark("A");
+  assert_equals(markEntry.detail, null);
+}, "When accessing detail from a mark entry and the detail is not provided, just return a null value.");
+
+test(function() {
+  performance.clearMarks();
+  const detail = { unserializable: Symbol() };
+  assert_throws("DataCloneError", ()=>{
+    new PerformanceMark("A", { detail });
+  }, "Trying to structured-serialize a Symbol.");
+}, "Mark: Throw an exception when the detail property cannot be structured-serialized.");
+
+test(function() {
+  performance.clearMeasures();
+  const detail = { randomInfo: 123 }
+  const measureEntry = performance.measure("A", { detail });
+  assert_not_equals(measureEntry.detail, detail);
+}, "The detail property in the measure method should be structured-clone.");
+
+test(function() {
+  performance.clearMeasures();
+  const detail = { randomInfo: 123 }
+  const measureEntry = performance.measure("A", { detail });
+  assert_equals(measureEntry.detail, measureEntry.detail);
+}, "The detail property in the measure method should be the same reference.");
+
+test(function() {
+  performance.clearMeasures();
+  const measureEntry = performance.measure("A");
+  assert_equals(measureEntry.detail, null);
+}, "When accessing detail from a measure entry and the detail is not provided, just return a null value.");
+
+test(function() {
+  performance.clearMeasures();
+  const detail = { unserializable: Symbol() };
+  assert_throws("DataCloneError", ()=>{
+    performance.measure("A", { detail });
+  }, "Trying to structured-serialize a Symbol.");
+}, "Measure: Throw an exception when the detail property cannot be structured-serialized.");
+
+test(function() {
+  const bar = { 1: 2 };
+  const detail = { foo: 1, bar };
+  const mark = performance.mark("m", { detail });
+  detail.foo = 2;
+  assert_equals(mark.detail.foo, 1);
+}, "The detail object is cloned when passed to mark API.");
diff --git a/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.html b/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.html
deleted file mode 100644
index c9689fe3..0000000
--- a/third_party/blink/web_tests/external/wpt/user-timing/structured-serialize-detail.html
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<title>UserTiming L3: the detail property should be serialized-cloned.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-  test(function() {
-    performance.clearMarks();
-    const detail = { randomInfo: 123 }
-    const markEntry = new PerformanceMark("A", { detail });
-    assert_equals(markEntry.detail.randomInfo, detail.randomInfo);
-    assert_not_equals(markEntry.detail, detail);
-  }, "The detail property in the mark constructor should be structured-clone.");
-
-  test(function() {
-    performance.clearMarks();
-    const detail = { randomInfo: 123 }
-    const markEntry = performance.mark("A", { detail });
-    assert_not_equals(markEntry.detail, detail);
-  }, "The detail property in the mark method should be structured-clone.");
-
-  test(function() {
-    performance.clearMarks();
-    const markEntry = performance.mark("A");
-    assert_equals(markEntry.detail, null);
-  }, "When accessing detail from a mark entry and the detail is not provided, just return a null value.");
-
-  test(function() {
-    performance.clearMarks();
-    const detail = { unserializable: Symbol() };
-    assert_throws("DataCloneError", ()=>{
-      new PerformanceMark("A", { detail });
-    }, "Trying to structured-serialize a Symbol.");
-  }, "Mark: Throw an exception when the detail property cannot be structured-serialized.");
-
-  test(function() {
-    performance.clearMeasures();
-    const detail = { randomInfo: 123 }
-    const measureEntry = performance.measure("A", { detail });
-    assert_not_equals(measureEntry.detail, detail);
-  }, "The detail property in the measure method should be structured-clone.");
-
-  test(function() {
-    performance.clearMeasures();
-    const detail = { randomInfo: 123 }
-    const measureEntry = performance.measure("A", { detail });
-    assert_equals(measureEntry.detail, measureEntry.detail);
-  }, "The detail property in the measure method should be the same reference.");
-
-  test(function() {
-    performance.clearMeasures();
-    const measureEntry = performance.measure("A");
-    assert_equals(measureEntry.detail, null);
-  }, "When accessing detail from a measure entry and the detail is not provided, just return a null value.");
-
-  test(function() {
-    performance.clearMeasures();
-    const detail = { unserializable: Symbol() };
-    assert_throws("DataCloneError", ()=>{
-      performance.measure("A", { detail });
-    }, "Trying to structured-serialize a Symbol.");
-  }, "Measure: Throw an exception when the detail property cannot be structured-serialized.");
-
-  test(function() {
-    const bar = { 1: 2 };
-    const detail = { foo: 1, bar };
-    const mark = performance.mark("m", { detail });
-    detail.foo = 2;
-    assert_equals(mark.detail.foo, 1);
-  }, "The detail object is cloned when passed to mark API.");
-</script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height-expected.txt b/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height-expected.txt
index ec113cad..86237d4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height-expected.txt
@@ -191,7 +191,7 @@
 Resizing
 Creating element for 15
 Creating element for 16
-----list[length=20][height=84]----
+----list[length=20][height=83]----
  [0] top
 *[0] 0
 *[10] 1
@@ -216,29 +216,29 @@
 Creating element for 17
 Creating element for 18
 Creating element for 19
-----list[length=20][height=84]----
- [-116] top
- [-86] 3
- [-76] 4
- [-66] 5
- [-56] 6
- [-46] 7
- [-36] 8
- [-26] 9
- [-16] 10
-+[-6] 11
-*[4] 12
-*[14] 13
-*[24] 14
-*[34] 15
-*[44] 16
-*[54] 17
-*[64] 18
-*[74] 19
- [84] bottom
+----list[length=20][height=83]----
+ [-117] top
+ [-87] 3
+ [-77] 4
+ [-67] 5
+ [-57] 6
+ [-47] 7
+ [-37] 8
+ [-27] 9
+ [-17] 10
++[-7] 11
+*[3] 12
+*[13] 13
+*[23] 14
+*[33] 15
+*[43] 16
+*[53] 17
+*[63] 18
+*[73] 19
+ [83] bottom
 
 Scrolling to 5
-----list[length=20][height=84]----
+----list[length=20][height=83]----
  [-50] top
  [-50] 0
  [-40] 1
@@ -263,7 +263,7 @@
  [150] bottom
 
 Scrolling to 12
-----list[length=20][height=84]----
+----list[length=20][height=83]----
  [-50] top
  [-50] 0
  [-40] 1
@@ -288,29 +288,29 @@
  [150] bottom
 
 Scrolling to 13
-----list[length=20][height=84]----
- [-56] top
- [-56] 0
- [-46] 1
- [-36] 2
- [-26] 3
- [-16] 4
-+[-6] 5
-*[4] 6
-*[14] 7
-*[24] 8
-*[34] 9
-*[44] 10
-*[54] 11
-*[64] 12
-*[74] 13
- [84] 14
- [94] 15
- [104] 16
- [114] 17
- [124] 18
- [134] 19
- [144] bottom
+----list[length=20][height=83]----
+ [-57] top
+ [-57] 0
+ [-47] 1
+ [-37] 2
+ [-27] 3
+ [-17] 4
++[-7] 5
+*[3] 6
+*[13] 7
+*[23] 8
+*[33] 9
+*[43] 10
+*[53] 11
+*[63] 12
+*[73] 13
+ [83] 14
+ [93] 15
+ [103] 16
+ [113] 17
+ [123] 18
+ [133] 19
+ [143] bottom
 
 Changing the item height
 Creating element for 0
@@ -328,87 +328,87 @@
 Creating element for 12
 Creating element for 13
 Creating element for 14
-----list[length=20][height=84]----
- [-56] top
- [-56] 0
- [-41] 1
- [-26] 2
-+[-11] 3
-*[4] 4
-*[19] 5
-*[34] 6
-*[49] 7
-*[64] 8
-+[79] 9
- [94] 10
- [109] 11
- [124] 12
- [139] 13
- [154] 14
- [169] bottom
+----list[length=20][height=83]----
+ [-57] top
+ [-57] 0
+ [-42] 1
+ [-27] 2
++[-12] 3
+*[3] 4
+*[18] 5
+*[33] 6
+*[48] 7
+*[63] 8
++[78] 9
+ [93] 10
+ [108] 11
+ [123] 12
+ [138] 13
+ [153] 14
+ [168] bottom
 
 Selecting 7
 Selection changed from null to 7
-----list[length=20][height=84]----
- [-56] top
- [-56] 0
- [-41] 1
- [-26] 2
-+[-11] 3
-*[4] 4
-*[19] 5
-*[34] 6
-*[49] 7 (selected)
-*[64] 8
-+[79] 9
- [94] 10
- [109] 11
- [124] 12
- [139] 13
- [154] 14
- [169] bottom
+----list[length=20][height=83]----
+ [-57] top
+ [-57] 0
+ [-42] 1
+ [-27] 2
++[-12] 3
+*[3] 4
+*[18] 5
+*[33] 6
+*[48] 7 (selected)
+*[63] 8
++[78] 9
+ [93] 10
+ [108] 11
+ [123] 12
+ [138] 13
+ [153] 14
+ [168] bottom
 
 Replacing 7 with 27
 Creating element for 27
 Selection changed from 7 to 10
-----list[length=20][height=84]----
- [-56] top
- [-56] 0
- [-41] 1
- [-26] 2
-+[-11] 3
-*[4] 4
-*[19] 5
-*[34] 6
-*[49] 27
-*[64] 8
-+[79] 9
- [94] 10 (selected)
- [109] 11
- [124] 12
- [139] 13
- [154] 14
- [169] bottom
+----list[length=20][height=83]----
+ [-57] top
+ [-57] 0
+ [-42] 1
+ [-27] 2
++[-12] 3
+*[3] 4
+*[18] 5
+*[33] 6
+*[48] 27
+*[63] 8
++[78] 9
+ [93] 10 (selected)
+ [108] 11
+ [123] 12
+ [138] 13
+ [153] 14
+ [168] bottom
 
 Replacing 18, 19 with 28, 29
-----list[length=20][height=84]----
- [-56] top
- [-56] 0
- [-41] 1
- [-26] 2
-+[-11] 3
-*[4] 4
-*[19] 5
-*[34] 6
-*[49] 27
-*[64] 8
-+[79] 9
- [94] 10 (selected)
- [109] 11
- [124] 12
- [139] 13
- [154] 14
- [169] bottom
+----list[length=20][height=83]----
+ [-57] top
+ [-57] 0
+ [-42] 1
+ [-27] 2
++[-12] 3
+*[3] 4
+*[18] 5
+*[33] 6
+*[48] 27
+*[63] 8
++[78] 9
+ [93] 10 (selected)
+ [108] 11
+ [123] 12
+ [138] 13
+ [153] 14
+ [168] bottom
 
 PageDown
 Creating element for 15
@@ -417,144 +417,144 @@
 Creating element for 28
 Creating element for 29
 Selection changed from 10 to 17
-----list[length=20][height=84]----
- [-186] top
- [-96] 6
- [-81] 27
- [-66] 8
- [-51] 9
- [-36] 10
- [-21] 11
-+[-6] 12
-*[9] 13
-*[24] 14
-*[39] 15
-*[54] 16
-*[69] 17 (selected)
- [84] 28
- [99] 29
- [114] bottom
+----list[length=20][height=83]----
+ [-187] top
+ [-97] 6
+ [-82] 27
+ [-67] 8
+ [-52] 9
+ [-37] 10
+ [-22] 11
++[-7] 12
+*[8] 13
+*[23] 14
+*[38] 15
+*[53] 16
+*[68] 17 (selected)
+ [83] 28
+ [98] 29
+ [113] bottom
 
 Replacing 1, 2, 3 with [31-43]
-----list[length=30][height=84]----
- [-336] top
- [-96] 6
- [-81] 27
- [-66] 8
- [-51] 9
- [-36] 10
- [-21] 11
-+[-6] 12
-*[9] 13
-*[24] 14
-*[39] 15
-*[54] 16
-*[69] 17 (selected)
- [84] 28
- [99] 29
- [114] bottom
+----list[length=30][height=83]----
+ [-337] top
+ [-97] 6
+ [-82] 27
+ [-67] 8
+ [-52] 9
+ [-37] 10
+ [-22] 11
++[-7] 12
+*[8] 13
+*[23] 14
+*[38] 15
+*[53] 16
+*[68] 17 (selected)
+ [83] 28
+ [98] 29
+ [113] bottom
 
 Scrolling to 13 (center)
-----list[length=30][height=84]----
- [-310] top
- [-85] 5
- [-70] 6
- [-55] 27
- [-40] 8
- [-25] 9
-+[-10] 10
-*[5] 11
-*[20] 12
-*[35] 13
-*[50] 14
-*[65] 15
-+[80] 16
- [95] 17 (selected)
- [110] 28
- [125] 29
- [140] bottom
+----list[length=30][height=83]----
+ [-311] top
+ [-86] 5
+ [-71] 6
+ [-56] 27
+ [-41] 8
+ [-26] 9
++[-11] 10
+*[4] 11
+*[19] 12
+*[34] 13
+*[49] 14
+*[64] 15
++[79] 16
+ [94] 17 (selected)
+ [109] 28
+ [124] 29
+ [139] bottom
 
 ArrowUp
 Selection changed from 17 to 15
-----list[length=30][height=84]----
- [-310] top
- [-85] 5
- [-70] 6
- [-55] 27
- [-40] 8
- [-25] 9
-+[-10] 10
-*[5] 11
-*[20] 12
-*[35] 13
-*[50] 14
-*[65] 15 (selected)
-+[80] 16
- [95] 17
- [110] 28
- [125] 29
- [140] bottom
+----list[length=30][height=83]----
+ [-311] top
+ [-86] 5
+ [-71] 6
+ [-56] 27
+ [-41] 8
+ [-26] 9
++[-11] 10
+*[4] 11
+*[19] 12
+*[34] 13
+*[49] 14
+*[64] 15 (selected)
++[79] 16
+ [94] 17
+ [109] 28
+ [124] 29
+ [139] bottom
 
 Selecting -1
 Selection changed from 15 to null
-----list[length=30][height=84]----
- [-310] top
- [-85] 5
- [-70] 6
- [-55] 27
- [-40] 8
- [-25] 9
-+[-10] 10
-*[5] 11
-*[20] 12
-*[35] 13
-*[50] 14
-*[65] 15
-+[80] 16
- [95] 17
- [110] 28
- [125] 29
- [140] bottom
+----list[length=30][height=83]----
+ [-311] top
+ [-86] 5
+ [-71] 6
+ [-56] 27
+ [-41] 8
+ [-26] 9
++[-11] 10
+*[4] 11
+*[19] 12
+*[34] 13
+*[49] 14
+*[64] 15
++[79] 16
+ [94] 17
+ [109] 28
+ [124] 29
+ [139] bottom
 
 ArrowUp
 Selection changed from null to 17
-----list[length=30][height=84]----
- [-336] top
- [-96] 6
- [-81] 27
- [-66] 8
- [-51] 9
- [-36] 10
- [-21] 11
-+[-6] 12
-*[9] 13
-*[24] 14
-*[39] 15
-*[54] 16
-*[69] 17 (selected)
- [84] 28
- [99] 29
- [114] bottom
+----list[length=30][height=83]----
+ [-337] top
+ [-97] 6
+ [-82] 27
+ [-67] 8
+ [-52] 9
+ [-37] 10
+ [-22] 11
++[-7] 12
+*[8] 13
+*[23] 14
+*[38] 15
+*[53] 16
+*[68] 17 (selected)
+ [83] 28
+ [98] 29
+ [113] bottom
 
 Selecting -1
 Selection changed from 17 to null
-----list[length=30][height=84]----
- [-336] top
- [-96] 6
- [-81] 27
- [-66] 8
- [-51] 9
- [-36] 10
- [-21] 11
-+[-6] 12
-*[9] 13
-*[24] 14
-*[39] 15
-*[54] 16
-*[69] 17
- [84] 28
- [99] 29
- [114] bottom
+----list[length=30][height=83]----
+ [-337] top
+ [-97] 6
+ [-82] 27
+ [-67] 8
+ [-52] 9
+ [-37] 10
+ [-22] 11
++[-7] 12
+*[8] 13
+*[23] 14
+*[38] 15
+*[53] 16
+*[68] 17
+ [83] 28
+ [98] 29
+ [113] bottom
 
 ArrowDown
 Creating element for 41
@@ -569,7 +569,7 @@
 Creating element for 32
 Creating element for 31
 Selection changed from null to 0
-----list[length=30][height=84]----
+----list[length=30][height=83]----
  [0] top
 *[0] 0 (selected)
 *[15] 31
@@ -587,7 +587,7 @@
 
 Selecting -1
 Selection changed from 0 to null
-----list[length=30][height=84]----
+----list[length=30][height=83]----
  [0] top
 *[0] 0
 *[15] 31
@@ -607,37 +607,37 @@
 Creating element for 42
 Creating element for 43
 Selection changed from null to 12
-----list[length=30][height=84]----
- [-261] top
- [-96] 41
- [-81] 42
- [-66] 43
- [-51] 4
- [-36] 5
- [-21] 6
-+[-6] 27
-*[9] 8
-*[24] 9
-*[39] 10
-*[54] 11
-*[69] 12 (selected)
- [84] 13
- [99] 14
- [114] 15
- [129] 16
- [144] 17
- [159] 28
- [174] bottom
+----list[length=30][height=83]----
+ [-262] top
+ [-97] 41
+ [-82] 42
+ [-67] 43
+ [-52] 4
+ [-37] 5
+ [-22] 6
++[-7] 27
+*[8] 8
+*[23] 9
+*[38] 10
+*[53] 11
+*[68] 12 (selected)
+ [83] 13
+ [98] 14
+ [113] 15
+ [128] 16
+ [143] 17
+ [158] 28
+ [173] bottom
 
 Replacing all but 29 with []
 Selection changed from 12 to null
-----list[length=1][height=84]----
+----list[length=1][height=83]----
  [0] top
 *[0] 29
 *[15] bottom
 
 ArrowDown
-----list[length=1][height=84]----
+----list[length=1][height=83]----
  [0] top
 *[0] 29
 *[15] bottom
@@ -646,7 +646,7 @@
 Creating element for 5
 Creating element for 6
 Creating element for 7
-----list[length=1][height=84]----
+----list[length=1][height=83]----
  [0] top
 *[0] 5
 *[15] 6
@@ -655,7 +655,7 @@
 
 Pushing 8
 Creating element for 8
-----list[length=1][height=84]----
+----list[length=1][height=83]----
  [0] top
 *[0] 5
 *[15] 6
@@ -664,7 +664,7 @@
 *[60] bottom
 
 Pushing 9 to old model
-----list[length=2][height=84]----
+----list[length=2][height=83]----
  [0] top
 *[0] 5
 *[15] 6
diff --git a/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height.js b/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height.js
index 15cafb0d..d89bfb1 100644
--- a/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height.js
+++ b/third_party/blink/web_tests/http/tests/devtools/unit/list-control-equal-height.js
@@ -118,7 +118,7 @@
   dumpList();
 
   TestRunner.addResult('Resizing');
-  list.element.style.height = '84px';
+  list.element.style.height = '83px';
   list.viewportResized();
   dumpList();
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header-expected.txt b/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header-expected.txt
new file mode 100644
index 0000000..289a663
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header-expected.txt
@@ -0,0 +1,5 @@
+Tests workspace view file system headers
+
+File system name: test
+File system path: file:///this/is/a/test
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header.js b/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header.js
new file mode 100644
index 0000000..634d9c7f
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/workspace-view-file-system-header.js
@@ -0,0 +1,22 @@
+// 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 workspace view file system headers\n');
+  await TestRunner.loadModule('bindings_test_runner');
+
+  const fs = new BindingsTestRunner.TestFileSystem('file:///this/is/a/test');
+  await fs.reportCreatedPromise();
+
+  await UI.viewManager.showView('workspace');
+  const workspaceElement = (await UI.viewManager.view('workspace').widget()).element;
+
+  const fsName = workspaceElement.querySelector('.file-system-name').textContent;
+  const fsPath = workspaceElement.querySelector('.file-system-path').textContent;
+
+  TestRunner.addResult(`File system name: ${fsName}`);
+  TestRunner.addResult(`File system path: ${fsPath}`);
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt b/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt
new file mode 100644
index 0000000..90d9b8f
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt
@@ -0,0 +1,12 @@
+CONSOLE MESSAGE: line 38: Testing main world. Eval should be blocked by main world CSP.
+CONSOLE MESSAGE: line 7: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
+
+CONSOLE MESSAGE: line 13: PASS: eval blocked as expected.
+CONSOLE MESSAGE: line 44: Testing isolated world with no csp. Eval should be allowed.
+CONSOLE MESSAGE: PASS: eval allowed as expected.
+CONSOLE MESSAGE: line 55: Testing isolated world with strict csp.
+CONSOLE MESSAGE: line 58: internals.runtimeFlags.isolatedWorldCSPEnabled is false
+CONSOLE MESSAGE: PASS: eval allowed as expected.
+CONSOLE MESSAGE: line 68: Testing isolated world with permissive csp.
+CONSOLE MESSAGE: PASS: eval allowed as expected.
+This tests the handling of unsafe-eval CSP checks and its interaction with the isolated world CSP.
diff --git a/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp.html b/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp.html
new file mode 100644
index 0000000..5585b518
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/security/isolatedWorld/isolated-world-eval-csp.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
+<script src="resources/isolated-world-eval-csp.js"></script>
+</head>
+<body id="body">
+    <p>
+        This tests the handling of unsafe-eval CSP checks and its interaction
+        with the isolated world CSP.
+    </p>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/security/isolatedWorld/resources/isolated-world-eval-csp.js b/third_party/blink/web_tests/http/tests/security/isolatedWorld/resources/isolated-world-eval-csp.js
new file mode 100644
index 0000000..617233a3
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/security/isolatedWorld/resources/isolated-world-eval-csp.js
@@ -0,0 +1,100 @@
+function testEval(expectBlocked) {
+  let evalBlocked;
+  try {
+    const x = eval('200');
+    evalBlocked = (x != 200);
+  } catch(e) {
+    console.log(e);
+    evalBlocked = true;
+  }
+  finally {
+    if (expectBlocked === evalBlocked) {
+      if (expectBlocked)
+        console.log('PASS: eval blocked as expected.');
+      else
+        console.log('PASS: eval allowed as expected.');
+    } else {
+      if (expectBlocked)
+        console.log('FAIL: eval allowed unexpectedly.');
+      else
+        console.log('FAIL: eval blocked unexpectedly.');
+    }
+    window.postMessage('next', '*');
+  }
+}
+
+let isolatedWorldId = 1;
+const isolatedWorldSecurityOrigin = 'chrome-extensions://123';
+
+function testEvalInIsolatedWorld(expectBlocked) {
+  const expectBlockedStr = expectBlocked ? 'true' : 'false';
+  testRunner.evaluateScriptInIsolatedWorld(
+      isolatedWorldId,
+      String(testEval.toString()) + `\ntestEval(${expectBlockedStr});`);
+}
+
+const tests = [
+  function() {
+    console.log(
+        'Testing main world. Eval should be blocked by main world CSP.');
+    testEval(true);
+  },
+  function() {
+    // TODO(karandeepb): Ideally we should use the main world CSP in this case.
+    console.log(
+        'Testing isolated world with no csp. Eval should be allowed.');
+    testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
+    testEvalInIsolatedWorld(false);
+
+    // We use a different isolated world ID for each test since the eval-based
+    // CSP checks are set-up when a v8::context is initialized. This happens for
+    // an isolated world when a script is executed in it for the first time.
+    isolatedWorldId++;
+  },
+  function() {
+    console.log('Testing isolated world with strict csp.');
+    testRunner.setIsolatedWorldInfo(
+        isolatedWorldId, isolatedWorldSecurityOrigin, 'script-src \'none\'');
+    console.log(
+        'internals.runtimeFlags.isolatedWorldCSPEnabled is ' +
+        internals.runtimeFlags.isolatedWorldCSPEnabled);
+    const expectBlocked = internals.runtimeFlags.isolatedWorldCSPEnabled;
+    testEvalInIsolatedWorld(expectBlocked);
+
+    testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
+    isolatedWorldId++;
+  },
+  function() {
+    console.log('Testing isolated world with permissive csp.');
+    testRunner.setIsolatedWorldInfo(
+        isolatedWorldId, isolatedWorldSecurityOrigin,
+        'script-src \'unsafe-eval\'');
+    testEvalInIsolatedWorld(false);
+
+    testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
+    isolatedWorldId++;
+  },
+];
+
+// This test is meaningless without testRunner.
+if (window.testRunner) {
+  testRunner.dumpAsText();
+  testRunner.waitUntilDone();
+
+  let currentTest = 0;
+  window.addEventListener('message', function(e) {
+    if (e.data == 'next') {
+      // Move to the next test.
+      currentTest++;
+      if (currentTest == tests.length) {
+        testRunner.notifyDone();
+        return;
+      }
+
+      // Move to the next sub-test.
+      tests[currentTest]();
+    }
+  }, false);
+
+  tests[0]();
+}
diff --git a/third_party/blink/web_tests/images/fallback-when-image-loading-disabled-expected.html b/third_party/blink/web_tests/images/fallback-when-image-loading-disabled-expected.html
new file mode 100644
index 0000000..b13eefc
--- /dev/null
+++ b/third_party/blink/web_tests/images/fallback-when-image-loading-disabled-expected.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>Fallback content should be shown when image loading is disabled (reference)</title>
+<img src="not-resources/mu.png" alt="Chair spinning">
diff --git a/third_party/blink/web_tests/images/fallback-when-image-loading-disabled.html b/third_party/blink/web_tests/images/fallback-when-image-loading-disabled.html
new file mode 100644
index 0000000..4b67062
--- /dev/null
+++ b/third_party/blink/web_tests/images/fallback-when-image-loading-disabled.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Fallback content should be shown when image loading is disabled</title>
+<script>
+  if (window.internals)
+    internals.settings.setLoadsImagesAutomatically(false);
+</script>
+<body></body>
+<script>
+  let image = new Image();
+  image.src = "resources/mu.png?" + Math.random();
+  image.alt = "Chair spinning";
+  document.body.appendChild(image);
+</script>
diff --git a/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html b/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
index 5627716..f44025a 100644
--- a/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
+++ b/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
@@ -13,26 +13,36 @@
 
     video.oncanplaythrough = t.step_func_done(function() {
         var cue = textTrackCueElementByIndex(video, 0).firstChild;
+        var displayTree = textTrackCueElementByIndex(video, 0);
         var cueStyle = getComputedStyle(cue);
+        var displayTreeStyle = getComputedStyle(displayTree);
         // These are the expected default cue settings per spec
         // http://dev.w3.org/html5/webvtt/#applying-css-properties-to-webvtt-node-objects
         assert_equals(cueStyle.color, "rgb(255, 255, 255)");
         assert_equals(cueStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
         assert_equals(cueStyle.fontFamily, "sans-serif");
+        assert_equals(displayTreeStyle.backgroundColor, "rgba(0, 0, 0, 0)");
+        assert_equals(displayTreeStyle.padding, "0px");
 
         // Apply user settings for color and font-size and verify that the other internal settings are retained.
         internals.settings.setTextTrackTextColor("purple");
         internals.settings.setTextTrackTextSize("14px");
+        internals.settings.setTextTrackWindowColor("rgba(0, 0, 0, 0.8)");
+        internals.settings.setTextTrackWindowPadding("5px");
 
         video.currentTime = 0.3;
 
-        cue = textTrackCueElementByIndex(video, 0).firstChild;
-        cueStyle = getComputedStyle(cue);
+        var cue = textTrackCueElementByIndex(video, 0).firstChild;
+        var displayTree = textTrackCueElementByIndex(video, 0);
+        var cueStyle = getComputedStyle(cue);
+        var displayTreeStyle = getComputedStyle(displayTree);
         assert_equals(cueStyle.color, "rgb(128, 0, 128)");
         assert_equals(cueStyle.fontSize, "14px");
+        assert_equals(displayTreeStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
+        assert_equals(displayTreeStyle.padding, "5px");
         // When there is no user setting specified for background-color and font-family, the internal settings are applied.
         assert_equals(cueStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
         assert_equals(cueStyle.fontFamily, "sans-serif");
     });
 });
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/media/track/track-cue-rendering-snap-to-lines-not-set.html b/third_party/blink/web_tests/media/track/track-cue-rendering-snap-to-lines-not-set.html
index d08e1f18..a92b0bc 100644
--- a/third_party/blink/web_tests/media/track/track-cue-rendering-snap-to-lines-not-set.html
+++ b/third_party/blink/web_tests/media/track/track-cue-rendering-snap-to-lines-not-set.html
@@ -35,7 +35,7 @@
     ];
     var cueRenderingPosition = [
             // Number of active cues 1.
-            [[0 ,100, "center"]],
+            [[0, 100, "center"]],
             [[0, 50, "center"]],
             [[0, 0, "start"]],
             [[0, 0, "end"]],
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 7df4966..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 9b0aff8d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 9b0aff8d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.11/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 9b0aff8d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.12/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 9b0aff8d..0000000
--- a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/mac-retina/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
index d8c22a3f..1c857e81 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
index d8c22a3f..1c857e81 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-expected.png
index 9b0aff8d..dfd5dd9b 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
index 812b4172..a3d35b18 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
index 2e8ba71..1708a703 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
index 2e8ba71..1708a703 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-cross-fade-png-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-expected.png
index 7df4966..3fb2ae1 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-image-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
index 1dde38a..5874dc2 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/cross-fade-background-size-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
new file mode 100644
index 0000000..302ccbb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-repeat-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
new file mode 100644
index 0000000..72b8ba2f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
new file mode 100644
index 0000000..2a2b17a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-expected.png
deleted file mode 100644
index 7df4966..0000000
--- a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-image-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
deleted file mode 100644
index ca051172..0000000
--- a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/color-profile-mask-image-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png b/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
deleted file mode 100644
index b9f331a..0000000
--- a/third_party/blink/web_tests/platform/win7/virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.svg b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.svg
new file mode 100644
index 0000000..cbf6053
--- /dev/null
+++ b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+    <rect  x="50" y="50"  width="400" height="400" rx="0"  ry="0"  fill="#00ff00" />
+    <rect x="150" y="100" width="200" height="300" rx="50" ry="50" fill="#0000ff" />
+    <rect x="200" y="150" width="100" height="200" rx="50" ry="50" fill="#ff0000" />
+</svg>
diff --git a/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.txt b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.txt
new file mode 100644
index 0000000..210335f
--- /dev/null
+++ b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii-expected.txt
@@ -0,0 +1 @@
+CONSOLE ERROR: line 4: Error: <rect> attribute ry: A negative value is not valid. ("-100")
diff --git a/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii.svg b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii.svg
new file mode 100644
index 0000000..fe98961
--- /dev/null
+++ b/third_party/blink/web_tests/svg/custom/rect-calc-corner-radii.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+    <rect  x="50" y="50"  width="400" height="400" rx="calc(-50)"  ry="calc(-50)"  fill="#00ff00" />
+    <rect x="150" y="100" width="200" height="300" rx="calc(20% - 50)" ry="50" fill="#0000ff" />
+    <rect x="200" y="150" width="100" height="200" rx="50" ry="-100" fill="#ff0000" />
+</svg>
diff --git a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-button-negative-tabindex.html b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-button-negative-tabindex.html
new file mode 100644
index 0000000..bccdec77
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-button-negative-tabindex.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="../../../../../resources/testharness.js"></script>
+<script src="../../../../../resources/testharnessreport.js"></script>
+<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/page/spatial_navigation.mojom.js"></script>
+<script src="../../../../../fast/spatial-navigation/resources/mock-snav-service.js"></script>
+<script src="../../../../../fast/spatial-navigation/resources/snav-testharness.js"></script>
+
+<style>
+  div {
+    width: 130px;
+    height: 130px;
+    border: 1px solid black;
+  }
+</style>
+<button tabindex="-1" id="unselectable">Unselectable</button>
+<br>
+<br>
+<button id="selectable"">Selectable</button>
+
+<script>
+  const unselectable = document.getElementById("unselectable");
+  const selectable = document.getElementById("selectable");
+
+  // This test checks that tabindex="-1" is effective on a button element,
+  // which overrides Element::IsKeyboardFocusable.
+  test(() => {
+    assert_true(!!window.internals);
+
+    snav.triggerMove('Down');
+    assert_equals(window.internals.interestedElement,
+                  selectable,
+                  "Expected interest to move to |selectable| button.");
+
+    snav.triggerMove('Up');
+    assert_equals(window.internals.interestedElement,
+                  selectable,
+                  "Expected interest to stay on |selectable| button.");
+  }, "Cannot navigate to button with tabindex'-1'");
+</script>
diff --git a/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png b/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
index aeb4daf..bb4b4529 100644
--- a/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
+++ b/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-background-image-cover-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png b/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
index 8d371ab..e433e7a 100644
--- a/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
+++ b/third_party/blink/web_tests/virtual/gpu-rasterization/images/color-profile-image-canvas-pattern-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/isolated_world_csp/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt b/third_party/blink/web_tests/virtual/isolated_world_csp/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt
new file mode 100644
index 0000000..af40f2b54
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/isolated_world_csp/http/tests/security/isolatedWorld/isolated-world-eval-csp-expected.txt
@@ -0,0 +1,14 @@
+CONSOLE MESSAGE: line 38: Testing main world. Eval should be blocked by main world CSP.
+CONSOLE MESSAGE: line 7: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
+
+CONSOLE MESSAGE: line 13: PASS: eval blocked as expected.
+CONSOLE MESSAGE: line 44: Testing isolated world with no csp. Eval should be allowed.
+CONSOLE MESSAGE: PASS: eval allowed as expected.
+CONSOLE MESSAGE: line 55: Testing isolated world with strict csp.
+CONSOLE MESSAGE: line 58: internals.runtimeFlags.isolatedWorldCSPEnabled is true
+CONSOLE MESSAGE: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'none'".
+
+CONSOLE MESSAGE: PASS: eval blocked as expected.
+CONSOLE MESSAGE: line 68: Testing isolated world with permissive csp.
+CONSOLE MESSAGE: PASS: eval allowed as expected.
+This tests the handling of unsafe-eval CSP checks and its interaction with the isolated world CSP.
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-entry-constructor.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-errors.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-l3.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/mark-measure-return-objects.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-l3.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict.any-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict.any-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict.any.worker-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/measure-with-dict.any.worker-expected.txt
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any-expected.txt
similarity index 90%
rename from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt
rename to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any-expected.txt
index 92ca97d75..40cdb1cd 100644
--- a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt
+++ b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any-expected.txt
@@ -3,14 +3,14 @@
 FAIL The detail property in the mark method should be structured-clone. Cannot read property 'detail' of null
 FAIL When accessing detail from a mark entry and the detail is not provided, just return a null value. Cannot read property 'detail' of null
 FAIL Mark: Throw an exception when the detail property cannot be structured-serialized. assert_throws: Trying to structured-serialize a Symbol. function "()=>{
-      new PerformanceMark("A", { detail });
-    }" did not throw
+    new PerformanceMark("A", { detail });
+  }" did not throw
 FAIL The detail property in the measure method should be structured-clone. Cannot read property 'detail' of null
 FAIL The detail property in the measure method should be the same reference. Cannot read property 'detail' of null
 FAIL When accessing detail from a measure entry and the detail is not provided, just return a null value. Cannot read property 'detail' of null
 FAIL Measure: Throw an exception when the detail property cannot be structured-serialized. assert_throws: Trying to structured-serialize a Symbol. function "()=>{
-      performance.measure("A", { detail });
-    }" did not throw
+    performance.measure("A", { detail });
+  }" did not throw
 FAIL The detail object is cloned when passed to mark API. Cannot read property 'detail' of null
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any.worker-expected.txt
similarity index 90%
copy from third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt
copy to third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any.worker-expected.txt
index 92ca97d75..40cdb1cd 100644
--- a/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail-expected.txt
+++ b/third_party/blink/web_tests/virtual/user-timing-l2/external/wpt/user-timing/structured-serialize-detail.any.worker-expected.txt
@@ -3,14 +3,14 @@
 FAIL The detail property in the mark method should be structured-clone. Cannot read property 'detail' of null
 FAIL When accessing detail from a mark entry and the detail is not provided, just return a null value. Cannot read property 'detail' of null
 FAIL Mark: Throw an exception when the detail property cannot be structured-serialized. assert_throws: Trying to structured-serialize a Symbol. function "()=>{
-      new PerformanceMark("A", { detail });
-    }" did not throw
+    new PerformanceMark("A", { detail });
+  }" did not throw
 FAIL The detail property in the measure method should be structured-clone. Cannot read property 'detail' of null
 FAIL The detail property in the measure method should be the same reference. Cannot read property 'detail' of null
 FAIL When accessing detail from a measure entry and the detail is not provided, just return a null value. Cannot read property 'detail' of null
 FAIL Measure: Throw an exception when the detail property cannot be structured-serialized. assert_throws: Trying to structured-serialize a Symbol. function "()=>{
-      performance.measure("A", { detail });
-    }" did not throw
+    performance.measure("A", { detail });
+  }" did not throw
 FAIL The detail object is cloned when passed to mark API. Cannot read property 'detail' of null
 Harness: the test ran to completion.
 
diff --git a/third_party/libjingle_xmpp/BUILD.gn b/third_party/libjingle_xmpp/BUILD.gn
index 6eec6ca..3364a1a3 100644
--- a/third_party/libjingle_xmpp/BUILD.gn
+++ b/third_party/libjingle_xmpp/BUILD.gn
@@ -53,11 +53,6 @@
 
   deps = [
     "//base",
-
-    # TODO(mbonadei): The dependency on the TQ is not directly
-    # needed but removing it causes a linker error that it is
-    # difficult to track down.
-    "//third_party/webrtc_overrides:task_queue_impl",
   ]
   public_deps = [
     "//third_party/expat",
@@ -104,11 +99,6 @@
     "//net",
     "//third_party/webrtc/rtc_base/third_party/base64",
     "//third_party/webrtc/rtc_base/third_party/sigslot",
-
-    # TODO(mbonadei): The dependency on the TQ is not directly
-    # needed but removing it causes a linker error that it is
-    # difficult to track down.
-    "//third_party/webrtc_overrides:task_queue_impl",
   ]
   public_deps = [
     ":rtc_task_runner",
@@ -138,7 +128,6 @@
     "//base/test:test_support",
     "//testing/gtest",
     "//third_party/webrtc/rtc_base:rtc_base",
-    "//third_party/webrtc_overrides:task_queue_impl",
   ]
 
   sources = [
diff --git a/third_party/metrics_proto/BUILD.gn b/third_party/metrics_proto/BUILD.gn
index 2e5c90c..cef117f 100644
--- a/third_party/metrics_proto/BUILD.gn
+++ b/third_party/metrics_proto/BUILD.gn
@@ -29,6 +29,7 @@
     "ukm/report.proto",
     "ukm/source.proto",
     "user_action_event.proto",
+    "user_demographics.proto",
   ]
   proto_in_dir = "."
 }
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index 03cf8ea..dfaf4d2 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 255196992
-Date: 2019/06/26 UTC
+Version: 257643598
+Date: 2019/07/11 UTC
 License: BSD
 Security Critical: Yes
 
diff --git a/third_party/metrics_proto/chrome_user_metrics_extension.proto b/third_party/metrics_proto/chrome_user_metrics_extension.proto
index d4a3920..361aeb7 100644
--- a/third_party/metrics_proto/chrome_user_metrics_extension.proto
+++ b/third_party/metrics_proto/chrome_user_metrics_extension.proto
@@ -24,8 +24,9 @@
 import "trace_log.proto";
 import "translate_event.proto";
 import "user_action_event.proto";
+import "user_demographics.proto";
 
-// Next tag: 21
+// Next tag: 22
 message ChromeUserMetricsExtension {
   // The product (i.e. end user application) for a given UMA log.
   enum Product {
@@ -66,6 +67,11 @@
   // Information about the user's browser and system configuration.
   optional SystemProfileProto system_profile = 3;
 
+  // The user's demographic information. This data is made available to Chrome
+  // via syncable priority pref, so is only available if the user is signed-in
+  // and syncing.
+  optional UserDemographicsProto user_demographics = 21;
+
   // This message will log one or more of the following event types:
   repeated UserActionEventProto user_action_event = 4;
   repeated OmniboxEventProto omnibox_event = 5;
diff --git a/third_party/metrics_proto/system_profile.proto b/third_party/metrics_proto/system_profile.proto
index a17a8ce4..5bee73a 100644
--- a/third_party/metrics_proto/system_profile.proto
+++ b/third_party/metrics_proto/system_profile.proto
@@ -15,7 +15,7 @@
 
 // Stores information about the user's brower and system configuration.
 // The system configuration fields are recorded once per client session.
-// Next tag: 30
+// Next tag: 31
 message SystemProfileProto {
   // The time when the client was compiled/linked, in seconds since the epoch.
   optional int64 build_timestamp = 1;
@@ -130,7 +130,7 @@
   optional OS os = 5;
 
   // Information on the user's hardware.
-  // Next tag: 19
+  // Next tag: 20
   message Hardware {
     // CPU architecture. Taken from uname -m and modified in Chromium logic.
     // Common options are: x86, x86_64, armv7l, armv8l, aarch64.
@@ -184,6 +184,18 @@
     optional float max_dpi_x = 9;
     optional float max_dpi_y = 10;
 
+    // Device form factors.
+    enum FormFactor {
+      FORM_FACTOR_UNKNOWN = 0;
+      FORM_FACTOR_DESKTOP = 1;
+      FORM_FACTOR_PHONE = 2;
+      FORM_FACTOR_TABLET = 3;
+      FORM_FACTOR_KIOSK = 4;
+    }
+
+    // The device form factor.
+    optional FormFactor form_factor = 19;
+
     // Information on the CPU obtained by CPUID.
     message CPU {
       // A 12 character string naming the vendor, e.g. "GeniuneIntel".
@@ -298,6 +310,62 @@
     }
     optional Bluetooth bluetooth = 11;
 
+    // Information about connected USB devices on Chrome OS systems.
+    message USB {
+      // The number of USB buses on the system.
+      optional uint32 bus_count = 1;
+
+      // Describes an attached USB device.
+      message USBDevice {
+        // Vendor ID (assigned by USB-IF).
+        optional fixed32 vendor_id = 1;
+
+        // Product ID (assigned by manufacturer).
+        optional fixed32 product_id = 2;
+
+        // Base Class as defined by USB-IF.
+        optional uint32 device_class = 3;
+
+        // SubClass as defined by USB-IF.
+        optional uint32 device_subclass = 4;
+
+        // The device release number with the decimal shifted out. The bcdDevice
+        // field from which this value originates is a 2-byte BCD in the range
+        // [00.00 - 99.99]. This value is multiplied by 100 to remove the
+        // decimal (device_release_number = bcdDevice * 100).
+        optional uint32 device_release_number = 5;
+
+        // A subset of information from the interface descriptors.
+        message InterfaceDescriptor {
+          // Base Class as defined by USB-IF.
+          optional uint32 interface_class = 1;
+
+          // SubClass as defined by USB-IF.
+          optional uint32 interface_subclass = 2;
+
+          // The configuration value for the configuration that holds this
+          // interface. Typically, all interfaces will belong to the same
+          // configuration.
+          optional uint32 configuration_value = 3;
+        }
+        repeated InterfaceDescriptor interface_descriptors = 6;
+
+        // The number of hubs above this device, including the root hub.
+        optional uint32 hops_from_root = 7;
+
+        // The vendor ID of the parent hub, if present.
+        optional fixed32 parent_vendor_id = 8;
+
+        // The product ID of the parent hub, if present.
+        optional fixed32 parent_product_id = 9;
+
+        // If this device is a hub, the number of ports on this hub.
+        optional uint32 port_count = 10;
+      }
+      repeated USBDevice usb_devices = 2;
+    }
+    optional USB usb = 30;
+
     // Whether the internal display produces touch events. Omitted if unknown.
     // Logged on ChromeOS only.
     optional bool internal_display_supports_touch = 14;
diff --git a/third_party/metrics_proto/user_demographics.proto b/third_party/metrics_proto/user_demographics.proto
new file mode 100644
index 0000000..0a1cc7f
--- /dev/null
+++ b/third_party/metrics_proto/user_demographics.proto
@@ -0,0 +1,46 @@
+// 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+option java_outer_classname = "UserDemographicsProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Demographics information about the user.
+//
+// This data is drawn from the signed-in user's GAIA id and is provided to
+// Chrome via syncable priority pref.
+//
+// Next tag: 3
+message UserDemographicsProto {
+  // The (noisy) year of birth specified by the user. The value reported in this
+  // field cannot safely be interpreted as the true value for the user; it may
+  // be offset by a randomly chosen number of years from [-2, +2].
+  optional int32 birth_year = 1;
+
+  // The supported gender identifiers. These values correspond to those offered
+  // in the "Gender" section of the Google Account settings. Note that these
+  // values deliberately do not address the rich spectrum of possible gender
+  // identies with high granalurity because we do not want these values to be
+  // highly identifying.
+  enum Gender {
+    // The default value for clients that do not have any gender information as
+    // well as for those users who prefer not to disclose a gender and those who
+    // provide a custom gender.
+    GENDER_UNKNOWN_OR_OTHER = 0;
+    // The user specified that they prefer to be referred to as male.
+    GENDER_MALE = 1;
+    // The user specified that they prefer to be referred to as female.
+    GENDER_FEMALE = 2;
+  }
+
+  // The gender specified by the user. If the user's gender is unknown or non-
+  // binary, this field will be omitted, corresponding to a default
+  // value of GENDER_UNKNOWN_OR_OTHER.
+  optional Gender gender = 2;
+}
diff --git a/third_party/polymer/v1_0/components-chromium/paper-ripple/paper-ripple.html b/third_party/polymer/v1_0/components-chromium/paper-ripple/paper-ripple.html
index c170faf..5275d0e 100644
--- a/third_party/polymer/v1_0/components-chromium/paper-ripple/paper-ripple.html
+++ b/third_party/polymer/v1_0/components-chromium/paper-ripple/paper-ripple.html
@@ -100,8 +100,6 @@
         position: absolute;
         right: 0;
         top: 0;
-        /* For rounded corners: http://jsbin.com/temexa/4. */
-        transform: translate3d(0, 0, 0);
       }
 
       .ripple {
diff --git a/third_party/webrtc_overrides/BUILD.gn b/third_party/webrtc_overrides/BUILD.gn
index 61048ce8..4483d30 100644
--- a/third_party/webrtc_overrides/BUILD.gn
+++ b/third_party/webrtc_overrides/BUILD.gn
@@ -61,7 +61,6 @@
   ]
   deps = [
     ":metrics",
-    ":task_queue_impl",
     "//base",
     "//third_party/webrtc/rtc_base",
     "//third_party/webrtc/system_wrappers",
@@ -100,19 +99,6 @@
   ]
 }
 
-# TODO(bugs.webrtc.org/10284): Remove when TaskQueue factory is propagated
-# explicetely to all components that needs it.
-source_set("task_queue_impl") {
-  sources = [
-    "task_queue_impl.cc",
-  ]
-  configs += [ "//third_party/webrtc:library_impl_config" ]
-  deps = [
-    ":task_queue_factory",
-    "//third_party/webrtc/api/task_queue",
-  ]
-}
-
 source_set("task_queue_factory") {
   sources = [
     "task_queue_factory.cc",
diff --git a/third_party/webrtc_overrides/task_queue_impl.cc b/third_party/webrtc_overrides/task_queue_impl.cc
deleted file mode 100644
index 0d9b158f..0000000
--- a/third_party/webrtc_overrides/task_queue_impl.cc
+++ /dev/null
@@ -1,18 +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.
-
-#include <memory>
-
-#include "third_party/webrtc/api/task_queue/task_queue_factory.h"
-#include "third_party/webrtc_overrides/task_queue_factory.h"
-
-namespace webrtc {
-
-// Temporary link-time injection for the webrtc TaskQueueFactory implementation.
-// This will not be needed once bugs.webrtc.org/10284 is resolved.
-std::unique_ptr<TaskQueueFactory> CreateDefaultTaskQueueFactory() {
-  return CreateWebRtcTaskQueueFactory();
-}
-
-}  // namespace webrtc
diff --git a/tools/fuchsia/local-sdk.py b/tools/fuchsia/local-sdk.py
index 4cee6b6..a95d6b4c 100755
--- a/tools/fuchsia/local-sdk.py
+++ b/tools/fuchsia/local-sdk.py
@@ -64,7 +64,7 @@
   EnsureEmptyDir(output_dir)
 
   original_dir = os.getcwd()
-  fuchsia_root = args[0]
+  fuchsia_root = os.path.abspath(args[0])
   merged_manifest = None
   manifest_parts = set()
 
diff --git a/tools/grit/grit.py b/tools/grit/grit.py
index d8074e4..98b99bb 100755
--- a/tools/grit/grit.py
+++ b/tools/grit/grit.py
@@ -6,6 +6,8 @@
 '''Bootstrapping for GRIT.
 '''
 
+from __future__ import print_function
+
 import sys
 
 import grit.grit_runner
diff --git a/tools/grit/grit/clique.py b/tools/grit/grit/clique.py
index f5549d8..cd5b44c 100644
--- a/tools/grit/grit/clique.py
+++ b/tools/grit/grit/clique.py
@@ -6,6 +6,8 @@
 collections of cliques (uber-cliques).
 '''
 
+from __future__ import print_function
+
 import re
 import types
 
@@ -206,10 +208,12 @@
     '''
     def Callback(id, structure):
       if id not in self.cliques_:
-        if debug: print "Ignoring translation #%s" % id
+        if debug:
+          print("Ignoring translation #%s" % id)
         return
 
-      if debug: print "Adding translation #%s" % id
+      if debug:
+        print("Adding translation #%s" % id)
 
       # We fetch placeholder information from the original message (the XTB file
       # only contains placeholder names).
@@ -468,8 +472,8 @@
 
     original = self.MessageForLanguage(self.source_language, False)
     if len(original.GetPlaceholders()) != len(translation.GetPlaceholders()):
-      print ("ERROR: '%s' translation of message id %s does not match" %
-             (language, translation.GetId()))
+      print("ERROR: '%s' translation of message id %s does not match" %
+            (language, translation.GetId()))
       assert False
 
     transl_msg = tclib.Translation(id=self.GetId(),
@@ -478,7 +482,7 @@
 
     if (self.custom_type and
         not self.custom_type.ValidateAndModify(language, transl_msg)):
-      print "WARNING: %s translation failed validation: %s" % (
-        language, transl_msg.GetId())
+      print("WARNING: %s translation failed validation: %s" %
+            (language, transl_msg.GetId()))
 
     self.clique[language] = transl_msg
diff --git a/tools/grit/grit/clique_unittest.py b/tools/grit/grit/clique_unittest.py
index ff2c3a31..99f3f11 100755
--- a/tools/grit/grit/clique_unittest.py
+++ b/tools/grit/grit/clique_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.clique'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/constants.py b/tools/grit/grit/constants.py
index 02f2928..3a05c11 100644
--- a/tools/grit/grit/constants.py
+++ b/tools/grit/grit/constants.py
@@ -5,6 +5,7 @@
 '''Constant definitions for GRIT.
 '''
 
+from __future__ import print_function
 
 # This is the Icelandic noun meaning "grit" and is used to check that our
 # input files are in the correct encoding.  The middle character gets encoded
diff --git a/tools/grit/grit/exception.py b/tools/grit/grit/exception.py
index 1a1bed34..ad7f087 100644
--- a/tools/grit/grit/exception.py
+++ b/tools/grit/grit/exception.py
@@ -5,6 +5,8 @@
 '''Exception types for GRIT.
 '''
 
+from __future__ import print_function
+
 class Base(Exception):
   '''A base exception that uses the class's docstring in addition to any
   user-provided message as the body of the Base.
diff --git a/tools/grit/grit/extern/BogoFP.py b/tools/grit/grit/extern/BogoFP.py
index 5af21fd..fc90145 100644
--- a/tools/grit/grit/extern/BogoFP.py
+++ b/tools/grit/grit/extern/BogoFP.py
@@ -9,6 +9,7 @@
     grit.py -h grit.extern.BogoFP xmb /tmp/foo
 """
 
+from __future__ import print_function
 
 import grit.extern.FP
 
diff --git a/tools/grit/grit/extern/FP.py b/tools/grit/grit/extern/FP.py
index 0932f75..f4ec4d9 100644
--- a/tools/grit/grit/extern/FP.py
+++ b/tools/grit/grit/extern/FP.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 try:
   import hashlib
   _new_md5 = hashlib.md5
@@ -14,7 +16,7 @@
 
 Usage:
     from extern import FP
-    print 'Fingerprint is %ld' % FP.FingerPrint('Hello world!')
+    print('Fingerprint is %ld' % FP.FingerPrint('Hello world!'))
 """
 
 
diff --git a/tools/grit/grit/extern/tclib.py b/tools/grit/grit/extern/tclib.py
index bbefd7f..9952a87 100644
--- a/tools/grit/grit/extern/tclib.py
+++ b/tools/grit/grit/extern/tclib.py
@@ -10,6 +10,8 @@
 # for creating Windows .rc and .h files.  These are the only parts needed by
 # the Chrome build process.
 
+from __future__ import print_function
+
 from grit.extern import FP
 
 # This module assumes that within a bundle no two messages can have the
diff --git a/tools/grit/grit/format/android_xml.py b/tools/grit/grit/format/android_xml.py
index 2d878673..c969eae 100644
--- a/tools/grit/grit/format/android_xml.py
+++ b/tools/grit/grit/format/android_xml.py
@@ -59,6 +59,8 @@
   </plurals>
 """
 
+from __future__ import print_function
+
 import os
 import re
 import types
diff --git a/tools/grit/grit/format/android_xml_unittest.py b/tools/grit/grit/format/android_xml_unittest.py
index 6f496b61..1d1a7847 100755
--- a/tools/grit/grit/format/android_xml_unittest.py
+++ b/tools/grit/grit/format/android_xml_unittest.py
@@ -5,6 +5,8 @@
 
 """Unittest for android_xml.py."""
 
+from __future__ import print_function
+
 import os
 import StringIO
 import sys
diff --git a/tools/grit/grit/format/c_format.py b/tools/grit/grit/format/c_format.py
index 6d5d9e6..e944b7f 100644
--- a/tools/grit/grit/format/c_format.py
+++ b/tools/grit/grit/format/c_format.py
@@ -5,6 +5,8 @@
 """Formats as a .C file for compilation.
 """
 
+from __future__ import print_function
+
 import os
 import re
 import types
diff --git a/tools/grit/grit/format/c_format_unittest.py b/tools/grit/grit/format/c_format_unittest.py
index ba1c5c71..dd50788 100755
--- a/tools/grit/grit/format/c_format_unittest.py
+++ b/tools/grit/grit/format/c_format_unittest.py
@@ -6,6 +6,8 @@
 """Unittest for c_format.py.
 """
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/format/chrome_messages_json.py b/tools/grit/grit/format/chrome_messages_json.py
index 43853547..6af08b45 100644
--- a/tools/grit/grit/format/chrome_messages_json.py
+++ b/tools/grit/grit/format/chrome_messages_json.py
@@ -5,6 +5,8 @@
 """Formats as a .json file that can be used to localize Google Chrome
 extensions."""
 
+from __future__ import print_function
+
 from json import JSONEncoder
 import re
 import types
diff --git a/tools/grit/grit/format/chrome_messages_json_unittest.py b/tools/grit/grit/format/chrome_messages_json_unittest.py
index ae9c7a6..87b6588 100755
--- a/tools/grit/grit/format/chrome_messages_json_unittest.py
+++ b/tools/grit/grit/format/chrome_messages_json_unittest.py
@@ -6,6 +6,8 @@
 """Unittest for chrome_messages_json.py.
 """
 
+from __future__ import print_function
+
 import json
 import os
 import sys
diff --git a/tools/grit/grit/format/data_pack.py b/tools/grit/grit/format/data_pack.py
index 3a39aaf..d54f4aa 100755
--- a/tools/grit/grit/format/data_pack.py
+++ b/tools/grit/grit/format/data_pack.py
@@ -7,6 +7,8 @@
 files.
 """
 
+from __future__ import print_function
+
 import collections
 import exceptions
 import os
@@ -114,7 +116,7 @@
     return struct.unpack('<HI', data[offset:offset + kIndexEntrySize])
 
   prev_resource_id, prev_offset = entry_at_index(0)
-  for i in xrange(1, resource_count + 1):
+  for i in range(1, resource_count + 1):
     resource_id, offset = entry_at_index(i)
     resources[prev_resource_id] = data[prev_offset:offset]
     prev_resource_id, prev_offset = resource_id, offset
@@ -127,7 +129,7 @@
     return struct.unpack('<HH', data[offset:offset + kAliasEntrySize])
 
   aliases = {}
-  for i in xrange(alias_count):
+  for i in range(alias_count):
     resource_id, index = alias_at_index(i)
     aliased_id = entry_at_index(index)[0]
     aliases[resource_id] = aliased_id
@@ -276,7 +278,7 @@
                       if key not in whitelist]
       if not suppress_removed_key_output:
         for key in removed_keys:
-          print 'RePackFromDataPackStrings Removed Key:', key
+          print('RePackFromDataPackStrings Removed Key:', key)
     else:
       resources.update(input_resources)
 
@@ -292,7 +294,7 @@
   WriteDataPack(data, 'datapack1.pak', UTF8)
   data2 = {1000: 'test', 5: 'five'}
   WriteDataPack(data2, 'datapack2.pak', UTF8)
-  print 'wrote datapack1 and datapack2 to current directory.'
+  print('wrote datapack1 and datapack2 to current directory.')
 
 
 if __name__ == '__main__':
diff --git a/tools/grit/grit/format/data_pack_unittest.py b/tools/grit/grit/format/data_pack_unittest.py
index ba36590..4bb60a2 100755
--- a/tools/grit/grit/format/data_pack_unittest.py
+++ b/tools/grit/grit/format/data_pack_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.format.data_pack'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/format/gen_predetermined_ids.py b/tools/grit/grit/format/gen_predetermined_ids.py
index 57a6047..1b585f7 100755
--- a/tools/grit/grit/format/gen_predetermined_ids.py
+++ b/tools/grit/grit/format/gen_predetermined_ids.py
@@ -9,6 +9,8 @@
 a while and its output checked in. See tools/gritsettings/README.md for details.
 """
 
+from __future__ import print_function
+
 import fnmatch
 import os
 import re
@@ -128,7 +130,7 @@
   output_resource_map = GenerateResourceMapping(original_resources,
                                                 ordered_resource_ids)
   for res_id in sorted(output_resource_map.keys()):
-    print "{} {}".format(output_resource_map[res_id], res_id)
+    print(output_resource_map[res_id], res_id)
 
 
 def main(argv):
diff --git a/tools/grit/grit/format/gen_predetermined_ids_unittest.py b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
index 0866147..86820efb 100755
--- a/tools/grit/grit/format/gen_predetermined_ids_unittest.py
+++ b/tools/grit/grit/format/gen_predetermined_ids_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for the gen_predetermined_ids module.'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/format/gzip_string.py b/tools/grit/grit/format/gzip_string.py
index 4f54aac1..4f8bc64 100644
--- a/tools/grit/grit/format/gzip_string.py
+++ b/tools/grit/grit/format/gzip_string.py
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 """Provides gzip utilities for strings.
 """
+
+from __future__ import print_function
+
 import cStringIO
 import gzip
 import subprocess
diff --git a/tools/grit/grit/format/gzip_string_unittest.py b/tools/grit/grit/format/gzip_string_unittest.py
index a920693..f7e53fd 100755
--- a/tools/grit/grit/format/gzip_string_unittest.py
+++ b/tools/grit/grit/format/gzip_string_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.format.gzip_string'''
 
+from __future__ import print_function
+
 import gzip
 import io
 import os
diff --git a/tools/grit/grit/format/html_inline.py b/tools/grit/grit/format/html_inline.py
index 88ad571..7e44da7 100755
--- a/tools/grit/grit/format/html_inline.py
+++ b/tools/grit/grit/format/html_inline.py
@@ -10,6 +10,8 @@
 dependencies. It recursively inlines the included files.
 """
 
+from __future__ import print_function
+
 import os
 import re
 import sys
@@ -587,8 +589,8 @@
 
 def main():
   if len(sys.argv) <= 2:
-    print "Flattens a HTML file by inlining its external resources.\n"
-    print "html_inline.py inputfile outputfile"
+    print("Flattens a HTML file by inlining its external resources.\n")
+    print("html_inline.py inputfile outputfile")
   else:
     InlineToFile(sys.argv[1], sys.argv[2], None)
 
diff --git a/tools/grit/grit/format/html_inline_unittest.py b/tools/grit/grit/format/html_inline_unittest.py
index 96f1e26..37c6339f 100755
--- a/tools/grit/grit/format/html_inline_unittest.py
+++ b/tools/grit/grit/format/html_inline_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.format.html_inline'''
 
+from __future__ import print_function
 
 import os
 import re
diff --git a/tools/grit/grit/format/minifier.py b/tools/grit/grit/format/minifier.py
index a45b5545..011cbea 100644
--- a/tools/grit/grit/format/minifier.py
+++ b/tools/grit/grit/format/minifier.py
@@ -3,6 +3,8 @@
 # found in the LICENSE file.
 """Framework for stripping whitespace and comments from resource files"""
 
+from __future__ import print_function
+
 from os import path
 import subprocess
 import sys
@@ -26,7 +28,7 @@
       stderr=subprocess.PIPE)
   (stdout, stderr) = p.communicate(source)
   if p.returncode != 0:
-    print 'Minification failed for %s' % filename
-    print stderr
+    print('Minification failed for %s' % filename)
+    print(stderr)
     sys.exit(p.returncode)
   return stdout
diff --git a/tools/grit/grit/format/policy_templates_json.py b/tools/grit/grit/format/policy_templates_json.py
index f4531f5..2f9330b 100644
--- a/tools/grit/grit/format/policy_templates_json.py
+++ b/tools/grit/grit/format/policy_templates_json.py
@@ -5,6 +5,8 @@
 """Translates policy_templates.json files.
 """
 
+from __future__ import print_function
+
 from grit.node import structure
 
 
diff --git a/tools/grit/grit/format/policy_templates_json_unittest.py b/tools/grit/grit/format/policy_templates_json_unittest.py
index d3816ad30..64eae1b 100755
--- a/tools/grit/grit/format/policy_templates_json_unittest.py
+++ b/tools/grit/grit/format/policy_templates_json_unittest.py
@@ -7,6 +7,8 @@
 """Unittest for policy_templates_json.py.
 """
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/format/rc.py b/tools/grit/grit/format/rc.py
index e7d716d3..7037278 100644
--- a/tools/grit/grit/format/rc.py
+++ b/tools/grit/grit/format/rc.py
@@ -5,6 +5,8 @@
 '''Support for formatting an RC file for compilation.
 '''
 
+from __future__ import print_function
+
 import os
 import types
 import re
@@ -260,7 +262,7 @@
   if _LANGUAGE_CHARSET_PAIR.has_key(language):
     return _LANGUAGE_CHARSET_PAIR[language]
   if language != 'no-specific-language':
-    print 'Warning:GetLangCharsetPair() found undefined language %s' % language
+    print('Warning:GetLangCharsetPair() found undefined language %s' % language)
   return ''
 
 def GetLangDirectivePair(language):
@@ -271,7 +273,7 @@
   # function should only get called when output is being formatted,
   # and at that point we would not want to get
   # 'no-specific-language' passed as the language.
-  print 'Warning:GetLangDirectivePair() found undefined language %s' % language
+  print('Warning:GetLangDirectivePair() found undefined language %s' % language)
   return 'unknown language: see tools/grit/format/rc.py'
 
 def GetLangIdHex(language):
@@ -280,7 +282,7 @@
     lang_id = '0x' + langcharset[0:4]
     return lang_id
   if language != 'no-specific-language':
-    print 'Warning:GetLangIdHex() found undefined language %s' % language
+    print('Warning:GetLangIdHex() found undefined language %s' % language)
   return ''
 
 
@@ -290,7 +292,7 @@
     charset_decimal = int(langcharset[4:], 16)
     return str(charset_decimal)
   if language != 'no-specific-language':
-    print 'Warning:GetCharsetIdDecimal() found undefined language %s' % language
+    print('Warning:GetCharsetIdDecimal() found undefined language %s' % language)
   return ''
 
 
diff --git a/tools/grit/grit/format/rc_header.py b/tools/grit/grit/format/rc_header.py
index da2f1565..5a789fb 100644
--- a/tools/grit/grit/format/rc_header.py
+++ b/tools/grit/grit/format/rc_header.py
@@ -5,6 +5,8 @@
 '''Item formatters for RC headers.
 '''
 
+from __future__ import print_function
+
 from grit.node import message
 
 
diff --git a/tools/grit/grit/format/rc_header_unittest.py b/tools/grit/grit/format/rc_header_unittest.py
index f709f934..ab2d16d 100755
--- a/tools/grit/grit/format/rc_header_unittest.py
+++ b/tools/grit/grit/format/rc_header_unittest.py
@@ -8,6 +8,8 @@
 # GRD samples exceed the 80 character limit.
 # pylint: disable-msg=C6310
 
+from __future__ import print_function
+
 import os
 import sys
 import unittest
diff --git a/tools/grit/grit/format/rc_unittest.py b/tools/grit/grit/format/rc_unittest.py
index 497f315c..43714ad62 100755
--- a/tools/grit/grit/format/rc_unittest.py
+++ b/tools/grit/grit/format/rc_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.format.rc'''
 
+from __future__ import print_function
+
 import os
 import re
 import sys
diff --git a/tools/grit/grit/format/resource_map.py b/tools/grit/grit/format/resource_map.py
index c9a73c46..95a8b83 100644
--- a/tools/grit/grit/format/resource_map.py
+++ b/tools/grit/grit/format/resource_map.py
@@ -6,6 +6,8 @@
 resource_map_source files.  A resource map is a mapping between resource names
 (string) and the internal resource ID.'''
 
+from __future__ import print_function
+
 import os
 from functools import partial
 
diff --git a/tools/grit/grit/format/resource_map_unittest.py b/tools/grit/grit/format/resource_map_unittest.py
index cd290a71..ec423193 100755
--- a/tools/grit/grit/format/resource_map_unittest.py
+++ b/tools/grit/grit/format/resource_map_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.format.resource_map'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/gather/admin_template.py b/tools/grit/grit/gather/admin_template.py
index 1b8340a..15876b4 100644
--- a/tools/grit/grit/gather/admin_template.py
+++ b/tools/grit/grit/gather/admin_template.py
@@ -5,6 +5,8 @@
 '''Gatherer for administrative template files.
 '''
 
+from __future__ import print_function
+
 import re
 
 from grit.gather import regexp
diff --git a/tools/grit/grit/gather/admin_template_unittest.py b/tools/grit/grit/gather/admin_template_unittest.py
index c63c08f..ff065c3 100755
--- a/tools/grit/grit/gather/admin_template_unittest.py
+++ b/tools/grit/grit/gather/admin_template_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for the admin template gatherer.'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/gather/chrome_html.py b/tools/grit/grit/gather/chrome_html.py
index 5e90980..a0d6c47 100644
--- a/tools/grit/grit/gather/chrome_html.py
+++ b/tools/grit/grit/gather/chrome_html.py
@@ -14,6 +14,8 @@
 referencing all available images.
 """
 
+from __future__ import print_function
+
 import os
 import re
 
diff --git a/tools/grit/grit/gather/chrome_html_unittest.py b/tools/grit/grit/gather/chrome_html_unittest.py
index f21caf3..5406a8c 100755
--- a/tools/grit/grit/gather/chrome_html_unittest.py
+++ b/tools/grit/grit/gather/chrome_html_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.gather.chrome_html'''
 
+from __future__ import print_function
 
 import os
 import re
diff --git a/tools/grit/grit/gather/chrome_scaled_image.py b/tools/grit/grit/gather/chrome_scaled_image.py
index 91116ab..d077274 100644
--- a/tools/grit/grit/gather/chrome_scaled_image.py
+++ b/tools/grit/grit/gather/chrome_scaled_image.py
@@ -5,6 +5,8 @@
 '''Gatherer for <structure type="chrome_scaled_image">.
 '''
 
+from __future__ import print_function
+
 import os
 import struct
 
diff --git a/tools/grit/grit/gather/chrome_scaled_image_unittest.py b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
index 6a6b3a4..cbc5b815 100755
--- a/tools/grit/grit/gather/chrome_scaled_image_unittest.py
+++ b/tools/grit/grit/gather/chrome_scaled_image_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for ChromeScaledImage.'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/gather/interface.py b/tools/grit/grit/gather/interface.py
index 5d3ef1d..6149167 100644
--- a/tools/grit/grit/gather/interface.py
+++ b/tools/grit/grit/gather/interface.py
@@ -5,6 +5,7 @@
 '''Interface for all gatherers.
 '''
 
+from __future__ import print_function
 
 import os.path
 import types
diff --git a/tools/grit/grit/gather/json_loader.py b/tools/grit/grit/gather/json_loader.py
index ca1f435..ee0d932b 100644
--- a/tools/grit/grit/gather/json_loader.py
+++ b/tools/grit/grit/gather/json_loader.py
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
 
 from grit.gather import interface
 
diff --git a/tools/grit/grit/gather/policy_json.py b/tools/grit/grit/gather/policy_json.py
index 6e67212..0aee2c6d 100644
--- a/tools/grit/grit/gather/policy_json.py
+++ b/tools/grit/grit/gather/policy_json.py
@@ -5,6 +5,8 @@
 '''Support for "policy_templates.json" format used by the policy template
 generator as a source for generating ADM,ADMX,etc files.'''
 
+from __future__ import print_function
+
 import json
 import types
 import sys
@@ -275,7 +277,7 @@
 
     self.text_ = self._LoadInputFile()
     if util.IsExtraVerbose():
-      print self.text_
+      print(self.text_)
 
     self.data = eval(self.text_)
 
diff --git a/tools/grit/grit/gather/policy_json_unittest.py b/tools/grit/grit/gather/policy_json_unittest.py
index 187dac1c..b48e7fc 100755
--- a/tools/grit/grit/gather/policy_json_unittest.py
+++ b/tools/grit/grit/gather/policy_json_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.gather.policy_json'''
 
+from __future__ import print_function
+
 import json
 import os
 import re
diff --git a/tools/grit/grit/gather/rc.py b/tools/grit/grit/gather/rc.py
index a332758..03135bdd 100644
--- a/tools/grit/grit/gather/rc.py
+++ b/tools/grit/grit/gather/rc.py
@@ -5,6 +5,7 @@
 '''Support for gathering resources from RC files.
 '''
 
+from __future__ import print_function
 
 import re
 
diff --git a/tools/grit/grit/gather/rc_unittest.py b/tools/grit/grit/gather/rc_unittest.py
index c4be35e..949f109 100755
--- a/tools/grit/grit/gather/rc_unittest.py
+++ b/tools/grit/grit/gather/rc_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.gather.rc'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/gather/regexp.py b/tools/grit/grit/gather/regexp.py
index de64c17..d06bf9c 100644
--- a/tools/grit/grit/gather/regexp.py
+++ b/tools/grit/grit/gather/regexp.py
@@ -5,6 +5,8 @@
 '''A baseclass for simple gatherers based on regular expressions.
 '''
 
+from __future__ import print_function
+
 import re
 
 from grit.gather import skeleton_gatherer
diff --git a/tools/grit/grit/gather/skeleton_gatherer.py b/tools/grit/grit/gather/skeleton_gatherer.py
index 564ccce..c6127d11 100644
--- a/tools/grit/grit/gather/skeleton_gatherer.py
+++ b/tools/grit/grit/gather/skeleton_gatherer.py
@@ -6,6 +6,8 @@
 list.
 '''
 
+from __future__ import print_function
+
 import types
 
 from grit.gather import interface
diff --git a/tools/grit/grit/gather/tr_html.py b/tools/grit/grit/gather/tr_html.py
index f0bd1322..ba59ff65 100644
--- a/tools/grit/grit/gather/tr_html.py
+++ b/tools/grit/grit/gather/tr_html.py
@@ -6,6 +6,7 @@
 portions.  We wanted to reuse extern.tclib.api.handlers.html.TCHTMLParser
 but this proved impossible due to the fact that the TotalRecall HTML templates
 are in general quite far from parseable HTML and the TCHTMLParser derives
+
 from HTMLParser.HTMLParser which requires relatively well-formed HTML.  Some
 examples of "HTML" from the TotalRecall HTML templates that wouldn't be
 parseable include things like:
@@ -48,6 +49,7 @@
 extern.tclib.api.handlers.html.TCHTMLParser.
 '''
 
+from __future__ import print_function
 
 import re
 import types
@@ -208,7 +210,7 @@
 _DEBUG = 0
 def _DebugPrint(text):
   if _DEBUG:
-    print text.encode('utf-8')
+    print(text.encode('utf-8'))
 
 
 class HtmlChunks(object):
diff --git a/tools/grit/grit/gather/tr_html_unittest.py b/tools/grit/grit/gather/tr_html_unittest.py
index 3400ad6f..bc4cc61 100755
--- a/tools/grit/grit/gather/tr_html_unittest.py
+++ b/tools/grit/grit/gather/tr_html_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.gather.tr_html'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/gather/txt.py b/tools/grit/grit/gather/txt.py
index a2a6cfc..e5c10ab 100644
--- a/tools/grit/grit/gather/txt.py
+++ b/tools/grit/grit/gather/txt.py
@@ -5,6 +5,8 @@
 '''Supports making amessage from a text file.
 '''
 
+from __future__ import print_function
+
 from grit.gather import interface
 from grit import tclib
 
diff --git a/tools/grit/grit/gather/txt_unittest.py b/tools/grit/grit/gather/txt_unittest.py
index 80586526..35150939 100755
--- a/tools/grit/grit/gather/txt_unittest.py
+++ b/tools/grit/grit/gather/txt_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for TxtFile gatherer'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/grd_reader.py b/tools/grit/grit/grd_reader.py
index e0ccdac..200b6d1 100755
--- a/tools/grit/grit/grd_reader.py
+++ b/tools/grit/grit/grd_reader.py
@@ -6,6 +6,8 @@
 '''Class for reading GRD files into memory, without processing them.
 '''
 
+from __future__ import print_function
+
 import os.path
 import types
 import xml.sax
@@ -46,14 +48,14 @@
   def startElement(self, name, attrs):
     if self.ignore_depth or name in self.tags_to_ignore:
       if self.debug and self.ignore_depth == 0:
-        print "Ignoring element %s and its children" % name
+        print("Ignoring element %s and its children" % name)
       self.ignore_depth += 1
       return
 
     if self.debug:
       attr_list = ' '.join('%s="%s"' % kv for kv in attrs.items())
-      print ("Starting parsing of element %s with attributes %r" %
-          (name, attr_list or '(none)'))
+      print("Starting parsing of element %s with attributes %r" %
+            (name, attr_list or '(none)'))
 
     typeattr = attrs.get('type')
     node = mapping.ElementToClass(name, typeattr)()
@@ -98,7 +100,7 @@
         self.source = oldsource
 
     if self.debug:
-      print "End parsing of element %s" % name
+      print("End parsing of element %s" % name)
     self.stack.pop().EndParsing()
 
     if name == self.stop_after:
@@ -202,7 +204,7 @@
     pass
   except:
     if not debug:
-      print "parse exception: run GRIT with the -x flag to debug .grd problems"
+      print("parse exception: run GRIT with the -x flag to debug .grd problems")
     raise
 
   if handler.root.name != 'grit':
@@ -232,4 +234,4 @@
 
 if __name__ == '__main__':
   util.ChangeStdoutEncoding()
-  print unicode(Parse(sys.argv[1]))
+  print(unicode(Parse(sys.argv[1])))
diff --git a/tools/grit/grit/grd_reader_unittest.py b/tools/grit/grit/grd_reader_unittest.py
index 7e98f80..0095355 100755
--- a/tools/grit/grit/grd_reader_unittest.py
+++ b/tools/grit/grit/grd_reader_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grd_reader package'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/grit_runner.py b/tools/grit/grit/grit_runner.py
index 09994da..8f9f175 100755
--- a/tools/grit/grit/grit_runner.py
+++ b/tools/grit/grit/grit_runner.py
@@ -7,6 +7,8 @@
 GRIT tools.
 """
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
@@ -111,7 +113,7 @@
     if not _HIDDEN in info.keys():
       tool_list += '    %-12s %s\n' % (tool, info[_FACTORY]().ShortDescription())
 
-  print """GRIT - the Google Resource and Internationalization Tool
+  print("""GRIT - the Google Resource and Internationalization Tool
 
 Usage: grit [GLOBALOPTIONS] TOOL [args to tool]
 
@@ -139,7 +141,7 @@
 %s
   For more information on how to use a particular tool, and the specific
   arguments you can send to that tool, execute 'grit help TOOL'
-""" % (tool_list)
+""" % (tool_list))
 
 
 class Options(object):
@@ -203,11 +205,11 @@
   try:
     args = options.ReadOptions(args)  # args may be shorter after this
   except getopt.GetoptError as e:
-    print "grit:", str(e)
-    print "Try running 'grit help' for valid options."
+    print("grit:", str(e))
+    print("Try running 'grit help' for valid options.")
     return 1
   if not args:
-    print "No tool provided.  Try running 'grit help' for a list of tools."
+    print("No tool provided.  Try running 'grit help' for a list of tools.")
     return 2
 
   tool = args[0]
@@ -218,29 +220,29 @@
     else:
       tool = args[1]
       if not _GetToolInfo(tool):
-        print "No such tool.  Try running 'grit help' for a list of tools."
+        print("No such tool.  Try running 'grit help' for a list of tools.")
         return 2
 
-      print ("Help for 'grit %s' (for general help, run 'grit help'):\n"
-             % (tool))
+      print("Help for 'grit %s' (for general help, run 'grit help'):\n" %
+            (tool,))
       _GetToolInfo(tool)[_FACTORY]().ShowUsage()
       return 0
   if not _GetToolInfo(tool):
-    print "No such tool.  Try running 'grit help' for a list of tools."
+    print("No such tool.  Try running 'grit help' for a list of tools.")
     return 2
 
   try:
     if _GetToolInfo(tool)[_REQUIRES_INPUT]:
       os.stat(options.input)
   except OSError:
-    print ('Input file %s not found.\n'
-           'To specify a different input file:\n'
-           '  1. Use the GRIT_INPUT environment variable.\n'
-           '  2. Use the -i command-line option.  This overrides '
-           'GRIT_INPUT.\n'
-           '  3. Specify neither GRIT_INPUT or -i and GRIT will try to load '
-           "'resource.grd'\n"
-           '     from the current directory.' % options.input)
+    print('Input file %s not found.\n'
+          'To specify a different input file:\n'
+          '  1. Use the GRIT_INPUT environment variable.\n'
+          '  2. Use the -i command-line option.  This overrides '
+          'GRIT_INPUT.\n'
+          '  3. Specify neither GRIT_INPUT or -i and GRIT will try to load '
+          "'resource.grd'\n"
+          '     from the current directory.' % options.input)
     return 2
 
   if options.hash:
@@ -255,8 +257,8 @@
     else:
       return toolobject.Run(options, args[1:])
   except getopt.GetoptError as e:
-    print "grit: %s: %s" % (tool, str(e))
-    print "Try running 'grit help %s' for valid options." % (tool,)
+    print("grit: %s: %s" % (tool, str(e)))
+    print("Try running 'grit help %s' for valid options." % (tool,))
     return 1
 
 
diff --git a/tools/grit/grit/grit_runner_unittest.py b/tools/grit/grit/grit_runner_unittest.py
index 591eeae..be2c64d 100755
--- a/tools/grit/grit/grit_runner_unittest.py
+++ b/tools/grit/grit/grit_runner_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.py'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/lazy_re.py b/tools/grit/grit/lazy_re.py
index ae67d048..5c461e87 100644
--- a/tools/grit/grit/lazy_re.py
+++ b/tools/grit/grit/lazy_re.py
@@ -8,6 +8,8 @@
 time in some cases.
 '''
 
+from __future__ import print_function
+
 import re
 
 
diff --git a/tools/grit/grit/lazy_re_unittest.py b/tools/grit/grit/lazy_re_unittest.py
index 99fe1ec..8488b454 100755
--- a/tools/grit/grit/lazy_re_unittest.py
+++ b/tools/grit/grit/lazy_re_unittest.py
@@ -6,6 +6,8 @@
 '''Unit test for lazy_re.
 '''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/node/base.py b/tools/grit/grit/node/base.py
index bd6b207..ca92a91 100644
--- a/tools/grit/grit/node/base.py
+++ b/tools/grit/grit/node/base.py
@@ -5,6 +5,8 @@
 '''Base types for nodes in a GRIT resource tree.
 '''
 
+from __future__ import print_function
+
 import ast
 import os
 import sys
@@ -54,7 +56,7 @@
 
   def __exit__(self, exc_type, exc_value, traceback):
     if exc_type is not None:
-      print u'Error processing node %s' % unicode(self)
+      print(u'Error processing node %s' % unicode(self))
 
   def __iter__(self):
     '''A preorder iteration through the tree that this node is the root of.'''
diff --git a/tools/grit/grit/node/base_unittest.py b/tools/grit/grit/node/base_unittest.py
index 556c7456..d6dd3c3 100755
--- a/tools/grit/grit/node/base_unittest.py
+++ b/tools/grit/grit/node/base_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for base.Node functionality (as used in various subclasses)'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/node/custom/filename.py b/tools/grit/grit/node/custom/filename.py
index 416eac5..cee18ec2 100644
--- a/tools/grit/grit/node/custom/filename.py
+++ b/tools/grit/grit/node/custom/filename.py
@@ -4,6 +4,8 @@
 
 '''A CustomType for filenames.'''
 
+from __future__ import print_function
+
 from grit import clique
 from grit import lazy_re
 
diff --git a/tools/grit/grit/node/custom/filename_unittest.py b/tools/grit/grit/node/custom/filename_unittest.py
index 9ea8eb9..8e2a6dd 100755
--- a/tools/grit/grit/node/custom/filename_unittest.py
+++ b/tools/grit/grit/node/custom/filename_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.node.custom.filename'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/node/empty.py b/tools/grit/grit/node/empty.py
index b821752..e19d2c4 100644
--- a/tools/grit/grit/node/empty.py
+++ b/tools/grit/grit/node/empty.py
@@ -5,6 +5,7 @@
 '''Container nodes that don't have any logic.
 '''
 
+from __future__ import print_function
 
 from grit.node import base
 from grit.node import include
diff --git a/tools/grit/grit/node/include.py b/tools/grit/grit/node/include.py
index fd517bdd..fcb805b 100644
--- a/tools/grit/grit/node/include.py
+++ b/tools/grit/grit/node/include.py
@@ -5,6 +5,8 @@
 """Handling of the <include> element.
 """
 
+from __future__ import print_function
+
 import os
 
 from grit import exception
diff --git a/tools/grit/grit/node/include_unittest.py b/tools/grit/grit/node/include_unittest.py
index b4dcb8a79..5dc2059 100755
--- a/tools/grit/grit/node/include_unittest.py
+++ b/tools/grit/grit/node/include_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for include.IncludeNode'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/node/mapping.py b/tools/grit/grit/node/mapping.py
index 083f77b..6297f0b 100644
--- a/tools/grit/grit/node/mapping.py
+++ b/tools/grit/grit/node/mapping.py
@@ -6,6 +6,7 @@
 When adding a new node type, you add to this mapping.
 '''
 
+from __future__ import print_function
 
 from grit import exception
 
diff --git a/tools/grit/grit/node/message.py b/tools/grit/grit/node/message.py
index ed065bb..a21c38c 100644
--- a/tools/grit/grit/node/message.py
+++ b/tools/grit/grit/node/message.py
@@ -5,6 +5,8 @@
 '''Handling of the <message> element.
 '''
 
+from __future__ import print_function
+
 import re
 import types
 
@@ -167,7 +169,7 @@
       if isinstance(item, types.StringTypes):
         # Not a <ph> element: fail if any <ph> formatters are detected.
         if _FORMATTERS.search(item):
-          print _BAD_PLACEHOLDER_MSG % (item, self.source)
+          print(_BAD_PLACEHOLDER_MSG % (item, self.source))
           raise exception.PlaceholderNotInsidePhNode
         text += item
       else:
@@ -198,7 +200,7 @@
         # Fail if <ph> special chars remain in cdata.
         if re.search(r'[%\$]', cdata):
           message_id = self.attrs['name'] + ' ' + original;
-          print _INVALID_PH_CHAR_MSG % (message_id, self.source)
+          print(_INVALID_PH_CHAR_MSG % (message_id, self.source))
           raise exception.InvalidCharactersInsidePhNode
 
         # Otherwise, accept this <ph> placeholder.
diff --git a/tools/grit/grit/node/message_unittest.py b/tools/grit/grit/node/message_unittest.py
index 5cf78c12..19fd594c 100755
--- a/tools/grit/grit/node/message_unittest.py
+++ b/tools/grit/grit/node/message_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.node.message'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/node/misc.py b/tools/grit/grit/node/misc.py
index 5d983ee9..f01bf7bf 100644
--- a/tools/grit/grit/node/misc.py
+++ b/tools/grit/grit/node/misc.py
@@ -5,6 +5,8 @@
 """Miscellaneous node types.
 """
 
+from __future__ import print_function
+
 import os.path
 import re
 import sys
@@ -191,8 +193,8 @@
                                        % (id, id_reasons[id], reason))
 
       if id < 101:
-        print ('WARNING: Numeric resource IDs should be greater than 100 to\n'
-               'avoid conflicts with system-defined resource IDs.')
+        print('WARNING: Numeric resource IDs should be greater than 100 to\n'
+              'avoid conflicts with system-defined resource IDs.')
 
       if tid not in predetermined_tids and id in predetermined_ids:
         raise exception.IdRangeOverlap('ID %d overlaps between %s and %s'
@@ -600,12 +602,12 @@
         try:
           id_list = first_ids[filename][node.name]
         except KeyError, e:
-          print '-' * 78
-          print 'Resource id not set for %s (%s)!' % (filename, node.name)
-          print ('Please update %s to include an entry for %s.  See the '
-                 'comments in resource_ids for information on why you need to '
-                 'update that file.' % (first_ids_filename, filename))
-          print '-' * 78
+          print('-' * 78)
+          print('Resource id not set for %s (%s)!' % (filename, node.name))
+          print('Please update %s to include an entry for %s.  See the '
+                'comments in resource_ids for information on why you need to '
+                'update that file.' % (first_ids_filename, filename))
+          print('-' * 78)
           raise e
 
         try:
diff --git a/tools/grit/grit/node/misc_unittest.py b/tools/grit/grit/node/misc_unittest.py
index 30df9fe..6ed63a9 100755
--- a/tools/grit/grit/node/misc_unittest.py
+++ b/tools/grit/grit/node/misc_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for misc.GritNode'''
 
+from __future__ import print_function
 
 import StringIO
 import contextlib
diff --git a/tools/grit/grit/node/node_io.py b/tools/grit/grit/node/node_io.py
index 99423ad1..c67add8 100644
--- a/tools/grit/grit/node/node_io.py
+++ b/tools/grit/grit/node/node_io.py
@@ -5,6 +5,8 @@
 '''The <output> and <file> elements.
 '''
 
+from __future__ import print_function
+
 import os
 
 from grit import xtb_reader
@@ -47,7 +49,7 @@
                               defs=defs,
                               target_platform=target_platform)
     except:
-      print "Exception during parsing of %s" % self.GetInputPath()
+      print("Exception during parsing of %s" % self.GetInputPath())
       raise
     # Translation console uses non-standard language codes 'iw' and 'no' for
     # Hebrew and Norwegian Bokmal instead of 'he' and 'nb' used in Chrome.
diff --git a/tools/grit/grit/node/node_io_unittest.py b/tools/grit/grit/node/node_io_unittest.py
index f82887e..3b2ae3b 100755
--- a/tools/grit/grit/node/node_io_unittest.py
+++ b/tools/grit/grit/node/node_io_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for node_io.FileNode'''
 
+from __future__ import print_function
+
 import StringIO
 import os
 import sys
diff --git a/tools/grit/grit/node/structure.py b/tools/grit/grit/node/structure.py
index 6b795d0..69784f5 100644
--- a/tools/grit/grit/node/structure.py
+++ b/tools/grit/grit/node/structure.py
@@ -5,6 +5,8 @@
 '''The <structure> element.
 '''
 
+from __future__ import print_function
+
 import os
 import platform
 import re
@@ -235,8 +237,8 @@
 
   def RunPreSubstitutionGatherer(self, debug=False):
     if debug:
-      print 'Running gatherer %s for file %s' % (
-          str(type(self.gatherer)), self.GetInputPath())
+      print('Running gatherer %s for file %s' %
+            (type(self.gatherer), self.GetInputPath()))
 
     # Note: Parse() is idempotent, therefore this method is also.
     self.gatherer.Parse()
diff --git a/tools/grit/grit/node/structure_unittest.py b/tools/grit/grit/node/structure_unittest.py
index 683a0626..9160edb 100755
--- a/tools/grit/grit/node/structure_unittest.py
+++ b/tools/grit/grit/node/structure_unittest.py
@@ -6,6 +6,8 @@
 '''Unit tests for <structure> nodes.
 '''
 
+from __future__ import print_function
+
 import os
 import os.path
 import sys
diff --git a/tools/grit/grit/node/variant.py b/tools/grit/grit/node/variant.py
index 7d3a526..ab183f0 100644
--- a/tools/grit/grit/node/variant.py
+++ b/tools/grit/grit/node/variant.py
@@ -5,6 +5,7 @@
 '''The <skeleton> element.
 '''
 
+from __future__ import print_function
 
 from grit.node import base
 
diff --git a/tools/grit/grit/pseudo.py b/tools/grit/grit/pseudo.py
index 23b784d..0434026 100644
--- a/tools/grit/grit/pseudo.py
+++ b/tools/grit/grit/pseudo.py
@@ -21,6 +21,8 @@
 the latin-1 character set which will stress character encoding bugs.
 '''
 
+from __future__ import print_function
+
 from grit import lazy_re
 from grit import tclib
 
diff --git a/tools/grit/grit/pseudo_rtl.py b/tools/grit/grit/pseudo_rtl.py
index f307ea049..2240b57 100644
--- a/tools/grit/grit/pseudo_rtl.py
+++ b/tools/grit/grit/pseudo_rtl.py
@@ -7,6 +7,8 @@
 More info at https://sites.google.com/a/chromium.org/dev/Home/fake-bidi
 '''
 
+from __future__ import print_function
+
 import re
 
 from grit import lazy_re
diff --git a/tools/grit/grit/pseudo_unittest.py b/tools/grit/grit/pseudo_unittest.py
index ecf34ff04..b1d53ff 100755
--- a/tools/grit/grit/pseudo_unittest.py
+++ b/tools/grit/grit/pseudo_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.pseudo'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/shortcuts.py b/tools/grit/grit/shortcuts.py
index bae61cc..0db2ce4 100644
--- a/tools/grit/grit/shortcuts.py
+++ b/tools/grit/grit/shortcuts.py
@@ -5,6 +5,8 @@
 '''Stuff to prevent conflicting shortcuts.
 '''
 
+from __future__ import print_function
+
 from grit import lazy_re
 
 
diff --git a/tools/grit/grit/shortcuts_unittest.py b/tools/grit/grit/shortcuts_unittest.py
index ed788e63..4580b2a 100644
--- a/tools/grit/grit/shortcuts_unittest.py
+++ b/tools/grit/grit/shortcuts_unittest.py
@@ -5,6 +5,8 @@
 '''Unit tests for grit.shortcuts
 '''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/tclib.py b/tools/grit/grit/tclib.py
index da8ad32..c73664f 100644
--- a/tools/grit/grit/tclib.py
+++ b/tools/grit/grit/tclib.py
@@ -5,6 +5,7 @@
 '''Adaptation of the extern.tclib classes for our needs.
 '''
 
+from __future__ import print_function
 
 import re
 import types
diff --git a/tools/grit/grit/tclib_unittest.py b/tools/grit/grit/tclib_unittest.py
index 87849c5..94673a2 100755
--- a/tools/grit/grit/tclib_unittest.py
+++ b/tools/grit/grit/tclib_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.tclib'''
 
+from __future__ import print_function
 
 import sys
 import os.path
diff --git a/tools/grit/grit/test_suite_all.py b/tools/grit/grit/test_suite_all.py
index 8d7f76b..6e7727cf 100755
--- a/tools/grit/grit/test_suite_all.py
+++ b/tools/grit/grit/test_suite_all.py
@@ -5,6 +5,8 @@
 
 '''Unit test suite that collects all test cases for GRIT.'''
 
+from __future__ import print_function
+
 import os
 import sys
 
diff --git a/tools/grit/grit/tool/android2grd.py b/tools/grit/grit/tool/android2grd.py
index dd47fda..5ef9204 100644
--- a/tools/grit/grit/tool/android2grd.py
+++ b/tools/grit/grit/tool/android2grd.py
@@ -4,6 +4,7 @@
 
 """The 'grit android2grd' tool."""
 
+from __future__ import print_function
 
 import getopt
 import os.path
@@ -161,8 +162,8 @@
     """
     args = self.ParseOptions(args)
     if len(args) != 1:
-      print ('Tool requires one argument, the path to the Android '
-             'strings.xml resource file to be converted.')
+      print('Tool requires one argument, the path to the Android '
+            'strings.xml resource file to be converted.')
       return 2
     self.SetOptions(opts)
 
@@ -243,12 +244,12 @@
         description = ' '.join(child.data.split())
       elif child.nodeType == Node.ELEMENT_NODE:
         if child.tagName != 'string':
-          print 'Warning: ignoring unknown tag <%s>' % child.tagName
+          print('Warning: ignoring unknown tag <%s>' % child.tagName)
         else:
           translatable = self.IsTranslatable(child)
           raw_name = child.getAttribute('name')
           if not _STRING_NAME.match(raw_name):
-            print 'Error: illegal string name: %s' % raw_name
+            print('Error: illegal string name: %s' % raw_name)
           grd_name = 'IDS_' + raw_name.upper()
           # Transform the <string> node contents into a tclib.Message, taking
           # care to handle whitespace transformations and escaped characters,
@@ -291,14 +292,14 @@
           placeholder_text = self.__FormatPlaceholderText(node)
           placeholder_example = node.getAttribute('example')
           if not placeholder_example:
-            print ('Info: placeholder does not contain an example: %s' %
-                   node.toxml())
+            print('Info: placeholder does not contain an example: %s' %
+                  node.toxml())
             placeholder_example = placeholder_id.upper()
           msg.AppendPlaceholder(tclib.Placeholder(placeholder_id,
               placeholder_text, placeholder_example))
         else:
-          print ('Warning: removing tag <%s> which must be inside a '
-                 'placeholder: %s' % (node.tagName, node.toxml()))
+          print('Warning: removing tag <%s> which must be inside a '
+                'placeholder: %s' % (node.tagName, node.toxml()))
           msg.AppendText(self.__FormatPlaceholderText(node))
 
       # Handle other nodes.
@@ -348,17 +349,17 @@
     output = ''.join(output)
 
     if is_quoted_section:
-      print 'Warning: unbalanced quotes in string: %s' % android_string
+      print('Warning: unbalanced quotes in string: %s' % android_string)
 
     if is_backslash_sequence:
-      print 'Warning: trailing backslash in string: %s' % android_string
+      print('Warning: trailing backslash in string: %s' % android_string)
 
     # Check for format specifiers outside of placeholder tags.
     if not inside_placeholder:
       format_specifier = _FORMAT_SPECIFIER.search(output)
       if format_specifier:
-        print ('Warning: format specifiers are not inside a placeholder '
-               '<xliff:g/> tag: %s' % output)
+        print('Warning: format specifiers are not inside a placeholder '
+              '<xliff:g/> tag: %s' % output)
 
     return output
 
@@ -380,15 +381,15 @@
     declare a string resource along with a programmatic id.
     """
     if not description:
-      print 'Warning: no description for %s' % grd_name
+      print('Warning: no description for %s' % grd_name)
     # Check that we actually fit within the character limit we've specified.
     match = _CHAR_LIMIT.search(description)
     if match:
       char_limit = int(match.group(1))
       msg_content = msg.GetRealContent()
       if len(msg_content) > char_limit:
-        print ('Warning: char-limit for %s is %d, but length is %d: %s' %
-               (grd_name, char_limit, len(msg_content), msg_content))
+        print('Warning: char-limit for %s is %d, but length is %d: %s' %
+              (grd_name, char_limit, len(msg_content), msg_content))
     return message.MessageNode.Construct(parent=messages_node,
                                          name=grd_name,
                                          message=msg,
@@ -476,7 +477,7 @@
     if android_string.hasAttribute('translatable'):
       value = android_string.getAttribute('translatable').lower()
       if value not in ('true', 'false'):
-        print 'Warning: translatable attribute has invalid value: %s' % value
+        print('Warning: translatable attribute has invalid value: %s' % value)
       return value == 'true'
     else:
       return True
diff --git a/tools/grit/grit/tool/android2grd_unittest.py b/tools/grit/grit/tool/android2grd_unittest.py
index 0c7ed54a..802d216 100755
--- a/tools/grit/grit/tool/android2grd_unittest.py
+++ b/tools/grit/grit/tool/android2grd_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.tool.android2grd'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/tool/build.py b/tools/grit/grit/tool/build.py
index 89f94f1..fde614d 100644
--- a/tools/grit/grit/tool/build.py
+++ b/tools/grit/grit/tool/build.py
@@ -5,6 +5,8 @@
 '''The 'grit build' tool.
 '''
 
+from __future__ import print_function
+
 import codecs
 import filecmp
 import getopt
@@ -210,7 +212,7 @@
         sys.exit(0)
 
     if len(args):
-      print 'This tool takes no tool-specific arguments.'
+      print('This tool takes no tool-specific arguments.')
       return 2
     self.SetOptions(opts)
     self.VerboseOut('Output directory: %s (absolute path: %s)\n' %
@@ -401,7 +403,7 @@
     warnings = shortcuts.GenerateDuplicateShortcutsWarnings(
         self.res.UberClique(), self.res.GetTcProject())
     if warnings:
-      print '\n'.join(warnings)
+      print('\n'.join(warnings))
 
     # Print out any fallback warnings, and missing translation errors, and
     # exit with an error code if there are missing translations in a non-pseudo
@@ -411,7 +413,7 @@
     if warnings:
       self.VerboseOut(warnings)
     if self.res.UberClique().HasMissingTranslations():
-      print self.res.UberClique().missing_translations_
+      print(self.res.UberClique().missing_translations_)
       sys.exit(-1)
 
 
@@ -442,8 +444,8 @@
 Extra output files:
 %s
 '''
-      print error % ('\n'.join(asserted), '\n'.join(actual), '\n'.join(missing),
-          '\n'.join(extra))
+      print(error % ('\n'.join(asserted), '\n'.join(actual), '\n'.join(missing),
+                     ' \n'.join(extra)))
       return False
     return True
 
diff --git a/tools/grit/grit/tool/build_unittest.py b/tools/grit/grit/tool/build_unittest.py
index 985882c..a4be0876 100755
--- a/tools/grit/grit/tool/build_unittest.py
+++ b/tools/grit/grit/tool/build_unittest.py
@@ -6,6 +6,8 @@
 '''Unit tests for the 'grit build' tool.
 '''
 
+from __future__ import print_function
+
 import codecs
 import os
 import sys
diff --git a/tools/grit/grit/tool/buildinfo.py b/tools/grit/grit/tool/buildinfo.py
index 5ef1a01..845aea9f 100644
--- a/tools/grit/grit/tool/buildinfo.py
+++ b/tools/grit/grit/tool/buildinfo.py
@@ -5,6 +5,8 @@
 """Output the list of files to be generated by GRIT from an input.
 """
 
+from __future__ import print_function
+
 import getopt
 import os
 import sys
@@ -41,7 +43,7 @@
         self.ShowUsage()
         sys.exit(0)
     if len(args) > 0:
-      print 'This tool takes exactly one argument: the output directory via -o'
+      print('This tool takes exactly one argument: the output directory via -o')
       return 2
     self.SetOptions(opts)
 
@@ -64,13 +66,13 @@
             if path:
               path = os.path.join(self.output_directory, path)
               path = os.path.normpath(path)
-              print '%s|%s' % ('rc_all', path)
+              print('%s|%s' % ('rc_all', path))
       res_tree.SetOutputLanguage(old_output_language)
 
     for output in res_tree.GetOutputFiles():
       path = os.path.join(self.output_directory, output.GetFilename())
       path = os.path.normpath(path)
-      print '%s|%s' % (output.GetType(), path)
+      print('%s|%s' % (output.GetType(), path))
 
     for infile in res_tree.GetInputFiles():
-      print 'input|%s' % os.path.normpath(infile)
+      print('input|%s' % os.path.normpath(infile))
diff --git a/tools/grit/grit/tool/buildinfo_unittest.py b/tools/grit/grit/tool/buildinfo_unittest.py
index b07bcd62..e7b086d9 100755
--- a/tools/grit/grit/tool/buildinfo_unittest.py
+++ b/tools/grit/grit/tool/buildinfo_unittest.py
@@ -6,6 +6,8 @@
 """Unit tests for the 'grit buildinfo' tool.
 """
 
+from __future__ import print_function
+
 import os
 import StringIO
 import sys
diff --git a/tools/grit/grit/tool/count.py b/tools/grit/grit/tool/count.py
index 1a45d89..ab37f2d 100644
--- a/tools/grit/grit/tool/count.py
+++ b/tools/grit/grit/tool/count.py
@@ -4,6 +4,8 @@
 
 '''Count number of occurrences of a given message ID.'''
 
+from __future__ import print_function
+
 import getopt
 import sys
 
@@ -32,8 +34,8 @@
   def Run(self, opts, args):
     args = self.ParseOptions(args)
     if len(args) != 1:
-      print ('This tool takes a single tool-specific argument, the message '
-             'ID to count.')
+      print('This tool takes a single tool-specific argument, the message '
+            'ID to count.')
       return 2
     self.SetOptions(opts)
 
@@ -47,4 +49,4 @@
       if c.GetId() == id:
         count += 1
 
-    print "There are %d occurrences of message %s." % (count, id)
+    print("There are %d occurrences of message %s." % (count, id))
diff --git a/tools/grit/grit/tool/diff_structures.py b/tools/grit/grit/tool/diff_structures.py
index 681d01ec..f007d01 100644
--- a/tools/grit/grit/tool/diff_structures.py
+++ b/tools/grit/grit/tool/diff_structures.py
@@ -5,6 +5,8 @@
 '''The 'grit sdiff' tool.
 '''
 
+from __future__ import print_function
+
 import os
 import getopt
 import sys
@@ -67,11 +69,11 @@
         sys.exit(0)
 
     if len(args) != 2:
-      print "Incorrect usage - 'grit help sdiff' for usage details."
+      print("Incorrect usage - 'grit help sdiff' for usage details.")
       return 2
 
     if 'P4DIFF' not in os.environ:
-      print "Environment variable P4DIFF not set; defaulting to 'windiff'."
+      print("Environment variable P4DIFF not set; defaulting to 'windiff'.")
       diff_program = 'windiff'
     else:
       diff_program = os.environ['P4DIFF']
diff --git a/tools/grit/grit/tool/interface.py b/tools/grit/grit/tool/interface.py
index 27fbbb7..e9232052 100644
--- a/tools/grit/grit/tool/interface.py
+++ b/tools/grit/grit/tool/interface.py
@@ -5,6 +5,7 @@
 '''Base class and interface for tools.
 '''
 
+from __future__ import print_function
 
 class Tool(object):
   '''Base class for all tools.  Tools should use their docstring (i.e. the
@@ -40,7 +41,7 @@
 
   def ShowUsage(self):
     '''Show usage text for this tool.'''
-    print self.__doc__
+    print(self.__doc__)
 
   def SetOptions(self, opts):
     self.o = opts
diff --git a/tools/grit/grit/tool/menu_from_parts.py b/tools/grit/grit/tool/menu_from_parts.py
index c8c3619c..b8371ab 100644
--- a/tools/grit/grit/tool/menu_from_parts.py
+++ b/tools/grit/grit/tool/menu_from_parts.py
@@ -4,6 +4,8 @@
 
 '''The 'grit menufromparts' tool.'''
 
+from __future__ import print_function
+
 import types
 
 from grit import grd_reader
@@ -63,7 +65,8 @@
           if isinstance(part, types.StringTypes):
             id = grit.extern.tclib.GenerateMessageId(part)
             if id not in xtb:
-              print "WARNING didn't find all translations for menu %s" % node.attrs['name']
+              print("WARNING didn't find all translations for menu %s" %
+                    (node.attrs['name'],))
               translation = []
               break
             translation.append(xtb[id])
diff --git a/tools/grit/grit/tool/newgrd.py b/tools/grit/grit/tool/newgrd.py
index 385a806..18579596 100644
--- a/tools/grit/grit/tool/newgrd.py
+++ b/tools/grit/grit/tool/newgrd.py
@@ -5,6 +5,8 @@
 '''Tool to create a new, empty .grd file with all the basic sections.
 '''
 
+from __future__ import print_function
+
 import getopt
 import sys
 
@@ -74,9 +76,10 @@
   def Run(self, opts, args):
     args = self.ParseOptions(args)
     if len(args) != 1:
-      print 'This tool requires exactly one argument, the name of the output file.'
+      print('This tool requires exactly one argument, the name of the output '
+            'file.')
       return 2
     filename = args[0]
     with util.WrapOutputStream(open(filename, 'w'), 'utf-8') as out:
       out.write(_FILE_CONTENTS)
-    print "Wrote file %s" % filename
+    print("Wrote file %s" % filename)
diff --git a/tools/grit/grit/tool/postprocess_interface.py b/tools/grit/grit/tool/postprocess_interface.py
index 08566eb..4bb8c5871 100644
--- a/tools/grit/grit/tool/postprocess_interface.py
+++ b/tools/grit/grit/tool/postprocess_interface.py
@@ -5,6 +5,7 @@
 ''' Base class for postprocessing of RC files.
 '''
 
+from __future__ import print_function
 
 class PostProcessor(object):
   ''' Base class for postprocessing of the RC file data before being
diff --git a/tools/grit/grit/tool/postprocess_unittest.py b/tools/grit/grit/tool/postprocess_unittest.py
index 91f02d69..77fe228b 100755
--- a/tools/grit/grit/tool/postprocess_unittest.py
+++ b/tools/grit/grit/tool/postprocess_unittest.py
@@ -8,6 +8,8 @@
    modify the grd data tree, changing the message name attributes.
 '''
 
+from __future__ import print_function
+
 import os
 import re
 import sys
diff --git a/tools/grit/grit/tool/preprocess_interface.py b/tools/grit/grit/tool/preprocess_interface.py
index 97404e9f..67974e7 100644
--- a/tools/grit/grit/tool/preprocess_interface.py
+++ b/tools/grit/grit/tool/preprocess_interface.py
@@ -5,6 +5,7 @@
 ''' Base class for preprocessing of RC files.
 '''
 
+from __future__ import print_function
 
 class PreProcessor(object):
   ''' Base class for preprocessing of the RC file data before being
diff --git a/tools/grit/grit/tool/preprocess_unittest.py b/tools/grit/grit/tool/preprocess_unittest.py
index da4242b..40b95cd 100755
--- a/tools/grit/grit/tool/preprocess_unittest.py
+++ b/tools/grit/grit/tool/preprocess_unittest.py
@@ -8,6 +8,8 @@
    provide the actual rctext data.
 '''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/tool/rc2grd.py b/tools/grit/grit/tool/rc2grd.py
index 4850e824..f11560d5 100644
--- a/tools/grit/grit/tool/rc2grd.py
+++ b/tools/grit/grit/tool/rc2grd.py
@@ -4,6 +4,7 @@
 
 '''The 'grit rc2grd' tool.'''
 
+from __future__ import print_function
 
 import os.path
 import getopt
@@ -189,8 +190,8 @@
   def Run(self, opts, args):
     args = self.ParseOptions(args)
     if len(args) != 1:
-      print ('This tool takes a single tool-specific argument, the path to the\n'
-             '.rc file to process.')
+      print('This tool takes a single tool-specific argument, the path to the\n'
+            '.rc file to process.')
       return 2
     self.SetOptions(opts)
 
@@ -203,7 +204,8 @@
     with util.WrapOutputStream(file(out_path, 'w'), 'utf-8') as outfile:
       outfile.write(grd_text)
 
-    print 'Wrote output file %s.\nPlease check for TODO items in the file.' % out_path
+    print('Wrote output file %s.\nPlease check for TODO items in the file.' %
+          (out_path,))
 
 
   def Process(self, rctext, rc_path):
@@ -411,5 +413,5 @@
 
       return msg
     except:
-      print 'Exception processing message with text "%s"' % text
+      print('Exception processing message with text "%s"' % text)
       raise
diff --git a/tools/grit/grit/tool/rc2grd_unittest.py b/tools/grit/grit/tool/rc2grd_unittest.py
index 0e48f07f..9976df7 100755
--- a/tools/grit/grit/tool/rc2grd_unittest.py
+++ b/tools/grit/grit/tool/rc2grd_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for grit.tool.rc2grd'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/tool/resize.py b/tools/grit/grit/tool/resize.py
index 3891717..d44160d 100644
--- a/tools/grit/grit/tool/resize.py
+++ b/tools/grit/grit/tool/resize.py
@@ -5,6 +5,8 @@
 '''The 'grit resize' tool.
 '''
 
+from __future__ import print_function
+
 import getopt
 import os
 import sys
@@ -253,7 +255,7 @@
       ).replace('[[DIALOG_NAME]]', project_name)
     fname = os.path.join(dir_path, '%s.vcproj' % project_name)
     self.WriteFile(fname, project_text)
-    print "Wrote %s" % fname
+    print("Wrote %s" % fname)
 
     # Create the .rc file
     # Output all <include> nodes since the dialogs might depend on them (e.g.
@@ -277,14 +279,14 @@
 
     fname = os.path.join(dir_path, '%s.rc' % project_name)
     self.WriteFile(fname, rc_text, self.GetEncoding())
-    print "Wrote %s" % fname
+    print("Wrote %s" % fname)
 
     # Create the resource.h file
     header_defines = ''.join(rc_header.FormatDefines(grd))
     header_text = HEADER_TEMPLATE.replace('[[DEFINES]]', header_defines)
     fname = os.path.join(dir_path, 'resource.h')
     self.WriteFile(fname, header_text)
-    print "Wrote %s" % fname
+    print("Wrote %s" % fname)
 
   def WriteFile(self, filename, contents, encoding='cp1252'):
     with open(filename, 'wb') as f:
diff --git a/tools/grit/grit/tool/test.py b/tools/grit/grit/tool/test.py
index 1fcf23c..241a976 100644
--- a/tools/grit/grit/tool/test.py
+++ b/tools/grit/grit/tool/test.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 from grit.tool import interface
 
 class TestTool(interface.Tool):
@@ -14,9 +16,9 @@
     return 'A do-nothing tool for testing command-line parsing.'
 
   def Run(self, global_options, my_arguments):
-    print 'NOTE This tool is only for testing the parsing of global options and'
-    print 'tool-specific arguments that it receives.  You may have intended to'
-    print 'run "grit unit" which is the unit-test suite for GRIT.'
-    print 'Options: %s' % repr(global_options)
-    print 'Arguments: %s' % repr(my_arguments)
+    print('NOTE This tool is only for testing the parsing of global options and')
+    print('tool-specific arguments that it receives.  You may have intended to')
+    print('run "grit unit" which is the unit-test suite for GRIT.')
+    print('Options: %s' % repr(global_options))
+    print('Arguments: %s' % repr(my_arguments))
     return 0
diff --git a/tools/grit/grit/tool/transl2tc.py b/tools/grit/grit/tool/transl2tc.py
index d75e2b5c..2302682d 100644
--- a/tools/grit/grit/tool/transl2tc.py
+++ b/tools/grit/grit/tool/transl2tc.py
@@ -5,6 +5,8 @@
 '''The 'grit transl2tc' tool.
 '''
 
+from __future__ import print_function
+
 import sys
 
 from grit import grd_reader
diff --git a/tools/grit/grit/tool/transl2tc_unittest.py b/tools/grit/grit/tool/transl2tc_unittest.py
index db64d10..61f1dad 100755
--- a/tools/grit/grit/tool/transl2tc_unittest.py
+++ b/tools/grit/grit/tool/transl2tc_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for the 'grit transl2tc' tool.'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit/tool/unit.py b/tools/grit/grit/tool/unit.py
index c78ae0b9..3b6f45d 100644
--- a/tools/grit/grit/tool/unit.py
+++ b/tools/grit/grit/tool/unit.py
@@ -4,6 +4,8 @@
 
 '''GRIT tool that runs the unit test suite for GRIT.'''
 
+from __future__ import print_function
+
 import getopt
 import sys
 import unittest
@@ -31,7 +33,7 @@
   def Run(self, opts, args):
     args = self.ParseOptions(args)
     if args:
-      print 'This tool takes no arguments.'
+      print('This tool takes no arguments.')
       return 2
 
     return unittest.TextTestRunner(verbosity=2).run(
diff --git a/tools/grit/grit/tool/xmb.py b/tools/grit/grit/tool/xmb.py
index 150652b4..c2f5a87 100644
--- a/tools/grit/grit/tool/xmb.py
+++ b/tools/grit/grit/tool/xmb.py
@@ -5,6 +5,8 @@
 """The 'grit xmb' tool.
 """
 
+from __future__ import print_function
+
 import getopt
 import os
 import sys
@@ -192,8 +194,8 @@
         self.ShowUsage()
         sys.exit(0)
     if not len(args) == 1:
-      print ('grit xmb takes exactly one argument, the path to the XMB file '
-             'to output.')
+      print('grit xmb takes exactly one argument, the path to the XMB file '
+            'to output.')
       return 2
 
     xmb_path = args[0]
@@ -208,7 +210,7 @@
         res_tree, output_file, limit_file, limit_is_grd, limit_file_dir)
     if limit_file:
       limit_file.close()
-    print "Wrote %s" % xmb_path
+    print("Wrote %s" % xmb_path)
 
   def Process(self, res_tree, output_file, limit_file=None, limit_is_grd=False,
               dir=None):
diff --git a/tools/grit/grit/tool/xmb_unittest.py b/tools/grit/grit/tool/xmb_unittest.py
index 200ecd9a..2df5951b 100755
--- a/tools/grit/grit/tool/xmb_unittest.py
+++ b/tools/grit/grit/tool/xmb_unittest.py
@@ -5,6 +5,8 @@
 
 '''Unit tests for 'grit xmb' tool.'''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
diff --git a/tools/grit/grit/util.py b/tools/grit/grit/util.py
index d98a0bf..3b8a3db 100644
--- a/tools/grit/grit/util.py
+++ b/tools/grit/grit/util.py
@@ -5,6 +5,8 @@
 '''Utilities used by GRIT.
 '''
 
+from __future__ import print_function
+
 import codecs
 import htmlentitydefs
 import os
@@ -434,7 +436,7 @@
   if lang in _LANG_TO_CODEPAGE:
     return _LANG_TO_CODEPAGE[lang]
   else:
-    print "Not sure which codepage to use for %s, assuming cp1252" % lang
+    print("Not sure which codepage to use for %s, assuming cp1252" % lang)
     return 1252
 
 def NewClassInstance(class_name, class_type):
diff --git a/tools/grit/grit/util_unittest.py b/tools/grit/grit/util_unittest.py
index ecadfa3c..a88a032 100755
--- a/tools/grit/grit/util_unittest.py
+++ b/tools/grit/grit/util_unittest.py
@@ -6,6 +6,8 @@
 '''Unit test that checks some of util functions.
 '''
 
+from __future__ import print_function
+
 import os
 import sys
 if __name__ == '__main__':
@@ -82,7 +84,7 @@
       with open('testfile', 'wb') as f:
         f.write(data)
       if util.ReadFile('testfile', encoding) != expected_result:
-        print (util.ReadFile('testfile', encoding), expected_result)
+        print(util.ReadFile('testfile', encoding), expected_result)
       self.failUnless(util.ReadFile('testfile', encoding) == expected_result)
 
     test_std_newline = '\xEF\xBB\xBFabc\ndef'  # EF BB BF is UTF-8 BOM
diff --git a/tools/grit/grit/xtb_reader.py b/tools/grit/grit/xtb_reader.py
index 8d5f58f..ac2f1eae 100644
--- a/tools/grit/grit/xtb_reader.py
+++ b/tools/grit/grit/xtb_reader.py
@@ -5,6 +5,7 @@
 '''Fast and efficient parser for XTB files.
 '''
 
+from __future__ import print_function
 
 import sys
 import xml.sax
diff --git a/tools/grit/grit/xtb_reader_unittest.py b/tools/grit/grit/xtb_reader_unittest.py
index bab019c..a6d6ad61 100755
--- a/tools/grit/grit/xtb_reader_unittest.py
+++ b/tools/grit/grit/xtb_reader_unittest.py
@@ -5,6 +5,7 @@
 
 '''Unit tests for grit.xtb_reader'''
 
+from __future__ import print_function
 
 import os
 import sys
diff --git a/tools/grit/grit_info.py b/tools/grit/grit_info.py
index 7c57aff..4e08c3b 100755
--- a/tools/grit/grit_info.py
+++ b/tools/grit/grit_info.py
@@ -6,6 +6,8 @@
 '''Tool to determine inputs and outputs of a grit file.
 '''
 
+from __future__ import print_function
+
 import optparse
 import os
 import posixpath
@@ -104,9 +106,9 @@
 
 
 def PrintUsage():
-  print 'USAGE: ./grit_info.py --inputs [-D foo] [-f resource_ids] <grd-file>'
-  print ('       ./grit_info.py --outputs [-D foo] [-f resource_ids] ' +
-      '<out-prefix> <grd-file>')
+  print('USAGE: ./grit_info.py --inputs [-D foo] [-f resource_ids] <grd-file>')
+  print('       ./grit_info.py --outputs [-D foo] [-f resource_ids] ' +
+        '<out-prefix> <grd-file>')
 
 
 def DoMain(argv):
@@ -175,9 +177,9 @@
     result = DoMain(argv[1:])
   except WrongNumberOfArguments, e:
     PrintUsage()
-    print e
+    print(e)
     return 1
-  print result
+  print(result)
   return 0
 
 
diff --git a/tools/grit/pak_util.py b/tools/grit/pak_util.py
index b71a20d..bd38dce1 100755
--- a/tools/grit/pak_util.py
+++ b/tools/grit/pak_util.py
@@ -9,6 +9,8 @@
 https://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
 """
 
+from __future__ import print_function
+
 import argparse
 import hashlib
 import os
diff --git a/tools/grit/stamp_grit_sources.py b/tools/grit/stamp_grit_sources.py
index d43d4b8..bc7265c6 100644
--- a/tools/grit/stamp_grit_sources.py
+++ b/tools/grit/stamp_grit_sources.py
@@ -12,6 +12,8 @@
 # Usage:
 #    stamp_grit_sources.py <directory> <stamp-file> <.d-file>
 
+from __future__ import print_function
+
 import os
 import sys
 
@@ -39,7 +41,7 @@
 
 def main(argv):
   if len(argv) != 4:
-    print "Error: expecting 3 args."
+    print("Error: expecting 3 args.")
     return 1
 
   grit_root_dir = sys.argv[1]
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 9b0e724..4cab0957 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -814,6 +814,7 @@
     },
 
     'tryserver.chromium.win': {
+      'gpu-fyi-try-win-xr-builder': 'gpu_fyi_tests_release_trybot_x86',
       'gpu-fyi-try-win7-amd-dbg': 'gpu_fyi_tests_debug_trybot_x86',
       'gpu-fyi-try-win7-amd-dqp': 'deqp_release_trybot_x86',
       'gpu-fyi-try-win7-amd-rel': 'gpu_fyi_tests_release_trybot_x86',
@@ -1181,11 +1182,11 @@
     # Cast Linux takes very long in linking, possibly due to being on GCE
     # (crbug/794423).
     'cast_release_bot': [
-      'cast', 'release_bot', 'minimal_symbols',
+      'cast', 'cast_exo', 'release_bot', 'minimal_symbols',
     ],
 
     'cast_release_trybot': [
-      'cast', 'release_trybot',
+      'cast', 'cast_exo', 'release_trybot',
     ],
 
     'cast_audio_release_bot': [
@@ -1955,6 +1956,10 @@
       'gn_args': 'is_cast_audio_only=true'
     },
 
+    'cast_exo': {
+      'gn_args': 'enable_cast_wayland_server=true',
+    },
+
     'cfi': {
       'gn_args': 'is_cfi=true',
     },
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f8a6504..2e288dac 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9344,7 +9344,9 @@
   <int value="15" label="RESOURCE_TYPE_SERVICE_WORKER"/>
   <int value="16" label="RESOURCE_TYPE_CSP_REPORT"/>
   <int value="17" label="RESOURCE_TYPE_PLUGIN_RESOURCE"/>
-  <int value="18" label="RESOURCE_TYPE_NAVIGATION_PRELOAD"/>
+  <int value="18" label="RESOURCE_TYPE_NAVIGATION_PRELOAD (obsolete)"/>
+  <int value="19" label="RESOURCE_TYPE_NAVIGATION_PRELOAD_MAIN_FRAME"/>
+  <int value="20" label="RESOURCE_TYPE_NAVIGATION_PRELOAD_SUB_FRAME"/>
 </enum>
 
 <enum name="ContentSetting">
@@ -34198,6 +34200,7 @@
   <int value="-1250611337" label="ChromeVoxArcSupport:disabled"/>
   <int value="-1248478422" label="enable-zip-archiver-packer"/>
   <int value="-1246840031" label="OptInImeMenu:disabled"/>
+  <int value="-1243694853" label="DeferAllScript:disabled"/>
   <int value="-1241747717" label="enable-android-password-link"/>
   <int value="-1241002324" label="FocusMode:enabled"/>
   <int value="-1239262870" label="TextFragmentAnchor:enabled"/>
@@ -34881,9 +34884,11 @@
   <int value="-262122630" label="ArcEnableDocumentsProviderInFilesApp:enabled"/>
   <int value="-260355779" label="UnfilteredBluetoothDevices:enabled"/>
   <int value="-258081634" label="AutofillAssistantDirectActions:disabled"/>
+  <int value="-255264176" label="OmniboxSearchEngineLogo:disabled"/>
   <int value="-254887599" label="google-profile-info"/>
   <int value="-250822813" label="PwaImprovedSplashScreen:enabled"/>
   <int value="-250721831" label="AndroidAutofillAccessibility:disabled"/>
+  <int value="-250543540" label="DeferAllScript:enabled"/>
   <int value="-248223420" label="AutofillKeyboardAccessory:disabled"/>
   <int value="-243323793" label="OmniboxOnDeviceHeadProvider:enabled"/>
   <int value="-241353344" label="MidiManagerWinrt:disabled"/>
@@ -35135,6 +35140,7 @@
   <int value="98134240" label="material-design-ink-drop-animation-speed"/>
   <int value="98218116" label="ContextualSearchLongpressResolve:disabled"/>
   <int value="99177659" label="OmniboxUICuesForSearchHistoryMatches:disabled"/>
+  <int value="103347629" label="WinrtSensorsImplementation:enabled"/>
   <int value="103932290" label="show-autofill-type-predictions"/>
   <int value="105046382" label="ParallelDownloading:disabled"/>
   <int value="106840653" label="mus"/>
@@ -35395,6 +35401,7 @@
       label="AutofillEnforceMinRequiredFieldsForQuery:enabled"/>
   <int value="510814146" label="OfflineBookmarks:enabled"/>
   <int value="511179195" label="ShoppingAssist:disabled"/>
+  <int value="513258875" label="WinrtSensorsImplementation:disabled"/>
   <int value="513356954" label="InstantTethering:disabled"/>
   <int value="513372959" label="ViewsProfileChooser:enabled"/>
   <int value="517429103" label="AutofillImportDynamicForms:enabled"/>
@@ -36505,6 +36512,7 @@
   <int value="2126203058" label="force-show-update-menu-badge"/>
   <int value="2129184006" label="NTPOfflinePageDownloadSuggestions:enabled"/>
   <int value="2129929643" label="enable-use-zoom-for-dsf"/>
+  <int value="2132595171" label="OmniboxSearchEngineLogo:enabled"/>
   <int value="2134480727" label="MediaSessionAccelerators:disabled"/>
   <int value="2135408204" label="OverscrollHistoryNavigation:disabled"/>
   <int value="2137113620"
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 4a3ecc5..1d7fc19 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -4923,8 +4923,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.AppsProfile.Creation.Duration" units="ms"
-    expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time needed to create a lock screen apps profile. This metric
     is recorded only if the profile creation was successful. The lock screen
@@ -4935,8 +4936,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.AppsProfile.Creation.Success"
-    units="BooleanSuccess" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    units="BooleanSuccess" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     Boolean indicating whether the lock screen apps profile creation succeeded.
     The lock screen apps profile is created if the user has an app enabled on
@@ -4946,8 +4948,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.DataItemStorage.ClearTextItemSize"
-    units="bytes" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    units="bytes" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The size of a data item stored in the lock screen data item storage using
     chrome.lockScreen.data API as sent from the app - the item will be encrypted
@@ -4956,8 +4959,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.DataItemStorage.EncryptedItemSize"
-    units="bytes" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    units="bytes" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The size of an encrypted data item stored in the lock screen data item
     storage using chrome.lockScreen.data API.
@@ -4965,8 +4969,10 @@
 </histogram>
 
 <histogram base="true"
-    name="Apps.LockScreen.DataItemStorage.FailedOperationDuration" units="ms">
-  <owner>tbarzic@chromium.org</owner>
+    name="Apps.LockScreen.DataItemStorage.FailedOperationDuration" units="ms"
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time it took to complete a lock screen data item storage
     operation. Reported only on the operation failure.
@@ -4974,8 +4980,9 @@
 </histogram>
 
 <histogram base="true" name="Apps.LockScreen.DataItemStorage.OperationDuration"
-    units="ms">
-  <owner>tbarzic@chromium.org</owner>
+    units="ms" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time it took to complete a lock screen data item storage
     operation. Reported only on the operation success.
@@ -4983,8 +4990,9 @@
 </histogram>
 
 <histogram base="true" name="Apps.LockScreen.DataItemStorage.OperationResult"
-    enum="LockScreenDataItemOperationResult">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenDataItemOperationResult" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The result of a lock screen data item storage operation returned through
     chrome.lockScreen.data extension API.
@@ -4992,8 +5000,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.DataItemStorage.RegisteredItemsCount"
-    expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     The number of data items saved in the lock screen data item storage per app.
     This is recorded on startup, when the app attempts to use the
@@ -5002,8 +5011,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.AppStatusOnNoteLaunch"
-    enum="LockScreenNoteAppStatusOnLaunch" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenNoteAppStatusOnLaunch" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     Reported when a user attempts to launch a note taking app on the lock
     screen. It reports the note taking app state in the lock screen apps profile
@@ -5013,8 +5023,9 @@
 </histogram>
 
 <histogram base="true" name="Apps.LockScreen.NoteTakingApp.AppWindowLifeTime"
-    units="ms">
-  <owner>tbarzic@chromium.org</owner>
+    units="ms" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time a lock screen enabled app window spent in a certain state
@@ -5025,8 +5036,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.AvailabilityOnScreenLock"
-    enum="LockScreenActionAvailability" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenActionAvailability" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The note taking action availability state on the lock screen, recorded when
@@ -5035,8 +5046,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.FinalAppSessionState"
-    enum="LockScreenAppSessionState" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenAppSessionState" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The state in which lock screen enabled note taking app was when the note
@@ -5045,8 +5056,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.LaunchDurationAtLaunchCancel"
-    units="ms">
-  <owner>tbarzic@chromium.org</owner>
+    units="ms" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount time a lock screen app had been launching when the app launch was
@@ -5055,8 +5066,9 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.LockScreen.NoteTakingApp.LaunchRequestOrdinalNumber">
-  <owner>tbarzic@chromium.org</owner>
+<histogram name="Apps.LockScreen.NoteTakingApp.LaunchRequestOrdinalNumber"
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     Ordinal number of a note taking app launch request from a lock screen within
@@ -5067,8 +5079,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.LaunchRequestReason"
-    enum="NewLockScreenNoteRequestType" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="NewLockScreenNoteRequestType" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The user action that launched note taking from the lock screen.
@@ -5076,8 +5088,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.LockScreenAppUnloaded"
-    enum="LockScreenAppUnloadStatus" expires_after="M78">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenAppUnloadStatus" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     Reported when a note taking app is unloaded from the lock screen apps
     profile while lock screen note taking is available. Reports the unload
@@ -5089,8 +5102,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.LockScreenInstallationDuration"
-    units="ms" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    units="ms" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     Amount of time needed to install a copy of a lock screen note taking app
@@ -5099,8 +5112,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.NoteTakingExitReason"
-    enum="LockScreenNoteTakingExitReason" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="LockScreenNoteTakingExitReason" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The reason the note taking on lock screen was ended (and lock screen app
@@ -5109,8 +5122,9 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.ReloadCountOnAppTermination"
-    expires_after="M78">
-  <owner>tbarzic@chromium.org</owner>
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
+  <owner>tbuckley@chromium.org</owner>
   <summary>
     Reported when a note taking app is terminated in the lock screen apps
     profile. It reports the number of times the app was reloaded in the lock
@@ -5120,8 +5134,8 @@
 </histogram>
 
 <histogram name="Apps.LockScreen.NoteTakingApp.TimeToLoadAppWindowContents"
-    units="ms">
-  <owner>tbarzic@chromium.org</owner>
+    units="ms" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time needed to load a note taking app window contents on the
@@ -5130,8 +5144,9 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.LockScreen.NoteTakingApp.TimeToShowWindow" units="ms">
-  <owner>tbarzic@chromium.org</owner>
+<histogram name="Apps.LockScreen.NoteTakingApp.TimeToShowWindow" units="ms"
+    expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The amount of time needed to launch a note taking app window from the lock
@@ -5142,6 +5157,9 @@
 
 <histogram name="Apps.LockScreen.NoteTakingApp.UnlockUIAction"
     enum="LockScreenNoteTakingUnlockUIAction">
+  <obsolete>
+    Obsolete - tracks actions in UI that did not launch.
+  </obsolete>
   <owner>tbarzic@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
@@ -5162,8 +5180,8 @@
 </histogram>
 
 <histogram name="Apps.NoteTakingApp.DefaultLaunchResult"
-    enum="NoteTakingAppLaunchResult">
-  <owner>tbarzic@chromium.org</owner>
+    enum="NoteTakingAppLaunchResult" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The result of attempting to launch a default note-taking app on Chrome OS.
@@ -5172,8 +5190,8 @@
 </histogram>
 
 <histogram name="Apps.NoteTakingApp.PreferredLaunchResult"
-    enum="NoteTakingAppLaunchResult" expires_after="M77">
-  <owner>tbarzic@chromium.org</owner>
+    enum="NoteTakingAppLaunchResult" expires_after="M85">
+  <owner>dstockwell@chromium.org</owner>
   <owner>tbuckley@chromium.org</owner>
   <summary>
     The result of attempting to launch the user-specified preferred note-taking
@@ -11828,8 +11846,9 @@
   </summary>
 </histogram>
 
-<histogram name="BackgroundSync.Event.BatchSize" expires_after="M77">
-  <owner>iclelland@chromium.org</owner>
+<histogram name="BackgroundSync.Event.BatchSize" expires_after="M85">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records the number of sync events which were fired in a batch. A batch is
     defined as the set of sync events dispatched at the same time by the
@@ -11850,8 +11869,9 @@
 </histogram>
 
 <histogram name="BackgroundSync.Event.OneShotResultPattern"
-    enum="BackgroundSyncResultPattern" expires_after="M77">
-  <owner>jkarlin@chromium.org</owner>
+    enum="BackgroundSyncResultPattern" expires_after="M85">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records whether a periodic sync event succeeded or failed and whether the
     sync event finished in the foreground or background.
@@ -11863,8 +11883,9 @@
 </histogram>
 
 <histogram name="BackgroundSync.Event.OneShotStartedInForeground"
-    enum="BooleanInForeground" expires_after="M77">
-  <owner>jkarlin@chromium.org</owner>
+    enum="BooleanInForeground" expires_after="M85">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records whether a one-shot sync started firing in the foreground or
     background. Called shortly before the event is fired.
@@ -11909,8 +11930,9 @@
   </summary>
 </histogram>
 
-<histogram name="BackgroundSync.Event.Time" units="ms">
-  <owner>iclelland@chromium.org</owner>
+<histogram name="BackgroundSync.Event.Time" units="ms" expires_after="M85">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Time taken to execute a batch of sync events. A batch is defined as the set
     of sync events dispatched at the same time by the BackgroundSyncManager.
@@ -11921,8 +11943,9 @@
 </histogram>
 
 <histogram name="BackgroundSync.LaunchTask.CancelSuccess" enum="BooleanSuccess"
-    expires_after="M77">
-  <owner>iclelland@chromium.org</owner>
+    expires_after="M80">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records the result of attempting to cancel a future browser launch using the
     GCM Network Manager on Android.
@@ -11930,8 +11953,9 @@
 </histogram>
 
 <histogram name="BackgroundSync.LaunchTask.PlayServicesAvailable"
-    enum="Boolean" expires_after="M77">
-  <owner>iclelland@chromium.org</owner>
+    enum="Boolean" expires_after="M80">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records whether Google Play Services is available to the Background Sync
     system on Android, for scheduling future sync events when the browser is not
@@ -11941,7 +11965,8 @@
 
 <histogram name="BackgroundSync.LaunchTask.ScheduleSuccess"
     enum="BooleanSuccess">
-  <owner>iclelland@chromium.org</owner>
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records the result of attempting to schedule a future browser launch using
     the GCM Network Manager on Android.
@@ -11950,7 +11975,8 @@
 
 <histogram name="BackgroundSync.NetworkObserver.HasPermission" enum="Boolean"
     expires_after="M78">
-  <owner>iclelland@chromium.org</owner>
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records whether the browser has sufficient permissions to create a
     BackgroundSyncNetworkObserver object on Android, at the point when it tries
@@ -11986,7 +12012,8 @@
 
 <histogram name="BackgroundSync.Registration.OneShot"
     enum="BackgroundSyncStatus">
-  <owner>iclelland@chromium.org</owner>
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records the result of attempting to register a one-shot sync.
   </summary>
@@ -11994,7 +12021,8 @@
 
 <histogram name="BackgroundSync.Registration.OneShot.CouldFire"
     enum="BooleanCouldFireImmediately">
-  <owner>iclelland@chromium.org</owner>
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records the result of attempting to register a one-shot sync in a situation
     where the sync could fire immediately.
@@ -12015,7 +12043,8 @@
 
 <histogram name="BackgroundSync.Registration.OneShot.IsDuplicate"
     enum="BooleanRegistrationIsDuplicate" expires_after="M78">
-  <owner>iclelland@chromium.org</owner>
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     Records whether a one-shot sync registration exactly duplicates an existing
     registered sync.
@@ -20646,8 +20675,9 @@
 </histogram>
 
 <histogram name="ContentSettings.ExceptionScheme" enum="ContentSettingScheme"
-    expires_after="M77">
-  <owner>lshang@chromium.org</owner>
+    expires_after="M87">
+  <owner>alexmos@chromium.org</owner>
+  <owner>engedy@chromium.org</owner>
   <summary>
     Records the schemes of content setting exceptions at browser start. Each
     exception provides one sample.
@@ -20655,8 +20685,9 @@
 </histogram>
 
 <histogram name="ContentSettings.ExceptionSchemeFile.HasPath"
-    enum="BooleanHasPath" expires_after="M77">
+    enum="BooleanHasPath" expires_after="M87">
   <owner>alexmos@chromium.org</owner>
+  <owner>engedy@chromium.org</owner>
   <summary>
     Records how often a content settings exception for the file: scheme has a
     non-empty path. Recorded once per exception at browser start.
@@ -20664,8 +20695,9 @@
 </histogram>
 
 <histogram name="ContentSettings.ExceptionSchemeFile.Type.WithoutPath"
-    enum="ContentType" expires_after="M77">
+    enum="ContentType" expires_after="M87">
   <owner>alexmos@chromium.org</owner>
+  <owner>engedy@chromium.org</owner>
   <summary>
     Count of how often a specific content type has a content settings exception
     defined for a file: scheme with no path (i.e., wildcard path). Recorded once
@@ -20677,8 +20709,9 @@
 </histogram>
 
 <histogram name="ContentSettings.ExceptionSchemeFile.Type.WithPath"
-    enum="ContentType" expires_after="M77">
+    enum="ContentType" expires_after="M87">
   <owner>alexmos@chromium.org</owner>
+  <owner>engedy@chromium.org</owner>
   <summary>
     Count of how often a specific content type has a content settings exception
     defined for a file: scheme with a valid, non-empty path. Recorded once per
@@ -20733,7 +20766,7 @@
 </histogram>
 
 <histogram name="ContentSettings.MixedScript"
-    enum="ContentSettingMixedScriptAction" expires_after="M77">
+    enum="ContentSettingMixedScriptAction" expires_after="M80">
   <owner>estark@chromium.org</owner>
   <summary>
     Tracks whether the mixed content shield was shown, and how the user
@@ -40241,6 +40274,20 @@
   </summary>
 </histogram>
 
+<histogram name="Extensions.ForceInstalledTotalCandidateCount"
+    expires_after="2019-11-01">
+  <owner>poromov@chromium.org</owner>
+  <owner>askaraitzhan@google.com</owner>
+  <owner>burunduk@chromium.org</owner>
+  <summary>
+    Total amount of extensions in force installed list. Gets recorded on profile
+    open (which happens on every startup and when user logs in) and tries to
+    load extensions. Number of records should correspond to the sum of records
+    in &quot;Extensions.ForceInstalledLoadTime&quot; and records in
+    &quot;Extensions.ForceInstalledTimedOutAndNotInstalledCount&quot;.
+  </summary>
+</histogram>
+
 <histogram name="Extensions.FromWebstoreInconsistency"
     enum="ExtensionFromWebstoreInconcistencyEnum" expires_after="2018-08-30">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
@@ -44112,6 +44159,9 @@
 
 <histogram name="FileSystem.OriginFailedCanCommitURL" enum="BooleanHit"
     expires_after="M77">
+  <obsolete>
+    Removed July 2019.
+  </obsolete>
   <owner>alexmos@chromium.org</owner>
   <summary>
     Logged when we fail the permission validation of the filesystem URL origin,
@@ -52154,7 +52204,7 @@
 </histogram>
 
 <histogram name="IOS.RepeatedExternalAppPromptResponse"
-    enum="IOSRepeatedExternalAppPromptResponse" expires_after="M77">
+    enum="IOSRepeatedExternalAppPromptResponse" expires_after="2020-07-01">
   <owner>mrefaat@chromium.org</owner>
   <summary>
     The user reaction to the prompt that appears when a website tries to open an
@@ -52290,7 +52340,7 @@
 </histogram>
 
 <histogram name="IOS.StoreKit.ITunesURLsHandlingResult"
-    enum="IOSITunesURLsStoreKitHandlingResult" expires_after="M77">
+    enum="IOSITunesURLsStoreKitHandlingResult" expires_after="2020-07-01">
   <owner>mrefaat@chromium.org</owner>
   <owner>eugenebut@chromium.org</owner>
   <summary>
@@ -52302,7 +52352,7 @@
 </histogram>
 
 <histogram name="IOS.StoreKitLoadedSuccessfully" enum="BooleanSuccess"
-    expires_after="M77">
+    expires_after="2020-07-01">
   <owner>mrefaat@chromium.org</owner>
   <summary>
     Whether the StoreKit loaded the required iTunes product successfully or not.
@@ -52587,6 +52637,9 @@
 
 <histogram name="Keyboard.Shortcuts.CrosSearchKeyDelay" units="ms"
     expires_after="M77">
+  <obsolete>
+    Deprecated 2019-07. Search key shortcut analysis is done.
+  </obsolete>
   <owner>xiaohuic@chromium.org</owner>
   <owner>zalcorn@chromium.org</owner>
   <summary>
@@ -56428,7 +56481,11 @@
 </histogram>
 
 <histogram name="Media.DetectedTrackCount.Audio" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975315.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected audio tracks in HTML5 media. Not all may be usable by the
     player.
@@ -56436,15 +56493,23 @@
 </histogram>
 
 <histogram name="Media.DetectedTrackCount.Text" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975315.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected text tracks in HTML5 media. Not all may be usable by the
     player.
   </summary>
 </histogram>
 
-<histogram name="Media.DetectedTrackCount.Video">
+<histogram name="Media.DetectedTrackCount.Video" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975315.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected video tracks in HTML5 media. Not all may be usable by the
     player.
@@ -57692,8 +57757,12 @@
   <owner>wolenetz@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
-    Audio codec used in Media Source Extensions playback. Set when AddId() is
-    called during playback.
+    Audio codec used in Media Source Extensions playback. Set when MediaSource
+    addSourceBuffer() is successfully called during playback. Also set twice
+    during a successful SourceBuffer changeType() operation. See issue 535738
+    for reworking MSE codec histograms to record on each successfully parsed
+    initialization segment (possibly filtered to record only when actual new
+    codec configurations are parsed).
   </summary>
 </histogram>
 
@@ -57743,7 +57812,11 @@
 </histogram>
 
 <histogram name="Media.MSE.DetectedTrackCount.Audio" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975090.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected audio tracks in Media Source Extensions playback. Not all
     may be usable by the player.
@@ -57751,7 +57824,11 @@
 </histogram>
 
 <histogram name="Media.MSE.DetectedTrackCount.Text" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975090.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected text tracks in Media Source Extensions playback. Not all
     may be usable by the player. This count includes only explicitly signalled
@@ -57760,8 +57837,12 @@
   </summary>
 </histogram>
 
-<histogram name="Media.MSE.DetectedTrackCount.Video">
+<histogram name="Media.MSE.DetectedTrackCount.Video" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975090.
+  </obsolete>
   <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of detected video tracks in Media Source Extensions playback. Not all
     may be usable by the player.
@@ -57855,7 +57936,11 @@
 </histogram>
 
 <histogram name="Media.MSE.NumberOfTracks" expires_after="M77">
-  <owner>acolwell@chromium.org</owner>
+  <obsolete>
+    Deprecated 07/2019 in issue 975898.
+  </obsolete>
+  <owner>wolenetz@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
   <summary>
     Number of tracks specified to AddId() for Media Source Extensions playback.
     May be called multiple times per element if playback is dynamically altered.
@@ -57909,8 +57994,12 @@
   <owner>wolenetz@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
-    Video codec used in Media Source Extensions playback. Set when AddId() is
-    called during playback.
+    Video codec used in Media Source Extensions playback. Set when MediaSource
+    addSourceBuffer() is successfully called during playback. Also set twice
+    during a successful SourceBuffer changeType() operation. See issue 535738
+    for reworking MSE codec histograms to record on each successfully parsed
+    initialization segment (possibly filtered to record only when actual new
+    codec configurations are parsed).
   </summary>
 </histogram>
 
@@ -57922,7 +58011,11 @@
   <owner>media-dev@chromium.org</owner>
   <summary>
     Video codec used in Media Source Extensions playback if the media container
-    is MP4. Set when AddId() is called during playback.
+    is MP4. Set when MediaSource addSourceBuffer() is successfully called during
+    playback. Also set twice during a successful SourceBuffer changeType()
+    operation. See issue 535738 for reworking MSE codec histograms to record on
+    each successfully parsed initialization segment (possibly filtered to record
+    only when actual new codec configurations are parsed).
   </summary>
 </histogram>
 
@@ -57934,7 +58027,11 @@
   <owner>media-dev@chromium.org</owner>
   <summary>
     Video codec used in Media Source Extensions playback if the media container
-    is WebM. Set when AddId() is called during playback.
+    is WebM. Set when MediaSource addSourceBuffer() is successfully called
+    during playback. Also set twice during a successful SourceBuffer
+    changeType() operation. See issue 535738 for reworking MSE codec histograms
+    to record on each successfully parsed initialization segment (possibly
+    filtered to record only when actual new codec configurations are parsed).
   </summary>
 </histogram>
 
@@ -58530,6 +58627,9 @@
 
 <histogram name="Media.SRC.PreloadAutoHasPoster" enum="Boolean"
     expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975367.
+  </obsolete>
   <owner>media-dev@chromium.org</owner>
   <summary>
     Whether a SRC= playback had a poster set at load() when the effective
@@ -58541,6 +58641,9 @@
 
 <histogram name="Media.SRC.PreloadMetaDataHasPoster" enum="Boolean"
     expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 in issue 975367.
+  </obsolete>
   <owner>media-dev@chromium.org</owner>
   <summary>
     Whether a SRC= playback had a poster set at load() when the effective
@@ -59077,6 +59180,9 @@
 
 <histogram name="Media.Video.FullscreenOrientationLock.LockResult"
     enum="VideoFullscreenOrientationLockResult" expires_after="M77">
+  <obsolete>
+    Removed from code as of 07/2019.
+  </obsolete>
   <owner>mlamouri@chromium.org</owner>
   <summary>
     Result of the orientation lock attempt when a video enters fullscreen.
@@ -68520,6 +68626,16 @@
   </summary>
 </histogram>
 
+<histogram name="Net.CertVerifier.PathBuilderIterationCount"
+    expires_after="2020-07-01">
+  <owner>mattm@chromium.org</owner>
+  <owner>net-dev@chromium.org</owner>
+  <summary>
+    When using the builtin cert verifier, records the number of iterations taken
+    during path building for each attempted verification.
+  </summary>
+</histogram>
+
 <histogram name="Net.CertVerifier_First_Job_Latency" units="ms"
     expires_after="M83">
   <owner>mattm@chromium.org</owner>
@@ -78578,16 +78694,20 @@
 </histogram>
 
 <histogram name="Network.Shill.DarkResumeActionResult"
-    enum="ShillSuspendTerminationDarkResumeActionResult" expires_after="M77">
+    enum="ShillSuspendTerminationDarkResumeActionResult"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the number of dark resume
     actions that successfully complete or fail when shill suspends.
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.DarkResumeActionsTimeTaken" units="ms">
+<histogram name="Network.Shill.DarkResumeActionsTimeTaken" units="ms"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the time in milliseconds it
     takes dark resume actions to complete when shill suspends.
@@ -78606,8 +78726,10 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.DarkResumeScanNumRetries">
+<histogram name="Network.Shill.DarkResumeScanNumRetries"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network metric sampling the number of times a dark resume scan is
     retried in a single dark resume.
@@ -78615,8 +78737,9 @@
 </histogram>
 
 <histogram name="Network.Shill.DarkResumeScanRetryResult"
-    enum="DarkResumeScanRetryResult">
+    enum="DarkResumeScanRetryResult" expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network metric that tracks whether dark resume scan retries led to
     the system suspending from dark resume in a connected state. This metric is
@@ -78646,8 +78769,8 @@
 </histogram>
 
 <histogram name="Network.Shill.DHCPClientMTUValue" units="bytes"
-    expires_after="M77">
-  <owner>pstew@chromium.org</owner>
+    expires_after="2020-06-01">
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the MTU value proposed by the
     DHCP server. A sample is emitted each time the DHCP client completes
@@ -78655,8 +78778,9 @@
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.DHCPClientStatus" enum="NetworkDhcpClientStatus">
-  <owner>pstew@chromium.org</owner>
+<histogram name="Network.Shill.DHCPClientStatus" enum="NetworkDhcpClientStatus"
+    expires_after="2020-06-01">
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the current state of the DHCP
     client. A sample is emitted each time the DHCP client state changes.
@@ -78664,8 +78788,9 @@
 </histogram>
 
 <histogram name="Network.Shill.DHCPOptionFailureDetected"
-    enum="NetworkTechnology">
+    enum="NetworkTechnology" expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network metric that tracks the number of DHCP option failures
     encountered by Shill for each network technology. This indicates that Shill
@@ -78987,8 +79112,10 @@
   <summary>Chrome OS connection manager service errors seen.</summary>
 </histogram>
 
-<histogram name="Network.Shill.ServicesOnSameNetwork" expires_after="M77">
+<histogram name="Network.Shill.ServicesOnSameNetwork"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network metric sampling the number of services that are connected
     to the currently connected network.
@@ -78996,16 +79123,20 @@
 </histogram>
 
 <histogram name="Network.Shill.SuspendActionResult"
-    enum="ShillSuspendTerminationDarkResumeActionResult" expires_after="M77">
+    enum="ShillSuspendTerminationDarkResumeActionResult"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the number of suspend actions
     that successfully complete or fail when shill suspends.
   </summary>
 </histogram>
 
-<histogram name="Network.Shill.SuspendActionsTimeTaken" units="ms">
+<histogram name="Network.Shill.SuspendActionsTimeTaken" units="ms"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the time in milliseconds it
     takes suspend actions to complete when shill suspends.
@@ -79025,8 +79156,10 @@
 </histogram>
 
 <histogram name="Network.Shill.TerminationActionResult"
-    enum="ShillSuspendTerminationDarkResumeActionResult" expires_after="M77">
+    enum="ShillSuspendTerminationDarkResumeActionResult"
+    expires_after="2020-06-01">
   <owner>benchan@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
   <summary>
     Chrome OS network diagnostic metric sampling the number of termination
     actions that successfully complete or fail when shill terminates. Previously
@@ -89654,6 +89787,9 @@
 <histogram
     name="PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay3.Subframe"
     units="ms">
+  <obsolete>
+    Removed July 2019 in favor of FirstInputDelay4.
+  </obsolete>
   <owner>bmcquade@chromium.org</owner>
   <summary>
     Measures First Input Delay, the duration between the hardware timestamp and
@@ -89667,6 +89803,35 @@
 <histogram
     name="PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay3.Subframe.FullNavigation"
     units="ms" expires_after="M78">
+  <obsolete>
+    Removed July 2019 in favor of FirstInputDelay4.
+  </obsolete>
+  <owner>bmcquade@chromium.org</owner>
+  <summary>
+    Measures First Input Delay, the duration between the hardware timestamp and
+    the start of event processing on the main thread for the first meaningful
+    input per navigation, in an AMP subframe document. Recorded on first page
+    interaction. See https://goo.gl/tr1oTZ for a detailed explanation. Excludes
+    scrolls. Only non-same-document navigations are included.
+  </summary>
+</histogram>
+
+<histogram
+    name="PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay4.Subframe"
+    units="ms">
+  <owner>bmcquade@chromium.org</owner>
+  <summary>
+    Measures First Input Delay, the duration between the hardware timestamp and
+    the start of event processing on the main thread for the first meaningful
+    input per navigation, in an AMP subframe document. Recorded on first page
+    interaction. See https://goo.gl/tr1oTZ for a detailed explanation. Excludes
+    scrolls. Only same-document navigations are included.
+  </summary>
+</histogram>
+
+<histogram
+    name="PageLoad.Clients.AMP.InteractiveTiming.FirstInputDelay4.Subframe.FullNavigation"
+    units="ms">
   <owner>bmcquade@chromium.org</owner>
   <summary>
     Measures First Input Delay, the duration between the hardware timestamp and
@@ -110602,6 +110767,19 @@
   </summary>
 </histogram>
 
+<histogram
+    name="ResourceScheduler.BrowserInitiatedHeavyRequest.QueuingDuration"
+    units="ms" expires_after="M82">
+  <owner>tbansal@chromium.org</owner>
+  <summary>
+    The amount of time the ResourceScheduler queued a browser initiated request
+    that was expected to be heavy (i.e., has large request/response sizes).
+    Recorded when the request is dispatched by the resource scheduler to the
+    network stack. Recorded only when the traffic annotation tag of the request
+    is present in the set of annotation tags that can be throttled.
+  </summary>
+</histogram>
+
 <histogram name="ResourceScheduler.ClientLoadedTime.Active"
     expires_after="2016-01-29">
   <obsolete>
@@ -119014,8 +119192,9 @@
 </histogram>
 
 <histogram name="ServiceWorker.BackgroundSyncEvent.Time" units="ms"
-    expires_after="M77">
-  <owner>jkarlin@chromium.org</owner>
+    expires_after="M85">
+  <owner>nator@chromium.org</owner>
+  <owner>rayankans@chromium.org</owner>
   <summary>
     The time taken between dispatching a SyncEvent to a Service Worker and
     receiving a message that it finished handling the event. Includes the time
@@ -125347,6 +125526,21 @@
   </summary>
 </histogram>
 
+<histogram base="true"
+    name="SiteIsolation.CORBProtection.CacheHeuristic.ProtectedMimeType"
+    enum="BooleanSupported" expires_after="M79">
+<!-- suffixed with Block*WithRangeSupport -->
+
+  <owner>krstnmnlsn@chromium.org</owner>
+  <owner>creis@chromium.org</owner>
+  <summary>
+    True if the response has an Accept-Ranges header, which indicates the server
+    supports range requests on the resource and could be used to bypass CORB.
+    Only reported if the resource looked sensitive under the Cache heuristic and
+    was a protected MIME type.
+  </summary>
+</histogram>
+
 <histogram base="true" name="SiteIsolation.CORBProtection.CORSHeuristic"
     enum="CrossOriginProtectionDecision" expires_after="M79">
   <owner>krstnmnlsn@chromium.org</owner>
@@ -125359,6 +125553,21 @@
   </summary>
 </histogram>
 
+<histogram base="true"
+    name="SiteIsolation.CORBProtection.CORSHeuristic.ProtectedMimeType"
+    enum="BooleanSupported" expires_after="M79">
+<!-- suffixed with Block*WithRangeSupport -->
+
+  <owner>krstnmnlsn@chromium.org</owner>
+  <owner>creis@chromium.org</owner>
+  <summary>
+    True if the response has an Accept-Ranges header, which indicates the server
+    supports range requests on the resource and could be used to bypass CORB.
+    Only reported if the resource looked sensitive under the CORS heuristic and
+    was a protected MIME type.
+  </summary>
+</histogram>
+
 <histogram name="SiteIsolation.CORBProtection.SensitiveResource"
     enum="BooleanSensitive" expires_after="M79">
   <owner>krstnmnlsn@chromium.org</owner>
@@ -125376,7 +125585,7 @@
   <summary>
     True if the response has an Accept-Ranges header, which indicates the server
     supports range requests on the resource. Only reported if the response
-    looked sensitive under the CORS or CORB heuristics.
+    looked sensitive under the cache or CORS heuristics.
   </summary>
 </histogram>
 
@@ -134067,7 +134276,7 @@
 </histogram>
 
 <histogram name="Tab.ExternalApplicationOpened" enum="ExternalLauncherOption"
-    expires_after="M77">
+    expires_after="2020-07-01">
   <owner>mrefaat@chromium.org</owner>
   <summary>
     [iOS] Used on External App launcher Prompt to determine if the user clicked
@@ -134747,7 +134956,7 @@
 
 <histogram
     name="TabManager.BackgroundTabOpening.ForegroundTab.ExpectedTaskQueueingDuration"
-    units="ms">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135010,7 +135219,7 @@
 
 <histogram
     name="TabManager.Experimental.BackgroundTabOpening.CompressedPagesPerSecond"
-    units="pages/s">
+    units="pages/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135028,7 +135237,7 @@
 
 <histogram
     name="TabManager.Experimental.BackgroundTabOpening.DecompressedPagesPerSecond"
-    units="pages/s">
+    units="pages/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135045,7 +135254,7 @@
 </histogram>
 
 <histogram name="TabManager.Experimental.BackgroundTabOpening.SwapInPerSecond"
-    units="swaps/s">
+    units="swaps/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135061,7 +135270,7 @@
 </histogram>
 
 <histogram name="TabManager.Experimental.BackgroundTabOpening.SwapOutPerSecond"
-    units="swaps/s">
+    units="swaps/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135124,7 +135333,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.CompressedPagesPerSecond"
-    units="pages/s" expires_after="M78">
+    units="pages/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135143,7 +135352,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.DecompressedPagesPerSecond"
-    units="pages/s" expires_after="M78">
+    units="pages/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135162,7 +135371,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.ForegroundTab.FirstContentfulPaint"
-    units="ms" expires_after="M78">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135173,7 +135382,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.ForegroundTab.FirstMeaningfulPaint"
-    units="ms">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135184,7 +135393,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.ForegroundTab.FirstPaint"
-    units="ms" expires_after="M78">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135194,7 +135403,7 @@
 </histogram>
 
 <histogram name="TabManager.Experimental.SessionRestore.SwapInPerSecond"
-    units="swaps/s" expires_after="M77">
+    units="swaps/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135211,7 +135420,7 @@
 </histogram>
 
 <histogram name="TabManager.Experimental.SessionRestore.SwapOutPerSecond"
-    units="swaps/s" expires_after="M77">
+    units="swaps/s" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135255,7 +135464,7 @@
 
 <histogram
     name="TabManager.Experimental.SessionRestore.TabSwitchLoadTime.UntilTabIsLoaded"
-    units="ms" expires_after="M78">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135401,7 +135610,7 @@
 
 <histogram
     name="TabManager.SessionRestore.ForegroundTab.ExpectedTaskQueueingDuration"
-    units="ms">
+    units="ms" expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -135449,7 +135658,7 @@
 </histogram>
 
 <histogram name="TabManager.SessionRestore.SwitchToTab" enum="TabLoadingState"
-    expires_after="M77">
+    expires_after="M79">
   <owner>shaseley@chromium.org</owner>
   <owner>panicker@chromium.org</owner>
   <summary>
@@ -139642,6 +139851,10 @@
 </histogram>
 
 <histogram name="UMA.PersistentHistograms.TmpRemovals" expires_after="M77">
+  <obsolete>
+    Data showed many files being deleted during rollout and then tapering off to
+    near zero. Removed 2019/07.
+  </obsolete>
   <owner>bcwhite@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <summary>
@@ -145245,7 +145458,7 @@
 </histogram>
 
 <histogram name="WebController.ExternalURLRequestBlocking"
-    enum="IOSExternalURLRequestStatus" expires_after="M77">
+    enum="IOSExternalURLRequestStatus" expires_after="2020-07-01">
   <owner>mrefaat@chromium.org</owner>
   <summary>
     [iOS] Measures the proportion of external URL requests that originate from a
@@ -153893,6 +154106,17 @@
   <affected-histogram name="Cookie.AllAgesForSecure"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="CORBProtectionDecision" separator=".">
+  <suffix name="BlockedAfterSniffingWithRangeSupport"
+      label="Here the CORB protection decision was kBlockedAfterSniffing."/>
+  <suffix name="BlockedWithRangeSupport"
+      label="Here the CORB protection decision was kBlock."/>
+  <affected-histogram
+      name="SiteIsolation.CORBProtection.CacheHeuristic.ProtectedMimeType"/>
+  <affected-histogram
+      name="SiteIsolation.CORBProtection.CORSHeuristic.ProtectedMimeType"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="CreditCardScanSuccess" separator="_">
   <suffix name="Cancelled" label="Credit card scan was cancelled."/>
   <suffix name="Completed" label="Credit card scan completed."/>
@@ -162884,14 +163108,6 @@
   <affected-histogram name="Net.QuicSession.VerifyProofTime"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="RangeRequestSupportedCase" separator=".">
-  <suffix name="ProtectedMimeType.BlockedWithRangeSupport"
-      label="The response had a protected MIME type and the CORB protection
-             decision was kBlock."/>
-  <affected-histogram name="SiteIsolation.CORBProtection.CacheHeuristic"/>
-  <affected-histogram name="SiteIsolation.CORBProtection.CORSHeuristic"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="RasterBufferProvider" separator=".">
   <suffix name="Gpu" label="The GpuRasterBufferProvider was in use."/>
   <suffix name="OneCopy" label="The OneCopyRasterBufferProvider was in use."/>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 554d967d..b7f062cf 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -269,6 +269,17 @@
     </summary>
   </metric>
   <metric name="SubFrame.InteractiveTiming.FirstInputDelay3">
+    <obsolete>
+      Removed July 2019 in favor of FirstInputDelay4.
+    </obsolete>
+    <summary>
+      Measures First Input Delay, the duration between the hardware timestamp
+      and the start of event processing on the main thread for the first
+      meaningful input per navigation, in the AMP subframe. See
+      https://goo.gl/tr1oTZ for a detailed explanation. In milliseconds.
+    </summary>
+  </metric>
+  <metric name="SubFrame.InteractiveTiming.FirstInputDelay4">
     <summary>
       Measures First Input Delay, the duration between the hardware timestamp
       and the start of event processing on the main thread for the first
diff --git a/ui/events/blink/blink_features.cc b/ui/events/blink/blink_features.cc
index a2423c48..54864b1 100644
--- a/ui/events/blink/blink_features.cc
+++ b/ui/events/blink/blink_features.cc
@@ -29,7 +29,7 @@
     "DontSendKeyEventsToJavascript", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kSkipTouchEventFilter{"SkipTouchEventFilter",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
+                                          base::FEATURE_ENABLED_BY_DEFAULT};
 const char kSkipTouchEventFilterTypeParamName[] = "type";
 const char kSkipTouchEventFilterTypeParamValueDiscrete[] = "discrete";
 const char kSkipTouchEventFilterTypeParamValueAll[] = "all";
@@ -38,4 +38,4 @@
 const char kSkipTouchEventFilterFilteringProcessParamValueBrowser[] = "browser";
 const char kSkipTouchEventFilterFilteringProcessParamValueBrowserAndRenderer[] =
     "browser_and_renderer";
-}
+}  // namespace features
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index 9783b72..73a509b 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -474,6 +474,11 @@
       clip_visual_->SetClip(nullptr);
     }
   }
+
+  if (visual_info_.z_order != params.z_order) {
+    visual_info_.z_order = params.z_order;
+    layer_tree_->SetNeedsCommit();
+  }
 }
 
 bool SwapChainPresenter::TryPresentToDecodeSwapChain(
diff --git a/ui/gl/swap_chain_presenter.h b/ui/gl/swap_chain_presenter.h
index 7f31696..8758db11 100644
--- a/ui/gl/swap_chain_presenter.h
+++ b/ui/gl/swap_chain_presenter.h
@@ -185,6 +185,7 @@
     gfx::Transform transform;
     bool is_clipped = false;
     gfx::Rect clip_rect;
+    int z_order = 0;
   } visual_info_;
 
   // Direct composition visual containing the swap chain content.  Child of
diff --git a/ui/message_center/views/notification_view_md.cc b/ui/message_center/views/notification_view_md.cc
index 21d2931..245e7024 100644
--- a/ui/message_center/views/notification_view_md.cc
+++ b/ui/message_center/views/notification_view_md.cc
@@ -803,16 +803,20 @@
   header_row_->SetTimestamp(notification.timestamp());
   header_row_->SetAppNameElideBehavior(gfx::ELIDE_TAIL);
 
-  base::string16 app_name = notification.display_source();
-  if (notification.origin_url().is_valid() &&
-      notification.origin_url().SchemeIsHTTPOrHTTPS()) {
+  base::string16 app_name;
+  if (notification.UseOriginAsContextMessage()) {
     app_name = url_formatter::FormatUrlForSecurityDisplay(
         notification.origin_url(),
         url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
     header_row_->SetAppNameElideBehavior(gfx::ELIDE_HEAD);
-  } else if (app_name.empty() && notification.notifier_id().type ==
-                                     NotifierType::SYSTEM_COMPONENT) {
+  } else if (notification.display_source().empty() &&
+             notification.notifier_id().type ==
+                 NotifierType::SYSTEM_COMPONENT) {
     app_name = MessageCenter::Get()->GetSystemNotificationAppName();
+  } else if (!notification.context_message().empty()) {
+    app_name = notification.context_message();
+  } else {
+    app_name = notification.display_source();
   }
   header_row_->SetAppName(app_name);
 }
diff --git a/ui/message_center/views/notification_view_md.h b/ui/message_center/views/notification_view_md.h
index fb280a8..d9d9b35 100644
--- a/ui/message_center/views/notification_view_md.h
+++ b/ui/message_center/views/notification_view_md.h
@@ -207,6 +207,9 @@
                                  const base::string16& text) override;
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, AppNameExtension);
+  FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, AppNameSystemNotification);
+  FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, AppNameWebNotification);
   FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, CreateOrUpdateTest);
   FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, ExpandLongMessage);
   FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, InlineSettings);
diff --git a/ui/message_center/views/notification_view_md_unittest.cc b/ui/message_center/views/notification_view_md_unittest.cc
index 7054701..271de35c 100644
--- a/ui/message_center/views/notification_view_md_unittest.cc
+++ b/ui/message_center/views/notification_view_md_unittest.cc
@@ -1316,4 +1316,40 @@
   EXPECT_EQ(message_height, notification_view()->message_view_->height());
 }
 
+TEST_F(NotificationViewMDTest, AppNameExtension) {
+  base::string16 app_name = base::UTF8ToUTF16("extension name");
+  std::unique_ptr<Notification> notification = CreateSimpleNotification();
+  notification->set_context_message(app_name);
+
+  UpdateNotificationViews(*notification);
+
+  EXPECT_EQ(app_name, notification_view()->header_row_->app_name_for_testing());
+}
+
+TEST_F(NotificationViewMDTest, AppNameSystemNotification) {
+  base::string16 app_name = base::UTF8ToUTF16("system notification");
+  message_center::MessageCenter::Get()->SetSystemNotificationAppName(app_name);
+  RichNotificationData data;
+  data.settings_button_handler = SettingsButtonHandler::INLINE;
+  auto notification = std::make_unique<Notification>(
+      NOTIFICATION_TYPE_BASE_FORMAT, std::string(kDefaultNotificationId),
+      base::UTF8ToUTF16("title"), base::UTF8ToUTF16("message"), gfx::Image(),
+      base::string16(), GURL(),
+      NotifierId(NotifierType::SYSTEM_COMPONENT, "system"), data, nullptr);
+
+  UpdateNotificationViews(*notification);
+
+  EXPECT_EQ(app_name, notification_view()->header_row_->app_name_for_testing());
+}
+
+TEST_F(NotificationViewMDTest, AppNameWebNotification) {
+  std::unique_ptr<Notification> notification = CreateSimpleNotification();
+  notification->set_origin_url(GURL("http://example.com"));
+
+  UpdateNotificationViews(*notification);
+
+  EXPECT_EQ(base::UTF8ToUTF16("example.com"),
+            notification_view()->header_row_->app_name_for_testing());
+}
+
 }  // namespace message_center
diff --git a/ui/native_theme/caption_style.h b/ui/native_theme/caption_style.h
index c5084a1..811a6dd 100644
--- a/ui/native_theme/caption_style.h
+++ b/ui/native_theme/caption_style.h
@@ -36,6 +36,9 @@
   std::string text_shadow;
   std::string font_family;
   std::string font_variant;
+  std::string window_color;
+  std::string window_padding;
+  std::string window_radius;
 };
 
 }  // namespace ui
diff --git a/ui/native_theme/caption_style_mac.mm b/ui/native_theme/caption_style_mac.mm
index c39d09d..1e20188 100644
--- a/ui/native_theme/caption_style_mac.mm
+++ b/ui/native_theme/caption_style_mac.mm
@@ -121,6 +121,33 @@
     *font_variant = base::SysCFStringRefToUTF8(ct_font_face_name);
 }
 
+std::string GetMAWindowColorAsCSSColor() {
+  base::ScopedCFTypeRef<CGColorRef> cg_color(
+      MACaptionAppearanceCopyWindowColor(kUserDomain, nullptr));
+  float opacity = MACaptionAppearanceGetWindowOpacity(kUserDomain, nullptr);
+
+  SkColor rgba_color =
+      SkColorSetA(skia::CGColorRefToSkColor(cg_color.get()), 0xff * opacity);
+  return color_utils::SkColorToRgbaString(rgba_color);
+}
+
+// If the window is visible (its opacity is greater than 0), give it padding so
+// it surrounds the text track cue. If it is not visible, its padding should be
+// 0. Webkit uses 0.4em padding so we match that here.
+std::string GetMAWindowPaddingAsCSSNumberInEm() {
+  float opacity = MACaptionAppearanceGetWindowOpacity(kUserDomain, nullptr);
+  if (opacity > 0)
+    return "0.4em";
+
+  return "";
+}
+
+std::string GetMAWindowRadiusAsCSSNumberInPixels() {
+  float radius =
+      MACaptionAppearanceGetWindowRoundedCornerRadius(kUserDomain, nullptr);
+  return base::StringPrintf("%fpx", radius);
+}
+
 }  // namespace
 
 // static
@@ -134,6 +161,9 @@
   style.background_color = GetMABackgroundColorAndOpacityAsCSSColor();
   style.text_size = GetMATextScaleAsCSSPercent();
   style.text_shadow = GetMATextEdgeStyleAsCSSShadow();
+  style.window_color = GetMAWindowColorAsCSSColor();
+  style.window_padding = GetMAWindowPaddingAsCSSNumberInEm();
+  style.window_radius = GetMAWindowRadiusAsCSSNumberInPixels();
 
   GetMAFontAsCSSFontSpecifiers(&style.font_family, &style.font_variant);
 
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.cc b/ui/ozone/platform/drm/gpu/drm_thread.cc
index 02f98a6..2164b74 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread.cc
@@ -181,6 +181,19 @@
   }
 }
 
+void DrmThread::CreateBufferAsync(gfx::AcceleratedWidget widget,
+                                  const gfx::Size& size,
+                                  gfx::BufferFormat format,
+                                  gfx::BufferUsage usage,
+                                  uint32_t client_flags,
+                                  CreateBufferAsyncCallback callback) {
+  std::unique_ptr<GbmBuffer> buffer;
+  scoped_refptr<DrmFramebuffer> framebuffer;
+  CreateBuffer(widget, size, format, usage, client_flags, &buffer,
+               &framebuffer);
+  std::move(callback).Run(std::move(buffer), std::move(framebuffer));
+}
+
 void DrmThread::CreateBufferFromHandle(
     gfx::AcceleratedWidget widget,
     const gfx::Size& size,
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.h b/ui/ozone/platform/drm/gpu/drm_thread.h
index 2764633c..7ae9c04 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.h
+++ b/ui/ozone/platform/drm/gpu/drm_thread.h
@@ -72,6 +72,15 @@
                     uint32_t flags,
                     std::unique_ptr<GbmBuffer>* buffer,
                     scoped_refptr<DrmFramebuffer>* framebuffer);
+  using CreateBufferAsyncCallback =
+      base::OnceCallback<void(std::unique_ptr<GbmBuffer>,
+                              scoped_refptr<DrmFramebuffer>)>;
+  void CreateBufferAsync(gfx::AcceleratedWidget widget,
+                         const gfx::Size& size,
+                         gfx::BufferFormat format,
+                         gfx::BufferUsage usage,
+                         uint32_t flags,
+                         CreateBufferAsyncCallback callback);
   void CreateBufferFromHandle(gfx::AcceleratedWidget widget,
                               const gfx::Size& size,
                               gfx::BufferFormat format,
diff --git a/ui/ozone/platform/drm/gpu/drm_thread_proxy.cc b/ui/ozone/platform/drm/gpu/drm_thread_proxy.cc
index 8395a45..a63dadd 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread_proxy.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread_proxy.cc
@@ -15,6 +15,20 @@
 
 namespace ui {
 
+namespace {
+
+void OnBufferCreatedOnDrmThread(
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    DrmThreadProxy::CreateBufferAsyncCallback callback,
+    std::unique_ptr<GbmBuffer> buffer,
+    scoped_refptr<DrmFramebuffer> framebuffer) {
+  task_runner->PostTask(FROM_HERE,
+                        base::BindOnce(std::move(callback), std::move(buffer),
+                                       std::move(framebuffer)));
+}
+
+}  // namespace
+
 DrmThreadProxy::DrmThreadProxy() = default;
 
 DrmThreadProxy::~DrmThreadProxy() = default;
@@ -49,6 +63,24 @@
                      widget, size, format, usage, flags, buffer, framebuffer));
 }
 
+void DrmThreadProxy::CreateBufferAsync(gfx::AcceleratedWidget widget,
+                                       const gfx::Size& size,
+                                       gfx::BufferFormat format,
+                                       gfx::BufferUsage usage,
+                                       uint32_t flags,
+                                       CreateBufferAsyncCallback callback) {
+  DCHECK(drm_thread_.task_runner())
+      << "no task runner! in DrmThreadProxy::CreateBufferAsync";
+  drm_thread_.task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&DrmThread::CreateBufferAsync,
+                     base::Unretained(&drm_thread_), widget, size, format,
+                     usage, flags,
+                     base::BindOnce(OnBufferCreatedOnDrmThread,
+                                    base::ThreadTaskRunnerHandle::Get(),
+                                    std::move(callback))));
+}
+
 void DrmThreadProxy::CreateBufferFromHandle(
     gfx::AcceleratedWidget widget,
     const gfx::Size& size,
diff --git a/ui/ozone/platform/drm/gpu/drm_thread_proxy.h b/ui/ozone/platform/drm/gpu/drm_thread_proxy.h
index f3c54595..6c6f064 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread_proxy.h
+++ b/ui/ozone/platform/drm/gpu/drm_thread_proxy.h
@@ -48,6 +48,16 @@
                     std::unique_ptr<GbmBuffer>* buffer,
                     scoped_refptr<DrmFramebuffer>* framebuffer);
 
+  using CreateBufferAsyncCallback =
+      base::OnceCallback<void(std::unique_ptr<GbmBuffer>,
+                              scoped_refptr<DrmFramebuffer>)>;
+  void CreateBufferAsync(gfx::AcceleratedWidget widget,
+                         const gfx::Size& size,
+                         gfx::BufferFormat format,
+                         gfx::BufferUsage usage,
+                         uint32_t flags,
+                         CreateBufferAsyncCallback callback);
+
   void CreateBufferFromHandle(gfx::AcceleratedWidget widget,
                               const gfx::Size& size,
                               gfx::BufferFormat format,
diff --git a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
index b40c911..04c6d02a5 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
@@ -135,6 +135,18 @@
   return supported_buffer_formats;
 }
 
+void OnNativePixmapCreated(GbmSurfaceFactory::NativePixmapCallback callback,
+                           base::WeakPtr<GbmSurfaceFactory> weak_ptr,
+                           std::unique_ptr<GbmBuffer> buffer,
+                           scoped_refptr<DrmFramebuffer> framebuffer) {
+  if (!weak_ptr || !buffer) {
+    std::move(callback).Run(nullptr);
+  } else {
+    std::move(callback).Run(base::MakeRefCounted<GbmPixmap>(
+        weak_ptr.get(), std::move(buffer), std::move(framebuffer)));
+  }
+}
+
 }  // namespace
 
 GbmSurfaceFactory::GbmSurfaceFactory(DrmThreadProxy* drm_thread_proxy)
@@ -281,6 +293,18 @@
                                          std::move(framebuffer));
 }
 
+void GbmSurfaceFactory::CreateNativePixmapAsync(gfx::AcceleratedWidget widget,
+                                                VkDevice vk_device,
+                                                gfx::Size size,
+                                                gfx::BufferFormat format,
+                                                gfx::BufferUsage usage,
+                                                NativePixmapCallback callback) {
+  drm_thread_proxy_->CreateBufferAsync(
+      widget, size, format, usage, 0 /* flags */,
+      base::BindOnce(OnNativePixmapCreated, std::move(callback),
+                     weak_factory_.GetWeakPtr()));
+}
+
 scoped_refptr<gfx::NativePixmap>
 GbmSurfaceFactory::CreateNativePixmapFromHandleInternal(
     gfx::AcceleratedWidget widget,
diff --git a/ui/ozone/platform/drm/gpu/gbm_surface_factory.h b/ui/ozone/platform/drm/gpu/gbm_surface_factory.h
index b2bbb8e4..0329952f 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surface_factory.h
+++ b/ui/ozone/platform/drm/gpu/gbm_surface_factory.h
@@ -60,6 +60,12 @@
       gfx::Size size,
       gfx::BufferFormat format,
       gfx::BufferUsage usage) override;
+  void CreateNativePixmapAsync(gfx::AcceleratedWidget widget,
+                               VkDevice vk_device,
+                               gfx::Size size,
+                               gfx::BufferFormat format,
+                               gfx::BufferUsage usage,
+                               NativePixmapCallback callback) override;
   scoped_refptr<gfx::NativePixmap> CreateNativePixmapFromHandle(
       gfx::AcceleratedWidget widget,
       gfx::Size size,
@@ -94,6 +100,8 @@
 
   GetProtectedNativePixmapCallback get_protected_native_pixmap_callback_;
 
+  base::WeakPtrFactory<GbmSurfaceFactory> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(GbmSurfaceFactory);
 };
 
diff --git a/ui/ozone/public/surface_factory_ozone.cc b/ui/ozone/public/surface_factory_ozone.cc
index 24809d9..2d5a0fd3 100644
--- a/ui/ozone/public/surface_factory_ozone.cc
+++ b/ui/ozone/public/surface_factory_ozone.cc
@@ -78,6 +78,16 @@
   return nullptr;
 }
 
+void SurfaceFactoryOzone::CreateNativePixmapAsync(
+    gfx::AcceleratedWidget widget,
+    VkDevice vk_device,
+    gfx::Size size,
+    gfx::BufferFormat format,
+    gfx::BufferUsage usage,
+    NativePixmapCallback callback) {
+  std::move(callback).Run(nullptr);
+}
+
 scoped_refptr<gfx::NativePixmap>
 SurfaceFactoryOzone::CreateNativePixmapFromHandle(
     gfx::AcceleratedWidget widget,
diff --git a/ui/ozone/public/surface_factory_ozone.h b/ui/ozone/public/surface_factory_ozone.h
index 229ff0f4..4ef2487 100644
--- a/ui/ozone/public/surface_factory_ozone.h
+++ b/ui/ozone/public/surface_factory_ozone.h
@@ -121,6 +121,16 @@
       gfx::BufferFormat format,
       gfx::BufferUsage usage);
 
+  // Similar to CreateNativePixmap, but returns the result asynchronously.
+  using NativePixmapCallback =
+      base::OnceCallback<void(scoped_refptr<gfx::NativePixmap>)>;
+  virtual void CreateNativePixmapAsync(gfx::AcceleratedWidget widget,
+                                       VkDevice vk_device,
+                                       gfx::Size size,
+                                       gfx::BufferFormat format,
+                                       gfx::BufferUsage usage,
+                                       NativePixmapCallback callback);
+
   // Create a single native buffer from an existing handle. Takes ownership of
   // |handle| and can be called on any thread.
   virtual scoped_refptr<gfx::NativePixmap> CreateNativePixmapFromHandle(
diff --git a/ui/views/animation/installable_ink_drop.cc b/ui/views/animation/installable_ink_drop.cc
index d7d0caa..89ecb2f 100644
--- a/ui/views/animation/installable_ink_drop.cc
+++ b/ui/views/animation/installable_ink_drop.cc
@@ -14,13 +14,16 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/paint_context.h"
 #include "ui/compositor/paint_recorder.h"
+#include "ui/gfx/animation/animation_container.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/skia_util.h"
+#include "ui/views/animation/compositor_animation_runner.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
+#include "ui/views/widget/widget.h"
 
 namespace views {
 
@@ -31,7 +34,9 @@
     : view_(view),
       layer_(std::make_unique<ui::Layer>()),
       event_handler_(view_, this),
+      animation_container_(base::MakeRefCounted<gfx::AnimationContainer>()),
       animator_(&painter_,
+                animation_container_.get(),
                 base::Bind(&InstallableInkDrop::SchedulePaint,
                            base::Unretained(this))) {
   // Catch if |view_| is destroyed out from under us.
@@ -48,6 +53,14 @@
   layer_->SetBounds(gfx::Rect(view_->size()) +
                     layer_->bounds().OffsetFromOrigin());
   layer_->SchedulePaint(gfx::Rect(layer_->size()));
+
+  if (view_->GetWidget()) {
+    // Using CompositorAnimationRunner keeps our animation updates in sync with
+    // compositor frames and avoids jank.
+    animation_container_->SetAnimationRunner(
+        std::make_unique<CompositorAnimationRunner>(
+            view_->GetWidget()->GetCompositor()));
+  }
 }
 
 InstallableInkDrop::~InstallableInkDrop() {
@@ -89,12 +102,12 @@
 
 void InstallableInkDrop::SetHovered(bool is_hovered) {
   is_hovered_ = is_hovered;
-  UpdatePainterForCurrentState();
+  UpdateAnimatorHighlight();
 }
 
 void InstallableInkDrop::SetFocused(bool is_focused) {
   is_focused_ = is_focused;
-  UpdatePainterForCurrentState();
+  UpdateAnimatorHighlight();
 }
 
 bool InstallableInkDrop::IsHighlightFadingInOrVisible() const {
@@ -162,9 +175,8 @@
   layer_->SchedulePaint(gfx::Rect(layer_->size()));
 }
 
-void InstallableInkDrop::UpdatePainterForCurrentState() {
-  painter_.SetHighlighted(IsHighlightFadingInOrVisible());
-  SchedulePaint();
+void InstallableInkDrop::UpdateAnimatorHighlight() {
+  animator_.AnimateHighlight(is_hovered_ || is_focused_);
 }
 
 }  // namespace views
diff --git a/ui/views/animation/installable_ink_drop.h b/ui/views/animation/installable_ink_drop.h
index 5fea597f..13d3e5f2 100644
--- a/ui/views/animation/installable_ink_drop.h
+++ b/ui/views/animation/installable_ink_drop.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/feature_list.h"
+#include "base/memory/scoped_refptr.h"
 #include "ui/compositor/layer_delegate.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_event_handler.h"
@@ -20,6 +21,7 @@
 class SkPath;
 
 namespace gfx {
+class AnimationContainer;
 class Size;
 }  // namespace gfx
 
@@ -87,7 +89,7 @@
 
  private:
   void SchedulePaint();
-  void UpdatePainterForCurrentState();
+  void UpdateAnimatorHighlight();
 
   // The view this ink drop is showing for. |layer_| is added to the layer
   // hierarchy that |view_| belongs to. We track events on |view_| to update our
@@ -100,6 +102,11 @@
   // Observes |view_| and updates our visual state accordingly.
   InkDropEventHandler event_handler_;
 
+  // Used to synchronize the hover and activation animations within this ink
+  // drop. Since we use |views::CompositorAnimationRunner|, this also
+  // synchronizes them with compositor frames.
+  scoped_refptr<gfx::AnimationContainer> animation_container_;
+
   InstallableInkDropPainter painter_;
   InstallableInkDropAnimator animator_;
 
diff --git a/ui/views/animation/installable_ink_drop_animator.cc b/ui/views/animation/installable_ink_drop_animator.cc
index b205c5b..33810c0 100644
--- a/ui/views/animation/installable_ink_drop_animator.cc
+++ b/ui/views/animation/installable_ink_drop_animator.cc
@@ -14,8 +14,14 @@
 
 InstallableInkDropAnimator::InstallableInkDropAnimator(
     InstallableInkDropPainter* painter,
+    gfx::AnimationContainer* animation_container,
     base::RepeatingClosure repaint_callback)
-    : painter_(painter), repaint_callback_(repaint_callback) {}
+    : painter_(painter),
+      repaint_callback_(repaint_callback),
+      highlight_animation_(this) {
+  highlight_animation_.SetContainer(animation_container);
+  highlight_animation_.SetSlideDuration(kAnimationDuration.InMilliseconds());
+}
 
 InstallableInkDropAnimator::~InstallableInkDropAnimator() {
   transition_delay_timer_.Stop();
@@ -29,23 +35,23 @@
     case InkDropState::HIDDEN:
     case InkDropState::DEACTIVATED:
       target_state_ = InkDropState::HIDDEN;
-      painter_->SetActivated(false);
+      painter_->set_activated(false);
       break;
     case InkDropState::ACTION_PENDING:
     case InkDropState::ALTERNATE_ACTION_PENDING:
     case InkDropState::ACTIVATED:
       target_state_ = target_state;
-      painter_->SetActivated(true);
+      painter_->set_activated(true);
       break;
     case InkDropState::ACTION_TRIGGERED:
     case InkDropState::ALTERNATE_ACTION_TRIGGERED:
       if (last_state == InkDropState::ACTION_PENDING ||
           last_state == InkDropState::ALTERNATE_ACTION_PENDING) {
         target_state_ = InkDropState::HIDDEN;
-        painter_->SetActivated(false);
+        painter_->set_activated(false);
       } else {
         target_state_ = target_state;
-        painter_->SetActivated(true);
+        painter_->set_activated(true);
         transition_delay_timer_.Start(
             FROM_HERE, kAnimationDuration,
             base::Bind(&InstallableInkDropAnimator::AnimateToState,
@@ -57,4 +63,18 @@
   repaint_callback_.Run();
 }
 
+void InstallableInkDropAnimator::AnimateHighlight(bool fade_in) {
+  if (fade_in) {
+    highlight_animation_.Show();
+  } else {
+    highlight_animation_.Hide();
+  }
+}
+
+void InstallableInkDropAnimator::AnimationProgressed(
+    const gfx::Animation* animation) {
+  painter_->set_highlighted_ratio(highlight_animation_.GetCurrentValue());
+  repaint_callback_.Run();
+}
+
 }  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_animator.h b/ui/views/animation/installable_ink_drop_animator.h
index 2e3f0c2..bcb8ba8f 100644
--- a/ui/views/animation/installable_ink_drop_animator.h
+++ b/ui/views/animation/installable_ink_drop_animator.h
@@ -8,8 +8,14 @@
 #include "base/callback.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "ui/gfx/animation/animation_delegate.h"
+#include "ui/gfx/animation/slide_animation.h"
 #include "ui/views/animation/ink_drop_state.h"
 
+namespace gfx {
+class AnimationContainer;
+}
+
 namespace views {
 
 class InstallableInkDropPainter;
@@ -17,21 +23,34 @@
 // Manages animating the ink drop's visual state. This class is essentially a
 // state machine, using the current and target InkDropStates to affect the
 // InstallableInkDropPainter passed in. The animations are currently minimal.
-class VIEWS_EXPORT InstallableInkDropAnimator {
+class VIEWS_EXPORT InstallableInkDropAnimator : public gfx::AnimationDelegate {
  public:
   static constexpr base::TimeDelta kAnimationDuration =
       base::TimeDelta::FromMilliseconds(500);
 
-  explicit InstallableInkDropAnimator(InstallableInkDropPainter* painter,
-                                      base::RepeatingClosure repaint_callback);
-  ~InstallableInkDropAnimator();
+  // We use a shared gfx::AnimationContainer for our animations to allow them to
+  // update in sync. It is passed in at construction for two reasons: it allows
+  // |views::CompositorAnimationRunner| to be used for more efficient and less
+  // janky animations, and it enables easier unit testing.
+  explicit InstallableInkDropAnimator(
+      InstallableInkDropPainter* painter,
+      gfx::AnimationContainer* animation_container,
+      base::RepeatingClosure repaint_callback);
+  ~InstallableInkDropAnimator() override;
 
   // Set the target state and animate to it.
   void AnimateToState(InkDropState target_state);
 
+  // Animates the hover highlight in or out. Animates in if |fade_in| is true,
+  // and out otherwise.
+  void AnimateHighlight(bool fade_in);
+
   InkDropState target_state() const { return target_state_; }
 
  private:
+  // gfx::AnimationDelegate:
+  void AnimationProgressed(const gfx::Animation* animation) override;
+
   // The painter whose state we are controlling.
   InstallableInkDropPainter* const painter_;
 
@@ -40,6 +59,9 @@
 
   InkDropState target_state_ = InkDropState::HIDDEN;
 
+  // Used to animate the painter's highlight value in and out.
+  gfx::SlideAnimation highlight_animation_;
+
   base::OneShotTimer transition_delay_timer_;
 };
 
diff --git a/ui/views/animation/installable_ink_drop_animator_unittest.cc b/ui/views/animation/installable_ink_drop_animator_unittest.cc
index 71280cab..19a81b1 100644
--- a/ui/views/animation/installable_ink_drop_animator_unittest.cc
+++ b/ui/views/animation/installable_ink_drop_animator_unittest.cc
@@ -5,9 +5,11 @@
 #include "ui/views/animation/installable_ink_drop_animator.h"
 
 #include "base/bind_helpers.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/animation/animation_test_api.h"
 #include "ui/views/animation/ink_drop_state.h"
 #include "ui/views/animation/installable_ink_drop_painter.h"
 
@@ -29,7 +31,7 @@
 
 TEST_F(InstallableInkDropAnimatorTest, UpdatesTargetState) {
   InstallableInkDropPainter painter;
-  InstallableInkDropAnimator animator(&painter, base::DoNothing());
+  InstallableInkDropAnimator animator(&painter, nullptr, base::DoNothing());
   EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
 
   animator.AnimateToState(InkDropState::ACTIVATED);
@@ -42,7 +44,7 @@
   bool callback_called = false;
   base::RepeatingClosure callback = base::Bind(
       [](bool* callback_called) { *callback_called = true; }, &callback_called);
-  InstallableInkDropAnimator animator(&painter, callback);
+  InstallableInkDropAnimator animator(&painter, nullptr, callback);
   EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
   EXPECT_FALSE(painter.activated());
   EXPECT_FALSE(callback_called);
@@ -78,7 +80,7 @@
   bool callback_called = false;
   base::RepeatingClosure callback = base::Bind(
       [](bool* callback_called) { *callback_called = true; }, &callback_called);
-  InstallableInkDropAnimator animator(&painter, callback);
+  InstallableInkDropAnimator animator(&painter, nullptr, callback);
   EXPECT_EQ(InkDropState::HIDDEN, animator.target_state());
   EXPECT_FALSE(painter.activated());
   EXPECT_FALSE(callback_called);
@@ -97,4 +99,39 @@
   EXPECT_TRUE(callback_called);
 }
 
+TEST_F(InstallableInkDropAnimatorTest, HighlightAnimationFadesInAndOut) {
+  InstallableInkDropPainter painter;
+
+  auto animation_container = base::MakeRefCounted<gfx::AnimationContainer>();
+  gfx::AnimationContainerTestApi animation_test_api(animation_container.get());
+
+  bool callback_called = false;
+  base::RepeatingClosure callback = base::Bind(
+      [](bool* callback_called) { *callback_called = true; }, &callback_called);
+  InstallableInkDropAnimator animator(&painter, animation_container.get(),
+                                      callback);
+  EXPECT_EQ(0.0f, painter.highlighted_ratio());
+  EXPECT_FALSE(callback_called);
+
+  animator.AnimateHighlight(true);
+  EXPECT_EQ(0.0f, painter.highlighted_ratio());
+
+  callback_called = false;
+  animation_test_api.IncrementTime(
+      InstallableInkDropAnimator::kAnimationDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1.0f, painter.highlighted_ratio());
+  EXPECT_TRUE(callback_called);
+
+  animator.AnimateHighlight(false);
+  EXPECT_EQ(1.0f, painter.highlighted_ratio());
+
+  callback_called = false;
+  animation_test_api.IncrementTime(
+      InstallableInkDropAnimator::kAnimationDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0.0f, painter.highlighted_ratio());
+  EXPECT_TRUE(callback_called);
+}
+
 }  // namespace views
diff --git a/ui/views/animation/installable_ink_drop_painter.cc b/ui/views/animation/installable_ink_drop_painter.cc
index 421f5318..a4b15fc 100644
--- a/ui/views/animation/installable_ink_drop_painter.cc
+++ b/ui/views/animation/installable_ink_drop_painter.cc
@@ -13,8 +13,8 @@
 // these and make colors configurable, with same defaults as existing
 // ink drops.
 constexpr SkColor kInstallableInkDropBaseColor = SK_ColorBLACK;
-constexpr SkAlpha kInstallableInkDropHighlightedOpacity = 0.08 * SK_AlphaOPAQUE;
-constexpr SkAlpha kInstallableInkDropActivatedOpacity = 0.16 * SK_AlphaOPAQUE;
+constexpr float kInstallableInkDropHighlightedOpacity = 0.08;
+constexpr float kInstallableInkDropActivatedOpacity = 0.16;
 }  // namespace
 
 namespace views {
@@ -26,13 +26,15 @@
 void InstallableInkDropPainter::Paint(gfx::Canvas* canvas,
                                       const gfx::Size& size) {
   if (activated_) {
+    canvas->FillRect(
+        gfx::Rect(size),
+        SkColorSetA(kInstallableInkDropBaseColor,
+                    kInstallableInkDropActivatedOpacity * SK_AlphaOPAQUE));
+  } else if (highlighted_ratio_ > 0.0f) {
     canvas->FillRect(gfx::Rect(size),
                      SkColorSetA(kInstallableInkDropBaseColor,
-                                 kInstallableInkDropActivatedOpacity));
-  } else if (highlighted_) {
-    canvas->FillRect(gfx::Rect(size),
-                     SkColorSetA(kInstallableInkDropBaseColor,
-                                 kInstallableInkDropHighlightedOpacity));
+                                 kInstallableInkDropHighlightedOpacity *
+                                     highlighted_ratio_ * SK_AlphaOPAQUE));
   }
 }
 
diff --git a/ui/views/animation/installable_ink_drop_painter.h b/ui/views/animation/installable_ink_drop_painter.h
index e5c3d76..9b5bb37 100644
--- a/ui/views/animation/installable_ink_drop_painter.h
+++ b/ui/views/animation/installable_ink_drop_painter.h
@@ -18,19 +18,21 @@
   InstallableInkDropPainter() = default;
   ~InstallableInkDropPainter() override = default;
 
-  void SetActivated(bool activated) { activated_ = activated; }
+  void set_activated(bool activated) { activated_ = activated; }
   bool activated() const { return activated_; }
 
-  void SetHighlighted(bool highlighted) { highlighted_ = highlighted; }
-  bool highlighted() const { return highlighted_; }
+  void set_highlighted_ratio(float highlighted_ratio) {
+    highlighted_ratio_ = highlighted_ratio;
+  }
+  bool highlighted_ratio() const { return highlighted_ratio_; }
 
   // Painter:
   gfx::Size GetMinimumSize() const override;
   void Paint(gfx::Canvas* canvas, const gfx::Size& size) override;
 
  private:
+  float highlighted_ratio_ = 0.0f;
   bool activated_ = false;
-  bool highlighted_ = false;
 };
 
 }  // namespace views
diff --git a/ui/views/metadata/metadata_types.cc b/ui/views/metadata/metadata_types.cc
index 8550cc9..34cbd93 100644
--- a/ui/views/metadata/metadata_types.cc
+++ b/ui/views/metadata/metadata_types.cc
@@ -77,6 +77,15 @@
   return tmp;
 }
 
+bool ClassMetaData::ClassMemberIterator::IsLastMember() const {
+  return current_vector_index_ == current_collection_->members().size() - 1;
+}
+
+std::string ClassMetaData::ClassMemberIterator::GetCurrentCollectionName()
+    const {
+  return current_collection_->type_name();
+}
+
 void ClassMetaData::ClassMemberIterator::IncrementHelper() {
   DCHECK_LT(current_vector_index_, SIZE_MAX);
   ++current_vector_index_;
diff --git a/ui/views/metadata/metadata_types.h b/ui/views/metadata/metadata_types.h
index bdf4fa0..90cf132 100644
--- a/ui/views/metadata/metadata_types.h
+++ b/ui/views/metadata/metadata_types.h
@@ -90,6 +90,12 @@
       return current_collection_->members()[current_vector_index_];
     }
 
+    // Returns true if iterator currently on last member for that current
+    // collection.
+    bool IsLastMember() const;
+
+    std::string GetCurrentCollectionName() const;
+
    private:
     friend class ClassMetaData;
     explicit ClassMemberIterator(ClassMetaData* starting_container);
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 877b67e..e6b6502 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
@@ -186,17 +186,6 @@
     return this.$.dialog;
   },
 
-  /**
-   * @return {!Array<!Element>}
-   * @private
-   */
-  getOptions_: function() {
-    return Array
-        .from(this.querySelectorAll(
-            '.dropdown-item:not([hidden]):not([disabled])'))
-        .map(cr.ui.FocusRow.getFocusableElement);
-  },
-
   /** @private */
   removeListeners_: function() {
     window.removeEventListener('resize', this.boundClose_);
@@ -248,19 +237,29 @@
    */
   onKeyDown_: function(e) {
     e.stopPropagation();
-
     if (e.key == 'Tab' || e.key == 'Escape') {
       this.close();
       e.preventDefault();
       return;
     }
 
-    let selectNext = e.key == 'ArrowDown';
+    if (e.key != 'Enter' && e.key != 'ArrowUp' && e.key != 'ArrowDown') {
+      return;
+    }
+
+    const query = '.dropdown-item:not([disabled]):not([hidden])';
+    const options = Array.from(this.querySelectorAll(query));
+    if (options.length == 0) {
+      return;
+    }
+
+    const focused = getDeepActiveElement();
+    const index = options.findIndex(
+        option => cr.ui.FocusRow.getFocusableElement(option) == focused);
+
     if (e.key == 'Enter') {
       // If a menu item has focus, don't change focus or close menu on 'Enter'.
-      const focusedIndex =
-          this.getOptions_().indexOf(assert(getDeepActiveElement()));
-      if (focusedIndex != -1) {
+      if (index != -1) {
         return;
       }
 
@@ -269,26 +268,18 @@
         e.preventDefault();
         return;
       }
-      selectNext = true;
-    }
-
-    if (e.key !== 'ArrowUp' && !selectNext) {
-      return;
-    }
-
-    const nextOption = this.getNextOption_(selectNext ? 1 : -1);
-    if (nextOption) {
-      if (!this.hasMousemoveListener_) {
-        this.hasMousemoveListener_ = true;
-        listenOnce(this, 'mousemove', e => {
-          this.onMouseover_(e);
-          this.hasMousemoveListener_ = false;
-        });
-      }
-      nextOption.focus();
     }
 
     e.preventDefault();
+    this.updateFocus_(options, index, e.key != 'ArrowUp');
+
+    if (!this.hasMousemoveListener_) {
+      this.hasMousemoveListener_ = true;
+      this.addEventListener('mousemove', e => {
+        this.onMouseover_(e);
+        this.hasMousemoveListener_ = false;
+      }, {once: true});
+    }
   },
 
   /**
@@ -296,49 +287,28 @@
    * @private
    */
   onMouseover_: function(e) {
-    let i = 0;
-    let target;
-    do {
-      target = e.path[i++];
-      if (target.classList && target.classList.contains('dropdown-item') &&
-          !target.disabled) {
-        target.focus();
-        return;
-      }
-    } while (this != target);
-
-    // The user moved the mouse off the options. Reset focus to the dialog.
-    this.$.dialog.focus();
+    const query = '.dropdown-item:not([disabled])';
+    const item = e.composedPath().find(el => el.matches && el.matches(query));
+    (item || this.$.dialog).focus();
   },
 
   /**
-   * @param {number} step -1 for getting previous option (up), 1 for getting
-   *     next option (down).
-   * @return {?Element} The next focusable option, taking into account
-   *     disabled/hidden attributes, or null if no focusable option exists.
+   * @param {!Array<!HTMLElement>} options
+   * @param {number} focusedIndex
+   * @param {boolean} next
    * @private
    */
-  getNextOption_: function(step) {
-    // Using a counter to ensure no infinite loop occurs if all elements are
-    // hidden/disabled.
-    let counter = 0;
-    let nextOption = null;
-    const options = this.getOptions_();
+  updateFocus_: function(options, focusedIndex, next) {
     const numOptions = options.length;
-    let focusedIndex = options.indexOf(assert(getDeepActiveElement()));
-
-    // Handle case where nothing is focused and up is pressed.
-    if (focusedIndex === -1 && step === -1) {
-      focusedIndex = 0;
+    assert(numOptions > 0);
+    let index;
+    if (focusedIndex == -1) {
+      index = next ? 0 : numOptions - 1;
+    } else {
+      const delta = next ? 1 : -1;
+      index = (numOptions + focusedIndex + delta) % numOptions;
     }
-
-    do {
-      focusedIndex = (numOptions + focusedIndex + step) % numOptions;
-      nextOption = options[focusedIndex];
-      counter++;
-    } while (!nextOption && counter < numOptions);
-
-    return nextOption;
+    options[index].focus();
   },
 
   close: function() {
diff --git a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
index e40548a..bfff87451 100644
--- a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
+++ b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
@@ -64,7 +64,9 @@
 
   /** @private */
   hideRipple_: function() {
-    this.getRipple().clear();
+    if (this.hasRipple()) {
+      this.getRipple().clear();
+    }
   },
 
   /** @private */
@@ -112,6 +114,9 @@
       element.icon = icon;
       this.$.icon.appendChild(element);
     });
+    if (!this.hasRipple()) {
+      return;
+    }
     if (icons.length > 1) {
       this.getRipple().classList.remove('circle');
     } else {
diff --git a/ui/webui/resources/js/list_property_update_behavior.js b/ui/webui/resources/js/list_property_update_behavior.js
index 653c3cec..0986cbd 100644
--- a/ui/webui/resources/js/list_property_update_behavior.js
+++ b/ui/webui/resources/js/list_property_update_behavior.js
@@ -21,17 +21,15 @@
    * @param {string} propertyPath
    * @param {function(!Object): string} itemUidGetter
    * @param {!Array<!Object>} updatedList
+   * @param {boolean} uidBasedUpdate
    * @returns {boolean} True if notifySplices was called.
    */
-  updateList: function(propertyPath, itemUidGetter, updatedList) {
+  updateList: function(
+      propertyPath, itemUidGetter, updatedList, uidBasedUpdate = false) {
     const list = this.get(propertyPath);
     const splices = Polymer.ArraySplice.calculateSplices(
         updatedList.map(itemUidGetter), list.map(itemUidGetter));
 
-    if (splices.length == 0) {
-      return false;
-    }
-
     splices.forEach(splice => {
       const index = splice.index;
       const deleteCount = splice.removed.length;
@@ -45,7 +43,21 @@
       const spliceParams = [index, deleteCount].concat(added);
       list.splice.apply(list, spliceParams);
     });
-    this.notifySplices(propertyPath, splices);
-    return true;
+
+    let updated = splices.length > 0;
+    if (!uidBasedUpdate) {
+      list.forEach((item, index) => {
+        const updatedItem = updatedList[index];
+        if (JSON.stringify(item) != JSON.stringify(updatedItem)) {
+          this.set([propertyPath, index], updatedItem);
+          updated = true;
+        }
+      });
+    }
+
+    if (splices.length > 0) {
+      this.notifySplices(propertyPath, splices);
+    }
+    return updated;
   },
 };