diff --git a/BUILD.gn b/BUILD.gn
index a0153dd..93d9108a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -147,6 +147,7 @@
       "//third_party/webrtc/rtc_tools:frame_analyzer",
       "//tools/perf/clear_system_cache",
       "//tools/polymer:polymer_tools_python_unittests",
+      "//ui/accessibility:accessibility_perftests",
       "//ui/accessibility:accessibility_unittests",
       "//ui/accessibility/extensions",
     ]
diff --git a/DEPS b/DEPS
index de2cec8c..d6011c92 100644
--- a/DEPS
+++ b/DEPS
@@ -167,11 +167,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': 'e25df6cf7879d3059bf7a61eabb35bfbf878506e',
+  'skia_revision': '2638f3d44b021eb9a03e7bafb22f68852a65d575',
   # 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': '0b40cfb3db44d470a0f8c4ed80bb48cea0b4e64e',
+  'v8_revision': 'b8b45f95d46ba556184b321dc47643252d72ea20',
   # 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.
@@ -179,7 +179,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '66aae7e647fdcdd1a8e7b1ab0e4977fa5e071b56',
+  'angle_revision': '9122bec28506ddefc46974c0d210382a9881aaf0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -302,11 +302,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'shaderc_revision': '488a0bd3c7d3e9ca1ae7f7cd7bb40017837f9f4a',
+  'shaderc_revision': '61cd76748f7c54c6a4a550f7ad157975e5bb10d1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '2c8b5c637028dd25e47de5fa1db079619d27b122',
+  'dawn_revision': '7b3cc35cb6e52fbc9d9c0f1b89035e0a4c50df5d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -866,7 +866,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '62b5cdc733cc9c28dd2ace034d7cc6d5e5189ff5',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '8017a6a7fc260ec47bbf2b7fac188bc30afc6c6e',
       'condition': 'checkout_linux',
   },
 
@@ -1287,7 +1287,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '1872e136d13b7738e16cedff27357dbf5f513631',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e6207efbf4c295c0776c9fc68cda339c4e5f5c1f',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1477,7 +1477,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '2701c130839edbeb226735b0775966b6423d9e83',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'd15a0283d1d8a02a1deff0674a231106113eac67',
+    Var('webrtc_git') + '/src.git' + '@' + 'a043b2ba46b9f81f1a679f36e1945fc7802f8201',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1539,7 +1539,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4ec0e215bc4adabb588c5f23af7e0476a560bfbe',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2ba136292e50c1cb99abde0260cc8e5cdbb64bc5',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 7452a20..9691eac 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1535,7 +1535,7 @@
     'safety_tips': {
       'filepath': 'chrome/browser/component_updater/safety_tips_.*'\
                   '|chrome/browser/lookalikes/'\
-                  '|chrome/browser/resources/safety_tips'\
+                  '|chrome/browser/reputation/'\
                   '|chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.*',
     },
     'sampling_profiler': {
@@ -2239,7 +2239,8 @@
     'chromeos_attestation': ['dkrahn+watch@chromium.org'],
     'chromeos_bluetooth': ['hansberry+watch-bluetooth@chromium.org',
                            'jhawkins+watch-bluetooth@chromium.org',
-                           'khorimoto+watch-bluetooth@chromium.org'],
+                           'khorimoto+watch-bluetooth@chromium.org',
+                           'vecore+watch-bluetooth@google.com'],
     'chromeos_calculator': ['dharcourt@chromium.org'],
     'chromeos_cellular': ['azeemarshad+watch-cellular@chromium.org',
                           'benchan+watch-cellular@chromium.org',
@@ -2260,13 +2261,15 @@
                      'ejcaruso+watch-network@chromium.org',
                      'jhawkins+watch-network@chromium.org',
                      'khorimoto+watch-network@chromium.org',
-                     'stevenjb+watch-network@chromium.org'],
+                     'stevenjb+watch-network@chromium.org',
+                     'vecore+watch-network@google.com'],
     'chromeos_timezone': ['alemate+watch@chromium.org'],
     'chromeos_webui': ['alemate+watch@chromium.org'],
     'chromeos_wifi_sync': ['jhawkins+watch@chromium.org',
                            'jonmann+watch@chromium.org',
                            'khorimoto+watch@chromium.org',
-                           'stevenjb+watch@chromium.org'],
+                           'stevenjb+watch@chromium.org',
+                           'vecore+watch@google.com'],
     'chromevox': ['anastasi+watch@google.com'],
     'clang_update': ['dcheng@chromium.org',
                      'eugenis+clang@chromium.org',
@@ -2456,7 +2459,8 @@
                     'jordynass+watch-multidevice@chromium.org',
                     'khorimoto+watch-multidevice@chromium.org',
                     'nohle+watch-multidevice@chromium.org',
-                    'themaxli+watch-multidevice@chromium.org'],
+                    'themaxli+watch-multidevice@chromium.org',
+                    'vecore+watch-multidevice@google.com'],
     'nacl': ['native-client-reviews@googlegroups.com'],
     'native_client_sdk': ['binji+watch@chromium.org',
                           'sbc@chromium.org'],
@@ -2588,7 +2592,8 @@
                   'jhawkins+watch-smartlock@chromium.org',
                   'jordynass+watch-smartlock@chromium.org',
                   'khorimoto+watch-smartlock@chromium.org',
-                  'nohle+watch-smartlock@chromium.org'],
+                  'nohle+watch-smartlock@chromium.org',
+                  'vecore+watch-smartlock@google.com'],
     'smb': ['cros-enterprise-lax+smbwatch@chromium.org'],
     'source_idls': ['jmedley+watch@chromium.org'],
     'spellcheck': ['rlp+watch@chromium.org',
@@ -2634,7 +2639,8 @@
                'jhawkins+watch-tether@chromium.org',
                'jordynass+watch-tether@chromium.org',
                'khorimoto+watch-tether@chromium.org',
-               'nohle+watch-tether@chromium.org'],
+               'nohle+watch-tether@chromium.org',
+               'vecore+watch-tether@google.com'],
     'textinput': ['keithlee+watch@chromium.org',
                   'nona+watch@chromium.org',
                   'shuchen+watch@chromium.org',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index c14a246..aadb8f2 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -570,7 +570,6 @@
     "java/src/org/chromium/android_webview/NullAwViewMethods.java",
     "java/src/org/chromium/android_webview/OverScrollGlow.java",
     "java/src/org/chromium/android_webview/PopupTouchHandleDrawable.java",
-    "java/src/org/chromium/android_webview/ResourcesContextWrapperFactory.java",
     "java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java",
     "java/src/org/chromium/android_webview/SslUtil.java",
     "java/src/org/chromium/android_webview/VariationsSeedLoader.java",
@@ -620,7 +619,6 @@
     "//components/content_capture/android:java",
     "//components/crash/android:handler_java",
     "//components/crash/android:java",
-    "//components/embedder_support/android:application_java",
     "//components/embedder_support/android:web_contents_delegate_java",
     "//components/minidump_uploader:minidump_uploader_java",
     "//components/navigation_interception/android:navigation_interception_java",
diff --git a/android_webview/java/src/org/chromium/android_webview/ResourcesContextWrapperFactory.java b/android_webview/java/src/org/chromium/android_webview/ResourcesContextWrapperFactory.java
deleted file mode 100644
index dfa9b5a9..0000000
--- a/android_webview/java/src/org/chromium/android_webview/ResourcesContextWrapperFactory.java
+++ /dev/null
@@ -1,21 +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.
-
-package org.chromium.android_webview;
-
-import android.content.Context;
-
-import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory;
-
-/**
- * This class was migrated to embedder_support; this forwarding wrapper exists to avoid breaking
- * downstream code until it's updated.
- */
-public class ResourcesContextWrapperFactory {
-    private ResourcesContextWrapperFactory() {}
-
-    public static Context get(Context ctx) {
-        return ClassLoaderContextWrapperFactory.get(ctx);
-    }
-}
diff --git a/ash/app_list/app_list_controller_impl_unittest.cc b/ash/app_list/app_list_controller_impl_unittest.cc
index 11df82c..0b87bab 100644
--- a/ash/app_list/app_list_controller_impl_unittest.cc
+++ b/ash/app_list/app_list_controller_impl_unittest.cc
@@ -437,7 +437,8 @@
 
 // Tests for HomeScreenDelegate::GetInitialAppListItemScreenBoundsForWindow
 // implemtenation.
-TEST_F(AppListControllerImplTest, GetItemBoundsForWindow) {
+// Disabled due to crbug.com/1016843
+TEST_F(AppListControllerImplTest, DISABLED_GetItemBoundsForWindow) {
   // Populate app list model with 25 items, of which items at indices in
   // |folders| are folders containing a single item.
   const std::set<int> folders = {5, 23};
diff --git a/ash/assistant/assistant_interaction_controller.cc b/ash/assistant/assistant_interaction_controller.cc
index 80b97a5b..5a87386 100644
--- a/ash/assistant/assistant_interaction_controller.cc
+++ b/ash/assistant/assistant_interaction_controller.cc
@@ -408,7 +408,7 @@
     // pending query type will always be |kNull| here.
     if (model_.pending_query().type() == AssistantQueryType::kNull) {
       model_.SetPendingQuery(std::make_unique<AssistantTextQuery>(
-          metadata->query, AssistantQuerySource::kLibAssistantInitiated));
+          metadata->query, metadata->source));
     }
     model_.CommitPendingQuery();
     model_.SetMicState(MicState::kClosed);
@@ -878,7 +878,8 @@
       description, AssistantQuerySource::kProactiveSuggestions));
 
   OnInteractionStarted(AssistantInteractionMetadata::New(
-      AssistantInteractionType::kText, /*query=*/description));
+      AssistantInteractionType::kText,
+      AssistantQuerySource::kProactiveSuggestions, /*query=*/description));
 
   OnHtmlResponse(proactive_suggestions->html(), /*fallback=*/std::string());
 
@@ -913,7 +914,7 @@
   model_.SetPendingQuery(
       std::make_unique<AssistantTextQuery>(text, query_source));
 
-  assistant_->StartTextInteraction(text, allow_tts);
+  assistant_->StartTextInteraction(text, query_source, allow_tts);
 }
 
 void AssistantInteractionController::StartVoiceInteraction() {
diff --git a/ash/assistant/assistant_interaction_controller.h b/ash/assistant/assistant_interaction_controller.h
index dabb051..d56b253 100644
--- a/ash/assistant/assistant_interaction_controller.h
+++ b/ash/assistant/assistant_interaction_controller.h
@@ -44,6 +44,7 @@
       chromeos::assistant::mojom::AssistantInteractionResolution;
   using AssistantInteractionType =
       chromeos::assistant::mojom::AssistantInteractionType;
+  using AssistantQuerySource = chromeos::assistant::mojom::AssistantQuerySource;
   using AssistantSuggestion = chromeos::assistant::mojom::AssistantSuggestion;
   using AssistantSuggestionPtr =
       chromeos::assistant::mojom::AssistantSuggestionPtr;
diff --git a/ash/assistant/model/assistant_query.h b/ash/assistant/model/assistant_query.h
index de9fca5..8da9f26a4 100644
--- a/ash/assistant/model/assistant_query.h
+++ b/ash/assistant/model/assistant_query.h
@@ -9,6 +9,7 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
 
 namespace ash {
 
@@ -21,26 +22,13 @@
   kVoice,  // See AssistantVoiceQuery.
 };
 
-// Defines possible source of an Assistant query. These values are persisted
-// to logs. Entries should not be renumbered and numeric values should never
-// be reused. Only append to this enum is allowed if the possible source grows.
-enum class AssistantQuerySource {
-  kUnspecified = 0,
-  kDeepLink = 1,
-  kDialogPlateTextField = 2,
-  kStylus = 3,
-  kSuggestionChip = 4,
-  kVoiceInput = 5,
-  kProactiveSuggestions = 6,
-  kLibAssistantInitiated = 7,
-  kMaxValue = kLibAssistantInitiated
-};
-
 // AssistantQuery --------------------------------------------------------------
 
 // Base class for an Assistant query.
 class COMPONENT_EXPORT(ASSISTANT_MODEL) AssistantQuery {
  public:
+  using AssistantQuerySource = chromeos::assistant::mojom::AssistantQuerySource;
+
   virtual ~AssistantQuery() = default;
 
   // Returns the type for the query.
@@ -58,7 +46,6 @@
 
  private:
   const AssistantQueryType type_;
-
   const AssistantQuerySource source_;
 
   DISALLOW_COPY_AND_ASSIGN(AssistantQuery);
diff --git a/ash/assistant/test/test_assistant_service.cc b/ash/assistant/test/test_assistant_service.cc
index 491d9af..715d76d 100644
--- a/ash/assistant/test/test_assistant_service.cc
+++ b/ash/assistant/test/test_assistant_service.cc
@@ -199,9 +199,11 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
-void TestAssistantService ::StartTextInteraction(const std::string& query,
-                                                 bool allow_tts) {
-  StartInteraction(AssistantInteractionType::kText, query);
+void TestAssistantService ::StartTextInteraction(
+    const std::string& query,
+    chromeos::assistant::mojom::AssistantQuerySource source,
+    bool allow_tts) {
+  StartInteraction(AssistantInteractionType::kText, source, query);
   SendInteractionResponse();
 }
 
@@ -265,10 +267,11 @@
 
 void TestAssistantService::StartInteraction(
     chromeos::assistant::mojom::AssistantInteractionType type,
+    chromeos::assistant::mojom::AssistantQuerySource source,
     const std::string& query) {
   for (auto& subscriber : interaction_subscribers_) {
     subscriber->OnInteractionStarted(
-        AssistantInteractionMetadata::New(type, query));
+        AssistantInteractionMetadata::New(type, source, query));
   }
 }
 
diff --git a/ash/assistant/test/test_assistant_service.h b/ash/assistant/test/test_assistant_service.h
index 21fe6ad9..02691cb 100644
--- a/ash/assistant/test/test_assistant_service.h
+++ b/ash/assistant/test/test_assistant_service.h
@@ -81,7 +81,10 @@
   void StartCachedScreenContextInteraction() override;
   void StartEditReminderInteraction(const std::string& client_id) override;
   void StartMetalayerInteraction(const gfx::Rect& region) override;
-  void StartTextInteraction(const std::string& query, bool allow_tts) override;
+  void StartTextInteraction(
+      const std::string& query,
+      chromeos::assistant::mojom::AssistantQuerySource source,
+      bool allow_tts) override;
   void StartVoiceInteraction() override;
   void StartWarmerWelcomeInteraction(int num_warmer_welcome_triggered,
                                      bool allow_tts) override;
@@ -106,7 +109,9 @@
  private:
   void StartInteraction(
       chromeos::assistant::mojom::AssistantInteractionType type,
-      const std::string& query = {});
+      chromeos::assistant::mojom::AssistantQuerySource source =
+          chromeos::assistant::mojom::AssistantQuerySource::kUnspecified,
+      const std::string& query = std::string());
   void SendInteractionResponse();
   InteractionResponse PopInteractionResponse();
 
diff --git a/ash/assistant/util/BUILD.gn b/ash/assistant/util/BUILD.gn
index ca6f935e..4b16671 100644
--- a/ash/assistant/util/BUILD.gn
+++ b/ash/assistant/util/BUILD.gn
@@ -26,6 +26,7 @@
     "//ash/assistant/model",
     "//base",
     "//base:i18n",
+    "//chromeos/services/assistant/public/mojom",
     "//net",
     "//ui/base",
     "//ui/gfx",
diff --git a/ash/assistant/util/histogram_util.cc b/ash/assistant/util/histogram_util.cc
index 52f0657..7516854 100644
--- a/ash/assistant/util/histogram_util.cc
+++ b/ash/assistant/util/histogram_util.cc
@@ -4,9 +4,9 @@
 
 #include "ash/assistant/util/histogram_util.h"
 
-#include "ash/assistant/model/assistant_query.h"
 #include "ash/assistant/model/assistant_ui_model.h"
 #include "base/metrics/histogram_macros.h"
+#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
 
 namespace ash {
 namespace assistant {
@@ -30,7 +30,8 @@
                             AssistantButtonId::kMaxValue);
 }
 
-void RecordAssistantQuerySource(AssistantQuerySource source) {
+void RecordAssistantQuerySource(
+    chromeos::assistant::mojom::AssistantQuerySource source) {
   UMA_HISTOGRAM_ENUMERATION("Assistant.QuerySource", source);
 }
 
diff --git a/ash/assistant/util/histogram_util.h b/ash/assistant/util/histogram_util.h
index d72b951d..a409317 100644
--- a/ash/assistant/util/histogram_util.h
+++ b/ash/assistant/util/histogram_util.h
@@ -6,13 +6,13 @@
 #define ASH_ASSISTANT_UTIL_HISTOGRAM_UTIL_H_
 
 #include "base/component_export.h"
+#include "chromeos/services/assistant/public/mojom/assistant.mojom-forward.h"
 
 namespace ash {
 
 enum class AssistantButtonId;
 enum class AssistantEntryPoint;
 enum class AssistantExitPoint;
-enum class AssistantQuerySource;
 
 namespace assistant {
 namespace util {
@@ -35,7 +35,8 @@
 
 // Record the input source of each query (e.g. voice, typing).
 COMPONENT_EXPORT(ASSISTANT_UTIL)
-void RecordAssistantQuerySource(AssistantQuerySource source);
+void RecordAssistantQuerySource(
+    chromeos::assistant::mojom::AssistantQuerySource source);
 
 }  // namespace util
 }  // namespace assistant
diff --git a/ash/display/root_window_transformers.cc b/ash/display/root_window_transformers.cc
index afb4861b..b9a8ffb 100644
--- a/ash/display/root_window_transformers.cc
+++ b/ash/display/root_window_transformers.cc
@@ -310,7 +310,7 @@
   }
   gfx::Insets GetHostInsets() const override { return gfx::Insets(); }
   gfx::Transform GetInsetsAndScaleTransform() const override {
-    return gfx::Transform();
+    return transform_;
   }
 
  private:
diff --git a/ash/display/root_window_transformers_unittest.cc b/ash/display/root_window_transformers_unittest.cc
index 9755915..2ce189e 100644
--- a/ash/display/root_window_transformers_unittest.cc
+++ b/ash/display/root_window_transformers_unittest.cc
@@ -15,6 +15,7 @@
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/cursor_manager_test_api.h"
+#include "base/command_line.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/aura/env.h"
@@ -143,6 +144,24 @@
   DISALLOW_COPY_AND_ASSIGN(RootWindowTransformersTest);
 };
 
+class UnfiedRootWindowTransformersTest : public RootWindowTransformersTest {
+ public:
+  UnfiedRootWindowTransformersTest() = default;
+  ~UnfiedRootWindowTransformersTest() override = default;
+
+  // RootWindowTransformersTest:
+  void SetUp() override {
+    // kEnableUnifiedDesktop switch needs to be added before DisplayManager
+    // creation. Hence before calling SetUp.
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        switches::kEnableUnifiedDesktop);
+
+    RootWindowTransformersTest::SetUp();
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(UnfiedRootWindowTransformersTest);
+};
+
 }  // namespace
 
 TEST_F(RootWindowTransformersTest, RotateAndMagnify) {
@@ -569,4 +588,26 @@
   EXPECT_EQ(root_window->GetTargetBounds(), gfx::Rect(0, 0, 600, 800));
 }
 
+TEST_F(UnfiedRootWindowTransformersTest, HostBoundsAndTransform) {
+  UpdateDisplay("800x600,800x600");
+  // Has only one logical root window.
+  EXPECT_EQ(1u, Shell::GetAllRootWindows().size());
+
+  MirrorWindowTestApi test_api;
+  std::vector<aura::WindowTreeHost*> hosts = test_api.GetHosts();
+  // Have 2 WindowTreeHosts, one per display.
+  ASSERT_EQ(2u, hosts.size());
+
+  EXPECT_EQ(gfx::Rect(0, 0, 800, 600), hosts[0]->window()->GetBoundsInScreen());
+  gfx::Point viewport_0_origin(0, 0);
+  hosts[0]->window()->transform().TransformPointReverse(&viewport_0_origin);
+  EXPECT_EQ(gfx::Point(0, 0), viewport_0_origin);
+
+  EXPECT_EQ(gfx::Rect(800, 0, 800, 600),
+            hosts[1]->window()->GetBoundsInScreen());
+  gfx::Point viewport_1_origin(0, 0);
+  hosts[1]->window()->transform().TransformPointReverse(&viewport_1_origin);
+  EXPECT_EQ(gfx::Point(800, 0), viewport_1_origin);
+}
+
 }  // namespace ash
diff --git a/ash/home_screen/drag_window_from_shelf_controller.cc b/ash/home_screen/drag_window_from_shelf_controller.cc
index d8f44d2..6197d6d 100644
--- a/ash/home_screen/drag_window_from_shelf_controller.cc
+++ b/ash/home_screen/drag_window_from_shelf_controller.cc
@@ -6,6 +6,7 @@
 
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/home_screen/home_screen_controller.h"
+#include "ash/home_screen/home_screen_delegate.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/root_window_controller.h"
 #include "ash/scoped_animation_disabler.h"
@@ -108,12 +109,26 @@
   // Returns the transform that should be applied to the dragged window if we
   // should head to homescreen after dragging.
   gfx::Transform GetWindowTransformToHomeScreen() {
-    const gfx::Rect work_area =
-        screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
-            window_);
     gfx::Transform transform;
-    transform.Translate(work_area.width() / 2, work_area.height() / 2);
-    transform.Scale(kWindowScaleDownFactor, kWindowScaleDownFactor);
+    HomeScreenDelegate* home_screen_delegate =
+        Shell::Get()->home_screen_controller()->delegate();
+    DCHECK(home_screen_delegate);
+    const gfx::Rect app_list_item_bounds =
+        home_screen_delegate->GetInitialAppListItemScreenBoundsForWindow(
+            window_);
+    if (!app_list_item_bounds.IsEmpty()) {
+      const gfx::Rect window_bounds = window_->GetBoundsInScreen();
+      transform.Translate(app_list_item_bounds.x(), app_list_item_bounds.y());
+      transform.Scale(
+          float(app_list_item_bounds.width()) / float(window_bounds.width()),
+          float(app_list_item_bounds.height()) / float(window_bounds.height()));
+    } else {
+      const gfx::Rect work_area =
+          screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
+              window_);
+      transform.Translate(work_area.width() / 2, work_area.height() / 2);
+      transform.Scale(kWindowScaleDownFactor, kWindowScaleDownFactor);
+    }
     return transform;
   }
 
diff --git a/ash/shell/content/test/ash_content_perf_test_launcher.cc b/ash/shell/content/test/ash_content_perf_test_launcher.cc
index fb68206..947b77ff 100644
--- a/ash/shell/content/test/ash_content_perf_test_launcher.cc
+++ b/ash/shell/content/test/ash_content_perf_test_launcher.cc
@@ -34,10 +34,9 @@
  protected:
   // content::ContentTestSuiteBase:
   void Initialize() override {
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals. (Must run
+    // before the base class is initialized.)
     base::TestSuite::DisableCheckForLeakedGlobals();
-    base::TestSuite::DisableCheckForThreadPriorityAtTestEnd();
     ContentTestSuiteBase::Initialize();
     ui_controls::InstallUIControlsAura(ash::test::CreateAshUIControls());
   }
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index a36a45e..3fa7173 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1490,6 +1490,96 @@
     PositionWindows(/*animate=*/false);
 }
 
+int OverviewGrid::CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem* item,
+                                                           int height) {
+  const gfx::Size item_size(0, height);
+  gfx::RectF target_bounds = item->GetTargetBoundsInScreen();
+  float scale = item->GetItemScale(item_size);
+
+  // The drop target, unlike the other windows has its bounds set directly, so
+  // |GetTargetBoundsInScreen()| won't return the value we want. Instead, get
+  // the scale from the window it was meant to be a placeholder for.
+  if (IsDropTargetWindow(item->GetWindow())) {
+    aura::Window* dragged_window = nullptr;
+    OverviewItem* item =
+        overview_session_->window_drag_controller()
+            ? overview_session_->window_drag_controller()->item()
+            : nullptr;
+    if (item)
+      dragged_window = item->GetWindow();
+    else if (dragged_window_)
+      dragged_window = dragged_window_;
+
+    if (dragged_window && dragged_window->parent()) {
+      target_bounds = ::ash::GetTargetBoundsInScreen(dragged_window);
+      const gfx::SizeF inset_size(0, height - 2 * kWindowMargin);
+      scale = ScopedOverviewTransformWindow::GetItemScale(
+          target_bounds.size(), inset_size,
+          dragged_window->GetProperty(aura::client::kTopViewInset),
+          kHeaderHeightDp);
+    }
+  }
+
+  int width = std::max(
+      1, gfx::ToFlooredInt(target_bounds.width() * scale) + 2 * kWindowMargin);
+  switch (item->GetWindowDimensionsType()) {
+    case ScopedOverviewTransformWindow::GridWindowFillMode::kLetterBoxed:
+      width =
+          ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold * height;
+      break;
+    case ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed:
+      width =
+          height / ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold;
+      break;
+    default:
+      break;
+  }
+
+  // Get the bounds of the window if there is a snapped window or a window
+  // about to be snapped.
+  base::Optional<gfx::RectF> split_view_bounds =
+      GetSplitviewBoundsMaintainingAspectRatio(item->GetWindow());
+  if (!split_view_bounds) {
+    item->set_unclipped_size(base::nullopt);
+    return width;
+  }
+
+  const bool is_landscape = IsCurrentScreenOrientationLandscape();
+  gfx::Size unclipped_size;
+  if (is_landscape) {
+    unclipped_size.set_width(width - 2 * kWindowMargin);
+    unclipped_size.set_height(height - 2 * kWindowMargin);
+    // For landscape mode, shrink |width| so that the aspect ratio matches
+    // that of |split_view_bounds|.
+    width = std::max(1, gfx::ToFlooredInt(split_view_bounds->width() * scale) +
+                            2 * kWindowMargin);
+  } else {
+    // For portrait mode, we want |height| to stay the same, so calculate
+    // what the unclipped height would be based on |split_view_bounds|.
+
+    // Find the width so that it matches height and matches the aspect ratio of
+    // |split_view_bounds|. |height| includes the margins and overview header,
+    // so exclude those from the calculation.
+    width = (split_view_bounds->width() *
+             (height - 2 * kWindowMargin - kHeaderHeightDp) /
+             split_view_bounds->height());
+    // The unclipped height is the height which matches |width| but keeps the
+    // aspect ratio of |target_bounds|. Clipping takes the overview header into
+    // account, so add that back in.
+    const int unclipped_height =
+        width * target_bounds.height() / target_bounds.width();
+    unclipped_size.set_width(width);
+    unclipped_size.set_height(unclipped_height + kHeaderHeightDp);
+
+    // Add some space between this item and the next one (if it exists).
+    width += (2 * kWindowMargin);
+  }
+
+  DCHECK(!unclipped_size.IsEmpty());
+  item->set_unclipped_size(base::make_optional(unclipped_size));
+  return width;
+}
+
 void OverviewGrid::MaybeInitDesksWidget() {
   if (!desks_util::ShouldDesksBarBeCreated() || desks_widget_)
     return;
@@ -1798,82 +1888,4 @@
                              /*animate=*/false);
 }
 
-int OverviewGrid::CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem* item,
-                                                           int height) {
-  const gfx::Size item_size(0, height);
-  gfx::RectF target_bounds = item->GetTargetBoundsInScreen();
-  float scale = item->GetItemScale(item_size);
-
-  // The drop target, unlike the other windows has its bounds set directly, so
-  // |GetTargetBoundsInScreen()| won't return the value we want. Instead, get
-  // the scale from the window it was meant to be a placeholder for.
-  if (IsDropTargetWindow(item->GetWindow())) {
-    aura::Window* dragged_window = nullptr;
-    OverviewItem* item =
-        overview_session_->window_drag_controller()
-            ? overview_session_->window_drag_controller()->item()
-            : nullptr;
-    if (item)
-      dragged_window = item->GetWindow();
-    else if (dragged_window_)
-      dragged_window = dragged_window_;
-
-    if (dragged_window && dragged_window->parent()) {
-      target_bounds = ::ash::GetTargetBoundsInScreen(dragged_window);
-      const gfx::SizeF inset_size(0, height - 2 * kWindowMargin);
-      scale = ScopedOverviewTransformWindow::GetItemScale(
-          target_bounds.size(), inset_size,
-          dragged_window->GetProperty(aura::client::kTopViewInset),
-          kHeaderHeightDp);
-    }
-  }
-
-  int width = std::max(
-      1, gfx::ToFlooredInt(target_bounds.width() * scale) + 2 * kWindowMargin);
-  switch (item->GetWindowDimensionsType()) {
-    case ScopedOverviewTransformWindow::GridWindowFillMode::kLetterBoxed:
-      width =
-          ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold * height;
-      break;
-    case ScopedOverviewTransformWindow::GridWindowFillMode::kPillarBoxed:
-      width =
-          height / ScopedOverviewTransformWindow::kExtremeWindowRatioThreshold;
-      break;
-    default:
-      break;
-  }
-
-  // Get the bounds of the window if there is a snapped window or a window
-  // about to be snapped.
-  base::Optional<gfx::RectF> split_view_bounds =
-      GetSplitviewBoundsMaintainingAspectRatio(item->GetWindow());
-  if (!split_view_bounds) {
-    item->set_unclipped_size(base::nullopt);
-    return width;
-  }
-
-  const bool is_landscape = IsCurrentScreenOrientationLandscape();
-  gfx::Size unclipped_size;
-  if (is_landscape) {
-    unclipped_size.set_width(width - 2 * kWindowMargin);
-    unclipped_size.set_height(height - 2 * kWindowMargin);
-    // For landscape mode, shrink |width| so that the aspect ratio matches
-    // that of |split_view_bounds|.
-    width = std::max(1, gfx::ToFlooredInt(split_view_bounds->width() * scale) +
-                            2 * kWindowMargin);
-  } else {
-    // For portrait mode, we want |height| to stay the same, so calculate
-    // what the unclipped height would be based on |split_view_bounds|.
-    width = split_view_bounds->width() * height / split_view_bounds->height();
-    int unclipped_height =
-        (target_bounds.height() * width) / target_bounds.width();
-    unclipped_size.set_width(width);
-    unclipped_size.set_height(unclipped_height);
-  }
-
-  DCHECK(!unclipped_size.IsEmpty());
-  item->set_unclipped_size(base::make_optional(unclipped_size));
-  return width;
-}
-
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index ef7456a..c940aa1 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -297,6 +297,12 @@
 
   void EndScroll();
 
+  // Calculate the width of an item based on |height|. The width tries to keep
+  // the same aspect ratio as the original window, but may be modified if the
+  // bounds of the window are considered extreme, or if the window is in
+  // splitview or entering splitview.
+  int CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem* item, int height);
+
   // Returns true if the grid has no more windows.
   bool empty() const { return window_list_.empty(); }
 
@@ -396,12 +402,6 @@
   // the window's bounds if it has been resized.
   void AddDraggedWindowIntoOverviewOnDragEnd(aura::Window* dragged_window);
 
-  // Calculate the width of an item based on |height|. The width tries to keep
-  // the same aspect ratio as the original window, but may be modified if the
-  // bounds of the window are considered extreme, or if the window is in
-  // splitview or entering splitview.
-  int CalculateWidthAndMaybeSetUnclippedBounds(OverviewItem* item, int height);
-
   // Root window the grid is in.
   aura::Window* root_window_;
 
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index e7f4e08..1bd5e076 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -639,6 +639,17 @@
   gfx::RectF scaled_bounds = target_bounds();
   scaled_bounds.Inset(-scaled_bounds.width() * kDragWindowScale,
                       -scaled_bounds.height() * kDragWindowScale);
+  if (unclipped_size_) {
+    // If a clipped item is scaled up, we need to recalculate the unclipped
+    // size.
+    const int height = scaled_bounds.height();
+    const int width =
+        overview_grid_->CalculateWidthAndMaybeSetUnclippedBounds(this, height);
+    DCHECK(unclipped_size_);
+    const gfx::SizeF new_size(width, height);
+    scaled_bounds.set_size(new_size);
+    scaled_bounds.ClampToCenteredSize(new_size);
+  }
   SetBounds(scaled_bounds, animation_type);
 }
 
diff --git a/ash/wm/toplevel_window_event_handler.cc b/ash/wm/toplevel_window_event_handler.cc
index 1a7f0e7..c691d07 100644
--- a/ash/wm/toplevel_window_event_handler.cc
+++ b/ash/wm/toplevel_window_event_handler.cc
@@ -4,7 +4,9 @@
 
 #include "ash/wm/toplevel_window_event_handler.h"
 
+#include "ash/app_list/app_list_controller_impl.h"
 #include "ash/home_screen/home_screen_controller.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/session/session_controller_impl.h"
@@ -128,9 +130,13 @@
     return false;
   }
 
-  // Do not enable back gesture if home screen is visible.
-  if (shell->home_screen_controller()->IsHomeScreenVisible())
+  // Do not enable back gesture if home screen is visible but not in
+  // |kFullscreenSearch| state.
+  if (shell->home_screen_controller()->IsHomeScreenVisible() &&
+      shell->app_list_controller()->GetAppListViewState() !=
+          AppListViewState::kFullscreenSearch) {
     return false;
+  }
 
   views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(target);
   if (!widget)
diff --git a/ash/wm/toplevel_window_event_handler_unittest.cc b/ash/wm/toplevel_window_event_handler_unittest.cc
index 4ebcaa9..048e24c 100644
--- a/ash/wm/toplevel_window_event_handler_unittest.cc
+++ b/ash/wm/toplevel_window_event_handler_unittest.cc
@@ -5,6 +5,9 @@
 #include "ash/wm/toplevel_window_event_handler.h"
 
 #include "ash/accelerators/accelerator_controller_impl.h"
+#include "ash/app_list/test/app_list_test_helper.h"
+#include "ash/app_list/views/app_list_view.h"
+#include "ash/app_list/views/search_box_view.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/home_screen/home_screen_controller.h"
@@ -1162,11 +1165,13 @@
   EXPECT_EQ(0, target_back_press.accelerator_count());
   EXPECT_EQ(0, target_back_release.accelerator_count());
 
-  // Should not go back if home screen is not visible.
+  // Should not go back if home screen is visible and in |kFullscreenAllApps|
+  // state.
   shell->overview_controller()->EndOverview();
   ASSERT_FALSE(shell->overview_controller()->InOverviewSession());
   shell->home_screen_controller()->GoHome(GetPrimaryDisplay().id());
   ASSERT_TRUE(shell->home_screen_controller()->IsHomeScreenVisible());
+  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
   generator->GestureScrollSequence(
       start,
       gfx::Point(ToplevelWindowEventHandler::kSwipingDistanceForGoingBack + 10,
@@ -1174,6 +1179,22 @@
       base::TimeDelta::FromMilliseconds(100), 3);
   EXPECT_EQ(0, target_back_press.accelerator_count());
   EXPECT_EQ(0, target_back_release.accelerator_count());
+
+  // Should exit |kFullscreenSearch| to enter |kFullscreenAllApps| state while
+  // home screen search result page is opened.
+  generator->GestureTapAt(GetAppListTestHelper()
+                              ->GetAppListView()
+                              ->search_box_view()
+                              ->GetBoundsInScreen()
+                              .CenterPoint());
+  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);
+  generator->GestureScrollSequence(
+      start,
+      gfx::Point(ToplevelWindowEventHandler::kSwipingDistanceForGoingBack + 10,
+                 100),
+      base::TimeDelta::FromMilliseconds(100), 3);
+  EXPECT_EQ(1, target_back_release.accelerator_count());
+  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
 }
 
 namespace {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 200b8dae..ff5190e 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -629,6 +629,7 @@
     "profiler/stack_sampler_android.cc",
     "profiler/stack_sampler_impl.cc",
     "profiler/stack_sampler_impl.h",
+    "profiler/stack_sampler_ios.cc",
     "profiler/stack_sampler_mac.cc",
     "profiler/stack_sampler_win.cc",
     "profiler/stack_sampling_profiler.cc",
@@ -639,8 +640,6 @@
     "profiler/suspendable_thread_delegate_win.cc",
     "profiler/suspendable_thread_delegate_win.h",
     "profiler/thread_delegate.h",
-    "profiler/thread_delegate_android.cc",
-    "profiler/thread_delegate_android.h",
     "profiler/unwinder.h",
     "rand_util.cc",
     "rand_util.h",
@@ -1185,7 +1184,6 @@
       "process/process_handle_posix.cc",
       "process/process_metrics_posix.cc",
       "process/process_posix.cc",
-      "profiler/stack_sampler_posix.cc",
       "rand_util_posix.cc",
       "sampling_heap_profiler/module_cache_posix.cc",
       "strings/string_util_posix.h",
@@ -1222,6 +1220,14 @@
 
     if (is_posix) {
       sources += [ "base_paths_posix.h" ]
+
+      if (!is_mac && !is_ios) {
+        sources += [
+          "profiler/stack_sampler_posix.cc",
+          "profiler/thread_delegate_posix.cc",
+          "profiler/thread_delegate_posix.h",
+        ]
+      }
     }
 
     if (is_linux) {
@@ -1819,10 +1825,7 @@
 
   # Desktop Mac.
   if (is_mac) {
-    sources -= [
-      "process/launch_posix.cc",
-      "profiler/stack_sampler_posix.cc",
-    ]
+    sources -= [ "process/launch_posix.cc" ]
     sources += [
       "files/file_path_watcher_fsevents.cc",
       "files/file_path_watcher_fsevents.h",
diff --git a/base/debug/stack_trace_fuchsia.cc b/base/debug/stack_trace_fuchsia.cc
index 032e675..976456b 100644
--- a/base/debug/stack_trace_fuchsia.cc
+++ b/base/debug/stack_trace_fuchsia.cc
@@ -29,12 +29,8 @@
 
 namespace base {
 namespace debug {
-
 namespace {
 
-const char kProcessNamePrefix[] = "app:";
-const size_t kProcessNamePrefixLength = base::size(kProcessNamePrefix) - 1;
-
 struct BacktraceData {
   void** trace_array;
   size_t* count;
@@ -90,7 +86,7 @@
     const void* addr = nullptr;
     std::array<Segment, kMaxSegmentCount> segments;
     size_t segment_count = 0;
-    char name[ZX_MAX_NAME_LEN + kProcessNamePrefixLength + 1] = {0};
+    char name[ZX_MAX_NAME_LEN + 1] = {0};
     char build_id[kMaxBuildIdStringLength + 1] = {0};
   };
 
@@ -129,11 +125,19 @@
   // if we keep hitting problems with truncation, find a way to plumb argv[0]
   // through to here instead, e.g. using CommandLine::GetProgramName().
   char app_name[std::extent<decltype(SymbolMap::Module::name)>()];
-  strncpy(app_name, kProcessNamePrefix, sizeof(kProcessNamePrefix));
-  zx_status_t status = zx_object_get_property(
-      process, ZX_PROP_NAME, app_name + kProcessNamePrefixLength,
-      sizeof(app_name) - kProcessNamePrefixLength);
-  if (status != ZX_OK) {
+  zx_status_t status =
+      zx_object_get_property(process, ZX_PROP_NAME, app_name, sizeof(app_name));
+  if (status == ZX_OK) {
+    // The process name may have a process type suffix at the end (e.g.
+    // "context", "renderer", gpu"), which doesn't belong in the module list.
+    // Trim the suffix from the name.
+    for (size_t i = 0; i < base::size(app_name) && app_name[i] != '\0'; ++i) {
+      if (app_name[i] == ':') {
+        app_name[i] = 0;
+        break;
+      }
+    }
+  } else {
     DPLOG(WARNING)
         << "Couldn't get name, falling back to 'app' for program name: "
         << status;
diff --git a/base/profiler/register_context.h b/base/profiler/register_context.h
index 556b34c..46c4250 100644
--- a/base/profiler/register_context.h
+++ b/base/profiler/register_context.h
@@ -17,7 +17,7 @@
 #include <windows.h>
 #elif defined(OS_MACOSX)
 #include <mach/machine/thread_status.h>
-#elif defined(OS_ANDROID) && !defined(ARCH_CPU_64_BITS)
+#elif defined(OS_ANDROID) || defined(OS_LINUX)
 #include <sys/ucontext.h>
 #endif
 
@@ -85,7 +85,8 @@
   return AsUintPtr(&context->__rip);
 }
 
-#elif defined(OS_ANDROID) && defined(ARCH_CPU_ARM_FAMILY) && \
+#elif (defined(OS_ANDROID) || defined(OS_LINUX)) && \
+    defined(ARCH_CPU_ARM_FAMILY) &&                 \
     defined(ARCH_CPU_32_BITS)  // #if defined(OS_WIN)
 
 using RegisterContext = mcontext_t;
diff --git a/base/profiler/stack_sampler_android.cc b/base/profiler/stack_sampler_android.cc
index 85b3626b..98d16a5 100644
--- a/base/profiler/stack_sampler_android.cc
+++ b/base/profiler/stack_sampler_android.cc
@@ -9,7 +9,7 @@
 #include "base/profiler/native_unwinder_android.h"
 #include "base/profiler/stack_copier_signal.h"
 #include "base/profiler/stack_sampler_impl.h"
-#include "base/profiler/thread_delegate_android.h"
+#include "base/profiler/thread_delegate_posix.h"
 #include "base/threading/platform_thread.h"
 
 namespace base {
@@ -20,7 +20,7 @@
     StackSamplerTestDelegate* test_delegate) {
   return std::make_unique<StackSamplerImpl>(
       std::make_unique<StackCopierSignal>(
-          std::make_unique<ThreadDelegateAndroid>(thread_id)),
+          std::make_unique<ThreadDelegatePosix>(thread_id)),
       std::make_unique<NativeUnwinderAndroid>(), module_cache, test_delegate);
 }
 
diff --git a/base/profiler/stack_sampler_ios.cc b/base/profiler/stack_sampler_ios.cc
new file mode 100644
index 0000000..c6b8433
--- /dev/null
+++ b/base/profiler/stack_sampler_ios.cc
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Placeholder functions for the StackSampler on iOS, which is not currently
+// supported.
+
+#include "base/profiler/stack_sampler.h"
+
+namespace base {
+
+// static
+std::unique_ptr<StackSampler> StackSampler::Create(
+    PlatformThreadId thread_id,
+    ModuleCache* module_cache,
+    StackSamplerTestDelegate* test_delegate) {
+  return nullptr;
+}
+
+// static
+size_t StackSampler::GetStackBufferSize() {
+  return 0;
+}
+
+}  // namespace base
diff --git a/base/profiler/thread_delegate_android.h b/base/profiler/thread_delegate_android.h
deleted file mode 100644
index 96e1e3c..0000000
--- a/base/profiler/thread_delegate_android.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_PROFILER_THREAD_DELEGATE_ANDROID_H_
-#define BASE_PROFILER_THREAD_DELEGATE_ANDROID_H_
-
-#include "base/base_export.h"
-#include "base/profiler/thread_delegate.h"
-#include "base/threading/platform_thread.h"
-
-namespace base {
-
-// Platform- and thread-specific implementation in support of stack sampling on
-// Android.
-//
-// TODO(https://crbug.com/988579): Implement this class.
-class BASE_EXPORT ThreadDelegateAndroid : public ThreadDelegate {
- public:
-  ThreadDelegateAndroid(PlatformThreadId thread_id);
-
-  ThreadDelegateAndroid(const ThreadDelegateAndroid&) = delete;
-  ThreadDelegateAndroid& operator=(const ThreadDelegateAndroid&) = delete;
-
-  // ThreadDelegate
-  uintptr_t GetStackBaseAddress() const override;
-  std::vector<uintptr_t*> GetRegistersToRewrite(
-      RegisterContext* thread_context) override;
-
- private:
-  const uintptr_t thread_stack_base_address_;
-};
-
-}  // namespace base
-
-#endif  // BASE_PROFILER_THREAD_DELEGATE_ANDROID_H_
diff --git a/base/profiler/thread_delegate_android.cc b/base/profiler/thread_delegate_posix.cc
similarity index 73%
rename from base/profiler/thread_delegate_android.cc
rename to base/profiler/thread_delegate_posix.cc
index 4711cf6..76934dc0 100644
--- a/base/profiler/thread_delegate_android.cc
+++ b/base/profiler/thread_delegate_posix.cc
@@ -4,17 +4,10 @@
 
 #include <pthread.h>
 
-#include "base/profiler/thread_delegate_android.h"
+#include "base/profiler/thread_delegate_posix.h"
 
 #include "build/build_config.h"
 
-// IMPORTANT NOTE: Some functions within this implementation are invoked while
-// the target thread is suspended so it must not do any allocation from the
-// heap, including indirectly via use of DCHECK/CHECK or other logging
-// statements. Otherwise this code can deadlock on heap locks acquired by the
-// target thread before it was suspended. These functions are commented with "NO
-// HEAP ALLOCATIONS".
-
 namespace base {
 
 namespace {
@@ -30,14 +23,14 @@
 
 }  // namespace
 
-ThreadDelegateAndroid::ThreadDelegateAndroid(PlatformThreadId thread_id)
+ThreadDelegatePosix::ThreadDelegatePosix(PlatformThreadId thread_id)
     : thread_stack_base_address_(GetThreadStackBaseAddress(thread_id)) {}
 
-uintptr_t ThreadDelegateAndroid::GetStackBaseAddress() const {
+uintptr_t ThreadDelegatePosix::GetStackBaseAddress() const {
   return thread_stack_base_address_;
 }
 
-std::vector<uintptr_t*> ThreadDelegateAndroid::GetRegistersToRewrite(
+std::vector<uintptr_t*> ThreadDelegatePosix::GetRegistersToRewrite(
     RegisterContext* thread_context) {
 #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
   return {
@@ -59,6 +52,7 @@
       // addresses of executable code, not addresses in the stack.
   };
 #else  // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS)
+  // Unimplemented for other architectures.
   return {};
 #endif
 }
diff --git a/base/profiler/thread_delegate_posix.h b/base/profiler/thread_delegate_posix.h
new file mode 100644
index 0000000..60830d21
--- /dev/null
+++ b/base/profiler/thread_delegate_posix.h
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_THREAD_DELEGATE_POSIX_H_
+#define BASE_PROFILER_THREAD_DELEGATE_POSIX_H_
+
+#include "base/base_export.h"
+#include "base/profiler/thread_delegate.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+
+// Platform- and thread-specific implementation in support of stack sampling on
+// POSIX.
+class BASE_EXPORT ThreadDelegatePosix : public ThreadDelegate {
+ public:
+  ThreadDelegatePosix(PlatformThreadId thread_id);
+
+  ThreadDelegatePosix(const ThreadDelegatePosix&) = delete;
+  ThreadDelegatePosix& operator=(const ThreadDelegatePosix&) = delete;
+
+  // ThreadDelegate
+  uintptr_t GetStackBaseAddress() const override;
+  std::vector<uintptr_t*> GetRegistersToRewrite(
+      RegisterContext* thread_context) override;
+
+ private:
+  const uintptr_t thread_stack_base_address_;
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_THREAD_DELEGATE_POSIX_H_
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
index 50820a9..3b1de80 100644
--- a/base/test/test_suite.cc
+++ b/base/test/test_suite.cc
@@ -37,7 +37,6 @@
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_switches.h"
 #include "base/test/test_timeouts.h"
-#include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -189,31 +188,6 @@
 };
 #endif  // !defined(OS_IOS)
 
-class CheckThreadPriority : public testing::EmptyTestEventListener {
- public:
-  CheckThreadPriority(bool check_thread_priority_at_test_end)
-      : check_thread_priority_at_test_end_(check_thread_priority_at_test_end) {
-    CHECK_EQ(base::PlatformThread::GetCurrentThreadPriority(),
-             base::ThreadPriority::NORMAL);
-  }
-
-  void OnTestStart(const testing::TestInfo& test) override {
-    EXPECT_EQ(base::PlatformThread::GetCurrentThreadPriority(),
-              base::ThreadPriority::NORMAL);
-  }
-  void OnTestEnd(const testing::TestInfo& test) override {
-    if (check_thread_priority_at_test_end_) {
-      EXPECT_EQ(base::PlatformThread::GetCurrentThreadPriority(),
-                base::ThreadPriority::NORMAL);
-    }
-  }
-
- private:
-  const bool check_thread_priority_at_test_end_;
-
-  DISALLOW_COPY_AND_ASSIGN(CheckThreadPriority);
-};
-
 const std::string& GetProfileName() {
   static const NoDestructor<std::string> profile_name([]() {
     const CommandLine& command_line = *CommandLine::ForCurrentProcess();
@@ -402,14 +376,9 @@
   check_for_leaked_globals_ = false;
 }
 
-void TestSuite::DisableCheckForThreadAndProcessPriority() {
+void TestSuite::DisableCheckForProcessPriority() {
   DCHECK(!is_initialized_);
-  check_for_thread_and_process_priority_ = false;
-}
-
-void TestSuite::DisableCheckForThreadPriorityAtTestEnd() {
-  DCHECK(!is_initialized_);
-  check_for_thread_priority_at_test_end_ = false;
+  check_for_process_priority_ = false;
 }
 
 void TestSuite::UnitTestAssertHandler(const char* file,
@@ -598,13 +567,10 @@
   listeners.Append(new ResetCommandLineBetweenTests);
   if (check_for_leaked_globals_)
     listeners.Append(new CheckForLeakedGlobals);
-  if (check_for_thread_and_process_priority_) {
-    listeners.Append(
-        new CheckThreadPriority(check_for_thread_priority_at_test_end_));
 #if !defined(OS_IOS)
+  if (check_for_process_priority_)
     listeners.Append(new CheckProcessPriority);
 #endif
-  }
 
   AddTestLauncherResultPrinter();
 
diff --git a/base/test/test_suite.h b/base/test/test_suite.h
index 4d4d638..6e1e750 100644
--- a/base/test/test_suite.h
+++ b/base/test/test_suite.h
@@ -43,15 +43,8 @@
 
   int Run();
 
-  // Disables checks for thread and process priority at the beginning and end of
-  // each test. Most tests should not use this.
-  void DisableCheckForThreadAndProcessPriority();
-
-  // Disables checks for thread priority at the end of each test (still checks
-  // at the beginning of each test). This should be used for tests that run in
-  // their own process and should start with normal priorities but are allowed
-  // to end with different priorities.
-  void DisableCheckForThreadPriorityAtTestEnd();
+  // Disables checks for process priority. Most tests should not use this.
+  void DisableCheckForProcessPriority();
 
   // Disables checks for certain global objects being leaked across tests.
   void DisableCheckForLeakedGlobals();
@@ -100,8 +93,7 @@
   std::unique_ptr<logging::ScopedLogAssertHandler> assert_handler_;
 
   bool check_for_leaked_globals_ = true;
-  bool check_for_thread_and_process_priority_ = true;
-  bool check_for_thread_priority_at_test_end_ = true;
+  bool check_for_process_priority_ = true;
 
   bool is_initialized_ = false;
 
diff --git a/build/android/gyp/create_bundle_wrapper_script.pydeps b/build/android/gyp/create_bundle_wrapper_script.pydeps
index a83e696..d8825145 100644
--- a/build/android/gyp/create_bundle_wrapper_script.pydeps
+++ b/build/android/gyp/create_bundle_wrapper_script.pydeps
@@ -87,6 +87,7 @@
 ../../gn_helpers.py
 ../adb_command_line.py
 ../apk_operations.py
+../convert_dex_profile.py
 ../devil_chromium.py
 ../incremental_install/__init__.py
 ../incremental_install/installer.py
@@ -101,7 +102,9 @@
 ../pylib/utils/time_profile.py
 bundletool.py
 create_bundle_wrapper_script.py
+dex.py
 util/__init__.py
 util/build_utils.py
 util/md5_check.py
 util/resource_utils.py
+util/zipalign.py
diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py
index 043a08a..a0c6531 100755
--- a/build/android/gyp/dex.py
+++ b/build/android/gyp/dex.py
@@ -45,10 +45,6 @@
       '--incremental-dir',
       help='Path of directory to put intermediate dex files.')
   parser.add_argument(
-      '--merge-incrementals',
-      action='store_true',
-      help='Combine all per-class .dex files into a single classes.dex')
-  parser.add_argument(
       '--main-dex-list-path',
       help='File containing a list of the classes to include in the main dex.')
   parser.add_argument(
@@ -272,22 +268,22 @@
   return final_output
 
 
-def _CreateFinalDex(options, d8_inputs, tmp_dir, dex_cmd):
+def _CreateFinalDex(d8_inputs, output, tmp_dir, dex_cmd, options=None):
   tmp_dex_output = os.path.join(tmp_dir, 'tmp_dex_output.zip')
-  if (options.merge_incrementals or options.output.endswith('.dex')
+  if (output.endswith('.dex')
       or not all(f.endswith('.dex') for f in d8_inputs)):
-    if options.multi_dex and options.main_dex_list_path:
+    if options and options.main_dex_list_path:
       # Provides a list of classes that should be included in the main dex file.
       dex_cmd = dex_cmd + ['--main-dex-list', options.main_dex_list_path]
 
     tmp_dex_dir = os.path.join(tmp_dir, 'tmp_dex_dir')
     os.mkdir(tmp_dex_dir)
     _RunD8(dex_cmd, d8_inputs, tmp_dex_dir)
-    logging.info('Performed dex merging')
+    logging.debug('Performed dex merging')
 
     dex_files = [os.path.join(tmp_dex_dir, f) for f in os.listdir(tmp_dex_dir)]
 
-    if options.output.endswith('.dex'):
+    if output.endswith('.dex'):
       if len(dex_files) > 1:
         raise Exception('%d files created, expected 1' % len(dex_files))
       tmp_dex_output = dex_files[0]
@@ -296,13 +292,13 @@
   else:
     # Skip dexmerger. Just put all incrementals into the .jar individually.
     _ZipAligned(sorted(d8_inputs), tmp_dex_output)
-    logging.info('Quick-zipped %d files', len(d8_inputs))
+    logging.debug('Quick-zipped %d files', len(d8_inputs))
 
-  if options.dexlayout_profile:
+  if options and options.dexlayout_profile:
     tmp_dex_output = _PerformDexlayout(tmp_dir, tmp_dex_output, options)
 
   # The dex file is complete and can be moved out of tmp_dir.
-  shutil.move(tmp_dex_output, options.output)
+  shutil.move(tmp_dex_output, output)
 
 
 def _IntermediateDexFilePathsFromInputJars(class_inputs, incremental_dir):
@@ -354,18 +350,18 @@
     changes = None
   class_files = _ExtractClassFiles(changes, tmp_extract_dir,
                                    options.class_inputs)
-  logging.info('Extracted class files: %d', len(class_files))
+  logging.debug('Extracted class files: %d', len(class_files))
 
   # If the only change is deleting a file, class_files will be empty.
   if class_files:
     # Dex necessary classes into intermediate dex files.
     dex_cmd = dex_cmd + ['--intermediate', '--file-per-class']
     _RunD8(dex_cmd, class_files, options.incremental_dir)
-    logging.info('Dexed class files.')
+    logging.debug('Dexed class files.')
 
 
 def _OnStaleMd5(changes, options, final_dex_inputs, dex_cmd):
-  logging.info('_OnStaleMd5')
+  logging.debug('_OnStaleMd5')
   with build_utils.TempDir() as tmp_dir:
     if options.incremental_dir:
       # Create directory for all intermediate dex files.
@@ -373,11 +369,23 @@
         os.makedirs(options.incremental_dir)
 
       _DeleteStaleIncrementalDexFiles(options.incremental_dir, final_dex_inputs)
-      logging.info('Stale files deleted')
+      logging.debug('Stale files deleted')
       _CreateIntermediateDexFiles(changes, options, tmp_dir, dex_cmd)
 
-    _CreateFinalDex(options, final_dex_inputs, tmp_dir, dex_cmd)
-  logging.info('Dex finished for: %s', options.output)
+    _CreateFinalDex(
+        final_dex_inputs, options.output, tmp_dir, dex_cmd, options=options)
+  logging.debug('Dex finished for: %s', options.output)
+
+
+def MergeDexForIncrementalInstall(r8_jar_path, src_paths, dest_dex_jar):
+  dex_cmd = [
+      build_utils.JAVA_PATH,
+      '-jar',
+      r8_jar_path,
+      'd8',
+  ]
+  with build_utils.TempDir() as tmp_dir:
+    _CreateFinalDex(src_paths, dest_dex_jar, tmp_dir, dex_cmd)
 
 
 def main(args):
@@ -400,8 +408,10 @@
     final_dex_inputs = _IntermediateDexFilePathsFromInputJars(
         options.class_inputs, options.incremental_dir)
     output_paths += final_dex_inputs
+    track_subpaths_whitelist = options.class_inputs
   else:
     final_dex_inputs = list(options.class_inputs)
+    track_subpaths_whitelist = None
   final_dex_inputs += options.dex_inputs
 
   dex_cmd = [
@@ -421,7 +431,7 @@
       input_paths=input_paths,
       input_strings=dex_cmd + [bool(options.incremental_dir)],
       pass_changes=True,
-      track_subpaths_whitelist=options.class_inputs)
+      track_subpaths_whitelist=track_subpaths_whitelist)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index b98bd4f..c9a95da 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -991,8 +991,7 @@
 
   jar_path_options = ['jar_path', 'unprocessed_jar_path', 'interface_jar_path']
   required_options_map = {
-      'android_apk': ['build_config', 'dex_path', 'final_dex_path'] + \
-          jar_path_options,
+      'android_apk': ['build_config', 'dex_path'] + jar_path_options,
       'android_app_bundle_module': ['build_config', 'dex_path',
           'final_dex_path', 'res_size_info'] + jar_path_options,
       'android_assets': ['build_config'],
diff --git a/build/android/incremental_install/README.md b/build/android/incremental_install/README.md
index 0916e07..53a6d20 100644
--- a/build/android/incremental_install/README.md
+++ b/build/android/incremental_install/README.md
@@ -7,31 +7,19 @@
 
 ## Building
 
-**Option 1:** Add the gn arg:
+Add the gn arg:
 
-    incremental_apk_by_default = true
+    incremental_install = true
 
-This causes all apks to be built as incremental (except for blacklisted ones).
-
-**Option 2:** Add `_incremental` to the apk target name. E.g.:
-
-    ninja -C out/Debug chrome_public_apk_incremental
-    ninja -C out/Debug chrome_public_test_apk_incremental
+This causes all apks to be built as incremental except for blacklisted ones.
 
 ## Running
 
-It is not enough to `adb install` them. You must use a generated wrapper script:
+It is not enough to `adb install` them. You must use the generated wrapper
+script:
 
-    out/Debug/bin/install_chrome_public_apk_incremental
-    out/Debug/bin/run_chrome_public_test_apk_incremental  # Automatically sets --fast-local-dev
-
-## Caveats
-
-Isolated processes (on L+) are incompatible with incremental install. As a
-work-around, you can disable isolated processes only for incremental apks using
-gn arg:
-
-    disable_incremental_isolated_processes = true
+    out/Debug/bin/your_apk run
+    out/Debug/bin/run_chrome_public_test_apk  # Automatically sets --fast-local-dev
 
 # How it Works
 
@@ -59,7 +47,16 @@
  * The first time you run an incremental .apk, the `DexOpt` needs to run on all
    .dex files. This step is normally done during `adb install`, but is done on
    start-up for incremental apks.
-   * DexOpt results are cached, so subsequent runs are much faster
+   * DexOpt results are cached, so subsequent runs are faster.
+   * The slowdown varies significantly based on the Android version. Android O+
+     has almost no visible slow-down.
+
+Caveats:
+ * Isolated processes (on L+) are incompatible with incremental install. As a
+   work-around, isolated processes are disabled when building incremental apks.
+ * Android resources, assets, and `loadable_modules` are not side-loaded (they
+   remain in the apk), so builds & installs that modify any of these are not as
+   fast as those that modify only .java / .cc.
 
 ## The Code
 
diff --git a/build/android/incremental_install/installer.py b/build/android/incremental_install/installer.py
index eec18e9..76696eb 100755
--- a/build/android/incremental_install/installer.py
+++ b/build/android/incremental_install/installer.py
@@ -7,6 +7,8 @@
 """Install *_incremental.apk targets as well as their dependent files."""
 
 import argparse
+import collections
+import functools
 import glob
 import json
 import logging
@@ -14,14 +16,12 @@
 import posixpath
 import shutil
 import sys
-import zipfile
 
 sys.path.append(
     os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)))
 import devil_chromium
 from devil.android import apk_helper
 from devil.android import device_utils
-from devil.android.sdk import version_codes
 from devil.utils import reraiser_thread
 from devil.utils import run_tests_helper
 from pylib import constants
@@ -29,24 +29,20 @@
 
 prev_sys_path = list(sys.path)
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, 'gyp'))
+import dex
 from util import build_utils
 sys.path = prev_sys_path
 
 
+_R8_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'r8', 'lib',
+                        'r8.jar')
+
+
 def _DeviceCachePath(device):
   file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial()
   return os.path.join(constants.GetOutDirectory(), file_name)
 
 
-def _TransformDexPaths(paths):
-  """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"]."""
-  if len(paths) == 1:
-    return [os.path.basename(paths[0])]
-
-  prefix_len = len(os.path.commonprefix(paths))
-  return [p[prefix_len:].replace(os.sep, '.') for p in paths]
-
-
 def _Execute(concurrently, *funcs):
   """Calls all functions in |funcs| concurrently or in sequence."""
   timer = time_profile.TimeProfile()
@@ -64,10 +60,63 @@
   return '/data/local/tmp/incremental-app-%s' % package
 
 
-def _HasClasses(jar_path):
-  """Returns whether the given jar contains classes.dex."""
-  with zipfile.ZipFile(jar_path) as jar:
-    return 'classes.dex' in jar.namelist()
+def _IsStale(src_paths, dest):
+  """Returns if |dest| is older than any of |src_paths|, or missing."""
+  if not os.path.exists(dest):
+    return True
+  dest_time = os.path.getmtime(dest)
+  for path in src_paths:
+    if os.path.getmtime(path) > dest_time:
+      return True
+  return False
+
+
+def _AllocateDexShards(dex_files):
+  """Divides input dex files into buckets."""
+  # Goals:
+  # * Make shards small enough that they are fast to merge.
+  # * Minimize the number of shards so they load quickly on device.
+  # * Partition files into shards such that a change in one file results in only
+  #   one shard having to be re-created.
+  shards = collections.defaultdict(list)
+  # As of Oct 2019, 10 shards results in a min/max size of 582K/2.6M.
+  NUM_CORE_SHARDS = 10
+  # As of Oct 2019, 17 dex files are larger than 1M.
+  SHARD_THRESHOLD = 2**20
+  for src_path in dex_files:
+    if os.path.getsize(src_path) >= SHARD_THRESHOLD:
+      # Use the path as the name rather than an incrementing number to ensure
+      # that it shards to the same name every time.
+      name = os.path.relpath(src_path, constants.GetOutDirectory()).replace(
+          os.sep, '.')
+      shards[name].append(src_path)
+    else:
+      name = 'shard{}.dex.jar'.format(hash(src_path) % NUM_CORE_SHARDS)
+      shards[name].append(src_path)
+  logging.info('Sharding %d dex files into %d buckets', len(dex_files),
+               len(shards))
+  return shards
+
+
+def _CreateDexFiles(shards, dex_staging_dir, use_concurrency):
+  """Creates dex files within |dex_staging_dir| defined by |shards|."""
+  tasks = []
+  for name, src_paths in shards.iteritems():
+    dest_path = os.path.join(dex_staging_dir, name)
+    if _IsStale(src_paths, dest_path):
+      tasks.append(
+          functools.partial(dex.MergeDexForIncrementalInstall, _R8_PATH,
+                            src_paths, dest_path))
+
+  # TODO(agrieve): It would be more performant to write a custom d8.jar
+  #     wrapper in java that would process these in bulk, rather than spinning
+  #     up a new process for each one.
+  _Execute(use_concurrency, *tasks)
+
+  # Remove any stale shards.
+  for name in os.listdir(dex_staging_dir):
+    if name not in shards:
+      os.unlink(os.path.join(dex_staging_dir, name))
 
 
 def Uninstall(device, package, enable_device_cache=False):
@@ -109,6 +158,7 @@
   main_timer = time_profile.TimeProfile()
   install_timer = time_profile.TimeProfile()
   push_native_timer = time_profile.TimeProfile()
+  merge_dex_timer = time_profile.TimeProfile()
   push_dex_timer = time_profile.TimeProfile()
 
   def fix_path(p):
@@ -123,6 +173,10 @@
 
   apk_package = apk.GetPackageName()
   device_incremental_dir = _GetDeviceIncrementalDir(apk_package)
+  dex_staging_dir = os.path.join(constants.GetOutDirectory(),
+                                 'incremental-install',
+                                 install_dict['apk_path'])
+  device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
 
   # Install .apk(s) if any of them have changed.
   def do_install():
@@ -145,46 +199,37 @@
 
   # Push .so and .dex files to the device (if they have changed).
   def do_push_files():
-    push_native_timer.Start()
-    if native_libs:
-      with build_utils.TempDir() as temp_dir:
-        device_lib_dir = posixpath.join(device_incremental_dir, 'lib')
-        for path in native_libs:
-          # Note: Can't use symlinks as they don't work when
-          # "adb push parent_dir" is used (like we do here).
-          shutil.copy(path, os.path.join(temp_dir, os.path.basename(path)))
-        device.PushChangedFiles([(temp_dir, device_lib_dir)],
-                                delete_device_stale=True)
-    push_native_timer.Stop(log=False)
 
-    push_dex_timer.Start()
-    if dex_files:
-      # Put all .dex files to be pushed into a temporary directory so that we
-      # can use delete_device_stale=True.
-      with build_utils.TempDir() as temp_dir:
-        device_dex_dir = posixpath.join(device_incremental_dir, 'dex')
-        # Ensure no two files have the same name.
-        transformed_names = _TransformDexPaths(dex_files)
-        for src_path, dest_name in zip(dex_files, transformed_names):
-          # Binary targets with no extra classes create .dex.jar without a
-          # classes.dex (which Android chokes on).
-          if _HasClasses(src_path):
-            shutil.copy(src_path, os.path.join(temp_dir, dest_name))
-        device.PushChangedFiles([(temp_dir, device_dex_dir)],
-                                delete_device_stale=True)
-    push_dex_timer.Stop(log=False)
+    def do_push_native():
+      push_native_timer.Start()
+      if native_libs:
+        with build_utils.TempDir() as temp_dir:
+          device_lib_dir = posixpath.join(device_incremental_dir, 'lib')
+          for path in native_libs:
+            # Note: Can't use symlinks as they don't work when
+            # "adb push parent_dir" is used (like we do here).
+            shutil.copy(path, os.path.join(temp_dir, os.path.basename(path)))
+          device.PushChangedFiles([(temp_dir, device_lib_dir)],
+                                  delete_device_stale=True)
+      push_native_timer.Stop(log=False)
 
-  def check_selinux():
-    # Marshmallow has no filesystem access whatsoever. It might be possible to
-    # get things working on Lollipop, but attempts so far have failed.
-    # http://crbug.com/558818
-    has_selinux = device.build_version_sdk >= version_codes.LOLLIPOP
-    if has_selinux and apk.HasIsolatedProcesses():
-      raise Exception('Cannot use incremental installs on Android L+ without '
-                      'first disabling isolated processes.\n'
-                      'To do so, use GN arg:\n'
-                      '    disable_incremental_isolated_processes=true')
+    def do_merge_dex():
+      merge_dex_timer.Start()
+      shards = _AllocateDexShards(dex_files)
+      build_utils.MakeDirectory(dex_staging_dir)
+      _CreateDexFiles(shards, dex_staging_dir, use_concurrency)
+      merge_dex_timer.Stop(log=False)
 
+    def do_push_dex():
+      push_dex_timer.Start()
+      device.PushChangedFiles([(dex_staging_dir, device_dex_dir)],
+                              delete_device_stale=True)
+      push_dex_timer.Stop(log=False)
+
+    _Execute(use_concurrency, do_push_native, do_merge_dex)
+    do_push_dex()
+
+  def check_device_configured():
     target_sdk_version = int(apk.GetTargetSdkVersion())
     # Beta Q builds apply whitelist to targetSdk=28 as well.
     if target_sdk_version >= 28 and device.build_version_sdk >= 29:
@@ -243,18 +288,18 @@
   # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
   # been designed for multi-threading. Enabling only because this is a
   # developer-only tool.
-  setup_timer = _Execute(
-      use_concurrency, create_lock_files, restore_cache, check_selinux)
+  setup_timer = _Execute(use_concurrency, create_lock_files, restore_cache,
+                         check_device_configured)
 
   _Execute(use_concurrency, do_install, do_push_files)
 
   finalize_timer = _Execute(use_concurrency, release_installer_lock, save_cache)
 
   logging.info(
-      'Install of %s took %s seconds '
-      '(setup=%s, install=%s, libs=%s, dex=%s, finalize=%s)',
-      os.path.basename(apk.path), main_timer.GetDelta(), setup_timer.GetDelta(),
-      install_timer.GetDelta(), push_native_timer.GetDelta(),
+      'Install of %s took %s seconds (setup=%s, install=%s, lib_push=%s, '
+      'dex_merge=%s dex_push=%s, finalize=%s)', os.path.basename(apk.path),
+      main_timer.GetDelta(), setup_timer.GetDelta(), install_timer.GetDelta(),
+      push_native_timer.GetDelta(), merge_dex_timer.GetDelta(),
       push_dex_timer.GetDelta(), finalize_timer.GetDelta())
   if show_proguard_warning:
     logging.warning('Target had proguard enabled, but incremental install uses '
diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
index 10e438f..3b619ed0 100644
--- a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
+++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
@@ -18,6 +18,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Provides the ability to add native libraries and .dex files to an existing class loader.
@@ -164,9 +165,8 @@
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                 // TODO: Work around this issue by using APK splits to install each dex / lib.
                 throw new RuntimeException("Incremental install does not work on Android M+ "
-                        + "with isolated processes. Use the gn arg:\n"
-                        + "    disable_incremental_isolated_processes=true\n"
-                        + "and try again.");
+                        + "with isolated processes. Build system should have removed this. "
+                        + "Please file a bug.");
             }
             // Other processes: Waits for primary process to finish copying.
             LockFile.waitForRuntimeLock(lockFile, 10 * 1000);
@@ -203,18 +203,28 @@
     }
 
     private static void copyChangedFiles(File srcDir, File dstDir) throws IOException {
-        // No need to delete stale libs since libraries are loaded explicitly.
-        int numNotChanged = 0;
-        for (File f : srcDir.listFiles()) {
+        int numUpdated = 0;
+        File[] srcFiles = srcDir.listFiles();
+        for (File f : srcFiles) {
             // Note: Tried using hardlinks, but resulted in EACCES exceptions.
             File dest = new File(dstDir, f.getName());
-            if (!copyIfModified(f, dest)) {
-                numNotChanged++;
+            if (copyIfModified(f, dest)) {
+                numUpdated++;
             }
         }
-        if (numNotChanged > 0) {
-            Log.i(TAG, numNotChanged + " libs already up to date.");
+        // Delete stale files.
+        int numDeleted = 0;
+        for (File f : dstDir.listFiles()) {
+            File src = new File(srcDir, f.getName());
+            if (!src.exists()) {
+                numDeleted++;
+                f.delete();
+            }
         }
+        String msg = String.format(Locale.US,
+                "copyChangedFiles: %d of %d updated. %d stale files removed.", numUpdated,
+                srcFiles.length, numDeleted);
+        Log.i(TAG, msg);
     }
 
     @SuppressLint("SetWorldReadable")
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index e4b118c..7228436 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -127,10 +127,13 @@
 ../gn_helpers.py
 ../util/lib/common/chrome_test_server_spawner.py
 ../util/lib/common/unittest_util.py
+convert_dex_profile.py
 devil_chromium.py
+gyp/dex.py
 gyp/util/__init__.py
 gyp/util/build_utils.py
 gyp/util/md5_check.py
+gyp/util/zipalign.py
 incremental_install/__init__.py
 incremental_install/installer.py
 pylib/__init__.py
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 6ec9c6d..c01bdcba 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -182,14 +182,9 @@
     # Set to false to disable the Errorprone compiler
     use_errorprone_java_compiler = true
 
-    # Disables process isolation when building _incremental targets.
-    # Required for Android M+ due to SELinux policies (stronger sandboxing).
-    disable_incremental_isolated_processes = false
-
     # Build incremental targets whenever possible.
-    # Ex. with this arg set to true, the chrome_public_apk target result in
-    # chrome_public_apk_incremental being built.
-    incremental_apk_by_default = false
+    # See //build/android/incremental_install/README.md for more details.
+    incremental_install = false
 
     # When true, updates all android_aar_prebuilt() .info files during gn gen.
     # Refer to android_aar_prebuilt() for more details.
@@ -217,6 +212,9 @@
     trichrome_shared_assets = android_sdk_release == "q"
   }
 
+  # TODO(agrieve): Remove once unused downstream.
+  incremental_apk_by_default = incremental_install
+
   if (notouch_build && defined(extra_keymappings)) {
     keycode_conversion_data_android_path = extra_keymappings
   }
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index f482ef1c..7ba548f 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -407,14 +407,11 @@
     }
 
     if (defined(invoker.apk_path)) {
-      _rebased_apk_path = rebase_path(invoker.apk_path, root_build_dir)
-      _incremental_allowed =
-          defined(invoker.incremental_allowed) && invoker.incremental_allowed
-
       # TODO(tiborg): Remove APK path from build config and use
       # install_artifacts from metadata instead.
+      _rebased_apk_path = rebase_path(invoker.apk_path, root_build_dir)
       args += [ "--apk-path=$_rebased_apk_path" ]
-      if (_incremental_allowed) {
+      if (defined(invoker.incremental_apk_path)) {
         _rebased_incremental_apk_path =
             rebase_path(invoker.incremental_apk_path, root_build_dir)
         _rebased_incremental_install_json_path =
@@ -528,7 +525,17 @@
 
 template("generate_android_wrapper") {
   generate_wrapper(target_name) {
-    forward_variables_from(invoker, "*")
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "testonly",
+                             "visibility",
+                           ])
+    forward_variables_from(invoker,
+                           [
+                             "testonly",
+                             "visibility",
+                           ])
     generator_script = "//build/android/gyp/generate_android_wrapper.py"
     sources = [
       "//build/android/gyp/util/build_utils.py",
@@ -544,8 +551,7 @@
   testonly = true
   _test_name = invoker.test_name
   _test_type = invoker.test_type
-  _incremental_install =
-      defined(invoker.incremental_install) && invoker.incremental_install
+  _incremental_apk = defined(invoker.incremental_apk) && invoker.incremental_apk
 
   _runtime_deps =
       !defined(invoker.ignore_all_data_deps) || !invoker.ignore_all_data_deps
@@ -592,8 +598,19 @@
   }
 
   generate_android_wrapper(target_name) {
-    testonly = true
     wrapper_script = "$root_build_dir/bin/run_${_test_name}"
+    forward_variables_from(invoker,
+                           [
+                             "data_deps",
+                             "deps",
+                           ])
+    if (!defined(deps)) {
+      deps = []
+    }
+
+    if (!defined(data_deps)) {
+      data_deps = []
+    }
 
     if (defined(android_test_runner_script)) {
       executable = android_test_runner_script
@@ -673,7 +690,7 @@
       }
     } else if (_test_type == "instrumentation") {
       _test_apk = "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:apk_path))"
-      if (_incremental_install) {
+      if (_incremental_apk) {
         _test_apk = "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:incremental_apk_path))"
       }
       _rebased_test_jar = rebase_path(invoker.test_jar, root_build_dir)
@@ -684,7 +701,7 @@
         "@WrappedPath(${_rebased_test_jar})",
       ]
       if (defined(invoker.apk_under_test)) {
-        if (_incremental_install) {
+        if (_incremental_apk) {
           deps += [ "${invoker.apk_under_test}$build_config_target_suffix" ]
           _apk_under_test_build_config =
               get_label_info(invoker.apk_under_test, "target_gen_dir") + "/" +
@@ -773,7 +790,7 @@
     if (defined(invoker.shard_timeout)) {
       executable_args += [ "--shard-timeout=${invoker.shard_timeout}" ]
     }
-    if (_incremental_install) {
+    if (_incremental_apk) {
       executable_args += [
         "--test-apk-incremental-install-json",
         "@WrappedPath(@FileArg($_rebased_apk_build_config:deps_info:incremental_install_json_path))",
@@ -1132,6 +1149,7 @@
 
   # Variables
   #   apply_mapping: The path to the ProGuard mapping file to apply.
+  #   disable_incremental: Disable incremental dexing.
   template("dex") {
     assert(defined(invoker.output))
 
@@ -1359,17 +1377,14 @@
 
         if (_proguard_enabled) {
           deps += [ ":${_proguard_target_name}" ]
-        } else if (enable_incremental_d8) {
+        } else if (enable_incremental_d8 &&
+                   !(defined(invoker.disable_incremental) &&
+                     invoker.disable_incremental)) {
           # Don't use incremental dexing for ProGuarded inputs as a precaution.
           args += [
             "--incremental-dir",
             rebase_path("$target_out_dir/$target_name", root_build_dir),
           ]
-          if (is_java_debug) {
-            # The performance of incremental install is unbearable if each
-            # lib.dex.jar file has multiple classes.dex files in it.
-            args += [ "--merge-incrementals" ]
-          }
         }
 
         if (_enable_multidex) {
@@ -2683,167 +2698,6 @@
     }
   }
 
-  # Packages resources, assets, dex, and native libraries into an apk. Signs and
-  # zipaligns the apk.
-  template("create_apk") {
-    set_sources_assignment_filter([])
-    forward_variables_from(invoker, [ "testonly" ])
-
-    _final_apk_path = invoker.apk_path
-
-    if (defined(invoker.dex_path)) {
-      _dex_path = invoker.dex_path
-    }
-    _load_library_from_apk = invoker.load_library_from_apk
-    assert(_load_library_from_apk || true)
-
-    _deps = []
-    if (defined(invoker.deps)) {
-      _deps = invoker.deps
-    }
-    _incremental_deps = []
-    if (defined(invoker.incremental_deps)) {
-      _incremental_deps = invoker.incremental_deps
-    }
-    _native_libs = []
-    if (defined(invoker.native_libs)) {
-      _native_libs = invoker.native_libs
-    }
-    _native_libs_even_when_incremental = []
-    if (defined(invoker.native_libs_even_when_incremental) &&
-        invoker.native_libs_even_when_incremental != []) {
-      _native_libs_even_when_incremental =
-          invoker.native_libs_even_when_incremental
-    }
-
-    _shared_resources =
-        defined(invoker.shared_resources) && invoker.shared_resources
-    assert(_shared_resources || true)  # Mark as used.
-
-    _keystore_path = invoker.keystore_path
-    _keystore_name = invoker.keystore_name
-    _keystore_password = invoker.keystore_password
-
-    package_apk(target_name) {
-      forward_variables_from(invoker,
-                             [
-                               "apk_name",
-                               "assets_build_config",
-                               "native_lib_placeholders",
-                               "native_libs_filearg",
-                               "secondary_native_lib_placeholders",
-                               "secondary_abi_native_libs_filearg",
-                               "secondary_abi_loadable_modules",
-                               "uncompress_dex",
-                               "uncompress_shared_libraries",
-                               "write_asset_list",
-                             ])
-      if (!defined(uncompress_shared_libraries)) {
-        uncompress_shared_libraries = _load_library_from_apk
-      }
-      if (defined(invoker.optimized_resources_path)) {
-        packaged_resources_path = invoker.optimized_resources_path
-        not_needed(invoker, [ "packaged_resources_path" ])
-      } else {
-        packaged_resources_path = invoker.packaged_resources_path
-      }
-      deps = _deps
-      native_libs = _native_libs + _native_libs_even_when_incremental
-      keystore_path = _keystore_path
-      keystore_name = _keystore_name
-      keystore_password = _keystore_password
-
-      if (defined(_dex_path)) {
-        dex_path = _dex_path
-      }
-
-      output_apk_path = _final_apk_path
-    }
-
-    _incremental_allowed =
-        defined(invoker.incremental_allowed) && invoker.incremental_allowed
-    if (_incremental_allowed) {
-      _android_manifest = invoker.android_manifest
-      _base_path = invoker.base_path
-
-      _incremental_final_apk_path_helper =
-          process_file_template(
-              [ _final_apk_path ],
-              "{{source_dir}}/{{source_name_part}}_incremental.apk")
-      _incremental_final_apk_path = _incremental_final_apk_path_helper[0]
-
-      _incremental_compiled_resources_path = "${_base_path}_incremental.ap_"
-      _incremental_compile_resources_target_name =
-          "${target_name}_incremental__compile_resources"
-
-      _rebased_build_config =
-          rebase_path(invoker.assets_build_config, root_build_dir)
-
-      action_with_pydeps(_incremental_compile_resources_target_name) {
-        deps = _incremental_deps
-        script =
-            "//build/android/incremental_install/generate_android_manifest.py"
-        inputs = [
-          _android_manifest,
-          invoker.assets_build_config,
-          invoker.packaged_resources_path,
-        ]
-        outputs = [
-          _incremental_compiled_resources_path,
-        ]
-
-        args = [
-          "--src-manifest",
-          rebase_path(_android_manifest, root_build_dir),
-          "--in-apk",
-          rebase_path(invoker.packaged_resources_path, root_build_dir),
-          "--out-apk",
-          rebase_path(_incremental_compiled_resources_path, root_build_dir),
-          "--aapt2-path",
-          rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir),
-          "--android-sdk-jars=@FileArg($_rebased_build_config:android:sdk_jars)",
-        ]
-        if (disable_incremental_isolated_processes) {
-          args += [ "--disable-isolated-processes" ]
-        }
-      }
-
-      package_apk("${target_name}_incremental") {
-        forward_variables_from(invoker,
-                               [
-                                 "assets_build_config",
-                                 "secondary_abi_loadable_modules",
-                                 "uncompress_shared_libraries",
-                               ])
-        _dex_target = "//build/android/incremental_install:bootstrap_java__dex"
-        deps = _incremental_deps + [
-                 ":${_incremental_compile_resources_target_name}",
-                 _dex_target,
-               ]
-
-        if (defined(_dex_path)) {
-          dex_path =
-              get_label_info(_dex_target, "target_out_dir") + "/bootstrap.dex"
-        }
-
-        native_libs = _native_libs_even_when_incremental
-        keystore_path = _keystore_path
-        keystore_name = _keystore_name
-        keystore_password = _keystore_password
-
-        # http://crbug.com/384638
-        _has_native_libs =
-            defined(invoker.native_libs_filearg) || _native_libs != []
-        if (_has_native_libs && _native_libs_even_when_incremental == []) {
-          native_lib_placeholders = [ "libfix.crbug.384638.so" ]
-        }
-
-        output_apk_path = _incremental_final_apk_path
-        packaged_resources_path = _incremental_compiled_resources_path
-      }
-    }
-  }
-
   # Compile Java source files into a .jar file, potentially using an
   # annotation processor, and/or the errorprone compiler.
   #
@@ -3167,12 +3021,8 @@
   #    android_manifest.
   #  apk_under_test: For 'android_apk' targets used to test other APKs,
   #    this is the target name of APK being tested.
-  #  incremental_allowed: Optional (default false). True to allow the
-  #    generation of incremental APKs ('android_apk' targets only).
-  #  incremental_apk_path: If incremental_allowed, path to the incremental
-  #    output APK.
-  #  incremental_install_json_path: If incremental_allowed, path to the output
-  #    incremental install json configuration file.
+  #  incremental_apk_path: Path to the incremental APK.
+  #  incremental_install_json_path: Path to the incremental install json.
   #  native_lib_placeholders: Optional. List of placeholder filenames to add to
   #    the APK.
   #  proguard_mapping_path: Path to .mapping file produced from ProGuard step.
@@ -3421,7 +3271,6 @@
                                [
                                  "apk_path",
                                  "apk_under_test",
-                                 "incremental_allowed",
                                  "incremental_apk_path",
                                  "incremental_install_json_path",
                                ])
@@ -3654,6 +3503,10 @@
         if (defined(_dex_path)) {
           dex("${target_name}__dex") {
             input_class_jars = [ _final_jar_path ]
+
+            # There's no value in per-class dexing prebuilts since they never
+            # change just one class at a time.
+            disable_incremental = _is_prebuilt
             output = _dex_path
             deps = [
               ":$_process_prebuilt_target_name",
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index e453020..831baa0 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2006,7 +2006,7 @@
   #     from |shared_libraries| in that:
   #       * dependencies of this .so are not automatically included
   #       * ".cr.so" is never added
-  #       * they are not side-loaded for _incremental targets.
+  #       * they are not side-loaded when incremental_install=true.
   #       * load_library_from_apk, use_chromium_linker,
   #         and enable_relocation_packing do not apply
   #     Use this instead of shared_libraries when you are going to load the library
@@ -2128,7 +2128,6 @@
 
     _enable_multidex =
         !defined(invoker.enable_multidex) || invoker.enable_multidex
-    _final_dex_path = "$_out_dir/classes.dex.zip"
 
     if (!_is_bundle_module) {
       _final_apk_path = invoker.final_apk_path
@@ -2349,12 +2348,19 @@
     }
 
     # TODO(crbug.com/864142): Allow incremental installs of bundle modules.
-    _incremental_allowed =
-        !_is_bundle_module &&
-        !(defined(invoker.never_incremental) && invoker.never_incremental)
-    if (_incremental_allowed) {
+    _incremental_apk = !_is_bundle_module &&
+                       !(defined(invoker.never_incremental) &&
+                         invoker.never_incremental) && incremental_install
+    if (_incremental_apk) {
       _target_dir_name = get_label_info(target_name, "dir")
       _incremental_install_json_path = "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.incremental.json"
+      _incremental_apk_path = "${_final_apk_path_no_ext}_incremental.apk"
+    }
+
+    if (!_incremental_apk) {
+      # Bundle modules don't build the dex here, but need to write this path
+      # to their .build_config file.
+      _final_dex_path = "$_out_dir/classes.dex.zip"
     }
 
     _android_manifest =
@@ -2387,6 +2393,7 @@
 
     if (_generate_buildconfig_java &&
         defined(invoker.product_version_resources_dep)) {
+      # Needs to be added as a .build_config dep to pick up resources.
       _deps += [ invoker.product_version_resources_dep ]
     }
 
@@ -2611,7 +2618,6 @@
           ":$_compile_resources_target",
         ]
       }
-      _deps += [ ":${_compile_resources_target}__compile_only_dep" ]
     } else {
       _srcjar_deps += [ ":$_compile_resources_target" ]
     }
@@ -2747,15 +2753,17 @@
                                "secondary_native_lib_placeholders",
                                "static_library_dependent_targets",
                              ])
+      deps = _deps
+      if (_uses_static_library) {
+        deps += [ ":${_compile_resources_target}__compile_only_dep" ]
+      }
       if (_is_bundle_module) {
         type = "android_app_bundle_module"
         res_size_info_path = _res_size_info_path
         is_base_module = _is_base_module
 
-        if (!is_base_module) {
-          if (defined(invoker.base_module_target)) {
-            _deps += [ ":${_template_name}__group" ]
-          }
+        if (!is_base_module && defined(invoker.base_module_target)) {
+          deps += [ ":${_template_name}__group" ]
         }
       } else {
         type = "android_apk"
@@ -2765,10 +2773,11 @@
       supports_android = true
       requires_android = true
       min_sdk_version = _min_sdk_version
-      deps = _deps
       srcjar_deps = _srcjar_deps
       final_jar_path = _jar_path
-      final_dex_path = _final_dex_path
+      if (defined(_final_dex_path)) {
+        final_dex_path = _final_dex_path
+      }
 
       if (_is_bundle_module) {
         proto_resources_path = _proto_resources_path
@@ -2780,9 +2789,8 @@
         }
       } else {
         apk_path = _final_apk_path
-        incremental_allowed = _incremental_allowed
-        if (_incremental_allowed) {
-          incremental_apk_path = "${_final_apk_path_no_ext}_incremental.apk"
+        if (_incremental_apk) {
+          incremental_apk_path = _incremental_apk_path
           incremental_install_json_path = _incremental_install_json_path
         }
       }
@@ -2845,16 +2853,26 @@
       }
     }
 
-    if (_proguard_enabled && _uses_static_library) {
+    if (_uses_static_library && _proguard_enabled) {
       _final_dex_target_dep = "${invoker.static_library_provider}__dexsplitter"
-    } else if (!(_is_bundle_module && _proguard_enabled)) {
+    } else if (_is_bundle_module && _proguard_enabled) {
+      # A small sanity check to help developers with a subtle point!
+      assert(
+          !defined(invoker.proguard_jar_path),
+          "proguard_jar_path should not be used for app bundle modules " +
+              "when proguard is enabled. Pass it to the android_app_bundle() " +
+              "target instead!")
+
+      _final_deps += [ ":$_java_target" ]
+    } else if (_incremental_apk) {
+      if (defined(invoker.negative_main_dex_globs)) {
+        not_needed(invoker, [ "negative_main_dex_globs" ])
+      }
+    } else {
       # Dex generation for app bundle modules with proguarding enabled takes
       # place later due to synchronized proguarding. For more details,
       # read build/android/docs/android_app_bundles.md
       _final_dex_target_name = "${_template_name}__final_dex"
-      if (!_is_bundle_module) {
-        _final_dex_target_dep = ":$_final_dex_target_name"
-      }
       dex(_final_dex_target_name) {
         forward_variables_from(invoker,
                                [
@@ -2899,13 +2917,14 @@
           forward_variables_from(invoker, [ "negative_main_dex_globs" ])
           extra_main_dex_proguard_config = _generated_proguard_main_dex_config
           deps += [ ":$_compile_resources_target" ]
-        } else if (_enable_multidex) {
-          if (defined(invoker.negative_main_dex_globs)) {
-            not_needed(invoker, [ "negative_main_dex_globs" ])
-          }
+        } else if (_enable_multidex &&
+                   defined(invoker.negative_main_dex_globs)) {
+          not_needed(invoker, [ "negative_main_dex_globs" ])
         }
       }
 
+      _final_dex_target_dep = ":$_final_dex_target_name"
+
       # For static libraries, a single Proguard run is performed that includes
       # code from the static library APK and the APKs that use the static
       # library (done via. classpath merging in write_build_config.py).
@@ -2961,7 +2980,7 @@
             ]
           }
         }
-        _deps += [ ":$_static_library_dexsplitter_target" ]
+        _final_deps += [ ":$_static_library_dexsplitter_target" ]
         _validate_dex_target = "${_template_name}__validate_dex"
         action_with_pydeps(_validate_dex_target) {
           depfile = "$target_gen_dir/$target_name.d"
@@ -2996,21 +3015,13 @@
             ]
           }
         }
-        _deps += [ ":$_validate_dex_target" ]
+        _final_deps += [ ":$_validate_dex_target" ]
+        _final_dex_target_dep = ":$_static_library_dexsplitter_target"
       }
-    } else {
-      # A small sanity check to help developers with a subtle point!
-      assert(
-          !defined(invoker.proguard_jar_path),
-          "proguard_jar_path should not be used for app bundle modules " +
-              "when proguard is enabled. Pass it to the android_app_bundle() " +
-              "target instead!")
-
-      _final_deps += [ ":$_java_target" ]
     }
 
-    _extra_native_libs_even_when_incremental = []
-    assert(_extra_native_libs_even_when_incremental == [])  # Mark as used.
+    _loadable_modules = []
+    assert(_loadable_modules == [])  # Mark as used.
     if (_native_libs_deps != []) {
       _create_stack_script_rule_name = "${_template_name}__stack_script"
       _final_deps += [ ":${_create_stack_script_rule_name}" ]
@@ -3021,22 +3032,31 @@
     }
 
     if (defined(invoker.loadable_modules) && invoker.loadable_modules != []) {
-      _extra_native_libs_even_when_incremental += invoker.loadable_modules
+      _loadable_modules += invoker.loadable_modules
     }
 
-    _all_native_libs_deps = []
-    if (_native_libs_deps != [] ||
-        _extra_native_libs_even_when_incremental != []) {
+    _all_native_libs_deps = _native_libs_deps + _extra_native_libs_deps +
+                            _secondary_abi_native_libs_deps
+    if (_all_native_libs_deps != []) {
       _native_libs_file_arg_dep = ":$_build_config_target"
+      _all_native_libs_deps += [ _native_libs_file_arg_dep ]
+
       if (!_is_bundle_module) {
         _native_libs_file_arg =
             "@FileArg($_rebased_build_config:native:libraries)"
       }
-      _all_native_libs_deps += _native_libs_deps + _extra_native_libs_deps +
-                               [ _native_libs_file_arg_dep ]
     }
 
-    if (!_is_bundle_module) {
+    if (_is_bundle_module) {
+      _final_deps += [
+                       ":$_merge_manifest_target",
+                       ":$_build_config_target",
+                       ":$_compile_resources_target",
+                     ] + _all_native_libs_deps
+      if (defined(_final_dex_target_dep)) {
+        not_needed([ "_final_dex_target_dep" ])
+      }
+    } else {
       # Generate size-info/*.jar.info files.
       if (defined(invoker.name)) {
         # Create size info files for targets that care about size
@@ -3069,83 +3089,117 @@
         _keystore_password = invoker.keystore_password
       }
 
+      if (_incremental_apk) {
+        _incremental_compiled_resources_path = "${_base_path}_incremental.ap_"
+        _incremental_compile_resources_target_name =
+            "${target_name}__compile_incremental_resources"
+
+        action_with_pydeps(_incremental_compile_resources_target_name) {
+          deps = [
+            ":$_build_config_target",
+            ":$_compile_resources_target",
+            ":$_merge_manifest_target",
+          ]
+          script =
+              "//build/android/incremental_install/generate_android_manifest.py"
+          inputs = [
+            _android_manifest,
+            _build_config,
+            _arsc_resources_path,
+          ]
+          outputs = [
+            _incremental_compiled_resources_path,
+          ]
+
+          args = [
+            "--disable-isolated-processes",
+            "--src-manifest",
+            rebase_path(_android_manifest, root_build_dir),
+            "--in-apk",
+            rebase_path(_arsc_resources_path, root_build_dir),
+            "--out-apk",
+            rebase_path(_incremental_compiled_resources_path, root_build_dir),
+            "--aapt2-path",
+            rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir),
+            "--android-sdk-jars=@FileArg($_rebased_build_config:android:sdk_jars)",
+          ]
+        }
+      }
+
       _create_apk_target = "${_template_name}__create"
       _final_deps += [ ":$_create_apk_target" ]
-      create_apk("$_create_apk_target") {
+      package_apk("$_create_apk_target") {
         forward_variables_from(invoker,
                                [
                                  "native_lib_placeholders",
-                                 "public_deps",
                                  "secondary_native_lib_placeholders",
-                                 "shared_resources",
                                  "write_asset_list",
                                  "uncompress_dex",
+                                 "uncompress_shared_libraries",
                                ])
-        packaged_resources_path = _arsc_resources_path
 
-        if (_optimize_resources) {
-          optimized_resources_path = _optimized_arsc_resources_path
-        }
-
-        apk_path = _final_apk_path
         assets_build_config = _build_config
-        dex_path = _final_dex_path
-        load_library_from_apk = _load_library_from_apk
-
         keystore_name = _keystore_name
         keystore_path = _keystore_path
         keystore_password = _keystore_password
-
-        incremental_allowed = _incremental_allowed
-        if (_incremental_allowed) {
-          android_manifest = _android_manifest
-          base_path = _base_path
-        }
-
-        # Incremental apk does not use native libs nor final dex.
-        incremental_deps = _deps + [
-                             ":$_merge_manifest_target",
-                             ":$_build_config_target",
-                             ":$_compile_resources_target",
-                           ]
-
-        # This target generates the input file _all_resources_zip_path.
-        deps = _deps + [
-                 ":$_merge_manifest_target",
-                 ":$_build_config_target",
-                 "$_final_dex_target_dep",
-                 ":$_compile_resources_target",
-               ]
-
-        if (_native_libs_deps != [] ||
-            _extra_native_libs_even_when_incremental != []) {
-          native_libs_filearg = _native_libs_file_arg
-          native_libs = _extra_native_libs
-          native_libs_even_when_incremental =
-              _extra_native_libs_even_when_incremental
-        }
-        deps += _all_native_libs_deps
-        deps += _secondary_abi_native_libs_deps
-        secondary_abi_native_libs_filearg =
-            "@FileArg($_rebased_build_config:native:secondary_abi_libraries)"
-
         uncompress_shared_libraries = _uncompress_shared_libraries
+
+        native_libs = _loadable_modules
+        deps = _deps + [ ":$_build_config_target" ]
+
+        if (_incremental_apk) {
+          _dex_target =
+              "//build/android/incremental_install:bootstrap_java__dex"
+
+          deps += [
+            ":${_incremental_compile_resources_target_name}",
+            _dex_target,
+          ]
+
+          dex_path =
+              get_label_info(_dex_target, "target_out_dir") + "/bootstrap.dex"
+
+          # http://crbug.com/384638
+          _has_native_libs =
+              defined(invoker.native_libs_filearg) || _extra_native_libs != []
+          if (_has_native_libs && _loadable_modules == []) {
+            native_lib_placeholders = [ "libfix.crbug.384638.so" ]
+          }
+
+          packaged_resources_path = _incremental_compiled_resources_path
+          output_apk_path = _incremental_apk_path
+        } else {
+          deps += _all_native_libs_deps + [
+                    ":$_merge_manifest_target",
+                    ":$_compile_resources_target",
+                  ]
+
+          if (defined(_final_dex_path)) {
+            dex_path = _final_dex_path
+            deps += [ _final_dex_target_dep ]
+          }
+
+          native_libs += _extra_native_libs
+          if (_optimize_resources) {
+            packaged_resources_path = _optimized_arsc_resources_path
+          } else {
+            packaged_resources_path = _arsc_resources_path
+          }
+
+          if (defined(_native_libs_file_arg)) {
+            native_libs_filearg = _native_libs_file_arg
+            secondary_abi_native_libs_filearg = "@FileArg($_rebased_build_config:native:secondary_abi_libraries)"
+          }
+          output_apk_path = _final_apk_path
+        }
       }
-    } else {
-      _final_deps += [
-                       ":$_merge_manifest_target",
-                       ":$_build_config_target",
-                       ":$_compile_resources_target",
-                     ] + _all_native_libs_deps + _secondary_abi_native_libs_deps
     }
 
-    if (_incremental_allowed) {
+    if (_incremental_apk) {
       _write_installer_json_rule_name = "${_template_name}__incremental_json"
       action_with_pydeps(_write_installer_json_rule_name) {
         script = "//build/android/incremental_install/write_installer_json.py"
-        deps = [
-          ":$_build_config_target",
-        ]
+        deps = [ ":$_build_config_target" ] + _all_native_libs_deps
 
         data = [
           _incremental_install_json_path,
@@ -3157,12 +3211,12 @@
           _incremental_install_json_path,
         ]
 
-        _rebased_apk_path_no_ext =
-            rebase_path(_final_apk_path_no_ext, root_build_dir)
+        _rebased_incremental_apk_path =
+            rebase_path(_incremental_apk_path, root_build_dir)
         _rebased_incremental_install_json_path =
             rebase_path(_incremental_install_json_path, root_build_dir)
         args = [
-          "--apk-path=${_rebased_apk_path_no_ext}_incremental.apk",
+          "--apk-path=$_rebased_incremental_apk_path",
           "--output-path=$_rebased_incremental_install_json_path",
           "--dex-file=@FileArg($_rebased_build_config:final_dex:all_dex_files)",
         ]
@@ -3182,11 +3236,12 @@
           args += [ "--dont-even-try=Incremental builds do not work with load_library_from_apk. Try setting is_component_build=true in your GN args." ]
         }
       }
-      _incremental_apk_operations = []
+      _final_deps += [
+        ":$_write_installer_json_rule_name",
+        ":$_java_target",
+      ]
     }
 
-    _apk_operations = []
-
     # Generate apk operation related script.
     if (!_is_bundle_module &&
         (!defined(invoker.create_apk_script) || invoker.create_apk_script)) {
@@ -3209,8 +3264,6 @@
         args = [
           "--script-output-path",
           rebase_path(_generated_script, root_build_dir),
-          "--apk-path",
-          rebase_path(_final_apk_path, root_build_dir),
           "--target-cpu=$target_cpu",
         ]
         if (defined(invoker.command_line_flags_file)) {
@@ -3219,11 +3272,16 @@
             invoker.command_line_flags_file,
           ]
         }
-        if (_incremental_allowed) {
+        if (_incremental_apk) {
           args += [
             "--incremental-install-json-path",
             rebase_path(_incremental_install_json_path, root_build_dir),
           ]
+        } else {
+          args += [
+            "--apk-path",
+            rebase_path(_final_apk_path, root_build_dir),
+          ]
         }
         if (_proguard_enabled) {
           args += [
@@ -3232,39 +3290,21 @@
           ]
         }
       }
-      _apk_operations += [ ":$_apk_operations_target_name" ]
-      if (_incremental_allowed) {
-        _incremental_apk_operations += [ ":$_apk_operations_target_name" ]
-      }
+      _final_deps += [ ":$_apk_operations_target_name" ]
     }
 
     group(target_name) {
-      forward_variables_from(invoker, [ "metadata" ])
-      if (_incremental_allowed && incremental_apk_by_default) {
-        deps = [
-          ":${target_name}_incremental",
-        ]
-        assert(_apk_operations != [] || true)  # Prevent "unused variable".
-      } else {
-        forward_variables_from(invoker,
-                               [
-                                 "data",
-                                 "data_deps",
-                               ])
-        public_deps = _final_deps
+      forward_variables_from(invoker,
+                             [
+                               "data",
+                               "data_deps",
+                               "metadata",
+                             ])
 
-        # Generate apk related operations at runtime.
-        public_deps += _apk_operations
-      }
-    }
+      # Generate apk related operations at runtime.
+      public_deps = _final_deps
 
-    if (_incremental_allowed) {
-      group("${target_name}_incremental") {
-        forward_variables_from(invoker,
-                               [
-                                 "data",
-                                 "data_deps",
-                               ])
+      if (_incremental_apk) {
         if (!defined(data_deps)) {
           data_deps = []
         }
@@ -3272,18 +3312,6 @@
         # device/commands is used by the installer script to push files via .zip.
         data_deps += [ "//build/android/pylib/device/commands" ] +
                      _native_libs_deps + _extra_native_libs_deps
-
-        # Since the _incremental.apk does not include use .so nor .dex from the
-        # actual target, but instead loads them at runtime, we need to explicitly
-        # depend on them here.
-        public_deps = [
-          ":${_java_target}",
-          ":${_template_name}__create_incremental",
-          ":${_write_installer_json_rule_name}",
-        ]
-
-        # Generate incremental apk related operations at runtime.
-        public_deps += _incremental_apk_operations
       }
     }
   }
@@ -3559,102 +3587,39 @@
   #     apk_under_test: ":bar"
   #   }
   template("instrumentation_test_runner") {
-    testonly = true
-    assert(defined(invoker.android_test_apk))
-    assert(defined(invoker.android_test_apk_name))
-    _incremental_allowed =
-        !defined(invoker.never_incremental) || !invoker.never_incremental
-    _apk_target_name =
-        get_label_info(invoker.android_test_apk, "name") + "__apk"
-    _test_runner_target_name = "${target_name}__test_runner_script"
-    _dist_ijar_path = "$root_build_dir/test.lib.java/" +
-                      invoker.android_test_apk_name + ".jar"
-    if (_incremental_allowed) {
-      _incremental_test_runner_target_name =
-          "${_test_runner_target_name}_incremental"
-      _incremental_test_name = "${target_name}_incremental"
-    }
+    test_runner_script(target_name) {
+      forward_variables_from(invoker,
+                             [
+                               "additional_apks",
+                               "apk_under_test",
+                               "data",
+                               "data_deps",
+                               "deps",
+                               "extra_args",
+                               "fake_modules",
+                               "ignore_all_data_deps",
+                               "modules",
+                               "proguard_enabled",
+                               "public_deps",
+                             ])
+      test_name = invoker.target_name
+      test_type = "instrumentation"
+      _apk_target_name =
+          get_label_info(invoker.android_test_apk, "name") + "__apk"
+      apk_target = ":$_apk_target_name"
+      test_jar = "$root_build_dir/test.lib.java/" +
+                 invoker.android_test_apk_name + ".jar"
+      incremental_apk = !(defined(invoker.never_incremental) &&
+                          invoker.never_incremental) && incremental_install
 
-    if (incremental_apk_by_default && _incremental_allowed) {
-      _incremental_test_runner_target_name = _test_runner_target_name
-      _incremental_test_name = "${target_name}"
-    }
+      public_deps = [
+        ":$_apk_target_name",
 
-    if (!incremental_apk_by_default ||
-        (incremental_apk_by_default && !_incremental_allowed)) {
-      test_runner_script(_test_runner_target_name) {
-        forward_variables_from(invoker,
-                               [
-                                 "additional_apks",
-                                 "apk_under_test",
-                                 "data",
-                                 "data_deps",
-                                 "deps",
-                                 "extra_args",
-                                 "fake_modules",
-                                 "ignore_all_data_deps",
-                                 "modules",
-                                 "proguard_enabled",
-                                 "public_deps",
-                               ])
-        test_name = invoker.target_name
-        test_type = "instrumentation"
-        apk_target = ":$_apk_target_name"
-        test_jar = _dist_ijar_path
-      }
-    }
-    if (_incremental_allowed) {
-      if (defined(invoker.proguard_enabled)) {
-        not_needed(invoker, [ "proguard_enabled" ])
-      }
-      test_runner_script(_incremental_test_runner_target_name) {
-        forward_variables_from(invoker,
-                               [
-                                 "additional_apks",
-                                 "apk_under_test",
-                                 "data",
-                                 "data_deps",
-                                 "deps",
-                                 "ignore_all_data_deps",
-                                 "public_deps",
-                               ])
-        test_name = _incremental_test_name
-        test_type = "instrumentation"
-        apk_target = ":$_apk_target_name"
-        test_jar = _dist_ijar_path
-        incremental_install = true
-      }
-    }
-    group(target_name) {
-      if (incremental_apk_by_default && _incremental_allowed) {
-        public_deps = [
-          ":${target_name}_incremental",
-        ]
-      } else {
-        public_deps = [
-          ":$_apk_target_name",
-          ":$_test_runner_target_name",
-
-          # Required by test runner to enumerate test list.
-          ":${_apk_target_name}_dist_ijar",
-        ]
-        if (defined(invoker.apk_under_test)) {
-          public_deps += [ invoker.apk_under_test ]
-        }
-      }
-    }
-    if (_incremental_allowed) {
-      group("${target_name}_incremental") {
-        public_deps = [
-          ":$_incremental_test_runner_target_name",
-          ":${_apk_target_name}_incremental",
-
-          # Required by test runner to enumerate test list.
-          ":${_apk_target_name}_dist_ijar",
-        ]
-        if (defined(invoker.apk_under_test)) {
-          public_deps += [ "${invoker.apk_under_test}_incremental" ]
-        }
+        # Required by test runner to enumerate test list.
+        ":${_apk_target_name}_dist_ijar",
+      ]
+      if (defined(invoker.apk_under_test)) {
+        public_deps += [ invoker.apk_under_test ]
       }
     }
   }
@@ -3680,17 +3645,17 @@
   #     ]
   #   }
   template("android_test_apk") {
-    assert(defined(invoker.apk_name))
-    testonly = true
-    _incremental_allowed =
-        !defined(invoker.never_incremental) || !invoker.never_incremental
     _apk_target_name = "${target_name}__apk"
-    _dist_ijar_path =
-        "$root_build_dir/test.lib.java/" + invoker.apk_name + ".jar"
     android_apk(_apk_target_name) {
-      deps = []
-      data_deps = []
       forward_variables_from(invoker, "*")
+
+      testonly = true
+      if (!defined(deps)) {
+        deps = []
+      }
+      if (!defined(data_deps)) {
+        data_deps = []
+      }
       deps += [ "//testing/android/broker:broker_java" ]
       data_deps += [
         "//build/android/pylib/device/commands",
@@ -3703,6 +3668,21 @@
         enable_native_mocks = true
       }
 
+      # Ensure unstripped libraries are included in runtime deps so that
+      # symbolization can be done.
+      data_deps += [
+        ":${_apk_target_name}__secondary_abi_shared_library_list",
+        ":${_apk_target_name}__shared_library_list",
+      ]
+      if (defined(invoker.apk_under_test)) {
+        _under_test_label =
+            get_label_info(invoker.apk_under_test, "label_no_toolchain")
+        data_deps += [
+          "${_under_test_label}__secondary_abi_shared_library_list",
+          "${_under_test_label}__shared_library_list",
+        ]
+      }
+
       if (defined(additional_apks)) {
         data_deps += additional_apks
       }
@@ -3733,50 +3713,9 @@
         data_deps += [ "//build/android/stacktrace:java_deobfuscate" ]
       }
 
-      dist_ijar_path = _dist_ijar_path
+      dist_ijar_path = "$root_build_dir/test.lib.java/${invoker.apk_name}.jar"
       create_apk_script = false
     }
-
-    group(target_name) {
-      if (incremental_apk_by_default && _incremental_allowed) {
-        public_deps = [
-          ":${target_name}_incremental",
-        ]
-      } else {
-        public_deps = [
-          ":$_apk_target_name",
-        ]
-        if (defined(invoker.apk_under_test)) {
-          public_deps += [ invoker.apk_under_test ]
-        }
-      }
-
-      # Ensure unstripped libraries are included in runtime deps so that
-      # symbolization can be done.
-      deps = [
-        ":${_apk_target_name}__secondary_abi_shared_library_list",
-        ":${_apk_target_name}__shared_library_list",
-      ]
-      if (defined(invoker.apk_under_test)) {
-        _under_test_label =
-            get_label_info(invoker.apk_under_test, "label_no_toolchain")
-        deps += [
-          "${_under_test_label}__secondary_abi_shared_library_list",
-          "${_under_test_label}__shared_library_list",
-        ]
-      }
-    }
-
-    if (_incremental_allowed) {
-      group("${target_name}_incremental") {
-        public_deps = [
-          ":${_apk_target_name}_incremental",
-        ]
-        if (defined(invoker.apk_under_test)) {
-          public_deps += [ "${invoker.apk_under_test}_incremental" ]
-        }
-      }
-    }
   }
 
   # Declare an Android instrumentation test apk with wrapper script.
diff --git a/build/fuchsia/deploy_to_amber_repo.py b/build/fuchsia/deploy_to_amber_repo.py
index 6f36e0a6..57c47ac 100755
--- a/build/fuchsia/deploy_to_amber_repo.py
+++ b/build/fuchsia/deploy_to_amber_repo.py
@@ -14,6 +14,27 @@
 from common import PublishPackage
 
 
+# Populates the GDB-standard symbol directory structure |build_ids_path| with
+# the files and build IDs specified in |ids_txt_path|.
+def InstallSymbols(ids_txt_path, build_ids_path):
+  for entry in open(ids_txt_path, 'r'):
+    build_id, binary_relpath = entry.strip().split(' ')
+    binary_abspath = os.path.abspath(os.path.join(os.path.dirname(ids_txt_path),
+                                                  binary_relpath))
+    symbol_dir = os.path.join(build_ids_path, build_id[:2])
+    symbol_file = os.path.join(symbol_dir, build_id[2:] + '.debug')
+
+    if not os.path.exists(symbol_dir):
+      os.makedirs(symbol_dir)
+
+    if os.path.islink(symbol_file) or os.path.exists(symbol_file):
+      # Clobber the existing entry to ensure that the symlink's target is
+      # up to date.
+      os.unlink(symbol_file)
+
+    os.symlink(os.path.relpath(binary_abspath, symbol_dir), symbol_file)
+
+
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('--package', action='append', required=True,
@@ -32,11 +53,13 @@
                      '"default_fuchsia_build_dir_for_installation".\n')
     return 1
 
-  fuchsia_out_dir = args.fuchsia_out_dir.pop()
+  fuchsia_out_dir = os.path.expanduser(args.fuchsia_out_dir.pop())
   tuf_root = os.path.join(fuchsia_out_dir, 'amber-files')
-  print('Installing packages in Amber repo %s...' % tuf_root)
+  print('Installing packages and symbols in Amber repo %s...' % tuf_root)
   for package in args.package:
-    PublishPackage(package, os.path.expanduser(tuf_root))
+    PublishPackage(package, tuf_root)
+    InstallSymbols(os.path.join(os.path.dirname(package), 'ids.txt'),
+                   os.path.join(fuchsia_out_dir, '.build-id'))
 
   print('Installation success.')
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 0dc86b2..5593f5a 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8898945379932813792
\ No newline at end of file
+8898880721340197792
\ No newline at end of file
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 1d39b4e..71756f2 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -795,6 +795,7 @@
     "//chrome/android/webapk/libs/common:common_java",
     "//chrome/browser/android/metrics:ukm_java_test_support",
     "//chrome/browser/android/metrics:ukm_javatests",
+    "//chrome/browser/download/android:java",
     "//chrome/browser/ui/android/widget:java",
     "//chrome/browser/ui/android/widget:test_support_java",
     "//chrome/browser/util/android:java",
@@ -2383,7 +2384,8 @@
     # Having //clank present causes different flags because of how play services
     # is wired up.
     # The channel is required because manifest entries vary based on channel.
-    if (!enable_chrome_android_internal && android_channel == "stable") {
+    if (check_android_configuration && !enable_chrome_android_internal &&
+        android_channel == "stable") {
       verify_android_configuration = true
     }
   }
@@ -2489,7 +2491,6 @@
     "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
     "java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
-    "java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java",
     "java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java",
     "java/src/org/chromium/chrome/browser/browsing_data/UrlFilterBridge.java",
     "java/src/org/chromium/chrome/browser/childaccounts/ChildAccountFeedbackReporter.java",
@@ -2606,6 +2607,7 @@
     "java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java",
     "java/src/org/chromium/chrome/browser/metrics/LaunchMetrics.java",
     "java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java",
+    "java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
     "java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java",
     "java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
     "java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 32dbf1e..2b35e29 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -154,7 +154,6 @@
   "java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java",
   "java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivitySettingsLauncher.java",
   "java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java",
-  "java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java",
   "java/src/org/chromium/chrome/browser/browserservices/VerificationResultStore.java",
   "java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java",
   "java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationChannelPreserver.java",
@@ -487,11 +486,9 @@
   "java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeTabInfo.java",
   "java/src/org/chromium/chrome/browser/dom_distiller/TabDistillabilityProvider.java",
   "java/src/org/chromium/chrome/browser/download/ChromeDownloadDelegate.java",
-  "java/src/org/chromium/chrome/browser/download/DirectoryOption.java",
   "java/src/org/chromium/chrome/browser/download/DownloadActivity.java",
   "java/src/org/chromium/chrome/browser/download/DownloadBroadcastManager.java",
   "java/src/org/chromium/chrome/browser/download/DownloadController.java",
-  "java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java",
   "java/src/org/chromium/chrome/browser/download/DownloadForegroundService.java",
   "java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceManager.java",
   "java/src/org/chromium/chrome/browser/download/DownloadForegroundServiceObservers.java",
@@ -893,6 +890,7 @@
   "java/src/org/chromium/chrome/browser/metrics/PackageMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java",
   "java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java",
+  "java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
   "java/src/org/chromium/chrome/browser/metrics/UmaSessionStats.java",
   "java/src/org/chromium/chrome/browser/metrics/UmaUtils.java",
   "java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
diff --git a/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
index c9928dc..1c521301 100644
--- a/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
@@ -16,7 +16,9 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/bottom_sheet_peek_height"
         android:orientation="horizontal"
-        android:gravity="center_vertical">
+        android:gravity="center_vertical"
+        android:focusable="true"
+        android:focusableInTouchMode="true">
         <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_left_button"
             style="@style/BottomToolbarButton"
@@ -27,7 +29,6 @@
             tools:ignore="LabelFor"
             android:id="@+id/title"
             android:cursorVisible="false"
-            android:background="@android:color/transparent"
             android:layout_height="@dimen/bottom_sheet_peek_height"
             android:layout_width="0dp"
             android:layout_weight="1"
@@ -36,6 +37,7 @@
             android:textAppearance="@style/TextAppearance.BlackTitle1"
             android:inputType="text|textNoSuggestions"
             android:imeOptions="actionDone"
+            android:theme="@style/TabGridDialogTitleTheme"
             android:gravity="center"/>
         <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_right_button"
diff --git a/chrome/android/features/tab_ui/java/res/values/styles.xml b/chrome/android/features/tab_ui/java/res/values/styles.xml
index 74947715..db62a8d 100644
--- a/chrome/android/features/tab_ui/java/res/values/styles.xml
+++ b/chrome/android/features/tab_ui/java/res/values/styles.xml
@@ -16,4 +16,9 @@
         <item name="android:textColor">@color/default_text_color_dark</item>
     </style>
 
+    <style name="TabGridDialogTitleTheme">
+        <item name="colorControlNormal">@color/divider_bg_color</item>
+        <item name="colorControlActivated">@color/filled_button_bg_color</item>
+        <item name="colorControlHighlight">@color/filled_button_bg_color</item>
+    </style>
 </resources>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index f488a4c..06c4896 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -322,6 +322,7 @@
     private void setupToolbarEditText() {
         mKeyboardVisibilityListener = isShowing -> {
             mModel.set(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY, isShowing);
+            mModel.set(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED, isShowing);
             if (!isShowing) {
                 saveCurrentGroupModifiedTitle();
             }
@@ -420,6 +421,18 @@
         assert mTabGroupTitleEditor != null;
 
         Tab currentTab = mTabModelSelector.getTabById(mCurrentTabId);
+        if (mCurrentGroupModifiedTitle.length() == 0) {
+            // When dialog title is empty, delete previously stored title and restore default title.
+            mTabGroupTitleEditor.deleteTabGroupTitle(currentTab.getRootId());
+            int tabsCount = getRelatedTabs(mCurrentTabId).size();
+            assert tabsCount >= 2;
+
+            String originalTitle = mContext.getResources().getQuantityString(
+                    R.plurals.bottom_tab_grid_title_placeholder, tabsCount, tabsCount);
+            mModel.set(TabGridPanelProperties.HEADER_TITLE, originalTitle);
+            mTabGroupTitleEditor.updateTabGroupTitle(currentTab, originalTitle);
+            return;
+        }
         mTabGroupTitleEditor.storeTabGroupTitle(currentTab.getRootId(), mCurrentGroupModifiedTitle);
         mTabGroupTitleEditor.updateTabGroupTitle(currentTab, mCurrentGroupModifiedTitle);
         mModel.set(TabGridPanelProperties.HEADER_TITLE, mCurrentGroupModifiedTitle);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
index 0168d2c..a974a5201 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
@@ -24,7 +24,7 @@
             .WritableObjectPropertyKey<OnClickListener> ADD_CLICK_LISTENER =
             new PropertyModel.WritableObjectPropertyKey<OnClickListener>();
     public static final PropertyModel.WritableObjectPropertyKey<String> HEADER_TITLE =
-            new PropertyModel.WritableObjectPropertyKey<String>();
+            new PropertyModel.WritableObjectPropertyKey<String>(true);
     public static final PropertyModel.WritableIntPropertyKey CONTENT_TOP_MARGIN =
             new PropertyModel.WritableIntPropertyKey();
     public static final PropertyModel.WritableIntPropertyKey PRIMARY_COLOR =
@@ -69,11 +69,13 @@
             new PropertyModel.WritableObjectPropertyKey<>();
     public static final PropertyModel.WritableBooleanPropertyKey TITLE_CURSOR_VISIBILITY =
             new PropertyModel.WritableBooleanPropertyKey();
+    public static final PropertyModel.WritableBooleanPropertyKey IS_TITLE_TEXT_FOCUSED =
+            new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {COLLAPSE_CLICK_LISTENER,
             ADD_CLICK_LISTENER, HEADER_TITLE, CONTENT_TOP_MARGIN, PRIMARY_COLOR, TINT,
             IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_PARAMS, UNGROUP_BAR_STATUS,
             DIALOG_BACKGROUND_RESOUCE_ID, DIALOG_UNGROUP_BAR_BACKGROUND_COLOR_ID,
             DIALOG_UNGROUP_BAR_HOVERED_BACKGROUND_COLOR_ID, DIALOG_UNGROUP_BAR_TEXT_APPEARANCE,
             INITIAL_SCROLL_INDEX, IS_MAIN_CONTENT_VISIBLE, MENU_CLICK_LISTENER, TITLE_TEXT_WATCHER,
-            TITLE_TEXT_ON_FOCUS_LISTENER, TITLE_CURSOR_VISIBILITY};
+            TITLE_TEXT_ON_FOCUS_LISTENER, TITLE_CURSOR_VISIBILITY, IS_TITLE_TEXT_FOCUSED};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
index b362ef7..d44c7e6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
@@ -16,6 +16,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.INITIAL_SCROLL_INDEX;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.IS_DIALOG_VISIBLE;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.IS_MAIN_CONTENT_VISIBLE;
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.MENU_CLICK_LISTENER;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.PRIMARY_COLOR;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.SCRIMVIEW_OBSERVER;
@@ -127,6 +128,11 @@
                     model.get(TITLE_TEXT_ON_FOCUS_LISTENER));
         } else if (TITLE_CURSOR_VISIBILITY == propertyKey) {
             viewHolder.toolbarView.setTitleCursorVisibility(model.get(TITLE_CURSOR_VISIBILITY));
+        } else if (IS_TITLE_TEXT_FOCUSED == propertyKey) {
+            // Don't explicitly request focus since it should happen automatically.
+            if (!model.get(IS_TITLE_TEXT_FOCUSED)) {
+                viewHolder.toolbarView.clearTitleTextFocus();
+            }
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
index 76d9f2a..2fae307 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
@@ -72,6 +72,10 @@
         mTitleTextView.setCursorVisible(isVisible);
     }
 
+    void clearTitleTextFocus() {
+        mTitleTextView.clearFocus();
+    }
+
     ViewGroup getViewContainer() {
         return mContainerView;
     }
@@ -91,7 +95,6 @@
         if (mTitleTextView == null) {
             throw new IllegalStateException("Current Toolbar doesn't have a title text view");
         }
-
         mTitleTextView.setText(title);
     }
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
index 678ba177..5e9a94ee 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
@@ -419,6 +419,25 @@
         Assert.assertTrue(mTitleTextView.isCursorVisible());
     }
 
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testSetIsTitleTextFocused() {
+        Assert.assertFalse(mTitleTextView.isFocused());
+
+        mModel.set(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED, true);
+
+        // Binder should ignore set focus signal to avoid duplicate setting.
+        Assert.assertFalse(mTitleTextView.isFocused());
+
+        mTitleTextView.requestFocus();
+        Assert.assertTrue(mTitleTextView.isFocused());
+
+        mModel.set(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED, false);
+
+        Assert.assertFalse(mTitleTextView.isFocused());
+    }
+
     @Override
     public void tearDownTest() throws Exception {
         mMCP.destroy();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
index aec008f..264cebce 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
@@ -308,12 +308,15 @@
         KeyboardVisibilityDelegate.KeyboardVisibilityListener listener =
                 mMediator.getKeyboardVisibilityListenerForTesting();
         mModel.set(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY, false);
+        mModel.set(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED, false);
 
         listener.keyboardVisibilityChanged(true);
         assertThat(mModel.get(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY), equalTo(true));
+        assertThat(mModel.get(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED), equalTo(true));
 
         listener.keyboardVisibilityChanged(false);
         assertThat(mModel.get(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY), equalTo(false));
+        assertThat(mModel.get(TabGridPanelProperties.IS_TITLE_TEXT_FOCUSED), equalTo(false));
     }
 
     @Test
@@ -651,6 +654,32 @@
     }
 
     @Test
+    public void hideDialog_ModifiedGroupTitleEmpty() {
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+
+        // Mock that tab1 is in a group.
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID);
+
+        // Mock that we have a modified group title which is an empty string.
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        View.OnFocusChangeListener onFocusChangeListener =
+                mModel.get(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
+        onFocusChangeListener.onFocusChange(mTitleTextView, true);
+        doReturn("").when(mEditable).toString();
+        textWatcher.afterTextChanged(mEditable);
+        assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(), equalTo(""));
+
+        mMediator.hideDialog(false);
+
+        // When updated title is a empty string, delete stored title and restore default title in
+        // PropertyModel.
+        verify(mTabGroupTitleEditor).deleteTabGroupTitle(eq(TAB1_ID));
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
+        verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(DIALOG_TITLE2));
+    }
+
+    @Test
     public void hideDialog_NoModifiedGroupTitle() {
         mMediator.setCurrentTabIdForTest(TAB1_ID);
         mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
diff --git a/chrome/android/java/res/drawable/logo_partly_cloudy.xml b/chrome/android/java/res/drawable/logo_partly_cloudy.xml
index 8b484acc..c5457fa 100644
--- a/chrome/android/java/res/drawable/logo_partly_cloudy.xml
+++ b/chrome/android/java/res/drawable/logo_partly_cloudy.xml
@@ -3,19 +3,47 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<vector android:height="24dp" android:viewportHeight="36"
-    android:viewportWidth="36" android:width="24dp"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:targetApi="21">
-    <path android:fillColor="#D9D9D9" android:fillType="nonZero"
-        android:pathData="M31.143,24.2C31.2227,23.9095 31.2635,23.6098 31.2645,23.3086C31.2628,21.7387 30.199,20.367 28.6741,19.9687C27.1493,19.5703 25.5463,20.2454 24.7711,21.6123C23.7316,20.7741 22.301,20.6053 21.0935,21.1783C19.8861,21.7513 19.1167,22.9643 19.1157,24.2963C19.1166,24.5067 19.1365,24.7166 19.1752,24.9235C19.1132,24.9235 19.0537,24.9235 18.9917,24.9235C17.3958,24.9221 16.083,26.175 16.0165,27.763L32.9529,27.763C32.9775,27.5995 32.9907,27.4345 32.9926,27.2691C32.9973,25.9831 32.2848,24.8008 31.143,24.2L31.143,24.2Z"
-        />
-    <path android:fillColor="#FFE05A" android:fillType="nonZero"
-        android:pathData="M18.3719,24.2247C18.3968,22.763 19.1836,21.4196 20.4487,20.6782C21.7138,19.9369 23.2752,19.9044 24.5702,20.5926C25.3732,19.6503 26.5524,19.1083 27.7934,19.1111L27.9595,19.1111C28.4556,15.1787 26.5433,11.3318 23.1024,9.3405C19.6615,7.3492 15.3586,7.5991 12.1736,9.9753L12.3025,9.9753C14.1614,9.9793 15.7111,11.3932 15.8777,13.237C17.6034,13.5325 18.8653,15.0216 18.8678,16.7654L18.8678,16.8938L18.8355,17.6346L8.2066,17.6346L8.2066,17.8815C8.2068,22.2585 11.0998,26.1133 15.3149,27.3531C15.5377,25.7569 16.7758,24.49 18.3719,24.2247Z"
-        />
-    <path android:fillColor="#D9D9D9" android:fillType="nonZero"
-        android:pathData="M15.2727,13.9259C15.2207,13.9259 15.1711,13.9259 15.1215,13.9259C15.1379,13.8031 15.147,13.6794 15.1488,13.5556C15.1504,12.15 14.1192,10.9548 12.7237,10.7447C11.3282,10.5346 9.9884,11.3728 9.5727,12.716C9.128,12.2293 8.498,11.9514 7.8372,11.9506L7.7107,11.9506C6.3414,11.9506 5.2314,13.0561 5.2314,14.4198C5.2316,14.5856 5.2482,14.751 5.281,14.9136C4.153,14.9505 3.2098,15.779 3.0322,16.8889L18.124,16.8889C18.124,16.8469 18.124,16.8074 18.124,16.7654C18.124,15.1972 16.8474,13.9259 15.2727,13.9259Z"
-        />
+<vector android:height="36dp" android:viewportHeight="48"
+    android:viewportWidth="48" android:width="36dp"
+    xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FDD835" android:pathData="M24,24m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"/>
+    <path android:fillAlpha="0.1" android:fillColor="#FFFFFF" android:pathData="M24,4.25c11,0 19.93,8.89 20,19.88c0,-0.04 0,-0.08 0,-0.12c0,-11.05 -8.95,-20 -20,-20S4,12.95 4,24c0,0.04 0,0.08 0,0.12C4.07,13.14 13,4.25 24,4.25z"/>
+    <path android:fillAlpha="0.1" android:fillColor="#BF360C" android:pathData="M44,23.88C43.93,34.86 35,43.75 24,43.75S4.07,34.86 4,23.88c0,0.04 0,0.08 0,0.12c0,11.05 8.95,20 20,20s20,-8.95 20,-20C44,23.96 44,23.92 44,23.88z"/>
+    <group>
+        <clip-path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z M 0,0"/>
+        <path android:pathData="M44.25,36.69l-10.59,-10.35l-28.13,10.15l0,7.51l38.72,0z">
+            <aapt:attr name="android:fillColor">
+                <gradient android:endX="27.1957" android:endY="44.8146"
+                    android:startX="21.0923" android:startY="32.9892" android:type="linear">
+                    <item android:color="#19BF360C" android:offset="0"/>
+                    <item android:color="#05BF360C" android:offset="1"/>
+                </gradient>
+            </aapt:attr>
+        </path>
+    </group>
+    <group>
+        <clip-path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z M 0,0"/>
+        <path android:fillColor="#EEEEEE" android:pathData="M28,24h-2.67c-1.65,-4.66 -6.09,-8 -11.31,-8c-6.63,0 -12,5.37 -12,12s5.37,12 12,12H28c4.42,0 8,-3.58 8,-8C36,27.58 32.42,24 28,24z"/>
+    </group>
+    <group>
+        <clip-path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z M 0,0"/>
+        <path android:fillAlpha="0.2" android:fillColor="#FFFFFF" android:pathData="M14.01,16.25c5.22,0 9.67,3.34 11.31,8H28c4.38,0 7.93,3.51 8,7.88c0,-0.04 0,-0.08 0,-0.12c0,-4.42 -3.58,-8 -8,-8h-2.67c-1.65,-4.66 -6.09,-8 -11.31,-8c-6.63,0 -12,5.37 -12,12c0,0.04 0,0.08 0,0.12C2.08,21.56 7.43,16.25 14.01,16.25z"/>
+    </group>
+    <group>
+        <clip-path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z M 0,0"/>
+        <path android:fillAlpha="0.1" android:fillColor="#212121" android:pathData="M28,39.75H14.01c-6.59,0 -11.93,-5.31 -12,-11.88c0,0.04 0,0.08 0,0.12c0,6.63 5.37,12 12,12H28c4.42,0 8,-3.58 8,-8c0,-0.04 0,-0.08 0,-0.12C35.93,36.24 32.38,39.75 28,39.75z"/>
+    </group>
+    <group>
+        <clip-path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z M 0,0"/>
+        <path android:fillAlpha="0.1" android:fillColor="#BF360C" android:pathData="M28,40H14.01c-6.59,0 -11.93,-5.31 -12,-11.88c0,0.04 0,0.08 0,0.12c0,6.63 5.37,12 12,12H28c4.42,0 8,-3.58 8,-8c0,-0.04 0,-0.08 0,-0.12C35.93,36.49 32.38,40 28,40z"/>
+    </group>
+    <path android:pathData="M24,4C13.95,4 5.63,11.42 4.22,21.07c-1.39,1.96 -2.2,4.35 -2.2,6.93c0,5.84 4.18,10.71 9.71,11.78C15.11,42.42 19.37,44 24,44c11.05,0 20,-8.95 20,-20C44,12.95 35.05,4 24,4z">
+        <aapt:attr name="android:fillColor">
+            <gradient android:centerX="9.8293" android:centerY="9.8583"
+                android:gradientRadius="40.0515" android:type="radial">
+                <item android:color="#19FFFFFF" android:offset="0"/>
+                <item android:color="#00FFFFFF" android:offset="1"/>
+            </gradient>
+        </aapt:attr>
+    </path>
 </vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java
index f9fb5e1..b21cbbe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java
@@ -12,6 +12,7 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.metrics.UkmRecorder;
 import org.chromium.chrome.browser.tab.Tab;
 
 import java.lang.annotation.Retention;
@@ -60,7 +61,7 @@
     public void recordTwaOpened(@Nullable Tab tab) {
         RecordUserAction.record("BrowserServices.TwaOpened");
         if (tab != null) {
-            new UkmRecorder.Bridge().recordTwaOpened(tab);
+            new UkmRecorder.Bridge().recordEvent(tab.getWebContents(), "TrustedWebActivity.Open");
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java
deleted file mode 100644
index a075ea47..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/UkmRecorder.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.browserservices;
-
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.content_public.browser.WebContents;
-
-/**
- * An interface and classes to record User Keyed Metrics relevant to Trusted Web Activities. This
- * will allow us to concentrate on the use cases for the most used TWAs.
- */
-public abstract class UkmRecorder {
-    /**
-     * Records a TWA has been opened in given tab.
-     */
-    abstract void recordTwaOpened(Tab tab);
-
-    /**
-     * The actual recorder.
-     */
-    @JNINamespace("browserservices")
-    static class Bridge extends UkmRecorder {
-        @Override
-        public void recordTwaOpened(Tab tab) {
-            UkmRecorderJni.get().recordOpen(tab.getWebContents());
-        }
-    }
-
-    @NativeMethods
-    interface Natives {
-        void recordOpen(WebContents webContents);
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java
new file mode 100644
index 0000000..062783e
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.metrics;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * An interface and classes to record User Keyed Metrics.
+ */
+public abstract class UkmRecorder {
+    /**
+     * Records the occurrence of a (boolean) UKM event with name |eventName|.
+     * A UKM entry with |eventName| must be present in ukm.xml with a metric named `HasOccured`.
+     * For example,
+     * <event name="SomeFeature.SomeComponent">
+     * <owner>owner@chromium.org</owner>
+     * <summary>
+     * User triggered a specific feature.
+     * </summary>
+     * <metric name="HasOccurred" enum="Boolean">
+     * <summary>
+     * A boolean signaling that the event has occurred (typically only records
+     *  true values).
+     * </summary>
+     * </metric>
+     * </event>
+     */
+    abstract void recordEvent(WebContents webContents, String eventName);
+
+    /**
+     * The actual recorder.
+     */
+    @JNINamespace("metrics")
+    public static class Bridge extends UkmRecorder {
+        @Override
+        public void recordEvent(WebContents webContents, String eventName) {
+            UkmRecorderJni.get().recordEvent(webContents, eventName);
+        }
+    }
+
+    @NativeMethods
+    interface Natives {
+        void recordEvent(WebContents webContents, String eventName);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index e76f108..0fcc94ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -24,6 +24,8 @@
 import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
+import org.chromium.chrome.browser.metrics.UkmRecorder;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.vr.VrModeObserver;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -136,8 +138,10 @@
 
             mFindToolbarManager.showToolbar();
 
+            Tab tab = mActivity.getActivityTabProvider().get();
             if (fromMenu) {
                 RecordUserAction.record("MobileMenuFindInPage");
+                new UkmRecorder.Bridge().recordEvent(tab.getWebContents(), "MobileMenu.FindInPage");
             } else {
                 RecordUserAction.record("MobileShortcutFindInPage");
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index d6912e8..07d95bad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -1086,7 +1086,7 @@
                 }
             }
         }
-        return (toolbarHeight + mToolbarShadowHeight) / mContainerHeight;
+        return (toolbarHeight + mToolbarShadowHeight) / (float) mContainerHeight;
     }
 
     private View getToolbarView() {
@@ -1114,8 +1114,9 @@
         if (mContainerHeight <= 0) return 0;
         float customFullRatio =
                 mSheetContent != null ? mSheetContent.getCustomFullRatio() : INVALID_HEIGHT_RATIO;
-        return customFullRatio < 0 ? (mContainerHeight + mToolbarShadowHeight) / mContainerHeight
-                                   : customFullRatio;
+        return customFullRatio < 0
+                ? (mContainerHeight + mToolbarShadowHeight) / (float) mContainerHeight
+                : customFullRatio;
     }
 
     /**
@@ -1139,7 +1140,8 @@
             return;
         }
 
-        float screenRatio = mContainerHeight > 0 ? offsetWithBrowserControls / mContainerHeight : 0;
+        float screenRatio =
+                mContainerHeight > 0 ? offsetWithBrowserControls / (float) mContainerHeight : 0;
 
         // This ratio is relative to the peek and full positions of the sheet.
         float hiddenFullRatio = MathUtils.clamp(
@@ -1404,7 +1406,8 @@
 
     public boolean isSmallScreen() {
         // A small screen is defined by there being less than 160dp between half and full states.
-        float fullHeightRatio = (mContainerHeight + mToolbarShadowHeight) / mContainerHeight;
+        float fullHeightRatio =
+                (mContainerHeight + mToolbarShadowHeight) / (float) mContainerHeight;
         float fullToHalfDiff = (fullHeightRatio - HALF_HEIGHT_RATIO) * mContainerHeight;
         return fullToHalfDiff < mMinHalfFullDistance;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
index 5c4c5e62..107ef44 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
@@ -32,6 +32,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeSwitches;
@@ -179,7 +180,8 @@
      * in the intent).
      */
     @Test
-    @MediumTest
+    @DisabledTest(message = "crbug.com/1016743")
+    //@MediumTest
     @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP_MR1)
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
     public void testStatusBarColorCertificateError() throws ExecutionException, TimeoutException {
@@ -256,7 +258,8 @@
      * (and origin verification succeeds).
      */
     @Test
-    @MediumTest
+    @DisabledTest(message = "crbug.com/1016743")
+    //@MediumTest
     public void testToolbarVisibleCertificateError() throws ExecutionException, TimeoutException {
         final String pageWithoutCertError =
                 mEmbeddedTestServerRule.getServer().getURL("/chrome/test/data/android/about.html");
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/util/BitmapCacheTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/util/BitmapCacheTest.java
index 242b6fef..de065b13 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/util/BitmapCacheTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/util/BitmapCacheTest.java
@@ -97,13 +97,13 @@
         Assert.assertEquals(1, BitmapCache.dedupCacheSizeForTesting());
     }
 
-    /*
-     * {@link BitmapCache.clearDedupCacheForTesting} is supposed to be called in
+    /**
+     * {@link BitmapCache#clearDedupCacheForTesting} is supposed to be called in
      * setUp() only, and should not be called inside a @Test, or it would leak
      * too much implementation details. However, it is required to do it to
      * properly test the cache eviction without relying on GC. In order to make
-     * sure {@link testLowCapacity} tests what we want to test, this test verifies
-     * that calling {@link BitmapCache.clearDedupCacheForTesting} is not the reason
+     * sure {@link #testLowCapacity} tests what we want to test, this test verifies
+     * that calling {@link BitmapCache#clearDedupCacheForTesting} is not the reason
      * the cache returns null.
      */
     @Test
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 6039c304..d11ed97 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -429,7 +429,6 @@
     "//components/services/heap_profiling/public/mojom",
     "//components/translate/content/common",
     "//extensions/buildflags",
-    "//services/image_annotation/public/cpp:manifest",
     "//services/preferences/public/cpp:manifest",
     "//services/service_manager/public/cpp",
     "//third_party/blink/public/common",
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index 49bbeb4..1b8b431 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -35,9 +35,6 @@
 #include "components/safe_browsing/common/safe_browsing.mojom.h"
 #include "components/translate/content/common/translate.mojom.h"
 #include "extensions/buildflags/buildflags.h"
-#include "services/image_annotation/public/cpp/manifest.h"
-#include "services/image_annotation/public/mojom/constants.mojom.h"
-#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/preferences/public/cpp/manifest.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
 #include "third_party/blink/public/mojom/badging/badging.mojom.h"
@@ -111,8 +108,6 @@
         .RequireCapability("device", "device:geolocation_config")
         .RequireCapability("device", "device:geolocation_control")
         .RequireCapability("device", "device:ip_geolocator")
-        .RequireCapability(image_annotation::mojom::kServiceName,
-                           image_annotation::mojom::kAnnotationCapability)
         .RequireCapability("ime", "input_engine")
         .RequireCapability("mirroring", "mirroring")
         .RequireCapability("nacl_broker", "browser")
@@ -203,7 +198,6 @@
                 mojom::UsbInternalsPageHandler,
                 snippets_internals::mojom::PageHandlerFactory,
                 web_ui_test::mojom::TestRunner>())
-        .PackageService(image_annotation::GetManifest())
         .PackageService(prefs::GetManifest())
 #if defined(OS_CHROMEOS)
         .PackageService(chromeos::multidevice_setup::GetManifest())
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6e799437..a7fea0a 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1889,6 +1889,7 @@
     "//components/sync_device_info",
     "//content/public/browser",
     "//ipc",
+    "//services/image_annotation/public/mojom",
     "//sql",
   ]
   deps = [
@@ -2136,7 +2137,6 @@
     "//services/identity:lib",
     "//services/identity/public/cpp:cpp_types",
     "//services/image_annotation:service",
-    "//services/image_annotation/public/mojom",
     "//services/metrics/public/cpp:ukm_builders",
     "//services/network:network_service",
     "//services/network/public/cpp",
@@ -2239,7 +2239,6 @@
       "android/bookmarks/partner_bookmarks_shim.h",
       "android/bottombar/overlay_panel_content.cc",
       "android/bottombar/overlay_panel_content.h",
-      "android/browserservices/ukm_recorder.cc",
       "android/browsing_data/browsing_data_bridge.cc",
       "android/browsing_data/browsing_data_counter_bridge.cc",
       "android/browsing_data/browsing_data_counter_bridge.h",
@@ -2475,6 +2474,7 @@
       "android/metrics/background_task_memory_metrics_emitter.cc",
       "android/metrics/background_task_memory_metrics_emitter.h",
       "android/metrics/launch_metrics.cc",
+      "android/metrics/ukm_recorder.cc",
       "android/metrics/uma_session_stats.cc",
       "android/metrics/uma_session_stats.h",
       "android/metrics/uma_utils.cc",
@@ -5297,7 +5297,6 @@
     "//chrome/browser/engagement:mojo_bindings_js",
     "//chrome/browser/media:mojo_bindings_js",
     "//chrome/browser/resources/browser_switch:app_module",
-    "//chrome/browser/resources/safety_tips:make_safety_tips_protobuf",
     "//chrome/browser/resources/ssl/ssl_error_assistant:make_ssl_error_assistant_protobuf",
     "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings_js",
     "//chrome/browser/ui/webui/interventions_internals:mojo_bindings_js",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 263afab..dacb510 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -860,6 +860,22 @@
      base::size(kOmniboxDocumentProviderServerAndClientScoring), nullptr}};
 #endif  // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_WIN)
 
+#ifdef OS_ANDROID
+const FeatureEntry::FeatureParam kOmniboxNTPZPSLocal[] = {
+    {"ZeroSuggestVariant:7:*", "Local"},
+    {"ZeroSuggestVariant:8:*", "Local"}};
+
+const FeatureEntry::FeatureParam kOmniboxNTPZPSRemote[] = {
+    {"ZeroSuggestVariant:7:*", "RemoteNoUrl"},
+    {"ZeroSuggestVariant:8:*", "RemoteNoUrl"}};
+
+const FeatureEntry::FeatureVariation kOmniboxOnFocusSuggestionsVariations[] = {
+    {"ZPS on NTP: Local History", kOmniboxNTPZPSLocal,
+     base::size(kOmniboxNTPZPSLocal), nullptr},
+    {"ZPS on NTP: Remote History", kOmniboxNTPZPSRemote,
+     base::size(kOmniboxNTPZPSRemote), "t3314248"},
+};
+#else
 const FeatureEntry::FeatureParam kOmniboxOnFocusSuggestionsParamSERP[] = {
     {"ZeroSuggestVariant:6:*", "RemoteSendUrl"}};
 const FeatureEntry::FeatureParam
@@ -889,6 +905,7 @@
      base::size(kOmniboxOnFocusSuggestionsParamNTPOmniboxRealboxRemoteLocal),
      "t3316133" /* variation_id */},
 };
+#endif
 
 const FeatureEntry::FeatureParam kOmniboxUIMaxAutocompleteMatches3[] = {
     {OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "3"}};
diff --git a/chrome/browser/accessibility/image_annotation_browsertest.cc b/chrome/browser/accessibility/image_annotation_browsertest.cc
index d3860e2..e3ce5e3 100644
--- a/chrome/browser/accessibility/image_annotation_browsertest.cc
+++ b/chrome/browser/accessibility/image_annotation_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -18,21 +19,16 @@
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/service_manager_connection.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
 #include "services/image_annotation/public/cpp/image_processor.h"
-#include "services/image_annotation/public/mojom/constants.mojom.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/cpp/connector.h"
-#include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_enum_util.h"
@@ -162,39 +158,29 @@
 
 // The fake ImageAnnotationService, which handles mojo calls from the renderer
 // process and passes them to FakeAnnotator.
-class FakeImageAnnotationService : public service_manager::Service {
+class FakeImageAnnotationService
+    : public image_annotation::mojom::ImageAnnotationService {
  public:
-  explicit FakeImageAnnotationService(
-      service_manager::mojom::ServiceRequest request)
-      : service_binding_(this, std::move(request)) {}
-
+  FakeImageAnnotationService() = default;
   ~FakeImageAnnotationService() override = default;
 
  private:
-  // service_manager::Service:
-  void OnBindInterface(const service_manager::BindSourceInfo& source_info,
-                       const std::string& interface_name,
-                       mojo::ScopedMessagePipeHandle interface_pipe) override {
-    registry_.BindInterface(interface_name, std::move(interface_pipe));
+  // image_annotation::mojom::ImageAnnotationService:
+  void BindAnnotator(mojo::PendingReceiver<image_annotation::mojom::Annotator>
+                         receiver) override {
+    annotator_.BindReceiver(std::move(receiver));
   }
 
-  void OnStart() override {
-    registry_.AddInterface<image_annotation::mojom::Annotator>(
-        base::BindRepeating(&FakeAnnotator::BindReceiver,
-                            base::Unretained(&annotator_)));
-  }
-
-  service_manager::BinderRegistry registry_;
-  service_manager::ServiceBinding service_binding_;
-
   FakeAnnotator annotator_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeImageAnnotationService);
 };
 
-void HandleImageAnnotatorServiceRequest(
-    service_manager::mojom::ServiceRequest request) {
-  new FakeImageAnnotationService(std::move(request));
+void BindImageAnnotatorService(
+    mojo::PendingReceiver<image_annotation::mojom::ImageAnnotationService>
+        receiver) {
+  mojo::MakeSelfOwnedReceiver(std::make_unique<FakeImageAnnotationService>(),
+                              std::move(receiver));
 }
 
 }  // namespace
@@ -221,13 +207,8 @@
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
 
-    content::ServiceManagerConnection* service_manager_connection =
-        content::BrowserContext::GetServiceManagerConnectionFor(
-            web_contents->GetBrowserContext());
-
-    service_manager_connection->AddServiceRequestHandler(
-        image_annotation::mojom::kServiceName,
-        base::BindRepeating(&HandleImageAnnotatorServiceRequest));
+    ProfileImpl::OverrideImageAnnotationServiceBinderForTesting(
+        base::BindRepeating(&BindImageAnnotatorService));
 
     ui::AXMode mode = ui::kAXModeComplete;
     mode.set_mode(ui::AXMode::kLabelImages, true);
@@ -236,6 +217,12 @@
     SetAcceptLanguages("en,fr");
   }
 
+  void TearDownOnMainThread() override {
+    ProfileImpl::OverrideImageAnnotationServiceBinderForTesting(
+        base::NullCallback());
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
   void SetAcceptLanguages(const std::string& accept_languages) {
     content::BrowserContext* context =
         static_cast<content::BrowserContext*>(browser()->profile());
diff --git a/chrome/browser/android/browserservices/ukm_recorder.cc b/chrome/browser/android/browserservices/ukm_recorder.cc
deleted file mode 100644
index a2b75f9..0000000
--- a/chrome/browser/android/browserservices/ukm_recorder.cc
+++ /dev/null
@@ -1,26 +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 "services/metrics/public/cpp/ukm_recorder.h"
-#include "base/android/jni_android.h"
-#include "chrome/android/chrome_jni_headers/UkmRecorder_jni.h"
-#include "components/ukm/content/source_url_recorder.h"
-#include "content/public/browser/web_contents.h"
-#include "services/metrics/public/cpp/ukm_builders.h"
-
-namespace browserservices {
-
-// Called by Java org.chromium.chrome.browser.browserservices.UkmRecorder.
-static void JNI_UkmRecorder_RecordOpen(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& java_web_contents) {
-  content::WebContents* web_contents =
-      content::WebContents::FromJavaWebContents(java_web_contents);
-  ukm::SourceId source_id =
-      ukm::GetSourceIdForWebContentsDocument(web_contents);
-  ukm::builders::TrustedWebActivity_Open(source_id).Record(
-      ukm::UkmRecorder::Get());
-}
-
-}  // namespace browserservices
diff --git a/chrome/browser/android/compositor/layer/contextual_search_layer.cc b/chrome/browser/android/compositor/layer/contextual_search_layer.cc
index a67d38f39..98d30d0 100644
--- a/chrome/browser/android/compositor/layer/contextual_search_layer.cc
+++ b/chrome/browser/android/compositor/layer/contextual_search_layer.cc
@@ -130,11 +130,22 @@
       search_bar_border_height, search_bar_shadow_visible, icon_color,
       drag_handlebar_color, close_icon_opacity, separator_line_color);
 
+  // -----------------------------------------------------------------
+  // Content setup, to center in space below drag handle (when present).
+  // -----------------------------------------------------------------
   bool is_rtl = l10n_util::IsLayoutRtl();
+  int content_height = search_bar_height;
+  int content_top = search_bar_top;
+  bool is_overlay_new_layout =
+      rounded_bar_top_resource_id != kInvalidResourceID;
+  if (is_overlay_new_layout) {
+    content_top += search_bar_margin_top;
+    content_height -= search_bar_margin_top;
+  }
 
-  // ---------------------------------------------------------------------------
-  // Bar Banner
-  // ---------------------------------------------------------------------------
+  // -----------------------------------------------------------------
+  // Bar Banner -- obsolete.  TODO(donnd): remove.
+  // -----------------------------------------------------------------
   if (search_bar_banner_visible) {
     // Grabs the Bar Banner resource.
     ui::Resource* bar_banner_text_resource = resource_manager_->GetResource(
@@ -223,15 +234,15 @@
   // ---------------------------------------------------------------------------
   // Search Term, Context and Search Caption
   // ---------------------------------------------------------------------------
-  int text_layer_height = SetupTextLayer(
-      search_bar_top, search_bar_height, search_text_layer_min_height,
-      search_caption_resource_id, search_caption_visible,
-      search_caption_animation_percentage, search_term_opacity,
-      search_context_resource_id, search_context_opacity,
-      search_term_caption_spacing);
+  int text_layer_height =
+      SetupTextLayer(content_top, content_height, search_text_layer_min_height,
+                     search_caption_resource_id, search_caption_visible,
+                     search_caption_animation_percentage, search_term_opacity,
+                     search_context_resource_id, search_context_opacity,
+                     search_term_caption_spacing);
 
   // ---------------------------------------------------------------------------
-  // Arrow Icon
+  // Arrow Icon.  Deprecated -- old layout only.
   // ---------------------------------------------------------------------------
   // Grabs the arrow icon resource.
   ui::Resource* arrow_icon_resource =
@@ -320,7 +331,7 @@
       progress_bar_opacity, progress_bar_completion, search_panel_width);
 
   // ---------------------------------------------------------------------------
-  // Divider Line separator
+  // Divider Line separator.  Deprecated -- old layout only.
   // ---------------------------------------------------------------------------
   if (divider_line_visibility_percentage > 0.f) {
     if (divider_line_->parent() != layer_)
@@ -349,13 +360,12 @@
   if (touch_highlight_visible) {
     if (touch_highlight_layer_->parent() != layer_)
       layer_->AddChild(touch_highlight_layer_);
-    bool is_overlay_new_layout =
-        rounded_bar_top_resource_id != kInvalidResourceID;
+    // In the new layout don't highlight the whole bar due to rounded corners.
     int highlight_height =
         is_overlay_new_layout ? text_layer_height : search_bar_height;
-    int highlight_top = search_bar_top;
+    int highlight_top = content_top;
     highlight_top +=
-        is_overlay_new_layout ? (search_bar_height - text_layer_height) / 2 : 0;
+        is_overlay_new_layout ? (content_height - text_layer_height) / 2 : 0;
     gfx::Size background_size(touch_highlight_width, highlight_height);
     touch_highlight_layer_->SetBounds(background_size);
     touch_highlight_layer_->SetPosition(
@@ -466,8 +476,8 @@
       gfx::PointF(side_margin, custom_image_y_offset));
 }
 
-int ContextualSearchLayer::SetupTextLayer(float bar_top,
-                                          float bar_height,
+int ContextualSearchLayer::SetupTextLayer(float content_top,
+                                          float content_height,
                                           float search_text_layer_min_height,
                                           int caption_resource_id,
                                           bool caption_visible,
@@ -538,7 +548,7 @@
   float layer_width =
       std::max(main_text->bounds().width(), search_caption_->bounds().width());
 
-  float layer_top = bar_top + (bar_height - layer_height) / 2;
+  float layer_top = content_top + (content_height - layer_height) / 2;
   text_layer_->SetBounds(gfx::Size(layer_width, layer_height));
   text_layer_->SetPosition(gfx::PointF(0.f, layer_top));
   text_layer_->SetMasksToBounds(true);
diff --git a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
index cccae9c..02383dc 100644
--- a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
+++ b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.cc
@@ -86,7 +86,8 @@
     float progress_bar_height,
     float progress_bar_opacity,
     int progress_bar_completion,
-    int separator_line_color) {
+    int separator_line_color,
+    bool is_new_layout) {
   if (web_contents_ != web_contents) {
     web_contents_ = web_contents;
     if (web_contents_) {
@@ -112,12 +113,18 @@
       bar_shadow_visible, icon_color, drag_handlebar_color,
       1.0f /* icon opacity */, separator_line_color);
 
-  SetupTextLayer(bar_top, bar_height, text_layer_min_height,
+  // Content setup, to center in space below drag handle (when present).
+  int content_top = bar_top;
+  int content_height = bar_height;
+  if (is_new_layout) {
+    content_top += bar_margin_top;
+    content_height -= bar_margin_top;
+  }
+  SetupTextLayer(content_top, content_height, text_layer_min_height,
                  caption_view_resource_id, caption_icon_resource_id,
-                 caption_icon_opacity,
-
-                 caption_animation_percentage, caption_visible,
-                 title_view_resource_id, title_caption_spacing);
+                 caption_icon_opacity, caption_animation_percentage,
+                 caption_visible, title_view_resource_id,
+                 title_caption_spacing);
 
   OverlayPanelLayer::SetProgressBar(
       progress_bar_background_resource_id, progress_bar_resource_id,
@@ -133,8 +140,8 @@
   panel_icon_->SetOpacity(1 - favicon_opacity);
 }
 
-void EphemeralTabLayer::SetupTextLayer(float bar_top,
-                                       float bar_height,
+void EphemeralTabLayer::SetupTextLayer(float content_top,
+                                       float content_height,
                                        float text_layer_min_height,
                                        int caption_view_resource_id,
                                        int caption_icon_resource_id,
@@ -196,7 +203,7 @@
   float layer_width =
       std::max(title_->bounds().width(), caption_->bounds().width());
 
-  float layer_top = bar_top + (bar_height - layer_height) / 2;
+  float layer_top = content_top + (content_height - layer_height) / 2;
   text_layer_->SetBounds(gfx::Size(layer_width, layer_height));
   text_layer_->SetPosition(gfx::PointF(0.f, layer_top));
   text_layer_->SetMasksToBounds(true);
diff --git a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
index 897c861..fc701f95 100644
--- a/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
+++ b/chrome/browser/android/compositor/layer/ephemeral_tab_layer.h
@@ -78,7 +78,8 @@
                      float progress_bar_height,
                      float progress_bar_opacity,
                      int progress_bar_completion,
-                     int separator_line_color);
+                     int separator_line_color,
+                     bool is_new_layout);
   void SetupTextLayer(float bar_top,
                       float bar_height,
                       float text_layer_min_height,
diff --git a/chrome/browser/android/compositor/layer/overlay_panel_layer.cc b/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
index 454095f..a440a9e 100644
--- a/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
+++ b/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
@@ -115,7 +115,17 @@
     panel_shadow_->SetPosition(shadow_position);
   }
 
+  // ---------------------------------------------------------------------------
+  // Content setup, to center in space below drag handle (when present).
+  // ---------------------------------------------------------------------------
+  int content_top_y = bar_top_y;
+  int content_height = bar_height;
   int rounded_top_adjust = 0;
+  if (rounded_bar_top_resource_id_ != kInvalidResourceID) {
+    content_top_y += bar_margin_top;
+    content_height -= bar_margin_top;
+  }
+
   // ---------------------------------------------------------------------------
   // Rounded Bar Top
   // ---------------------------------------------------------------------------
@@ -172,9 +182,10 @@
       ui::ANDROID_RESOURCE_TYPE_DYNAMIC, bar_text_resource_id_);
 
   if (bar_text_resource) {
-    // Centers the text vertically in the Search Bar.
-    float bar_padding_top =
-        bar_top_y + bar_height / 2 - bar_text_resource->size().height() / 2;
+    // Centers the text vertically in the section of the Search Bar below the
+    // drag handle.
+    float bar_padding_top = content_top_y + content_height / 2 -
+                            bar_text_resource->size().height() / 2;
     bar_text_->SetUIResourceId(bar_text_resource->ui_resource()->id());
     bar_text_->SetBounds(bar_text_resource->size());
     bar_text_->SetPosition(gfx::PointF(0.f, bar_padding_top));
@@ -202,7 +213,7 @@
 
     // Centers the Icon vertically in the bar.
     float icon_y =
-        bar_top_y + bar_height / 2 - icon_layer->bounds().height() / 2;
+        content_top_y + content_height / 2 - icon_layer->bounds().height() / 2;
 
     icon_layer->SetPosition(gfx::PointF(icon_x, icon_y));
   }
@@ -252,8 +263,8 @@
     }
 
     // Centers the Close Icon vertically in the bar.
-    float close_icon_top =
-        bar_top_y + bar_height / 2 - close_icon_resource->size().height() / 2;
+    float close_icon_top = content_top_y + content_height / 2 -
+                           close_icon_resource->size().height() / 2;
     close_icon_->SetUIResourceId(close_icon_resource->ui_resource()->id());
     close_icon_->SetBounds(close_icon_resource->size());
     close_icon_->SetPosition(gfx::PointF(close_icon_left, close_icon_top));
@@ -268,7 +279,7 @@
         resource_manager_->GetStaticResourceWithTint(open_tab_icon_resource_id_,
                                                      icon_tint);
     // Positions the icon at the end of the bar.
-    float open_tab_top = bar_top_y + bar_height / 2 -
+    float open_tab_top = content_top_y + content_height / 2 -
                          open_tab_icon_resource->size().height() / 2;
     float open_tab_left;
     float spacing_between_icons = 2 * bar_margin_side;
@@ -287,7 +298,7 @@
   }
 
   // ---------------------------------------------------------------------------
-  // Content
+  // Overlay Web Content
   // ---------------------------------------------------------------------------
   content_container_->SetPosition(
       gfx::PointF(0.f, content_offset_y));
diff --git a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
index 1ec8e71..ee29af5 100644
--- a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.cc
@@ -31,7 +31,8 @@
                                                const JavaRef<jobject>& jobj)
     : SceneLayer(env, jobj),
       base_page_brightness_(1.0f),
-      content_container_(cc::Layer::Create()) {
+      content_container_(cc::Layer::Create()),
+      is_new_layout_(false) {
   // Responsible for moving the base page without modifying the layer itself.
   content_container_->SetIsDrawable(true);
   content_container_->SetPosition(gfx::PointF(0.0f, 0.0f));
@@ -66,6 +67,7 @@
                                             jint drag_handlebar_resource_id,
                                             jint open_tab_icon_resource_id,
                                             jint close_icon_resource_id) {
+  is_new_layout_ = rounded_bar_top_resource_id > 0;
   ephemeral_tab_layer_->SetResourceIds(
       text_resource_id, panel_shadow_resource_id, rounded_bar_top_resource_id,
       bar_shadow_resource_id, panel_icon_resource_id,
@@ -138,7 +140,7 @@
       bar_border_visible, bar_border_height, bar_shadow_visible, icon_color,
       drag_handlebar_color, favicon_opacity, progress_bar_visible,
       progress_bar_height, progress_bar_opacity, progress_bar_completion,
-      separator_line_color);
+      separator_line_color, is_new_layout_);
   // Make the layer visible if it is not already.
   ephemeral_tab_layer_->layer()->SetHideLayerAndSubtree(false);
 }
diff --git a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
index 4d24c69..48ecdf7 100644
--- a/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/ephemeral_tab_scene_layer.h
@@ -90,6 +90,7 @@
   scoped_refptr<EphemeralTabLayer> ephemeral_tab_layer_;
   scoped_refptr<cc::SolidColorLayer> color_overlay_;
   scoped_refptr<cc::Layer> content_container_;
+  bool is_new_layout_;
   DISALLOW_COPY_AND_ASSIGN(EphemeralTabSceneLayer);
 };
 
diff --git a/chrome/browser/android/metrics/ukm_recorder.cc b/chrome/browser/android/metrics/ukm_recorder.cc
new file mode 100644
index 0000000..d891309
--- /dev/null
+++ b/chrome/browser/android/metrics/ukm_recorder.cc
@@ -0,0 +1,31 @@
+// 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 "services/metrics/public/cpp/ukm_recorder.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "chrome/android/chrome_jni_headers/UkmRecorder_jni.h"
+#include "components/ukm/content/source_url_recorder.h"
+#include "content/public/browser/web_contents.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_entry_builder.h"
+
+namespace metrics {
+
+// Called by Java org.chromium.chrome.browser.metrics.UkmRecorder.
+static void JNI_UkmRecorder_RecordEvent(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& j_web_contents,
+    const base::android::JavaParamRef<jstring>& j_event_name) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(j_web_contents);
+  const ukm::SourceId source_id =
+      ukm::GetSourceIdForWebContentsDocument(web_contents);
+  const std::string event_name(ConvertJavaStringToUTF8(env, j_event_name));
+  ukm::UkmEntryBuilder builder(source_id, event_name);
+  builder.SetMetric("HasOccurred", true);
+  builder.Record(ukm::UkmRecorder::Get());
+}
+
+}  // namespace metrics
diff --git a/chrome/browser/apps/app_service/app_icon_source.cc b/chrome/browser/apps/app_service/app_icon_source.cc
index 33bb1f6..997b847 100644
--- a/chrome/browser/apps/app_service/app_icon_source.cc
+++ b/chrome/browser/apps/app_service/app_icon_source.cc
@@ -57,10 +57,11 @@
 }
 
 void AppIconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
-  std::string path_lower = base::ToLowerASCII(path);
+  const std::string path_lower =
+      base::ToLowerASCII(content::URLDataSource::URLToRequestPath(url));
   std::vector<std::string> path_parts = base::SplitString(
       path_lower, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
 
diff --git a/chrome/browser/apps/app_service/app_icon_source.h b/chrome/browser/apps/app_service/app_icon_source.h
index 7db8b0ca0..296036d0 100644
--- a/chrome/browser/apps/app_service/app_icon_source.h
+++ b/chrome/browser/apps/app_service/app_icon_source.h
@@ -37,7 +37,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string&) override;
diff --git a/chrome/browser/autocomplete/shortcuts_provider_extension_unittest.cc b/chrome/browser/autocomplete/shortcuts_provider_extension_unittest.cc
index d495578..817ac0ad1 100644
--- a/chrome/browser/autocomplete/shortcuts_provider_extension_unittest.cc
+++ b/chrome/browser/autocomplete/shortcuts_provider_extension_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
@@ -58,6 +59,7 @@
   void TearDown() override;
 
   content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList feature_list_;
   TestingProfile profile_;
   ChromeAutocompleteProviderClient client_;
   scoped_refptr<ShortcutsBackend> backend_;
@@ -68,6 +70,9 @@
     : client_(&profile_) {}
 
 void ShortcutsProviderExtensionTest::SetUp() {
+  feature_list_.InitWithFeatures(
+      {history::HistoryService::kHistoryServiceUsesTaskScheduler}, {});
+
   ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
       &profile_,
       base::BindRepeating(
@@ -85,6 +90,8 @@
   // Run all pending tasks or else some threads hold on to the message loop
   // and prevent it from being deleted.
   base::RunLoop().RunUntilIdle();
+
+  profile_.BlockUntilHistoryBackendDestroyed();
 }
 
 // Actual tests ---------------------------------------------------------------
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 3bf46cb7..f2cd5f3 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -116,17 +116,13 @@
         <include name="IDR_BROWSER_SWITCH_INTERNALS_JS" file="resources\browser_switch\internals\browser_switch_internals.js" compress="gzip" type="BINDATA" />
       </if>
       <if expr="is_win">
-        <include name="IDR_ABOUT_CONFLICTS_HTML" file="resources\conflicts\about_conflicts.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-        <include name="IDR_ABOUT_CONFLICTS_JS" file="resources\conflicts\about_conflicts.js" type="BINDATA" />
-      </if>
-      <if expr="enable_plugins">
-        <include name="IDR_ABOUT_FLASH_HTML" file="resources\about_flash.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-        <include name="IDR_ABOUT_FLASH_JS" file="resources\about_flash.js" type="BINDATA" />
+        <include name="IDR_ABOUT_CONFLICTS_HTML" file="resources\conflicts\about_conflicts.html" compress="gzip" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_ABOUT_CONFLICTS_JS" file="resources\conflicts\about_conflicts.js" compress="gzip" type="BINDATA" />
       </if>
       <if expr="not disable_nacl">
-        <include name="IDR_ABOUT_NACL_HTML" file="resources\about_nacl\about_nacl.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-        <include name="IDR_ABOUT_NACL_CSS" file="resources\about_nacl\about_nacl.css" flattenhtml="true" type="chrome_html" />
-        <include name="IDR_ABOUT_NACL_JS" file="resources\about_nacl\about_nacl.js" type="BINDATA" />
+        <include name="IDR_ABOUT_NACL_HTML" file="resources\about_nacl\about_nacl.html" compress="gzip" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_ABOUT_NACL_CSS" file="resources\about_nacl\about_nacl.css" compress="gzip" flattenhtml="true" type="chrome_html" />
+        <include name="IDR_ABOUT_NACL_JS" file="resources\about_nacl\about_nacl.js" compress="gzip" type="BINDATA" />
       </if>
       <if expr="not is_android">
         <include name="IDR_ABOUT_SYS_HTML" file="resources\about_sys\about_sys.html" compress="gzip" type="BINDATA" />
@@ -138,9 +134,9 @@
       <include name="IDR_ACCESSIBILITY_JS" file="resources\accessibility\accessibility.js" flattenhtml="true" compress="gzip" type="BINDATA" />
       <include name="IDR_AD_NETWORK_HASHES" file="resources\ad_networks.dat" type="BINDATA" />
       <if expr="is_posix and not is_macosx">
-        <include name="IDR_CERTIFICATE_VIEWER_HTML" file="resources\certificate_viewer.html" type="BINDATA" />
-        <include name="IDR_CERTIFICATE_VIEWER_JS" file="resources\certificate_viewer.js" type="BINDATA" />
-        <include name="IDR_CERTIFICATE_VIEWER_CSS" file="resources\certificate_viewer.css" type="BINDATA" />
+        <include name="IDR_CERTIFICATE_VIEWER_HTML" file="resources\certificate_viewer.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_CERTIFICATE_VIEWER_JS" file="resources\certificate_viewer.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_CERTIFICATE_VIEWER_CSS" file="resources\certificate_viewer.css" compress="gzip" type="BINDATA" />
       </if>
       <if expr="enable_app_list">
         <include name="IDR_CHROME_APP_MANIFEST" file="resources\chrome_app\manifest.json" type="BINDATA" />
@@ -219,13 +215,13 @@
 
       <if expr="not is_android">
         <!-- History. -->
-        <include name="IDR_HISTORY_CONSTANTS_HTML" file="resources\history\constants.html" type="BINDATA" />
-        <include name="IDR_HISTORY_CONSTANTS_JS" file="resources\history\constants.js" type="BINDATA" />
+        <include name="IDR_HISTORY_CONSTANTS_HTML" file="resources\history\constants.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_HISTORY_CONSTANTS_JS" file="resources\history\constants.js" compress="gzip" type="BINDATA" />
         <include name="IDR_HISTORY_HISTORY_HTML" file="resources\history\history.html" preprocess="true" type="BINDATA" compress="gzip" />
-        <include name="IDR_HISTORY_HISTORY_JS" file="resources\history\history.js" type="BINDATA" />
-        <include name="IDR_HISTORY_IMAGES_SIGN_IN_PROMO_DARK_SVG" file="resources\history\images\sign_in_promo_dark.svg" type="BINDATA" />
-        <include name="IDR_HISTORY_IMAGES_SIGN_IN_PROMO_SVG" file="resources\history\images\sign_in_promo.svg" type="BINDATA" />
-        <include name="IDR_HISTORY_STRINGS_HTML" file="resources\history\strings.html" type="BINDATA" />
+        <include name="IDR_HISTORY_HISTORY_JS" file="resources\history\history.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_HISTORY_IMAGES_SIGN_IN_PROMO_DARK_SVG" file="resources\history\images\sign_in_promo_dark.svg" compress="gzip" type="BINDATA" />
+        <include name="IDR_HISTORY_IMAGES_SIGN_IN_PROMO_SVG" file="resources\history\images\sign_in_promo.svg" compress="gzip" type="BINDATA" />
+        <include name="IDR_HISTORY_STRINGS_HTML" file="resources\history\strings.html" compress="gzip" type="BINDATA" />
         <if expr="optimize_webui">
           <then>
             <include name="IDR_HISTORY_APP_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\history\app.vulcanized.html" preprocess="true" type="BINDATA" compress="gzip" use_base_dir="false" />
@@ -418,7 +414,7 @@
             <include name="IDR_INTERNET_DETAIL_DIALOG_JS" file="resources\chromeos\internet_detail_dialog\internet_detail_dialog.js" type="chrome_html" />
           </else>
         </if>
-        <include name="IDR_CERT_MANAGER_DIALOG_HTML" file="resources\chromeos\certificate_manager_dialog.html" flattenhtml="true" type="BINDATA" />
+        <include name="IDR_CERT_MANAGER_DIALOG_HTML" file="resources\chromeos\certificate_manager_dialog.html" flattenhtml="true" type="BINDATA" compress="gzip" />
         <include name="IDR_SLOW_CSS" file="resources\chromeos\slow.css" type="BINDATA" compress="gzip" />
         <include name="IDR_SLOW_HTML" file="resources\chromeos\slow.html" type="BINDATA" compress="gzip" />
         <include name="IDR_SLOW_JS" file="resources\chromeos\slow.js" type="BINDATA" compress="gzip" />
@@ -453,17 +449,17 @@
         <include name="IDR_USER_MANAGER_TUTORIAL_JS" file="resources\user_manager\user_manager_tutorial.js" type="BINDATA" />
       </if>
       <if expr="not is_android">
-        <include name="IDR_IDENTITY_INTERNALS_HTML" file="resources\identity_internals\identity_internals.html" type="BINDATA" />
-        <include name="IDR_IDENTITY_INTERNALS_CSS" file="resources\identity_internals\identity_internals.css" type="BINDATA" />
-        <include name="IDR_IDENTITY_INTERNALS_JS" file="resources\identity_internals\identity_internals.js" type="BINDATA" />
+        <include name="IDR_IDENTITY_INTERNALS_HTML" file="resources\identity_internals\identity_internals.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_IDENTITY_INTERNALS_CSS" file="resources\identity_internals\identity_internals.css" compress="gzip" type="BINDATA" />
+        <include name="IDR_IDENTITY_INTERNALS_JS" file="resources\identity_internals\identity_internals.js" compress="gzip" type="BINDATA" />
       </if>
       <include name="IDR_DEVICE_LOG_UI_HTML" file="resources\device_log_ui\device_log_ui.html" type="BINDATA" compress="gzip" />
       <include name="IDR_DEVICE_LOG_UI_JS" file="resources\device_log_ui\device_log_ui.js" type="BINDATA" compress="gzip" />
       <include name="IDR_DEVICE_LOG_UI_CSS" file="resources\device_log_ui\device_log_ui.css" type="BINDATA" compress="gzip" />
       <if expr="chromeos">
-        <include name="IDR_NETWORK_UI_HTML" file="resources\chromeos\network_ui\network_ui.html" type="BINDATA" />
-        <include name="IDR_NETWORK_UI_JS" file="resources\chromeos\network_ui\network_ui.js" type="BINDATA" />
-        <include name="IDR_NETWORK_UI_CSS" file="resources\chromeos\network_ui\network_ui.css" type="BINDATA" />
+        <include name="IDR_NETWORK_UI_HTML" file="resources\chromeos\network_ui\network_ui.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_NETWORK_UI_JS" file="resources\chromeos\network_ui\network_ui.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_NETWORK_UI_CSS" file="resources\chromeos\network_ui\network_ui.css" compress="gzip" type="BINDATA" />
       </if>
       <if expr="_google_chrome">
         <include name="IDR_PREF_HASH_SEED_BIN" file="resources\settings_internal\pref_hash_seed.bin" type="BINDATA" />
@@ -497,22 +493,22 @@
         <include name="IDR_SOUND_DICTATION_CANCEL_WAV" file="resources\chromeos\sounds\earcons\null_selection.wav" type="BINDATA" />
       </if>
      <if expr="chromeos">
-        <include name="IDR_ABOUT_POWER_HTML" file="resources\chromeos\power.html" type="BINDATA" />
-        <include name="IDR_ABOUT_POWER_JS" file="resources\chromeos\power.js" type="BINDATA" />
-        <include name="IDR_ABOUT_POWER_CSS" file="resources\chromeos\power.css" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_HTML" file="resources\chromeos\emulator\device_emulator.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_AUDIO_SETTINGS_HTML" file="resources\chromeos\emulator\audio_settings.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_AUDIO_SETTINGS_JS" file="resources\chromeos\emulator\audio_settings.js" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_BATTERY_SETTINGS_HTML" file="resources\chromeos\emulator\battery_settings.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_BATTERY_SETTINGS_JS" file="resources\chromeos\emulator\battery_settings.js" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_BLUETOOTH_SETTINGS_HTML" file="resources\chromeos\emulator\bluetooth_settings.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_BLUETOOTH_SETTINGS_JS" file="resources\chromeos\emulator\bluetooth_settings.js" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_ICONS_HTML" file="resources\chromeos\emulator\icons.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_INPUT_DEVICE_SETTINGS_HTML" file="resources\chromeos\emulator\input_device_settings.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_INPUT_DEVICE_SETTINGS_JS" file="resources\chromeos\emulator\input_device_settings.js" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_PAGES_HTML" file="resources\chromeos\emulator\device_emulator_pages.html" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_PAGES_JS" file="resources\chromeos\emulator\device_emulator_pages.js" type="BINDATA" />
-        <include name="IDR_DEVICE_EMULATOR_SHARED_STYLES_HTML" file="resources\chromeos\emulator\shared_styles.html" type="BINDATA" />
+        <include name="IDR_ABOUT_POWER_HTML" file="resources\chromeos\power.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_ABOUT_POWER_JS" file="resources\chromeos\power.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_ABOUT_POWER_CSS" file="resources\chromeos\power.css" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_HTML" file="resources\chromeos\emulator\device_emulator.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_AUDIO_SETTINGS_HTML" file="resources\chromeos\emulator\audio_settings.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_AUDIO_SETTINGS_JS" file="resources\chromeos\emulator\audio_settings.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_BATTERY_SETTINGS_HTML" file="resources\chromeos\emulator\battery_settings.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_BATTERY_SETTINGS_JS" file="resources\chromeos\emulator\battery_settings.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_BLUETOOTH_SETTINGS_HTML" file="resources\chromeos\emulator\bluetooth_settings.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_BLUETOOTH_SETTINGS_JS" file="resources\chromeos\emulator\bluetooth_settings.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_ICONS_HTML" file="resources\chromeos\emulator\icons.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_INPUT_DEVICE_SETTINGS_HTML" file="resources\chromeos\emulator\input_device_settings.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_INPUT_DEVICE_SETTINGS_JS" file="resources\chromeos\emulator\input_device_settings.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_PAGES_HTML" file="resources\chromeos\emulator\device_emulator_pages.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_PAGES_JS" file="resources\chromeos\emulator\device_emulator_pages.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_DEVICE_EMULATOR_SHARED_STYLES_HTML" file="resources\chromeos\emulator\shared_styles.html" compress="gzip" type="BINDATA" />
       </if>
       <if expr="chromeos">
         <include name="IDR_SET_TIME_HTML" file="resources\chromeos\set_time_dialog\set_time.html" type="BINDATA" compress="gzip" />
@@ -537,9 +533,9 @@
         <include name="IDR_BRAILLE_MANIFEST" file="resources\chromeos\braille_ime\manifest.json" type="BINDATA" />
       </if>
       <if expr="not is_android">
-        <include name="IDR_MEDIA_ROUTER_INTERNALS_HTML" file="resources\media_router\media_router_internals.html" type="BINDATA" />
-        <include name="IDR_MEDIA_ROUTER_INTERNALS_CSS" file="resources\media_router\media_router_internals.css" type="BINDATA" />
-        <include name="IDR_MEDIA_ROUTER_INTERNALS_JS" file="resources\media_router\media_router_internals.js" type="BINDATA" />
+        <include name="IDR_MEDIA_ROUTER_INTERNALS_HTML" file="resources\media_router\media_router_internals.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_MEDIA_ROUTER_INTERNALS_CSS" file="resources\media_router\media_router_internals.css" compress="gzip" type="BINDATA" />
+        <include name="IDR_MEDIA_ROUTER_INTERNALS_JS" file="resources\media_router\media_router_internals.js" compress="gzip" type="BINDATA" />
       </if>
       <if expr="chromeos or is_win or is_macosx">
         <include name="IDR_CAST_HTML" file="resources\cast\cast.html" type="BINDATA" />
@@ -570,12 +566,12 @@
         <include name="IDR_SMB_CREDENTIALS_DIALOG_CONTAINER_HTML" file="resources\chromeos\smb_shares\smb_credentials_dialog_container.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" />
         <include name="IDR_SMB_CREDENTIALS_DIALOG_HTML" file="resources\chromeos\smb_shares\smb_credentials_dialog.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" />
         <include name="IDR_SMB_CREDENTIALS_DIALOG_JS" file="resources\chromeos\smb_shares\smb_credentials_dialog.js" type="chrome_html" />
-        <include name="IDR_SYS_INTERNALS_HTML" file="resources\chromeos\sys_internals\index.html" type="BINDATA" />
-        <include name="IDR_SYS_INTERNALS_CSS" file="resources\chromeos\sys_internals\index.css" type="BINDATA" />
-        <include name="IDR_SYS_INTERNALS_JS" file="resources\chromeos\sys_internals\index.js" type="BINDATA" />
-        <include name="IDR_SYS_INTERNALS_CONSTANT_JS" file="resources\chromeos\sys_internals\constants.js" type="BINDATA" />
-        <include name="IDR_SYS_INTERNALS_LINE_CHART_CSS" file="resources\chromeos\sys_internals\line_chart\line_chart.css" type="BINDATA" />
-        <include name="IDR_SYS_INTERNALS_LINE_CHART_JS" file="resources\chromeos\sys_internals\line_chart\index.js" flattenhtml="true" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_HTML" file="resources\chromeos\sys_internals\index.html" compress="gzip" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_CSS" file="resources\chromeos\sys_internals\index.css" compress="gzip" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_JS" file="resources\chromeos\sys_internals\index.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_CONSTANT_JS" file="resources\chromeos\sys_internals\constants.js" compress="gzip" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_LINE_CHART_CSS" file="resources\chromeos\sys_internals\line_chart\line_chart.css" compress="gzip" type="BINDATA" />
+        <include name="IDR_SYS_INTERNALS_LINE_CHART_JS" file="resources\chromeos\sys_internals\line_chart\index.js" compress="gzip" flattenhtml="true" type="BINDATA" />
         <include name="IDR_SYS_INTERNALS_IMAGE_MENU_SVG" file="resources\chromeos\sys_internals\img\menu.svg" type="BINDATA" />
         <include name="IDR_SYS_INTERNALS_IMAGE_INFO_SVG" file="resources\chromeos\sys_internals\img\info.svg" type="BINDATA" />
         <include name="IDR_SYS_INTERNALS_IMAGE_CPU_SVG" file="resources\chromeos\sys_internals\img\cpu.svg" type="BINDATA" />
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index b28aa792..eccc37c 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -7,15 +7,14 @@
 #include "base/feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/insecure_sensitive_input_driver_factory.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_features.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "services/image_annotation/public/mojom/constants.mojom-forward.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
-#include "services/service_manager/public/cpp/connector.h"
 #include "third_party/blink/public/mojom/insecure_input/insecure_input_service.mojom.h"
 #include "third_party/blink/public/mojom/loader/navigation_predictor.mojom.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -36,13 +35,13 @@
 namespace chrome {
 namespace internal {
 
-// Forward image Annotator requests to the image_annotation service.
+// Forward image Annotator requests to the profile's ImageAnnotationService.
 void BindImageAnnotator(
     content::RenderFrameHost* const frame_host,
     mojo::PendingReceiver<image_annotation::mojom::Annotator> receiver) {
-  content::BrowserContext::GetConnectorFor(
-      frame_host->GetProcess()->GetBrowserContext())
-      ->Connect(image_annotation::mojom::kServiceName, std::move(receiver));
+  Profile::FromBrowserContext(frame_host->GetProcess()->GetBrowserContext())
+      ->GetImageAnnotationService()
+      ->BindAnnotator(std::move(receiver));
 }
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/chrome_service_worker_browsertest.cc b/chrome/browser/chrome_service_worker_browsertest.cc
index a0bf424..d8ee3bc 100644
--- a/chrome/browser/chrome_service_worker_browsertest.cc
+++ b/chrome/browser/chrome_service_worker_browsertest.cc
@@ -697,7 +697,7 @@
 
   // content::URLDataSource:
   std::string GetSource() override { return source_; }
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const content::WebContents::Getter& wc_getter,
                         const GotDataCallback& callback) override {
     std::string data(content_);
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 24bc3bc7..32aef26 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -1913,7 +1913,9 @@
 
   void SendTextQuery(const std::string& query, bool allow_tts) {
     // Start text interaction with Assistant server.
-    assistant_->StartTextInteraction(query, allow_tts);
+    assistant_->StartTextInteraction(
+        query, chromeos::assistant::mojom::AssistantQuerySource::kUnspecified,
+        allow_tts);
 
     query_status_->SetKey("queryText", base::Value(query));
   }
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest_base.cc
index e4ccdc3..c48bc46 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest_base.cc
@@ -42,9 +42,10 @@
   std::string GetSource() override { return "file_manager_test"; }
 
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override {
+    const std::string path = content::URLDataSource::URLToRequestPath(url);
     base::PostTask(FROM_HERE,
                    {base::ThreadPool(), base::MayBlock(),
                     base::TaskPriority::USER_BLOCKING},
diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
index 62b30c4..beaf3a3 100644
--- a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
@@ -19,6 +19,7 @@
 #include "base/sequenced_task_runner.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/login/helper.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
 #include "chrome/browser/chromeos/login/users/affiliation.h"
@@ -51,6 +52,8 @@
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "url/gurl.h"
@@ -187,8 +190,9 @@
   // for creating the invalidator for user remote commands. The invalidator must
   // not be initialized before then because the invalidation service cannot be
   // started because it depends on components initialized at the end of profile
-  // creation. https://crbug.com/171406
-  observed_profile_manager_.Add(g_browser_process->profile_manager());
+  // creation.
+  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED,
+                 content::Source<Profile>(profile));
 }
 
 void UserCloudPolicyManagerChromeOS::ForceTimeoutForTest() {
@@ -209,7 +213,7 @@
   system_url_loader_factory_for_tests_ = system_url_loader_factory;
 }
 
-UserCloudPolicyManagerChromeOS::~UserCloudPolicyManagerChromeOS() = default;
+UserCloudPolicyManagerChromeOS::~UserCloudPolicyManagerChromeOS() {}
 
 void UserCloudPolicyManagerChromeOS::Connect(
     PrefService* local_state,
@@ -348,7 +352,6 @@
 }
 
 void UserCloudPolicyManagerChromeOS::Shutdown() {
-  observed_profile_manager_.RemoveAll();
   app_install_event_log_uploader_.reset();
   if (client())
     client()->RemoveObserver(this);
@@ -732,11 +735,16 @@
                                 policy_prefs::kUserPolicyRefreshRate);
 }
 
-void UserCloudPolicyManagerChromeOS::OnProfileAdded(Profile* profile) {
-  if (profile != profile_)
-    return;
+void UserCloudPolicyManagerChromeOS::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  DCHECK_EQ(chrome::NOTIFICATION_PROFILE_ADDED, type);
 
-  observed_profile_manager_.RemoveAll();
+  // Now that the profile is fully created we can unsubscribe from the
+  // notification.
+  registrar_.Remove(this, chrome::NOTIFICATION_PROFILE_ADDED,
+                    content::Source<Profile>(profile_));
 
   // If true FCMInvalidationService will be used as invalidation service and
   // TiclInvalidationService otherwise.
diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h
index 0bbd1fe..87c1d4ec 100644
--- a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h
+++ b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h
@@ -14,12 +14,9 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
-#include "base/scoped_observer.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/chromeos/policy/wildcard_login_checker.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/profiles/profile_manager_observer.h"
 #include "components/account_id/account_id.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
@@ -27,6 +24,8 @@
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_manager.h"
 #include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
 
 class GoogleServiceAuthError;
 class PrefService;
@@ -52,7 +51,8 @@
 class UserCloudPolicyManagerChromeOS : public CloudPolicyManager,
                                        public CloudPolicyClient::Observer,
                                        public CloudPolicyService::Observer,
-                                       public ProfileManagerObserver {
+                                       public content::NotificationObserver,
+                                       public KeyedService {
  public:
   // Enum describing what behavior we want to enforce here.
   enum class PolicyEnforcement {
@@ -223,10 +223,12 @@
   // call it multiple times.
   void StartRefreshSchedulerIfReady();
 
-  // ProfileManagerObserver:
-  void OnProfileAdded(Profile* profile) override;
+  // content::NotificationObserver:
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override;
 
-  // Called on profile shutdown.
+  // Observer called on profile shutdown.
   void ProfileShutdown();
 
   // Profile associated with the current user.
@@ -286,6 +288,9 @@
   // injected in the constructor to make it easier to write tests.
   base::OnceClosure fatal_error_callback_;
 
+  // Used to register for notification that profile creation is complete.
+  content::NotificationRegistrar registrar_;
+
   // Invalidator used for remote commands to be delivered to this user.
   std::unique_ptr<RemoteCommandsInvalidator> invalidator_;
 
@@ -299,9 +304,6 @@
   scoped_refptr<network::SharedURLLoaderFactory>
       signin_url_loader_factory_for_tests_;
 
-  ScopedObserver<ProfileManager, ProfileManagerObserver>
-      observed_profile_manager_{this};
-
   // Refresh token used in tests instead of the user context refresh token to
   // fetch the policy OAuth token.
   base::Optional<std::string> user_context_refresh_token_for_tests_;
diff --git a/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chrome/browser/devtools/devtools_sanity_browsertest.cc
index c290c153..daa7291 100644
--- a/chrome/browser/devtools/devtools_sanity_browsertest.cc
+++ b/chrome/browser/devtools/devtools_sanity_browsertest.cc
@@ -2140,7 +2140,7 @@
 
   // content::URLDataSource:
   std::string GetSource() override { return source_; }
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const content::WebContents::Getter& wc_getter,
                         const GotDataCallback& callback) override {
     std::string data(content_);
diff --git a/chrome/browser/download/android/BUILD.gn b/chrome/browser/download/android/BUILD.gn
index 74285f8..fa4aa2a 100644
--- a/chrome/browser/download/android/BUILD.gn
+++ b/chrome/browser/download/android/BUILD.gn
@@ -5,10 +5,14 @@
 import("//build/config/android/rules.gni")
 
 android_library("java") {
-  java_files =
-      [ "java/src/org/chromium/chrome/browser/download/DownloadFilter.java" ]
+  java_files = [
+    "java/src/org/chromium/chrome/browser/download/DirectoryOption.java",
+    "java/src/org/chromium/chrome/browser/download/DownloadFilter.java",
+    "java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java",
+  ]
 
   deps = [
     "//base:base_java",
+    "//content/public/android:content_java",
   ]
 }
diff --git a/chrome/browser/download/android/DEPS b/chrome/browser/download/android/DEPS
index 17bf55f..077462f 100644
--- a/chrome/browser/download/android/DEPS
+++ b/chrome/browser/download/android/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+media/video",
+  "+content/public/android/java/src/org/chromium/content_public",
 ]
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DirectoryOption.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DirectoryOption.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/download/DirectoryOption.java
rename to chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DirectoryOption.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java
rename to chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java
index 17a48fe..1cda82d5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadDirectoryProvider.java
@@ -131,8 +131,9 @@
                 hasAddtionalDirectory = true;
             }
 
-            if (hasAddtionalDirectory)
+            if (hasAddtionalDirectory) {
                 recordDirectoryType(DirectoryOption.DownloadLocationDirectoryType.ADDITIONAL);
+            }
 
             return dirs;
         }
@@ -203,7 +204,7 @@
     private AllDirectoriesTask mAllDirectoriesTask;
     private ArrayList<DirectoryOption> mDirectoryOptions;
     private String mExternalStorageDirectory;
-    private ArrayList < Callback < ArrayList<DirectoryOption>>> mCallbacks = new ArrayList<>();
+    private ArrayList<Callback<ArrayList<DirectoryOption>>> mCallbacks = new ArrayList<>();
 
     protected DownloadDirectoryProvider() {
         registerSDCardReceiver();
diff --git a/chrome/browser/download/download_ui_controller.cc b/chrome/browser/download/download_ui_controller.cc
index 8171021..7f770f0 100644
--- a/chrome/browser/download/download_ui_controller.cc
+++ b/chrome/browser/download/download_ui_controller.cc
@@ -156,6 +156,10 @@
           "Security.SafetyTips.DownloadStarted",
           security_state_tab_helper->GetVisibleSecurityState()
               ->safety_tip_info.status);
+      UMA_HISTOGRAM_BOOLEAN(
+          "Security.LegacyTLS.DownloadStarted",
+          security_state::GetLegacyTLSWarningStatus(
+              *security_state_tab_helper->GetVisibleSecurityState()));
     }
   }
 
diff --git a/chrome/browser/download/download_ui_controller_unittest.cc b/chrome/browser/download/download_ui_controller_unittest.cc
index 7a1f58d..eccc6d1 100644
--- a/chrome/browser/download/download_ui_controller_unittest.cc
+++ b/chrome/browser/download/download_ui_controller_unittest.cc
@@ -13,29 +13,34 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/download/download_core_service_factory.h"
 #include "chrome/browser/download/download_core_service_impl.h"
 #include "chrome/browser/download/download_history.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "chrome/browser/ssl/tls_deprecation_test_utils.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/download/public/common/mock_download_item.h"
 #include "components/history/core/browser/download_row.h"
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/test/mock_download_manager.h"
+#include "content/public/test/navigation_simulator.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gmock_mutant.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using download::MockDownloadItem;
 using content::MockDownloadManager;
+using download::MockDownloadItem;
 using history::HistoryService;
+using testing::_;
 using testing::AnyNumber;
 using testing::Assign;
 using testing::CreateFunctor;
 using testing::Return;
+using testing::ReturnRef;
 using testing::ReturnRefOfCopy;
 using testing::SaveArg;
-using testing::_;
 
 namespace {
 
@@ -386,4 +391,82 @@
   EXPECT_EQ(static_cast<download::DownloadItem*>(item.get()), notified_item());
 }
 
+TEST_F(DownloadUIControllerTest, LegacyTLSMetrics) {
+  base::HistogramTester histograms;
+  SecurityStateTabHelper::CreateForWebContents(web_contents());
+  InitializeEmptyLegacyTLSConfig();
+
+  auto navigation =
+      CreateLegacyTLSNavigation(GURL(kLegacyTLSDefaultURL), web_contents());
+  navigation->Commit();
+
+  // Start a download from the same page, setting up the mock item to correctly
+  // associate with the WebContents of the previous navigation.
+  std::unique_ptr<MockDownloadItem> item(CreateMockInProgressDownload());
+  GURL download_url("https://download.test/file.bin");
+  EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(download_url));
+  EXPECT_CALL(*item, GetOriginalUrl()).WillRepeatedly(ReturnRef(download_url));
+  content::DownloadItemUtils::AttachInfo(item.get(), browser_context(),
+                                         web_contents());
+
+  DownloadUIController controller(manager(), GetTestDelegate());
+
+  ASSERT_TRUE(manager_observer());
+  manager_observer()->OnDownloadCreated(manager(), item.get());
+
+  histograms.ExpectUniqueSample("Security.LegacyTLS.DownloadStarted", true, 1);
+}
+
+TEST_F(DownloadUIControllerTest, LegacyTLSControlSiteMetrics) {
+  base::HistogramTester histograms;
+  SecurityStateTabHelper::CreateForWebContents(web_contents());
+  InitializeLegacyTLSConfigWithControl();
+
+  auto navigation =
+      CreateLegacyTLSNavigation(GURL(kLegacyTLSControlURL), web_contents());
+  navigation->Commit();
+
+  // Start a download from the same page, setting up the mock item to correctly
+  // associate with the WebContents of the previous navigation.
+  std::unique_ptr<MockDownloadItem> item(CreateMockInProgressDownload());
+  GURL download_url("https://download.test/file.bin");
+  EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(download_url));
+  EXPECT_CALL(*item, GetOriginalUrl()).WillRepeatedly(ReturnRef(download_url));
+  content::DownloadItemUtils::AttachInfo(item.get(), browser_context(),
+                                         web_contents());
+
+  DownloadUIController controller(manager(), GetTestDelegate());
+
+  ASSERT_TRUE(manager_observer());
+  manager_observer()->OnDownloadCreated(manager(), item.get());
+
+  histograms.ExpectUniqueSample("Security.LegacyTLS.DownloadStarted", false, 1);
+}
+
+TEST_F(DownloadUIControllerTest, LegacyTLSGoodSiteMetrics) {
+  base::HistogramTester histograms;
+  SecurityStateTabHelper::CreateForWebContents(web_contents());
+  InitializeEmptyLegacyTLSConfig();
+
+  auto navigation =
+      CreateNonlegacyTLSNavigation(GURL("https://good.test"), web_contents());
+  navigation->Commit();
+
+  // Start a download from the same page, setting up the mock item to correctly
+  // associate with the WebContents of the previous navigation.
+  std::unique_ptr<MockDownloadItem> item(CreateMockInProgressDownload());
+  GURL download_url("https://download.test/file.bin");
+  EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(download_url));
+  EXPECT_CALL(*item, GetOriginalUrl()).WillRepeatedly(ReturnRef(download_url));
+  content::DownloadItemUtils::AttachInfo(item.get(), browser_context(),
+                                         web_contents());
+
+  DownloadUIController controller(manager(), GetTestDelegate());
+
+  ASSERT_TRUE(manager_observer());
+  manager_observer()->OnDownloadCreated(manager(), item.get());
+
+  histograms.ExpectUniqueSample("Security.LegacyTLS.DownloadStarted", false, 1);
+}
+
 } // namespace
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 07871d2..e520e8a 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -1362,8 +1362,14 @@
   // does not show in the omnibox until it commits.  This avoids URL spoofs
   // since URLs can be opened on behalf of untrusted content.
   load_params.is_renderer_initiated = true;
+  // All renderer-initiated navigations need to have an initiator origin.
   load_params.initiator_origin = url::Origin::Create(
       Extension::GetBaseURLFromExtensionId(extension()->id()));
+  // |source_site_instance| needs to be set so that a renderer process
+  // compatible with |initiator_origin| is picked by Site Isolation.
+  load_params.source_site_instance = content::SiteInstance::CreateForURL(
+      web_contents_->GetBrowserContext(),
+      load_params.initiator_origin->GetURL());
 
   web_contents_->GetController().LoadURLWithParams(load_params);
 
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index a1c9759e..92f9f7c 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -2318,4 +2318,138 @@
   }
 }
 
+// Tests updating a URL of a web tab to an about:blank.  Verify that the new
+// frame is placed in the correct process, has the correct origin and that no
+// DCHECKs are hit anywhere.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabsUpdate_WebToAboutBlank) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const extensions::Extension* extension =
+      LoadExtension(test_data_dir_.AppendASCII("../simple_with_file"));
+  ASSERT_TRUE(extension);
+  GURL extension_url = extension->GetResourceURL("file.html");
+  url::Origin extension_origin = url::Origin::Create(extension_url);
+  GURL web_url = embedded_test_server()->GetURL("/title1.html");
+  url::Origin web_origin = url::Origin::Create(web_url);
+  GURL about_blank_url = GURL(url::kAboutBlankURL);
+
+  // Navigate a tab to an extension page.
+  ui_test_utils::NavigateToURL(browser(), extension_url);
+  content::WebContents* extension_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_EQ(extension_origin,
+            extension_contents->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Create another tab and navigate it to a web page.
+  content::WebContents* test_contents = nullptr;
+  {
+    content::WebContentsAddedObserver test_contents_observer;
+    content::TestNavigationObserver nav_observer(nullptr);
+    nav_observer.StartWatchingNewWebContents();
+    content::ExecuteScriptAsync(
+        extension_contents,
+        content::JsReplace("window.open($1, '_blank')", web_url));
+
+    test_contents = test_contents_observer.GetWebContents();
+    nav_observer.WaitForNavigationFinished();
+    EXPECT_TRUE(nav_observer.last_navigation_succeeded());
+    EXPECT_EQ(web_url, nav_observer.last_navigation_url());
+  }
+  EXPECT_EQ(web_origin,
+            test_contents->GetMainFrame()->GetLastCommittedOrigin());
+  EXPECT_NE(extension_contents->GetMainFrame()->GetProcess(),
+            test_contents->GetMainFrame()->GetProcess());
+
+  // Use |chrome.tabs.update| API to navigate |test_contents| to an about:blank
+  // URL.
+  {
+    content::TestNavigationObserver nav_observer(test_contents, 1);
+    int test_tab_id = ExtensionTabUtil::GetTabId(test_contents);
+    content::ExecuteScriptAsync(
+        extension_contents,
+        content::JsReplace("chrome.tabs.update($1, { url: $2 })", test_tab_id,
+                           about_blank_url));
+    nav_observer.WaitForNavigationFinished();
+    EXPECT_TRUE(nav_observer.last_navigation_succeeded());
+    EXPECT_EQ(about_blank_url, nav_observer.last_navigation_url());
+  }
+
+  // Verify the origin and process of the about:blank tab.
+  content::RenderFrameHost* test_frame = test_contents->GetMainFrame();
+  EXPECT_EQ(about_blank_url, test_frame->GetLastCommittedURL());
+  EXPECT_EQ(extension_contents->GetMainFrame()->GetProcess(),
+            test_contents->GetMainFrame()->GetProcess());
+
+  // The expectations below preserve the behavior at r704251.  It is not clear
+  // whether these are the right expectations - maybe about:blank should commit
+  // with an extension origin?  OTOH, committing with the extension origin
+  // wouldn't be possible when targeting an incognito window (see also
+  // IncognitoApiTest.Incognito test).
+  EXPECT_TRUE(test_frame->GetLastCommittedOrigin().opaque());
+  EXPECT_EQ(
+      extension_origin.GetTupleOrPrecursorTupleIfOpaque(),
+      test_frame->GetLastCommittedOrigin().GetTupleOrPrecursorTupleIfOpaque());
+}
+
+// Tests updating a URL of a web tab to a non-web-accessible-resource of an
+// extension - such navigation should be allowed.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabsUpdate_WebToNonWAR) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const extensions::Extension* extension =
+      LoadExtension(test_data_dir_.AppendASCII("../simple_with_file"));
+  ASSERT_TRUE(extension);
+  GURL extension_url = extension->GetResourceURL("file.html");
+  url::Origin extension_origin = url::Origin::Create(extension_url);
+  GURL web_url = embedded_test_server()->GetURL("/title1.html");
+  url::Origin web_origin = url::Origin::Create(web_url);
+  GURL non_war_url = extension_url;
+
+  // Navigate a tab to an extension page.
+  ui_test_utils::NavigateToURL(browser(), extension_url);
+  content::WebContents* extension_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_EQ(extension_origin,
+            extension_contents->GetMainFrame()->GetLastCommittedOrigin());
+
+  // Create another tab and navigate it to a web page.
+  content::WebContents* test_contents = nullptr;
+  {
+    content::WebContentsAddedObserver test_contents_observer;
+    content::TestNavigationObserver nav_observer(nullptr);
+    nav_observer.StartWatchingNewWebContents();
+    content::ExecuteScriptAsync(
+        extension_contents,
+        content::JsReplace("window.open($1, '_blank')", web_url));
+    test_contents = test_contents_observer.GetWebContents();
+    nav_observer.WaitForNavigationFinished();
+
+    EXPECT_TRUE(nav_observer.last_navigation_succeeded());
+    EXPECT_EQ(web_url, nav_observer.last_navigation_url());
+  }
+  EXPECT_EQ(web_origin,
+            test_contents->GetMainFrame()->GetLastCommittedOrigin());
+  EXPECT_NE(extension_contents->GetMainFrame()->GetProcess(),
+            test_contents->GetMainFrame()->GetProcess());
+
+  // Use |chrome.tabs.update| API to navigate |test_contents| to a
+  // non-web-accessible-resource of an extension.
+  {
+    content::TestNavigationObserver nav_observer(test_contents, 1);
+    int test_tab_id = ExtensionTabUtil::GetTabId(test_contents);
+    content::ExecuteScriptAsync(
+        extension_contents,
+        content::JsReplace("chrome.tabs.update($1, { url: $2 })", test_tab_id,
+                           non_war_url));
+    nav_observer.WaitForNavigationFinished();
+    EXPECT_TRUE(nav_observer.last_navigation_succeeded());
+    EXPECT_EQ(non_war_url, nav_observer.last_navigation_url());
+  }
+
+  // Verify the origin and process of the navigated tab.
+  content::RenderFrameHost* test_frame = test_contents->GetMainFrame();
+  EXPECT_EQ(non_war_url, test_frame->GetLastCommittedURL());
+  EXPECT_EQ(extension_origin, test_frame->GetLastCommittedOrigin());
+  EXPECT_EQ(extension_contents->GetMainFrame()->GetProcess(),
+            test_contents->GetMainFrame()->GetProcess());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/lookalikes/safety_tips/safety_tips.proto b/chrome/browser/lookalikes/safety_tips/safety_tips.proto
index ecc337a..caec9307 100644
--- a/chrome/browser/lookalikes/safety_tips/safety_tips.proto
+++ b/chrome/browser/lookalikes/safety_tips/safety_tips.proto
@@ -32,12 +32,11 @@
 message SafetyTipsConfig {
   optional uint32 version_id = 1;
 
-  // List of flagged pages on which to show an experimental Safety Tip UX that
-  // warns the user about possible unwanted behavior or deception.
-  // This needs to be sorted and can contain duplicates.
+  // List of pages on which to show the Safety Tip UX. This must be sorted and
+  // may contain duplicate patterns (when flagged with multiple FlagTypes).
   repeated FlaggedPage flagged_page = 2;
 
-  // List of URL patterns that must be explicitly allowed. This needs to be
-  // sorted and can contain duplicates.
+  // List of patterns that are explicitly allowed. This must be sorted. Used to
+  // mitigate false positives.
   repeated UrlPattern allowed_pattern = 3;
 }
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index 7430c9f..7e63e08c 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -210,7 +210,7 @@
         system_media_permissions::CheckSystemScreenCapturePermission() !=
             system_media_permissions::SystemPermission::kAllowed) {
       request_result =
-          blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+          blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
     }
 #endif
     if (request_result == blink::mojom::MediaStreamRequestResult::OK) {
diff --git a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
index ba029ab7..beee7794 100644
--- a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
+++ b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
@@ -166,27 +166,46 @@
 }
 
 // Heuristic to check screen capture permission on macOS 10.15.
-// See https://crbug.com/993692#c3.
+// Screen Capture is considered allowed if the name of at least one normal,
+// dock or status window running on another process is visible.
+// See https://crbug.com/993692.
 bool IsScreenCaptureAllowed() {
   if (@available(macOS 10.15, *)) {
     if (!base::FeatureList::IsEnabled(
             features::kMacSystemScreenCapturePermissionCheck)) {
       return true;
     }
+
     base::ScopedCFTypeRef<CFArrayRef> window_list(CGWindowListCopyWindowInfo(
         kCGWindowListOptionOnScreenOnly, kCGNullWindowID));
-    NSUInteger num_windows = CFArrayGetCount(window_list);
-    NSUInteger num_windows_with_name = 0;
-    for (NSDictionary* dict in base::mac::CFToNSCast(window_list.get())) {
-      if ([dict objectForKey:base::mac::CFToNSCast(kCGWindowName)]) {
-        num_windows_with_name++;
-      } else {
-        // No kCGWindowName detected implies no permission.
-        break;
+    int current_pid = [[NSProcessInfo processInfo] processIdentifier];
+    for (NSDictionary* window in base::mac::CFToNSCast(window_list.get())) {
+      NSNumber* window_pid =
+          [window objectForKey:base::mac::CFToNSCast(kCGWindowOwnerPID)];
+      if (!window_pid || [window_pid integerValue] == current_pid)
+        continue;
+
+      NSString* window_name =
+          [window objectForKey:base::mac::CFToNSCast(kCGWindowName)];
+      if (!window_name)
+        continue;
+
+      NSNumber* layer =
+          [window objectForKey:base::mac::CFToNSCast(kCGWindowLayer)];
+      if (!layer)
+        continue;
+
+      NSInteger layer_integer = [layer integerValue];
+      if (layer_integer == CGWindowLevelForKey(kCGNormalWindowLevelKey) ||
+          layer_integer == CGWindowLevelForKey(kCGDockWindowLevelKey) ||
+          layer_integer == CGWindowLevelForKey(kCGStatusWindowLevelKey)) {
+        return true;
       }
     }
-    return num_windows == num_windows_with_name;
+    return false;
   }
+
+  // Screen capture is always allowed in older macOS versions.
   return true;
 }
 
diff --git a/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.cc
index 1309bbe..6242c21 100644
--- a/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.cc
@@ -123,6 +123,11 @@
   base::UmaHistogramEnumeration(kSecurityLevelOnCommit, initial_security_level_,
                                 security_state::SECURITY_LEVEL_COUNT);
 
+  base::UmaHistogramBoolean(
+      "Security.LegacyTLS.OnCommit",
+      security_state::GetLegacyTLSWarningStatus(
+          *security_state_tab_helper_->GetVisibleSecurityState()));
+
   source_id_ = source_id;
   return CONTINUE_OBSERVING;
 }
@@ -200,6 +205,20 @@
                                                 safety_tip_status),
       GetDelegate().GetVisibilityTracker().GetForegroundDuration(),
       base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100);
+
+  // Record Legacy TLS UMA histograms.
+  base::UmaHistogramEnumeration(
+      security_state::GetLegacyTLSHistogramName(
+          kPageEndReasonPrefix,
+          *security_state_tab_helper_->GetVisibleSecurityState()),
+      GetDelegate().GetPageEndReason(),
+      page_load_metrics::PAGE_END_REASON_COUNT);
+  base::UmaHistogramCustomTimes(
+      security_state::GetLegacyTLSHistogramName(
+          kTimeOnPagePrefix,
+          *security_state_tab_helper_->GetVisibleSecurityState()),
+      GetDelegate().GetVisibilityTracker().GetForegroundDuration(),
+      base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100);
 }
 
 void SecurityStatePageLoadMetricsObserver::DidChangeVisibleSecurityState() {
diff --git a/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer_unittest.cc
new file mode 100644
index 0000000..a9af717
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.h"
+
+#include <memory>
+
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
+#include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "chrome/browser/ssl/tls_deprecation_config.h"
+#include "chrome/browser/ssl/tls_deprecation_config.pb.h"
+#include "chrome/browser/ssl/tls_deprecation_test_utils.h"
+#include "components/page_load_metrics/browser/page_load_tracker.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/navigation_simulator.h"
+#include "ui/base/scoped_visibility_tracker.h"
+
+class SecurityStatePageLoadMetricsObserverTest
+    : public page_load_metrics::PageLoadMetricsObserverTestHarness {
+ public:
+  SecurityStatePageLoadMetricsObserverTest()
+      : page_load_metrics::PageLoadMetricsObserverTestHarness() {
+    clock_ = std::make_unique<base::SimpleTestTickClock>();
+    clock_->SetNowTicks(base::TimeTicks::Now());
+  }
+  ~SecurityStatePageLoadMetricsObserverTest() override {}
+
+  void SetUp() override {
+    page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp();
+    SecurityStateTabHelper::CreateForWebContents(web_contents());
+    helper_ = SecurityStateTabHelper::FromWebContents(web_contents());
+  }
+
+  void AdvancePageDuration(base::TimeDelta delta) { clock_->Advance(delta); }
+
+ protected:
+  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
+    // Currently does not support testing Site Engagement metrics.
+    tracker->AddObserver(
+        std::make_unique<SecurityStatePageLoadMetricsObserver>(nullptr));
+
+    // Swap out the ui::ScopedVisibilityTracker to use the test clock.
+    ui::ScopedVisibilityTracker visibility_tracker(clock_.get(), true);
+    tracker->SetVisibilityTrackerForTesting(visibility_tracker);
+  }
+
+  SecurityStateTabHelper* security_state_tab_helper() { return helper_; }
+
+ private:
+  SecurityStateTabHelper* helper_;
+
+  // The clock used by the ui::ScopedVisibilityTracker.
+  std::unique_ptr<base::SimpleTestTickClock> clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(SecurityStatePageLoadMetricsObserverTest);
+};
+
+TEST_F(SecurityStatePageLoadMetricsObserverTest, LegacyTLSMetrics) {
+  InitializeEmptyLegacyTLSConfig();
+
+  auto navigation =
+      CreateLegacyTLSNavigation(GURL(kLegacyTLSDefaultURL), web_contents());
+  navigation->Commit();
+
+  tester()->histogram_tester().ExpectBucketCount("Security.LegacyTLS.OnCommit",
+                                                 true, 1);
+
+  const base::TimeDelta kMinForegroundTime =
+      base::TimeDelta::FromMilliseconds(10);
+  AdvancePageDuration(kMinForegroundTime);
+
+  navigation->Reload(web_contents());
+
+  tester()->histogram_tester().ExpectBucketCount(
+      "Security.PageEndReason.LegacyTLS_Triggered",
+      page_load_metrics::END_RELOAD, 1);
+
+  auto samples = tester()->histogram_tester().GetAllSamples(
+      "Security.TimeOnPage2.LegacyTLS_Triggered");
+  EXPECT_EQ(1u, samples.size());
+  EXPECT_LE(kMinForegroundTime.InMilliseconds(), samples.front().min);
+}
+
+TEST_F(SecurityStatePageLoadMetricsObserverTest, LegacyTLSControlSiteMetrics) {
+  InitializeLegacyTLSConfigWithControl();
+
+  auto navigation =
+      CreateLegacyTLSNavigation(GURL(kLegacyTLSControlURL), web_contents());
+  navigation->Commit();
+
+  tester()->histogram_tester().ExpectBucketCount("Security.LegacyTLS.OnCommit",
+                                                 false, 1);
+
+  const base::TimeDelta kMinForegroundTime =
+      base::TimeDelta::FromMilliseconds(10);
+  AdvancePageDuration(kMinForegroundTime);
+
+  navigation->Reload(web_contents());
+
+  tester()->histogram_tester().ExpectBucketCount(
+      "Security.PageEndReason.LegacyTLS_NotTriggered",
+      page_load_metrics::END_RELOAD, 1);
+
+  auto samples = tester()->histogram_tester().GetAllSamples(
+      "Security.TimeOnPage2.LegacyTLS_NotTriggered");
+  EXPECT_EQ(1u, samples.size());
+  EXPECT_LE(kMinForegroundTime.InMilliseconds(), samples.front().min);
+}
+
+TEST_F(SecurityStatePageLoadMetricsObserverTest, LegacyTLSGoodSiteMetrics) {
+  InitializeEmptyLegacyTLSConfig();
+
+  auto navigation =
+      CreateNonlegacyTLSNavigation(GURL("https://good.test"), web_contents());
+  navigation->Commit();
+
+  tester()->histogram_tester().ExpectBucketCount("Security.LegacyTLS.OnCommit",
+                                                 false, 1);
+
+  const base::TimeDelta kMinForegroundTime =
+      base::TimeDelta::FromMilliseconds(10);
+  AdvancePageDuration(kMinForegroundTime);
+
+  navigation->Reload(web_contents());
+
+  tester()->histogram_tester().ExpectBucketCount(
+      "Security.PageEndReason.LegacyTLS_NotTriggered",
+      page_load_metrics::END_RELOAD, 1);
+
+  auto samples = tester()->histogram_tester().GetAllSamples(
+      "Security.TimeOnPage2.LegacyTLS_NotTriggered");
+  EXPECT_EQ(1u, samples.size());
+  EXPECT_LE(kMinForegroundTime.InMilliseconds(), samples.front().min);
+}
diff --git a/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc b/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
index 9f85bdd..5a7c05c 100644
--- a/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_manager_captured_sites_interactive_uitest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/tab_dialogs.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/password_manager/core/browser/test_password_store.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "content/public/test/test_utils.h"
 
 using captured_sites_test_utils::CapturedSiteParams;
@@ -173,6 +174,8 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kUsernameFirstFlow}, {});
     InProcessBrowserTest::SetUpCommandLine(command_line);
     captured_sites_test_utils::TestRecipeReplayer::SetUpCommandLine(
         command_line);
@@ -204,6 +207,7 @@
  private:
   std::unique_ptr<captured_sites_test_utils::TestRecipeReplayer>
       recipe_replayer_;
+  base::test::ScopedFeatureList feature_list_;
   content::WebContents* web_contents_ = nullptr;
   std::unique_ptr<ServerUrlLoader> server_url_loader_;
 
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index 9e0d25b..7f74320 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -293,6 +293,11 @@
   return nullptr;
 }
 
+image_annotation::mojom::ImageAnnotationService*
+Profile::GetImageAnnotationService() {
+  return nullptr;
+}
+
 bool Profile::IsNewProfile() {
 #if !defined(OS_ANDROID)
   // The profile is new if the preference files has just been created, except on
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h
index 756fa14..17eb57b 100644
--- a/chrome/browser/profiles/profile.h
+++ b/chrome/browser/profiles/profile.h
@@ -45,6 +45,12 @@
 }  // namespace mojom
 }  // namespace identity
 
+namespace image_annotation {
+namespace mojom {
+class ImageAnnotationService;
+}
+}  // namespace image_annotation
+
 namespace policy {
 class SchemaRegistryService;
 class ProfilePolicyConnector;
@@ -388,6 +394,11 @@
   // null if the profile does not have a corresponding service instance.
   virtual identity::mojom::IdentityService* GetIdentityService();
 
+  // Exposes access to the profile's Image Annotation service instance. This may
+  // return null if the profile does not have a corresponding service instance.
+  virtual image_annotation::mojom::ImageAnnotationService*
+  GetImageAnnotationService();
+
   // Stop sending accessibility events until ResumeAccessibilityEvents().
   // Calls to Pause nest; no events will be sent until the number of
   // Resume calls matches the number of Pause calls received.
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 0343e41..6edddcf 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -23,6 +23,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
+#include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_number_conversions.h"
@@ -150,6 +151,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/shared_cors_origin_access_list.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/browser/system_connector.h"
 #include "content/public/browser/url_data_source.h"
 #include "content/public/common/content_constants.h"
 #include "extensions/buildflags/buildflags.h"
@@ -158,9 +160,9 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "printing/buildflags/buildflags.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/identity/identity_service.h"
 #include "services/image_annotation/image_annotation_service.h"
-#include "services/image_annotation/public/mojom/constants.mojom.h"
 #include "services/network/public/cpp/features.h"
 #include "services/preferences/public/cpp/in_process_service_factory.h"
 #include "services/preferences/public/mojom/preferences.mojom.h"
@@ -337,6 +339,28 @@
 }
 #endif  // defined(OS_CHROMEOS)
 
+class ImageAnnotatorClient : public image_annotation::Annotator::Client {
+ public:
+  ImageAnnotatorClient() = default;
+  ~ImageAnnotatorClient() override = default;
+
+  // image_annotation::Annotator::Client implementation:
+  void BindJsonParser(mojo::PendingReceiver<data_decoder::mojom::JsonParser>
+                          receiver) override {
+    content::GetSystemConnector()->Connect(data_decoder::mojom::kServiceName,
+                                           std::move(receiver));
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ImageAnnotatorClient);
+};
+
+ProfileImpl::ImageAnnotationServiceBinder&
+GetImageAnnotationServiceBinderOverride() {
+  static base::NoDestructor<ProfileImpl::ImageAnnotationServiceBinder> binder;
+  return *binder;
+}
+
 }  // namespace
 
 // static
@@ -1075,6 +1099,24 @@
   return remote_identity_service_.get();
 }
 
+image_annotation::mojom::ImageAnnotationService*
+ProfileImpl::GetImageAnnotationService() {
+  if (!remote_image_annotation_service_) {
+    auto receiver =
+        remote_image_annotation_service_.BindNewPipeAndPassReceiver();
+    const auto& binder = GetImageAnnotationServiceBinderOverride();
+    if (binder) {
+      binder.Run(std::move(receiver));
+    } else {
+      image_annotation_service_impl_ =
+          std::make_unique<image_annotation::ImageAnnotationService>(
+              std::move(receiver), APIKeyForChannel(), GetURLLoaderFactory(),
+              std::make_unique<ImageAnnotatorClient>());
+    }
+  }
+  return remote_image_annotation_service_.get();
+}
+
 PrefService* ProfileImpl::GetPrefs() {
   return const_cast<PrefService*>(
       static_cast<const ProfileImpl*>(this)->GetPrefs());
@@ -1295,11 +1337,6 @@
         ->CreatePrefService(std::move(request));
   }
 
-  if (service_name == image_annotation::mojom::kServiceName) {
-    return std::make_unique<image_annotation::ImageAnnotationService>(
-        std::move(request), APIKeyForChannel(), GetURLLoaderFactory());
-  }
-
 #if defined(OS_CHROMEOS)
   if (service_name == chromeos::multidevice_setup::mojom::kServiceName) {
     chromeos::android_sms::AndroidSmsService* android_sms_service =
@@ -1474,6 +1511,12 @@
   creation_time_ = creation_time;
 }
 
+// static
+void ProfileImpl::OverrideImageAnnotationServiceBinderForTesting(
+    ImageAnnotationServiceBinder binder) {
+  GetImageAnnotationServiceBinderOverride() = std::move(binder);
+}
+
 GURL ProfileImpl::GetHomePage() {
   // --homepage overrides any preferences.
   const base::CommandLine& command_line =
diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h
index 10d7388..4a4479c 100644
--- a/chrome/browser/profiles/profile_impl.h
+++ b/chrome/browser/profiles/profile_impl.h
@@ -26,6 +26,7 @@
 #include "extensions/buildflags/buildflags.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/identity/public/mojom/identity_service.mojom.h"
+#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 
 #if !defined(OS_ANDROID)
 #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h"
@@ -52,6 +53,10 @@
 class IdentityService;
 }
 
+namespace image_annotation {
+class ImageAnnotationService;
+}
+
 namespace policy {
 class ConfigurationPolicyProvider;
 class ProfilePolicyConnector;
@@ -166,6 +171,8 @@
   bool ShouldRestoreOldSessionCookies() override;
   bool ShouldPersistSessionCookies() override;
   identity::mojom::IdentityService* GetIdentityService() override;
+  image_annotation::mojom::ImageAnnotationService* GetImageAnnotationService()
+      override;
 
 #if defined(OS_CHROMEOS)
   void ChangeAppLocale(const std::string& locale, AppLocaleChangedVia) override;
@@ -175,6 +182,13 @@
 
   void SetCreationTimeForTesting(base::Time creation_time) override;
 
+  // Overrides how the ImageAnnotationService service instance is bound in a
+  // testing environment. If |binder| is null, any existing override is removed.
+  using ImageAnnotationServiceBinder = base::RepeatingCallback<void(
+      mojo::PendingReceiver<image_annotation::mojom::ImageAnnotationService>)>;
+  static void OverrideImageAnnotationServiceBinderForTesting(
+      ImageAnnotationServiceBinder binder);
+
  private:
 #if defined(OS_CHROMEOS)
   friend class chromeos::KioskTest;
@@ -247,6 +261,12 @@
   // |GetIdentityService()|.
   mojo::Remote<identity::mojom::IdentityService> remote_identity_service_;
 
+  // The Image Annotation service instance for this profile.
+  std::unique_ptr<image_annotation::ImageAnnotationService>
+      image_annotation_service_impl_;
+  mojo::Remote<image_annotation::mojom::ImageAnnotationService>
+      remote_image_annotation_service_;
+
   // !!! BIG HONKING WARNING !!!
   //  The order of the members below is important. Do not change it unless
   //  you know what you're doing. Also, if adding a new member here make sure
diff --git a/chrome/browser/resources/about_flash.html b/chrome/browser/resources/about_flash.html
deleted file mode 100644
index cd19013..0000000
--- a/chrome/browser/resources/about_flash.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
-<head>
-<meta charset="utf-8">
-<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-<style>
-.key {
-  font-weight: bold;
-}
-
-.value {
-  margin-left: 15px;
-}
-</style>
-</head>
-<body>
-<div id="loading-message">$i18n{loadingMessage}</div>
-<div id="body-container" style="visibility:hidden">
-  <div id="header"><h1>$i18n{flashLongTitle}</h1></div>
-
-  <div id="flashInfoTemplate">
-    <table cellpadding="2" cellspacing="0" border="0">
-      <tr jsselect="flashInfo">
-        <td><span dir="ltr" jscontent="key" class="key">KEY</span></td>
-        <td><span dir="ltr" jscontent="value" class="value">VALUE</span></td>
-      </tr>
-    </table>
-  </div>
-</div>
-<script src="chrome://flash/about_flash.js"></script>
-<script src="chrome://resources/js/jstemplate_compiled.js"></script>
-<script src="chrome://resources/js/util.js"></script>
-</body>
-</html>
diff --git a/chrome/browser/resources/about_flash.js b/chrome/browser/resources/about_flash.js
deleted file mode 100644
index cbcb627..0000000
--- a/chrome/browser/resources/about_flash.js
+++ /dev/null
@@ -1,38 +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.
-
-/**
- * Takes the |moduleListData| input argument which represents data about
- * the currently available modules and populates the html jstemplate
- * with that data. It expects an object structure like the above.
- * @param {Object} moduleListData Information about available modules
- */
-function renderTemplate(moduleListData) {
-  // This is the javascript code that processes the template:
-  const input = new JsEvalContext(moduleListData);
-  const output = $('flashInfoTemplate');
-  jstProcess(input, output);
-}
-
-/**
- * Asks the C++ FlashUIDOMHandler to get details about the Flash and return
- * the data in returnFlashInfo() (below).
- */
-function requestFlashInfo() {
-  chrome.send('requestFlashInfo');
-}
-
-/**
- * Called by the WebUI to re-populate the page with data representing the
- * current state of Flash.
- * @param {Object} moduleListData Information about available modules.
- */
-function returnFlashInfo(moduleListData) {
-  $('loading-message').style.visibility = 'hidden';
-  $('body-container').style.visibility = 'visible';
-  renderTemplate(moduleListData);
-}
-
-// Get data and have it displayed upon loading.
-document.addEventListener('DOMContentLoaded', requestFlashInfo);
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json b/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json
index f049fa2..4b86e1d 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/keymaps/next_keymap.json
@@ -1042,6 +1042,15 @@
           "keyCode": [65, 67]
         }
       }
+    },
+     {
+      "command": "readLinkURL",
+      "sequence": {
+        "cvoxModifier": true,
+        "keys": {
+          "keyCode": [65, 76]
+        }
+      }
     }
   ]
 }
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index 89071ed..b5c964a 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -2119,3 +2119,21 @@
         .replay();
   });
 });
+
+TEST_F('ChromeVoxBackgroundTest', 'ReadLinkURLTest', function() {
+  var mockFeedback = this.createMockFeedback();
+  this.runWithLoadedTree(function() {/*!
+    <a href="https://www.google.com/">A popular link</a>
+    <button>Not a link</button>
+  */}, function(root) {
+    mockFeedback.call(doCmd('nextLink'))
+        .expectSpeech('A popular link', 'Link', 'Press Search+Space to activate.')
+        .call(doCmd('readLinkURL'))
+        .expectSpeech('Link URL: https://www.google.com/')
+        .call(doCmd('nextObject'))
+        .expectSpeech('Not a link', 'Button', 'Press Search+Space to activate.')
+        .call(doCmd('readLinkURL'))
+        .expectSpeech('No URL found')
+        .replay();
+  });
+});
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
index f1d7e439..f6920d0 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
@@ -971,6 +971,23 @@
         }
       }
       return false;
+    case 'readLinkURL':
+      var node = ChromeVoxState.instance.currentRange.start.node;
+      var rootNode = node.root;
+      while (node && !node.url) {
+        // URL could be an ancestor of current range.
+        node = node.parent;
+      }
+      // Announce node's URL if it's not the root node; we don't want to
+      // announce the URL of the current page.
+      var url = (node && node !== rootNode) ? node.url : '';
+      new Output()
+          .withString(
+              url ? Msgs.getMsg('url_behind_link', [url]) :
+                    Msgs.getMsg('no_url_found'))
+          .withQueueMode(cvox.QueueMode.CATEGORY_FLUSH)
+          .go();
+      return false;
     default:
       return true;
   }
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index 60b38c2..d587c42 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -3745,6 +3745,9 @@
       <message desc="Announced when there is no available voice for a language." name="IDS_CHROMEVOX_VOICE_UNAVAILABLE_FOR_LANGUAGE">
         No voice available for language: <ph name="language">$1<ex>English</ex></ph>
       </message>
+      <message desc="Used to describe the link behind a url." name="IDS_CHROMEVOX_URL_BEHIND_LINK">
+        Link URL: <ph name="link_url">$1</ph>
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/resources/chromeos/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/switch_access/BUILD.gn
index 34d9973..3de463a 100644
--- a/chrome/browser/resources/chromeos/switch_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/switch_access/BUILD.gn
@@ -33,7 +33,6 @@
     "../select_to_speak/closure_shim.js",
     "//third_party/chromevox/third_party/closure-library/closure/goog/base.js",
     "auto_scan_manager.js",
-    "back_button_manager.js",
     "background.js",
     "commands.js",
     "event_helper.js",
@@ -70,12 +69,16 @@
     "menu_panel.js",
     "menu_panel_interface.js",
     "navigation_manager.js",
+    "nodes/back_button_node.js",
+    "nodes/keyboard_node.js",
+    "nodes/node_wrapper.js",
+    "nodes/switch_access_node.js",
+    "nodes/system_menu_node.js",
     "preferences.js",
     "rect_helper.js",
     "switch_access.js",
     "switch_access_constants.js",
     "switch_access_predicate.js",
-    "text_input_manager.js",
     "text_navigation_manager.js",
   ]
   rewrite_rules = [
@@ -173,22 +176,25 @@
 js_type_check("closure_compile") {
   deps = [
     ":auto_scan_manager",
-    ":back_button_manager",
+    ":back_button_node",
     ":background",
     ":commands",
     ":event_helper",
     ":focus_ring_manager",
+    ":keyboard_node",
     ":menu_manager",
     ":menu_panel",
     ":menu_panel_interface",
     ":navigation_manager",
+    ":node_wrapper",
     ":preferences",
     ":rect_helper",
     ":switch_access",
     ":switch_access_constants",
     ":switch_access_interface",
+    ":switch_access_node",
     ":switch_access_predicate",
-    ":text_input_manager",
+    ":system_menu_node",
     ":text_navigation_manager",
     "../chromevox:constants",
     "../chromevox:tree_walker",
@@ -202,28 +208,6 @@
   ]
 }
 
-js_library("navigation_manager") {
-  deps = [
-    ":back_button_manager",
-    ":event_helper",
-    ":focus_ring_manager",
-    ":menu_manager",
-    ":menu_panel_interface",
-    ":rect_helper",
-    ":switch_access_constants",
-    ":switch_access_interface",
-    ":switch_access_predicate",
-    ":text_input_manager",
-    ":text_navigation_manager",
-    "../chromevox:constants",
-    "../chromevox:tree_walker",
-  ]
-  externs_list = [
-    "$externs_path/accessibility_private.js",
-    "$externs_path/automation.js",
-  ]
-}
-
 js_library("background") {
   deps = [
     ":switch_access",
@@ -231,10 +215,13 @@
   externs_list = [ "$externs_path/chrome_extensions.js" ]
 }
 
-js_library("back_button_manager") {
+js_library("back_button_node") {
+  sources = [
+    "nodes/back_button_node.js",
+  ]
   deps = [
-    ":menu_panel_interface",
     ":rect_helper",
+    ":switch_access_node",
   ]
   externs_list = [
     "$externs_path/accessibility_private.js",
@@ -254,16 +241,35 @@
 
 js_library("focus_ring_manager") {
   deps = [
-    ":back_button_manager",
+    ":menu_panel_interface",
+    ":node_wrapper",
+    ":switch_access_node",
   ]
   externs_list = [ "$externs_path/accessibility_private.js" ]
 }
 
+js_library("keyboard_node") {
+  sources = [
+    "nodes/keyboard_node.js",
+  ]
+  deps = [
+    ":node_wrapper",
+    ":rect_helper",
+    ":switch_access_node",
+    ":switch_access_predicate",
+  ]
+  externs_list = [ "$externs_path/automation.js" ]
+}
+
 js_library("menu_manager") {
   deps = [
+    ":event_helper",
     ":menu_panel_interface",
+    ":node_wrapper",
     ":rect_helper",
     ":switch_access_interface",
+    ":switch_access_node",
+    ":switch_access_predicate",
   ]
   externs_list = [ "$externs_path/accessibility_private.js" ]
 }
@@ -277,6 +283,42 @@
 js_library("menu_panel_interface") {
 }
 
+js_library("navigation_manager") {
+  deps = [
+    ":event_helper",
+    ":focus_ring_manager",
+    ":keyboard_node",
+    ":menu_manager",
+    ":menu_panel_interface",
+    ":node_wrapper",
+    ":rect_helper",
+    ":switch_access_constants",
+    ":switch_access_interface",
+    ":switch_access_node",
+    ":switch_access_predicate",
+    ":system_menu_node",
+    ":text_navigation_manager",
+    "../chromevox:constants",
+    "../chromevox:tree_walker",
+  ]
+  externs_list = [
+    "$externs_path/accessibility_private.js",
+    "$externs_path/automation.js",
+  ]
+}
+
+js_library("node_wrapper") {
+  sources = [
+    "nodes/node_wrapper.js",
+  ]
+  deps = [
+    ":back_button_node",
+    ":switch_access_node",
+    ":switch_access_predicate",
+  ]
+  externs_list = [ "$externs_path/automation.js" ]
+}
+
 js_library("preferences") {
   deps = [
     ":switch_access_interface",
@@ -285,6 +327,9 @@
 }
 
 js_library("rect_helper") {
+  deps = [
+    ":switch_access_constants",
+  ]
   externs_list = [ "$externs_path/accessibility_private.js" ]
 }
 
@@ -313,15 +358,26 @@
   ]
 }
 
+js_library("switch_access_node") {
+  sources = [
+    "nodes/switch_access_node.js",
+  ]
+  externs_list = [ "$externs_path/automation.js" ]
+}
+
 js_library("switch_access_predicate") {
   externs_list = [ "$externs_path/automation.js" ]
 }
 
-js_library("text_input_manager") {
-  deps = [
-    ":event_helper",
+js_library("system_menu_node") {
+  sources = [
+    "nodes/system_menu_node.js",
   ]
-  externs_list = [ "$externs_path/accessibility_private.js" ]
+  deps = [
+    ":node_wrapper",
+    ":switch_access_node",
+  ]
+  externs_list = [ "$externs_path/automation.js" ]
 }
 
 js_library("text_navigation_manager") {
diff --git a/chrome/browser/resources/chromeos/switch_access/back_button_manager.js b/chrome/browser/resources/chromeos/switch_access/back_button_manager.js
deleted file mode 100644
index 75ff34a..0000000
--- a/chrome/browser/resources/chromeos/switch_access/back_button_manager.js
+++ /dev/null
@@ -1,119 +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.
-
-/**
- * Class to handle interactions with the Switch Access back button.
- */
-
-class BackButtonManager {
-  /**
-   * @param {!NavigationManager} navigationManager
-   */
-  constructor(navigationManager) {
-    /**
-     * Keeps track of when the back button is open and appears alone
-     * (rather than as part of the menu).
-     * @private {boolean}
-     */
-    this.backButtonOpen_ = false;
-
-    /** @private {!NavigationManager} */
-    this.navigationManager_ = navigationManager;
-
-    /** @private {PanelInterface} */
-    this.menuPanel_;
-
-    /** @private {chrome.automation.AutomationNode} */
-    this.backButtonNode_;
-  }
-
-  /**
-   * Shows the menu as just a back button in the upper right corner of the
-   * node.
-   * @param {chrome.accessibilityPrivate.ScreenRect=} nodeLocation
-   */
-  show(nodeLocation) {
-    if (!nodeLocation)
-      return;
-    this.backButtonOpen_ = true;
-    chrome.accessibilityPrivate.setSwitchAccessMenuState(
-        true, nodeLocation, 0 /* num_actions */);
-    this.menuPanel_.setFocusRing(SAConstants.BACK_ID, true);
-  }
-
-  /**
-   * Resets the effects of show().
-   */
-  hide() {
-    this.backButtonOpen_ = false;
-    chrome.accessibilityPrivate.setSwitchAccessMenuState(
-        false, RectHelper.ZERO_RECT, 0);
-    this.menuPanel_.setFocusRing(SAConstants.BACK_ID, false);
-  }
-
-  /**
-   * Selects the back button, hiding the button and exiting the current scope.
-   * @return {boolean} Whether or not the back button was successfully selected.
-   */
-  select() {
-    if (this.navigationManager_.selectBackButtonInMenu()) {
-      return true;
-    }
-
-    if (!this.backButtonOpen_) {
-      return false;
-    }
-
-    if (this.navigationManager_.leaveKeyboardIfNeeded()) {
-      return true;
-    }
-
-    this.navigationManager_.exitCurrentScope();
-    return true;
-  }
-
-  /**
-   * Returns the back button node, if we have found it.
-   * @return {chrome.automation.AutomationNode}
-   */
-  backButtonNode() {
-    return this.backButtonNode_;
-  }
-
-  /**
-   * Finds the back button node.
-   * @param {!chrome.automation.AutomationNode} desktop
-   * @private
-   */
-  findBackButtonNode_(desktop) {
-    this.backButtonNode_ =
-        new AutomationTreeWalker(
-            desktop, constants.Dir.FORWARD,
-            {visit: (node) => node.htmlAttributes.id === SAConstants.BACK_ID})
-            .next()
-            .node;
-    // TODO(anastasi): Determine appropriate event and listen for it, rather
-    // than setting a timeout.
-    if (!this.backButtonNode_) {
-      setTimeout(this.findBackButtonNode_.bind(this, desktop), 500);
-    }
-  }
-
-  /**
-   * Sets the reference to the menu panel, sets up a click handler for the
-   * back button, and finds the back button node.
-   * @param {!PanelInterface} menuPanel
-   * @param {!chrome.automation.AutomationNode} desktop
-   */
-  init(menuPanel, desktop) {
-    this.menuPanel_ = menuPanel;
-
-    // Ensures that the back button behaves the same way when clicked
-    // as when selected.
-    const backButtonElement = this.menuPanel_.backButtonElement();
-    backButtonElement.addEventListener('click', this.select.bind(this));
-
-    this.findBackButtonNode_(desktop);
-  }
-}
diff --git a/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js b/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js
index 21ef2617..5d884df 100644
--- a/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js
@@ -6,11 +6,7 @@
  * Class to handle focus rings.
  */
 class FocusRingManager {
-  /**
-   * @param {!SwitchAccessInterface} switchAccess
-   * @param {!BackButtonManager} backButtonManager
-   */
-  constructor(switchAccess, backButtonManager) {
+  constructor() {
     /**
      * A map of all the focus rings.
      * @private {!Map<SAConstants.Focus.ID,
@@ -26,22 +22,21 @@
     this.colorPattern_ = /^#[0-9A-F]{3,8}$/i;
 
     /**
-     * A reference to the Switch Access object.
-     * @private {!SwitchAccessInterface}
+     * Reference to the menu panel object.
+     * @private {PanelInterface}
      */
-    this.switchAccess_ = switchAccess;
+    this.menuPanel_;
+  }
 
-    /**
-     * The back button manager.
-     * @private {!BackButtonManager}
-     */
-    this.backButtonManager_ = backButtonManager;
+  /** @param {!PanelInterface} panel */
+  setMenuPanel(panel) {
+    this.menuPanel_ = panel;
   }
 
   /** Finishes setup of focus rings once the preferences are loaded. */
   onPrefsReady() {
     // Currently all focus rings share the same color.
-    // TODO(anastasi): Make the primary color a preference.
+    // TODO(crbug/996852): Make the primary focus color a preference.
     const color = SAConstants.Focus.PRIMARY_COLOR;
 
     // Create each focus ring.
@@ -84,15 +79,17 @@
   /**
    * Sets the primary and next focus rings based on the current primary and
    *     group nodes used for navigation.
-   * @param {!chrome.automation.AutomationNode} primary
-   * @param {!chrome.automation.AutomationNode} group
+   * @param {!SAChildNode} primary
+   * @param {!SARootNode} group
    */
   setFocusNodes(primary, group) {
     if (this.rings_.size === 0) return;
 
-    if (primary === this.backButtonManager_.backButtonNode()) {
-      this.backButtonManager_.show(group.location);
-
+    if (primary instanceof BackButtonNode) {
+      // TODO(anastasi): Use standard focus rings.
+      if (this.menuPanel_) {
+        this.menuPanel_.setFocusRing(SAConstants.BACK_ID, true);
+      }
       this.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [];
       // Clear the dashed ring between transitions, as the animation is
       // distracting.
@@ -102,16 +99,13 @@
       this.rings_.get(SAConstants.Focus.ID.NEXT).rects = [group.location];
       this.updateFocusRings_();
       return;
+    } else if (this.menuPanel_) {
+      this.menuPanel_.setFocusRing(SAConstants.BACK_ID, false);
     }
-    this.backButtonManager_.hide();
 
     // If the primary node is a group, show its first child as the "next" focus.
-    if (SwitchAccessPredicate.isGroup(primary, group)) {
-      const firstChild = new AutomationTreeWalker(
-                             primary, constants.Dir.FORWARD,
-                             SwitchAccessPredicate.restrictions(primary))
-                             .next()
-                             .node;
+    if (primary.isGroup()) {
+      const firstChild = primary.asRootNode().firstChild;
 
       // Clear the dashed ring between transitions, as the animation is
       // distracting.
@@ -119,13 +113,13 @@
       this.updateFocusRings_();
 
       let focusRect = primary.location;
-      if (firstChild && firstChild.location) {
+      let childRect = firstChild ? firstChild.location : null;
+      if (childRect) {
         // If the current element is not the back button, the focus rect should
         // expand to contain the child rect.
         focusRect = RectHelper.expandToFitWithPadding(
-            SAConstants.Focus.GROUP_BUFFER, focusRect, firstChild.location);
-        this.rings_.get(SAConstants.Focus.ID.NEXT).rects =
-            [firstChild.location];
+            SAConstants.Focus.GROUP_BUFFER, focusRect, childRect);
+        this.rings_.get(SAConstants.Focus.ID.NEXT).rects = [childRect];
       }
       this.rings_.get(SAConstants.Focus.ID.PRIMARY).rects = [focusRect];
       this.updateFocusRings_();
diff --git a/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2 b/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2
index 7b47e46..ce9bc41 100644
--- a/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2
+++ b/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2
@@ -12,7 +12,6 @@
   "background": {
     "scripts": [
       "auto_scan_manager.js",
-      "back_button_manager.js",
       "closure_shim.js",
       "commands.js",
       "constants.js",
@@ -20,12 +19,16 @@
       "focus_ring_manager.js",
       "menu_manager.js",
       "navigation_manager.js",
+      "nodes/switch_access_node.js",
+      "nodes/node_wrapper.js",
+      "nodes/back_button_node.js",
+      "nodes/keyboard_node.js",
+      "nodes/system_menu_node.js",
       "preferences.js",
       "rect_helper.js",
       "switch_access.js",
       "switch_access_constants.js",
       "switch_access_predicate.js",
-      "text_input_manager.js",
       "text_navigation_manager.js",
       "tree_walker.js",
       "background.js"
diff --git a/chrome/browser/resources/chromeos/switch_access/menu_manager.js b/chrome/browser/resources/chromeos/switch_access/menu_manager.js
index 8895de2..b233cad 100644
--- a/chrome/browser/resources/chromeos/switch_access/menu_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/menu_manager.js
@@ -26,6 +26,12 @@
     this.navigationManager_ = navigationManager;
 
     /**
+     * The text navigation manager.
+     * @private {!TextNavigationManager}
+     */
+    this.textNavigationManager_ = new TextNavigationManager();
+
+    /**
      * The root node of the screen.
      * @private {!chrome.automation.AutomationNode}
      */
@@ -39,20 +45,20 @@
 
     /**
      * The root node of the menu.
-     * @private {chrome.automation.AutomationNode}
+     * @private {SARootNode}
      */
     this.menuNode_;
 
     /**
      * The current node of the menu.
-     * @private {chrome.automation.AutomationNode}
+     * @private {SAChildNode}
      */
     this.node_;
 
     /**
      * The node that the menu has been opened for. Null if the menu is not
      * currently open.
-     * @private {chrome.automation.AutomationNode}
+     * @private {SAChildNode}
      */
     this.menuOriginNode_;
 
@@ -87,13 +93,24 @@
     this.menuPanel_;
 
     /**
-     * Callback for highlighting the first available action once a
-     * menu has been loaded in the panel. Bind creates a new function, so
-     * this function is saved as a field so that the event listener
-     * associated with this callback can be removed properly.
-     * @private {function(chrome.automation.AutomationEvent): undefined}
+     * Callback for highlighting the first available action once a menu has been
+     *     loaded in the panel. This function is created here, rather than below
+     *     with the other methods, because we need a consistent function object
+     *     to be able to add and remove the listener. If this function were a
+     *     method, it would need to have the |this| reference bound to it, and
+     *     each call to |.bind()| creates a new function (meaning the listener
+     *     could never be removed).
+     * Why does this work? While methods use call scoping (they look for
+     *     variables in the context of the call site), fat arrow functions,
+     *     like below, use lexical scoping (they look for variables in the
+     *     context of where the function was declared). So the proper |this|
+     *     object is referenced without the need for binding.
+     * @private {function()}
      */
-    this.onMenuPanelChildrenChanged_ = this.highlightFirstAction_.bind(this);
+    this.onMenuPanelChildrenChanged_ = () => {
+      this.buildMenuTree_();
+      this.highlightFirstAction_();
+    };
 
     /**
      * A stack to keep track of all menus that have been opened before
@@ -120,31 +137,38 @@
   /**
    * If multiple actions are available for the currently highlighted node,
    * opens the main menu. Otherwise, selects the node by default.
-   * @param {!chrome.automation.AutomationNode} navNode the currently
-   * highlighted node, for which the menu is to be displayed.
+   * @param {!SAChildNode} navNode the currently highlighted node, for which the
+   *     menu is to be displayed.
+   * @return {boolean} True if the menu opened or an action was selected, false
+   *     otherwise.
    */
   enter(navNode) {
     if (!this.menuPanel_) {
       console.log('Error: Menu panel has not loaded.');
-      return;
+      return false;
     }
 
+    // If the menu is already open, select the highlighted element.
+    if (this.selectCurrentNode()) return true;
+
     if (!this.openMenu_(navNode, SAConstants.MenuId.MAIN)) {
       // openMenu_ will return false (indicating that the menu was not opened
       // successfully) when there is only one interesting action (selection)
       // specific to this node. In this case, rather than forcing the user to
       // repeatedly disambiguate, we will simply select by default.
-      this.navigationManager_.selectCurrentNode();
-      return;
+      return false;
     }
 
     this.inMenu_ = true;
+    return true;
   }
 
   /**
    * Exits the menu.
    */
   exit() {
+    if (!this.inMenu_) return;
+
     this.closeCurrentMenu_();
     this.inMenu_ = false;
 
@@ -166,8 +190,8 @@
    * will be highlighted. Otherwise, the first available action will
    * be highlighted. Returns a boolean of whether or not the menu was
    * successfully opened.
-   * @param {!chrome.automation.AutomationNode} navNode The currently
-   *     highlighted node, for which the menu is being opened.
+   * @param {!SAChildNode} navNode The currently highlighted node, for which the
+   *     menu is being opened.
    * @param {!SAConstants.MenuId} menuId Indicates the menu being opened.
    * @param {boolean=} isSubmenu Whether or not the menu being opened is a
    *     submenu of the current menu.
@@ -177,7 +201,8 @@
   openMenu_(navNode, menuId, isSubmenu = false) {
     // Action currently highlighted in the menu (null if the menu was closed
     // before this function was called).
-    const actionNode = this.node_;
+    let actionNode = null;
+    if (this.node_) actionNode = this.node_.automationNode;
 
     const currentMenuId = this.menuPanel_.currentMenuId();
     const shouldReloadMenu = (currentMenuId === menuId);
@@ -198,14 +223,6 @@
       return false;
     }
 
-    if (!shouldReloadMenu) {
-      // Wait for the menu to appear in the panel before highlighting the
-      // first available action.
-      this.menuPanelNode_.addEventListener(
-          chrome.automation.EventType.CHILDREN_CHANGED,
-          this.onMenuPanelChildrenChanged_, false /** Don't use capture. */);
-    }
-
     // Converting to JSON strings to check equality of Array contents.
     if (JSON.stringify(actions) !== JSON.stringify(this.actions_)) {
       // Set new menu actions in the panel.
@@ -213,41 +230,48 @@
       this.menuPanel_.setActions(this.actions_, menuId);
     }
 
-    if (!navNode.location) {
+    const loc = navNode.location;
+    if (!loc) {
       console.log('Unable to show Switch Access menu.');
       return false;
     }
     // Show the menu panel.
     chrome.accessibilityPrivate.setSwitchAccessMenuState(
-        true, navNode.location, actions.length);
+        true, loc, actions.length);
 
     this.menuOriginNode_ = navNode;
-    if (!shouldReloadMenu && window.switchAccess.improvedTextInputEnabled()) {
+
+    const autoNode = this.menuOriginNode_.automationNode;
+    if (autoNode && !shouldReloadMenu &&
+        window.switchAccess.improvedTextInputEnabled()) {
       const callback = this.reloadMenuForSelectionChange_.bind(this);
 
-      this.menuOriginNode_.addEventListener(
+      autoNode.addEventListener(
           chrome.automation.EventType.TEXT_SELECTION_CHANGED, callback,
           false /** use_capture */);
-      this.onExitCallback_ = this.menuOriginNode_.removeEventListener.bind(
-          this.menuOriginNode_,
-          chrome.automation.EventType.TEXT_SELECTION_CHANGED, callback,
-          false /** use_capture */);
+      this.onExitCallback_ = autoNode.removeEventListener.bind(
+          autoNode, chrome.automation.EventType.TEXT_SELECTION_CHANGED,
+          callback, false /** use_capture */);
     }
 
-    if (shouldReloadMenu && actionNode) {
-      let buttonId = actionNode.htmlAttributes.id;
+    if (shouldReloadMenu) {
+      this.buildMenuTree_();
+      let buttonId = actionNode ? actionNode.htmlAttributes.id : '';
       if (actions.includes(buttonId)) {
         // Highlight the same action that was highlighted before the menu was
         // reloaded.
-        this.node_ = actionNode;
         this.updateFocusRing_();
       } else {
-        while (!actions.includes(buttonId) && buttonId != 'back') {
-          this.moveForward();
-          buttonId = this.node_.htmlAttributes.id;
-        }
+        this.highlightFirstAction_();
       }
+    } else {
+      // Wait for the menu to appear in the panel before highlighting the
+      // first available action.
+      this.menuPanelNode_.addEventListener(
+          chrome.automation.EventType.CHILDREN_CHANGED,
+          this.onMenuPanelChildrenChanged_, false /** use_capture */);
     }
+
     return true;
   }
 
@@ -257,9 +281,7 @@
    */
   closeCurrentMenu_() {
     this.clearFocusRing_();
-    if (this.node_) {
-      this.node_ = null;
-    }
+    if (this.node_) this.node_ = null;
     this.menuPanel_.clear();
     this.actions_ = [];
     this.menuNode_ = null;
@@ -268,8 +290,8 @@
   /**
    * Get the actions applicable for |navNode| from the menu with given
    * |menuId|.
-   * @param {!chrome.automation.AutomationNode} navNode The currently selected
-   *     node, for which the menu is being opened.
+   * @param {!SAChildNode} navNode The currently selected node, for which the
+   *     menu is being opened.
    * @param {SAConstants.MenuId} menuId
    * @return {Array<SAConstants.MenuAction>}
    * @private
@@ -308,16 +330,9 @@
    * @private
    */
   highlightFirstAction_() {
-    let firstNode =
-        this.menuNode().find({role: chrome.automation.RoleType.BUTTON});
-
-    while (firstNode && !this.isActionAvailable_(firstNode.htmlAttributes.id))
-      firstNode = firstNode.nextSibling;
-
-    if (firstNode) {
-      this.node_ = firstNode;
-      this.updateFocusRing_();
-    }
+    if (!this.menuNode_) return;
+    this.node_ = this.menuNode_.firstChild;
+    this.updateFocusRing_();
 
     // The event is fired multiple times when a new menu is opened in the
     // panel, so remove the listener once the callback has been called once.
@@ -334,20 +349,10 @@
    * @return {boolean} Whether this function had any effect.
    */
   moveForward() {
-    this.calculateCurrentNode();
-
-    if (!this.inMenu_ || !this.node_)
-      return false;
+    if (!this.inMenu_ || !this.node_) return false;
 
     this.clearFocusRing_();
-    const treeWalker = new AutomationTreeWalker(
-        this.node_, constants.Dir.FORWARD,
-        SwitchAccessPredicate.restrictions(this.menuNode()));
-    const node = treeWalker.next().node;
-    if (!node)
-      this.node_ = null;
-    else
-      this.node_ = node;
+    this.node_ = this.node_.next;
     this.updateFocusRing_();
     return true;
   }
@@ -358,29 +363,10 @@
    * @return {boolean} Whether this function had any effect.
    */
   moveBackward() {
-    this.calculateCurrentNode();
-
-    if (!this.inMenu_ || !this.node_)
-      return false;
+    if (!this.inMenu_ || !this.node_) return false;
 
     this.clearFocusRing_();
-    const treeWalker = new AutomationTreeWalker(
-        this.node_, constants.Dir.BACKWARD,
-        SwitchAccessPredicate.restrictions(this.menuNode()));
-    let node = treeWalker.next().node;
-
-    // If node is null, find the last enabled button.
-    let lastChild = this.menuNode().lastChild;
-    while (!node && lastChild) {
-      if (SwitchAccessPredicate.isActionable(lastChild)) {
-        node = lastChild;
-        break;
-      } else {
-        lastChild = lastChild.previousSibling;
-      }
-    }
-
-    this.node_ = node;
+    this.node_ = this.node_.previous;
     this.updateFocusRing_();
     return true;
   }
@@ -390,18 +376,14 @@
    * @return {boolean} Whether this function had any effect.
    */
   selectCurrentNode() {
-    this.calculateCurrentNode();
+    if (!this.inMenu_ || !this.node_) return false;
 
-    if (!this.inMenu_ || !this.node_) {
-      return false;
-    }
-
-    if (this.node_.role === RoleType.BUTTON) {
-      // A menu action was selected.
-      this.node_.doDefault();
-    } else {
+    if (this.node_ instanceof BackButtonNode) {
       // The back button was selected.
       this.selectBackButton();
+    } else {
+      // A menu action was selected.
+      this.node_.performAction(SAConstants.MenuAction.SELECT);
     }
     return true;
   }
@@ -449,36 +431,19 @@
       return;
     }
     this.menuPanelNode_ = node;
+    this.buildMenuTree_();
   }
 
   /**
-   * Get the menu node. With the current design, the menu panel should
-   * always contain at most one menu. When a menu is open in the panel,
-   * the menu node is the first child of the menu panel node.
-   * @return {!chrome.automation.AutomationNode}
+   * Builds the tree for the current menu.
    */
-  menuNode() {
-    if (this.menuNode_) {
-      return this.menuNode_;
+  buildMenuTree_() {
+    // menu_panel.html controls the contents of the menu panel, and we are
+    // guaranteed that the menu will be the first child.
+    if (this.menuPanelNode_ && this.menuPanelNode_.firstChild) {
+      this.menuNode_ =
+          RootNodeWrapper.buildTree(this.menuPanelNode_.firstChild);
     }
-
-    if (this.menuPanelNode_) {
-      if (this.menuPanelNode_.firstChild) {
-        this.menuNode_ = this.menuPanelNode_.firstChild;
-        return this.menuNode_;
-      }
-    }
-
-    return this.desktop_;
-  }
-
-  /**
-   * Whether or not the menu is currently open.
-   * @return {boolean}
-   * @public
-   */
-  inMenu() {
-    return this.inMenu_;
   }
 
   /**
@@ -507,13 +472,10 @@
    * @returns {boolean} whether or not there's a selection
    */
   nodeHasSelection_() {
-    if (this.menuOriginNode_) {
-      if (this.menuOriginNode_.textSelStart !==
-          this.menuOriginNode_.textSelEnd) {
-        return true;
-      } else {
-        return false;
-      }
+    const node = this.menuOriginNode_.automationNode;
+
+    if (node && node.textSelStart !== node.textSelEnd) {
+      return true;
     }
     return false;
   }
@@ -528,7 +490,7 @@
     if (this.selectionExists_ != newSelectionState) {
       this.selectionExists_ = newSelectionState;
       if (this.menuOriginNode_ &&
-          !this.navigationManager_.currentlySelecting()) {
+          !this.textNavigationManager_.currentlySelecting()) {
         let currentMenuId = this.menuPanel_.currentMenuId();
         if (currentMenuId) {
           this.openMenu_(this.menuOriginNode_, currentMenuId);
@@ -544,184 +506,133 @@
    * there are no node-specific actions, return |null|, to indicate that we
    * should select the current node automatically.
    *
-   * @param {!chrome.automation.AutomationNode} node
+   * @param {!SAChildNode} node
    * @return {Array<!SAConstants.MenuAction>}
    * @private
    */
   getMainMenuActionsForNode_(node) {
-    let actions = [];
+    let actions = node.actions;
 
-    let scrollableAncestor = node;
-    while (!scrollableAncestor.scrollable && scrollableAncestor.parent)
-      scrollableAncestor = scrollableAncestor.parent;
-
-    if (scrollableAncestor.scrollable) {
-      if (scrollableAncestor.scrollX > scrollableAncestor.scrollXMin)
-        actions.push(SAConstants.MenuAction.SCROLL_LEFT);
-      if (scrollableAncestor.scrollX < scrollableAncestor.scrollXMax)
-        actions.push(SAConstants.MenuAction.SCROLL_RIGHT);
-      if (scrollableAncestor.scrollY > scrollableAncestor.scrollYMin)
-        actions.push(SAConstants.MenuAction.SCROLL_UP);
-      if (scrollableAncestor.scrollY < scrollableAncestor.scrollYMax)
-        actions.push(SAConstants.MenuAction.SCROLL_DOWN);
-    }
-    const standardActions = /** @type {!Array<!SAConstants.MenuAction>} */ (
-        node.standardActions.filter(
-            action => Object.values(SAConstants.MenuAction).includes(action)));
-
-    actions = actions.concat(standardActions);
-
-    if (SwitchAccessPredicate.isTextInput(node)) {
-      actions.push(SAConstants.MenuAction.KEYBOARD);
-      actions.push(SAConstants.MenuAction.DICTATION);
-
-      if (window.switchAccess.improvedTextInputEnabled() &&
-          node.state[StateType.FOCUSED]) {
-        actions.push(SAConstants.MenuAction.MOVE_CURSOR);
-        actions.push(SAConstants.MenuAction.SELECT_START);
-        if (this.navigationManager_.currentlySelecting()) {
-          actions.push(SAConstants.MenuAction.SELECT_END);
-        }
-        if (this.selectionExists_) {
-          actions.push(SAConstants.MenuAction.CUT);
-          actions.push(SAConstants.MenuAction.COPY);
-        }
-        if (this.clipboardHasData_) {
-          actions.push(SAConstants.MenuAction.PASTE);
-        }
+    // Add text editing and navigation options.
+    // TODO(anastasi): Move these actions into the node.
+    const autoNode = node.automationNode;
+    if (autoNode && window.switchAccess.improvedTextInputEnabled() &&
+        SwitchAccessPredicate.isTextInput(autoNode) &&
+        autoNode.state[StateType.FOCUSED]) {
+      actions.push(SAConstants.MenuAction.MOVE_CURSOR);
+      actions.push(SAConstants.MenuAction.SELECT_START);
+      if (this.textNavigationManager_.currentlySelecting()) {
+        actions.push(SAConstants.MenuAction.SELECT_END);
       }
-    } else if (actions.length > 0) {
-      actions.push(SAConstants.MenuAction.SELECT);
+      if (this.selectionExists_) {
+        actions.push(SAConstants.MenuAction.CUT);
+        actions.push(SAConstants.MenuAction.COPY);
+      }
+      if (this.clipboardHasData_) {
+        actions.push(SAConstants.MenuAction.PASTE);
+      }
     }
 
-    if (actions.length === 0)
-      return null;
+    // If there is at most one available action, perform it by default.
+    if (actions.length <= 1) return null;
 
+
+    // Add global actions.
     actions.push(SAConstants.MenuAction.SETTINGS);
     return actions;
   }
 
   /**
-   * Verify if a specified action is available in the current menu.
-   * @param {!SAConstants.MenuAction} action
-   * @return {boolean}
-   * @private
-   */
-  isActionAvailable_(action) {
-    if (!this.inMenu_)
-      return false;
-    return this.actions_.includes(action);
-  }
-
-  /**
    * Perform a specified action on the Switch Access menu.
    * @param {!SAConstants.MenuAction} action
    */
   performAction(action) {
-    if (!window.switchAccess.improvedTextInputEnabled()) {
+    // Some actions involve navigation events. Handle those explicitly.
+    if (action === SAConstants.MenuAction.SELECT &&
+        this.menuOriginNode_.isGroup()) {
+      this.navigationManager_.enterGroup();
       this.exit();
+      return;
+    }
+    if (action === SAConstants.MenuAction.OPEN_KEYBOARD) {
+      this.navigationManager_.enterKeyboard();
+      this.exit();
+      return;
     }
 
+    // Handle global actions.
+    if (action === SAConstants.MenuAction.SETTINGS) {
+      chrome.accessibilityPrivate.openSettingsSubpage(
+          'manageAccessibility/switchAccess');
+      this.exit();
+      return;
+    }
+
+    // Handle text editing actions.
+    // TODO(anastasi): Move these actions into the nodes themselves.
     switch (action) {
-      case SAConstants.MenuAction.SELECT:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          // Explicitly call exit for actions where the menu closes after
-          // the icon is clicked.
-          this.exit();
-        }
-        this.navigationManager_.selectCurrentNode();
-        break;
-      case SAConstants.MenuAction.KEYBOARD:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          this.exit();
-        }
-        this.navigationManager_.openKeyboard();
-        break;
-      case SAConstants.MenuAction.DICTATION:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          this.exit();
-        }
-        chrome.accessibilityPrivate.toggleDictation();
-        break;
-      case SAConstants.MenuAction.SETTINGS:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          this.exit();
-        }
-        chrome.accessibilityPrivate.openSettingsSubpage(
-            'manageAccessibility/switchAccess');
-        break;
-      case SAConstants.MenuAction.SCROLL_DOWN:
-      case SAConstants.MenuAction.SCROLL_UP:
-      case SAConstants.MenuAction.SCROLL_LEFT:
-      case SAConstants.MenuAction.SCROLL_RIGHT:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          this.exit();
-        }
-        this.navigationManager_.scroll(action);
-        break;
       case SAConstants.MenuAction.MOVE_CURSOR:
         if (this.menuOriginNode_) {
           this.openMenu_(
               this.menuOriginNode_, SAConstants.MenuId.TEXT_NAVIGATION,
               true /** Opening a submenu. */);
         }
-        break;
+        return;
       case SAConstants.MenuAction.JUMP_TO_BEGINNING_OF_TEXT:
-        this.navigationManager_.jumpToBeginningOfText();
-        break;
+        this.textNavigationManager_.jumpToBeginning();
+        return;
       case SAConstants.MenuAction.JUMP_TO_END_OF_TEXT:
-        this.navigationManager_.jumpToEndOfText();
-        break;
+        this.textNavigationManager_.jumpToEnd();
+        return;
       case SAConstants.MenuAction.MOVE_BACKWARD_ONE_CHAR_OF_TEXT:
-        this.navigationManager_.moveBackwardOneCharOfText();
-        break;
+        this.textNavigationManager_.moveBackwardOneChar();
+        return;
       case SAConstants.MenuAction.MOVE_BACKWARD_ONE_WORD_OF_TEXT:
-        this.navigationManager_.moveBackwardOneWordOfText();
-        break;
+        this.textNavigationManager_.moveBackwardOneWord();
+        return;
       case SAConstants.MenuAction.MOVE_DOWN_ONE_LINE_OF_TEXT:
-        this.navigationManager_.moveDownOneLineOfText();
-        break;
+        this.textNavigationManager_.moveDownOneLine();
+        return;
       case SAConstants.MenuAction.MOVE_FORWARD_ONE_CHAR_OF_TEXT:
-        this.navigationManager_.moveForwardOneCharOfText();
-        break;
+        this.textNavigationManager_.moveForwardOneChar();
+        return;
       case SAConstants.MenuAction.MOVE_FORWARD_ONE_WORD_OF_TEXT:
-        this.navigationManager_.moveForwardOneWordOfText();
-        break;
+        this.textNavigationManager_.moveForwardOneWord();
+        return;
       case SAConstants.MenuAction.MOVE_UP_ONE_LINE_OF_TEXT:
-        this.navigationManager_.moveUpOneLineOfText();
-        break;
+        this.textNavigationManager_.moveUpOneLine();
+        return;
       case SAConstants.MenuAction.CUT:
-        this.navigationManager_.cut();
-        break;
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true});
+        return;
       case SAConstants.MenuAction.COPY:
-        this.navigationManager_.copy();
-        break;
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.C, {ctrl: true});
+        return;
       case SAConstants.MenuAction.PASTE:
-        this.navigationManager_.paste();
-        break;
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true});
+        return;
       case SAConstants.MenuAction.SELECT_START:
-        this.navigationManager_.saveSelectStart();
+        this.textNavigationManager_.saveSelectStart();
         if (this.menuOriginNode_) {
           this.openMenu_(this.menuOriginNode_, SAConstants.MenuId.MAIN);
         }
-        break;
+        return;
       case SAConstants.MenuAction.SELECT_END:
-        this.navigationManager_.endSelection();
-        if (this.menuOriginNode_)
+        this.textNavigationManager_.resetCurrentlySelecting();
+        if (this.menuOriginNode_) {
           this.openMenu_(this.menuOriginNode_, SAConstants.MenuId.MAIN);
-        break;
-      default:
-        if (window.switchAccess.improvedTextInputEnabled()) {
-          this.exit();
         }
-        this.navigationManager_.performActionOnCurrentNode(action);
+        return;
     }
+
+    // Otherwise, ask the node to perform the action itself.
+    if (this.menuOriginNode_.performAction(action)) this.exit();
   }
 
   /**
    * Send a message to the menu to update the focus ring around the current
    * node.
-   * TODO(anastasi): Revisit focus rings before launch
+   * TODO(anastasi): Use real focus rings in the menu
    * @private
    * @param {boolean=} opt_clear If true, will clear the focus ring.
    */
@@ -731,11 +642,8 @@
       return;
     }
 
-    this.calculateCurrentNode();
-
-    if (!this.inMenu_ || !this.node_)
-      return;
-    let id = this.node_.htmlAttributes.id;
+    if (!this.inMenu_ || !this.node_) return;
+    let id = this.node_.automationNode.htmlAttributes.id;
 
     // If the selection will close the menu, highlight the back button.
     if (id === this.menuPanel_.currentMenuId()) {
@@ -745,26 +653,4 @@
     const enable = !opt_clear;
     this.menuPanel_.setFocusRing(id, enable);
   }
-
-  /**
-   * Updates the value of |this.node_|.
-   *
-   * - If it has a value, change nothing.
-   * - Otherwise, if menu node has a reasonable value, set |this.node_| to menu
-   *   node.
-   * - If not, set it to null.
-   *
-   * Return |this.node_|'s value after the update.
-   *
-   * @private
-   * @return {chrome.automation.AutomationNode}
-   */
-  calculateCurrentNode() {
-    if (this.node_)
-      return this.node_;
-    this.node_ = this.menuNode();
-    if (this.node_ === this.desktop_)
-      this.node_ = null;
-    return this.node_;
-  }
 }
diff --git a/chrome/browser/resources/chromeos/switch_access/navigation_manager.js b/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
index 8f81ab8..cd71eb9 100644
--- a/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
@@ -3,565 +3,153 @@
 // found in the LICENSE file.
 
 /**
- * Class to manage interactions with the accessibility tree, including moving
- * to and selecting nodes.
+ * This class handles navigation amongst the elements onscreen.
  */
 class NavigationManager {
   /**
    * @param {!chrome.automation.AutomationNode} desktop
-   * @param {!SwitchAccessInterface} switchAccess
    */
-  constructor(desktop, switchAccess) {
-    /**
-     * Handles communication with Switch Access.
-     * @private {!SwitchAccessInterface}
-     */
-    this.switchAccess_ = switchAccess;
-
-    /**
-     * Handles communication with and navigation within the Switch Access menu.
-     * @private {!MenuManager}
-     */
-    this.menuManager_ = new MenuManager(this, desktop);
-
-    /**
-     * Handles the details of showing and hiding the back button, when it
-     * appears alone (rather than as part of the menu).
-     * @private {!BackButtonManager}
-     */
-    this.backButtonManager_ = new BackButtonManager(this);
-
-    /**
-     * Handles setting, changing, and clearing the focus rings onscreen.
-     * Can be accessed directly by NavigationManager's children.
-     * @public {!FocusRingManager}
-     */
-    this.focusRingManager =
-        new FocusRingManager(switchAccess, this.backButtonManager_);
-
-    /**
-     * Handles the details of text input, including typing and showing an
-     * additional focus ring for context when typing.
-     * @private {!TextInputManager}
-     */
-    this.textInputManager_ = new TextInputManager(this);
-
-    /**
-     * Handles text navigation actions.
-     * @private {!TextNavigationManager}
-     */
-    this.textNavigationManager_ = new TextNavigationManager(this);
-
-    /**
-     * The desktop node.
-     * @private {!chrome.automation.AutomationNode}
-     */
+  constructor(desktop) {
+    /** @private {!chrome.automation.AutomationNode} */
     this.desktop_ = desktop;
 
-    /**
-     * The currently highlighted node.
-     *
-     * @private {!chrome.automation.AutomationNode}
-     */
-    this.node_ = desktop;
+    /** @private {!SARootNode} */
+    this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_);
 
-    /**
-     * The root of the subtree that the user is navigating through.
-     *
-     * @private {!chrome.automation.AutomationNode}
-     */
-    this.scope_ = desktop;
+    /** @private {!SAChildNode} */
+    this.node_ = this.group_.firstChild;
 
-    /**
-     * A stack of past scopes. Allows user to traverse back to previous groups
-     * after selecting one or more groups. The most recent group is at the end
-     * of the array.
-     *
-     * @private {Array<!chrome.automation.AutomationNode>}
-     */
-    this.scopeStack_ = [];
+    /** @private {!Array<!SARootNode>} */
+    this.groupStack_ = [];
 
-    /**
-     * Keeps track of if we are currently in a system menu.
-     * @private {boolean}
-     */
-    this.inSystemMenu_ = false;
+    /** @private {!MenuManager} */
+    this.menuManager_ = new MenuManager(this, desktop);
+
+    /** @private {!FocusRingManager} */
+    this.focusRingManager_ = new FocusRingManager();
 
     this.init_();
   }
 
-  /**
-   * Get the currently highlighted node.
-   * @return {!chrome.automation.AutomationNode} the current node
-   */
-  currentNode() {
-    return this.node_;
-  }
+  // -------------------------------------------------------
+  // |                 Public Methods                      |
+  // -------------------------------------------------------
 
   /**
-   * Open the Switch Access menu for the currently highlighted node.
+   * Enters |this.node_|.
    */
-  enterMenu() {
-    // If we're currently visiting the Switch Access menu, this command should
-    // select the highlighted element.
-    if (this.menuManager_.selectCurrentNode()) {
-      return;
+  enterGroup() {
+    if (!this.node_.isGroup()) return;
+
+    const newGroup = this.node_.asRootNode();
+    if (newGroup) {
+      this.groupStack_.push(this.group_);
+      this.group_ = newGroup;
     }
 
-    // If the back button is focused, select it.
-    if (this.backButtonManager_.select()) {
-      return;
-    }
-
-    this.menuManager_.enter(this.node_);
-  }
-
-  /**
-   * Selects the back button while navigating in the menu.
-   * @return {boolean} Whether or not the back button was successfully selected.
-   */
-  selectBackButtonInMenu() {
-    if (!this.menuManager_.inMenu()) {
-      return false;
-    }
-
-    this.menuManager_.selectBackButton();
-    return true;
-  }
-
-  /**
-   * Find the previous interesting node and update |this.node_|. If there is no
-   * previous node, |this.node_| will be set to the back button.
-   */
-  moveBackward() {
-    if (this.menuManager_.moveBackward())
-      return;
-
-    if (this.node_ === this.backButtonManager_.backButtonNode()) {
-      if (SwitchAccessPredicate.isActionable(this.scope_))
-        this.setCurrentNode_(this.scope_);
-      else
-        this.setCurrentNode_(this.youngestDescendant_(this.scope_));
-      return;
-    }
-
-    this.startAtValidNode_();
-
-    let treeWalker = new AutomationTreeWalker(
-        this.node_, constants.Dir.BACKWARD,
-        SwitchAccessPredicate.restrictions(this.scope_));
-
-    let node = treeWalker.next().node;
-
-    if (node === this.scope_)
-      node = this.backButtonManager_.backButtonNode();
-
-    if (!node)
-      node = this.youngestDescendant_(this.scope_);
-
-    // We can't interact with the desktop node, so skip it.
-    if (node === this.desktop_) {
-      this.node_ = node;
-      this.moveBackward();
-      return;
-    }
-
-    this.setCurrentNode_(node);
-  }
-
-  /**
-   * Find the next interesting node, and update |this.node_|. If there is no
-   * next node, |this.node_| will be set equal to |this.scope_| to loop again.
-   */
-  moveForward() {
-    if (this.menuManager_.moveForward())
-      return;
-
-    this.startAtValidNode_();
-
-    const backButtonNode = this.backButtonManager_.backButtonNode();
-    if (this.node_ === this.scope_ && backButtonNode) {
-      this.setCurrentNode_(backButtonNode);
-      return;
-    }
-
-    // Replace the back button with the scope to find the following node.
-    if (this.node_ === backButtonNode)
-      this.node_ = this.scope_;
-
-    let treeWalker = new AutomationTreeWalker(
-        this.node_, constants.Dir.FORWARD,
-        SwitchAccessPredicate.restrictions(this.scope_));
-
-
-    let node = treeWalker.next().node;
-    // If treeWalker returns undefined, that means we're at the end of the tree
-    // and we should start over.
-    if (!node) {
-      if (SwitchAccessPredicate.isActionable(this.scope_)) {
-        node = this.scope_;
-      } else if (backButtonNode) {
-        node = backButtonNode;
-      } else {
-        this.node_ = this.scope_;
-        this.moveForward();
-        return;
-      }
-    }
-
-    // We can't interact with the desktop node, so skip it.
-    if (node === this.desktop_) {
-      this.node_ = node;
-      this.moveForward();
-      return;
-    }
-
-    this.setCurrentNode_(node);
-  }
-
-  /**
-   * Scrolls the current node in the direction indicated by |scrollAction|.
-   * @param {!SAConstants.MenuAction} scrollAction
-   */
-  scroll(scrollAction) {
-    // Find the closest ancestor to the current node that is scrollable.
-    let scrollNode = this.node_;
-    while (scrollNode && !scrollNode.scrollable)
-      scrollNode = scrollNode.parent;
-    if (!scrollNode)
-      return;
-
-    if (scrollAction === SAConstants.MenuAction.SCROLL_DOWN)
-      scrollNode.scrollDown(() => {});
-    else if (scrollAction === SAConstants.MenuAction.SCROLL_UP)
-      scrollNode.scrollUp(() => {});
-    else if (scrollAction === SAConstants.MenuAction.SCROLL_LEFT)
-      scrollNode.scrollLeft(() => {});
-    else if (scrollAction === SAConstants.MenuAction.SCROLL_RIGHT)
-      scrollNode.scrollRight(() => {});
-    else if (scrollAction === SAConstants.MenuAction.SCROLL_FORWARD)
-      scrollNode.scrollForward(() => {});
-    else if (scrollAction === SAConstants.MenuAction.SCROLL_BACKWARD)
-      scrollNode.scrollBackward(() => {});
-    else
-      console.log('Unrecognized scroll action: ', scrollAction);
-  }
-
-  /**
-   * Moves the text caret to the beginning of the current node.
-   * @public
-   */
-  jumpToBeginningOfText() {
-    this.textNavigationManager_.jumpToBeginning();
-  }
-
-  /**
-   * Moves the text caret to the end of the current node.
-   * @public
-   */
-  jumpToEndOfText() {
-    this.textNavigationManager_.jumpToEnd();
-  }
-
-  /**
-   * Moves the text caret backward one character in the current
-   * node.
-   * @public
-   */
-  moveBackwardOneCharOfText() {
-    this.textNavigationManager_.moveBackwardOneChar();
-  }
-
-  /**
-   * Moves the text caret backward one word in the current node.
-   * @public
-   */
-  moveBackwardOneWordOfText() {
-    this.textNavigationManager_.moveBackwardOneWord();
-  }
-
-  /**
-   * Moves the text caret down one line in the current node.
-   * @public
-   */
-  moveDownOneLineOfText() {
-    this.textNavigationManager_.moveDownOneLine();
-  }
-
-  /**
-   * Moves the text caret forward one character in the current
-   * node.
-   * @public
-   */
-  moveForwardOneCharOfText() {
-    this.textNavigationManager_.moveForwardOneChar();
-  }
-
-  /**
-   * Moves the text caret forward one word in the current node.
-   * @public
-   */
-  moveForwardOneWordOfText() {
-    this.textNavigationManager_.moveForwardOneWord();
-  }
-
-  /**
-   * Moves the text caret up one line in the current node.
-   * @public
-   */
-  moveUpOneLineOfText() {
-    this.textNavigationManager_.moveUpOneLine();
-  }
-
-  /**
-   * Sets the selectionStart variable based on the selection of the current
-   * node.
-   * @public
-   */
-  saveSelectStart() {
-    this.textNavigationManager_.saveSelectStart();
-  }
-
-  /**
-   * Sets the selectionEnd variable based on the selection of the current node.
-   * @public
-   */
-  endSelection() {
-    this.textNavigationManager_.resetCurrentlySelecting();
-  }
-
-  /**
-   * Returns whether or not a selection is being made.
-   * @return {boolean}
-   * @public
-   */
-  currentlySelecting() {
-    return this.textNavigationManager_.currentlySelecting();
-  }
-
-  /**
-   * Cuts selected text.
-   * @public
-   */
-  cut() {
-    this.textInputManager_.cut();
-  }
-
-  /**
-   * Copies selected text.
-   * @public
-   */
-  copy() {
-    this.textInputManager_.copy();
-  }
-
-  /**
-   * Pastes selected text.
-   * @public
-   */
-  paste() {
-    this.textInputManager_.paste();
-  }
-
-  /**
-   * Perform the default action for the currently highlighted node. If the node
-   * is the current scope, go back to the previous scope. If the node is a group
-   * other than the current scope, go into that scope. If the node is
-   * interesting, perform the default action on it.
-   */
-  selectCurrentNode() {
-    if (this.menuManager_.selectCurrentNode()) {
-      return;
-    }
-
-    if (this.backButtonManager_.select()) {
-      return;
-    }
-
-    if (!this.node_.role) {
-      return;
-    }
-
-    if (this.switchAccess_.improvedTextInputEnabled()) {
-      if (SwitchAccessPredicate.isTextInput(this.node_)) {
-        this.node_.focus();
-        return;
-      }
-    }
-
-    if (this.textInputManager_.pressKey(this.node_)) {
-      return;
-    }
-
-    if (this.node_ === this.scope_) {
-      this.node_.doDefault();
-      return;
-    }
-
-    if (SwitchAccessPredicate.isGroup(this.node_, this.scope_)) {
-      this.setScope_(this.node_);
-      return;
-    }
-
-    this.node_.doDefault();
-  }
-
-  /**
-   * Performs |action| on the current node, if an appropriate action exists.
-   * @param {!SAConstants.MenuAction} action
-   */
-  performActionOnCurrentNode(action) {
-    if (Object.values(chrome.automation.ActionType).includes(action)) {
-      this.node_.performStandardAction(
-          /** @type {chrome.automation.ActionType} */ (action));
-    }
-  }
-
-  /**
-   * @param {chrome.automation.AutomationNode=} opt_newNode Optionally set the
-   *     node that will have the primary focus after exiting.
-   */
-  exitCurrentScope(opt_newNode) {
-    if (this.scopeStack_.length === 0)
-      return;
-    if (this.inSystemMenu_)
-      this.closeSystemMenu_();
-
-    this.node_ = opt_newNode || this.scope_;
-    // Find a previous scope that is still valid. The stack here always has
-    // at least one valid scope (i.e., the desktop node).
-    do {
-      this.scope_ = this.scopeStack_.pop();
-    } while (!this.scope_.role && this.scopeStack_.length > 0);
-
-    this.focusRingManager.setFocusNodes(this.node_, this.scope_);
+    this.setNode_(this.group_.firstChild);
   }
 
   /**
    * Puts focus on the virtual keyboard, if the current node is a text input.
-   * TODO(946190): Handle the case where the user has not enabled the onscreen
-   *               keyboard.
+   * TODO(cbug/946190): Handle the case where the user has not enabled the
+   *     onscreen keyboard.
    */
-  openKeyboard() {
-    if (!this.textInputManager_.enterKeyboard(this.node_))
-      return;
-
-    this.switchAccess_.setInKeyboard(true);
-
-    chrome.accessibilityPrivate.setVirtualKeyboardVisible(
-        true /* is_visible */);
-    this.setScope_(this.textInputManager_.getKeyboard(this.desktop_));
-
-    if (this.switchAccess_.improvedTextInputEnabled()) {
-      this.switchAccess_.restartAutoScan();
-    }
+  enterKeyboard() {
+    const keyboard = KeyboardRootNode.buildTree(this.desktop_);
+    this.node_.performAction(SAConstants.MenuAction.OPEN_KEYBOARD);
+    this.jumpTo_(keyboard);
   }
 
   /**
-   * If exiting the current scope will not move from inside the keyboard to
-   * outside the keyboard, this method does nothing.
-   * When we are exiting the keyboard, it resets the scope and removes the text
-   * input focus ring.
-   * @return {boolean} Whether any action was taken.
+   * Open the Switch Access menu for the currently highlighted node. If there
+   * are not enough actions available to trigger the menu, the current element
+   * is selected.
    */
-  leaveKeyboardIfNeeded() {
-    const newScope = this.scopeStack_[this.scopeStack_.length - 1];
-    if (!this.textInputManager_.inVirtualKeyboard(this.scope_) ||
-        this.textInputManager_.inVirtualKeyboard(newScope)) {
-      return false;
+  enterMenu() {
+    if (this.menuManager_.enter(this.node_)) return;
+
+    // If the menu does not or cannot open, select the current node.
+    this.selectCurrentNode();
+  }
+
+  /**
+   * Move to the previous interesting node.
+   */
+  moveBackward() {
+    if (this.menuManager_.moveBackward()) {
+      // The menu navigation is handled separately. If we are in the menu, do
+      // not change the primary focus node.
+      return;
     }
 
-    this.textInputManager_.returnToTextFocus();
-    chrome.accessibilityPrivate.setVirtualKeyboardVisible(
-        false /* isVisible */);
-    this.switchAccess_.setInKeyboard(false);
-    return true;
+    this.setNode_(this.node_.previous);
   }
 
   /**
+   * Move to the next interesting node.
+   */
+  moveForward() {
+    if (this.menuManager_.moveForward()) {
+      // The menu navigation is handled separately. If we are in the menu, do
+      // not change the primary focus node.
+      return;
+    }
+
+    this.setNode_(this.node_.next);
+  }
+
+  /**
+   * Selects the current node.
+   */
+  selectCurrentNode() {
+    if (this.menuManager_.selectCurrentNode()) {
+      // The menu navigation is handled separately. If we are in the menu, do
+      // not change the primary focus node.
+      return;
+    }
+
+    if (this.node_.isGroup()) {
+      this.enterGroup();
+      return;
+    }
+
+    if (this.node_.hasAction(SAConstants.MenuAction.OPEN_KEYBOARD)) {
+      this.node_.performAction(SAConstants.MenuAction.OPEN_KEYBOARD);
+      this.enterKeyboard();
+      return;
+    }
+
+    if (this.node_.hasAction(SAConstants.MenuAction.SELECT)) {
+      this.node_.performAction(SAConstants.MenuAction.SELECT);
+    }
+  }
+
+  // -------------------------------------------------------
+  // |                 Event Handlers                      |
+  // -------------------------------------------------------
+
+  /**
    * Sets up the connection between the menuPanel and menuManager.
    * @param {!PanelInterface} menuPanel
    * @return {!MenuManager}
    */
   connectMenuPanel(menuPanel) {
-    this.backButtonManager_.init(menuPanel, this.desktop_);
+    menuPanel.backButtonElement().addEventListener(
+        'click', this.exitGroup_.bind(this));
+    this.focusRingManager_.setMenuPanel(menuPanel);
     return this.menuManager_.connectMenuPanel(menuPanel);
   }
 
-  // ----------------------Private Methods---------------------
-
   /**
-   * Create a new scope stack and set the current scope for |node|.
-   *
-   * @param {!chrome.automation.AutomationNode} node
-   * @private
-   */
-  buildScopeStack_(node) {
-    // Create list of |node|'s ancestors, with highest level ancestor at the
-    // end.
-    let ancestorList = [];
-    while (node.parent) {
-      ancestorList.push(node.parent);
-      node = node.parent;
-    }
-
-    // Starting with desktop as the scope, if an ancestor is a group, set it to
-    // the new scope and push the old scope onto the scope stack.
-    this.scopeStack_ = [];
-    this.scope_ = this.desktop_;
-    while (ancestorList.length > 0) {
-      let ancestor = ancestorList.pop();
-      if (ancestor.role === chrome.automation.RoleType.DESKTOP)
-        continue;
-      if (SwitchAccessPredicate.isGroup(ancestor, this.scope_)) {
-        this.scopeStack_.push(this.scope_);
-        this.scope_ = ancestor;
-      }
-    }
-  }
-
-  /**
-   * Closes a system menu when the back button is pressed.
-   */
-  closeSystemMenu_() {
-    EventHelper.simulateKeyPress(EventHelper.KeyCode.ESC);
-  }
-
-  /**
-   * When an interesting element gains focus on the page, move to it. If an
-   * element gains focus but is not interesting, move to the next interesting
-   * node after it.
-   *
+   * When focus shifts, move to the element. Find the closest interesting
+   *     element to engage with.
    * @param {!chrome.automation.AutomationEvent} event
    * @private
    */
   onFocusChange_(event) {
-    if (this.node_ === event.target)
-      return;
-
-    // Exit the menu if it is open.
-    if (this.menuManager_.inMenu()) {
-      this.menuManager_.exit();
-    }
-
-    // Rebuild scope stack and set scope for focused node.
-    this.buildScopeStack_(event.target);
-
-    this.textNavigationManager_.resetSelStartIndex();
-
-    // Restart auto-scan.
-    if (this.switchAccess_.improvedTextInputEnabled()) {
-      this.switchAccess_.restartAutoScan();
-    }
-
-    // Move to focused node.
-    this.node_ = event.target;
-
-    // In case the node that gained focus is not a subtreeLeaf.
-    if (SwitchAccessPredicate.isInteresting(this.node_, this.scope_))
-      this.focusRingManager.setFocusNodes(this.node_, this.scope_);
-    else
-      this.moveForward();
+    if (this.node_.isEquivalentTo(event.target)) return;
+    this.moveTo_(event.target);
   }
 
   /**
@@ -570,304 +158,185 @@
    * @private
    */
   onMenuStart_(event) {
-    this.setScope_(event.target);
-    this.inSystemMenu_ = true;
+    const menuRoot = SystemMenuRootNode.buildTree(event.target);
+    this.jumpTo_(menuRoot);
   }
 
   /**
-   * When a node is removed from the page, move to a new valid node.
-   *
+   * Notifies focus manager that the preferences have initially loaded.
+   */
+  onPrefsReady() {
+    this.focusRingManager_.onPrefsReady();
+    this.focusRingManager_.setFocusNodes(this.node_, this.group_);
+  }
+
+  /**
+   * When the automation tree changes, check if it affects any nodes we are
+   *     currently listening to.
    * @param {!chrome.automation.TreeChange} treeChange
    * @private
    */
-  onNodeRemoved_(treeChange) {
-    // TODO(elichtenberg): Only listen to NODE_REMOVED callbacks. Don't need
-    // any others.
-    if (treeChange.type !== chrome.automation.TreeChangeType.NODE_REMOVED)
+  onTreeChange_(treeChange) {
+    if (treeChange.type === chrome.automation.TreeChangeType.TEXT_CHANGED) {
       return;
+    }
+    this.moveToValidNode_();
+  }
 
-    // TODO(elichtenberg): Currently not getting NODE_REMOVED event when whole
-    // tree is deleted. Once fixed, can delete this. Should only need to check
-    // if target is current node.
-    let removedByRWA =
-        treeChange.target.role === chrome.automation.RoleType.ROOT_WEB_AREA &&
-        !this.node_.role;
+  // -------------------------------------------------------
+  // |                 Private Methods                     |
+  // -------------------------------------------------------
 
-    if (!removedByRWA && treeChange.target !== this.node_)
-      return;
-
-    if (this.inSystemMenu_) {
-      // The node removed was the system menu.
-      this.inSystemMenu_ = false;
+  /**
+   * Create a stack of the groups the specified node is in, and set
+   *      |this.group_| to the most proximal group.
+   *  @param {!chrome.automation.AutomationNode} node
+   *  @private
+   */
+  buildGroupStack_(node) {
+    // Create a list of ancestors.
+    let ancestorList = [];
+    while (node.parent) {
+      ancestorList.push(node.parent);
+      node = node.parent;
     }
 
-    this.focusRingManager.clearAll();
+    this.groupStack_ = [];
+    this.group_ = RootNodeWrapper.buildDesktopTree(this.desktop_);
+    while (ancestorList.length > 0) {
+      let ancestor = ancestorList.pop();
+      if (ancestor.role === chrome.automation.RoleType.DESKTOP) continue;
 
-    // Current node not invalid until after treeChange callback, so move to
-    // valid node after callback. Delay added to prevent moving to another
-    // node about to be made invalid. If already at a valid node (e.g., user
-    // moves to it or focus changes to it), won't need to move to a new node.
-    window.setTimeout(function() {
-      if (!this.node_.role)
-        this.moveForward();
-    }.bind(this), 100);
+      if (SwitchAccessPredicate.isGroup(ancestor, this.group_)) {
+        this.groupStack_.push(this.group_);
+        this.group_ = RootNodeWrapper.buildTree(ancestor);
+      }
+    }
   }
 
   /**
+   * Exits the current group.
    * @private
    */
+  exitGroup_() {
+    if (this.groupStack_.length === 0) return;
+
+    this.group_.onExit();
+
+    // Find a group that is still valid.
+    do {
+      this.group_ = this.groupStack_.pop();
+    } while (!this.group_.isValid() && this.groupStack_.length);
+
+    this.setNode_(this.group_.firstChild);
+  }
+
+
+  /** @private */
   init_() {
-    if (this.switchAccess_.prefsAreReady()) {
-      this.focusRingManager.onPrefsReady();
+    if (window.switchAccess.prefsAreReady()) {
+      this.onPrefsReady();
     }
 
     this.desktop_.addEventListener(
         chrome.automation.EventType.FOCUS, this.onFocusChange_.bind(this),
         false);
+
     this.desktop_.addEventListener(
         chrome.automation.EventType.MENU_START, this.onMenuStart_.bind(this),
         false);
 
-    // TODO(elichtenberg): Use a more specific filter than ALL_TREE_CHANGES.
     chrome.automation.addTreeChangeObserver(
         chrome.automation.TreeChangeObserverFilter.ALL_TREE_CHANGES,
-        this.onNodeRemoved_.bind(this));
+        this.onTreeChange_.bind(this));
   }
 
   /**
-   * Set |this.node_| to |node|, and update its appearance onscreen.
-   *
-   * @param {!chrome.automation.AutomationNode} node
+   * Jumps Switch Access focus to a specified node, such as when opening a menu
+   * or the keyboard. Does not modify the groups already in the group stack.
+   * @param {!SARootNode} group
+   * @private
    */
-  setCurrentNode_(node) {
+  jumpTo_(group) {
+    this.menuManager_.exit();
+
+    this.groupStack_.push(this.group_);
+    this.group_ = group;
+    this.setNode_(this.group_.firstChild);
+  }
+
+  /**
+   * Moves Switch Access focus to a specified node, based on a focus shift or
+   *     tree change event. Reconstructs the group stack to center on that node.
+   *
+   * This is a "permanent" move, while |jumpTo_| is a "temporary" move.
+   *
+   * @param {!chrome.automation.AutomationNode} automationNode
+   * @private
+   */
+  moveTo_(automationNode) {
+    this.buildGroupStack_(automationNode);
+    let node = this.group_.firstChild;
+    for (const child of this.group_.children) {
+      if (child.isEquivalentTo(automationNode)) node = child;
+    }
+    if (node.equals(this.node_)) return;
+
+    this.menuManager_.exit();
+    this.setNode_(node);
+  }
+
+  /**
+   * Moves the Switch Access focus up the group stack to the closest ancestor
+   *     that hasn't been invalidated.
+   * @private
+   */
+  moveToValidNode_() {
+    if (this.node_.role && this.group_.isValid()) return;
+
+    if (this.node_.role) {
+      // Our group has been invalidated. Move to this node to repair the group
+      // stack.
+      const node = this.node_.automationNode;
+      if (node) {
+        this.moveTo_(node);
+        return;
+      }
+    }
+
+    if (this.group_.isValid()) {
+      const group = this.group_.automationNode;
+      if (group) {
+        this.moveTo_(group);
+        return;
+      }
+    }
+
+    for (let group of this.groupStack_) {
+      if (group.isValid()) {
+        group = group.automationNode;
+        if (group) {
+          this.moveTo_(group);
+          return;
+        }
+      }
+    }
+
+    const desktop = RootNodeWrapper.buildDesktopTree(this.desktop_);
+    const nextNode = desktop.firstChild.automationNode;
+    if (nextNode) this.moveTo_(nextNode);
+  }
+
+  /**
+   * Set |this.node_| to |node|, and update what is displayed onscreen.
+   * @param {!SAChildNode} node
+   * @private
+   */
+  setNode_(node) {
+    this.node_.onUnfocus();
     this.node_ = node;
-    this.focusRingManager.setFocusNodes(this.node_, this.scope_);
-  }
-
-  /**
-   * Set the scope to the provided node.
-   * @param {chrome.automation.AutomationNode} node
-   */
-  setScope_(node) {
-    if (!node)
-      return;
-    this.scopeStack_.push(this.scope_);
-    this.scope_ = node;
-
-    // The first node will come immediately after the back button, so we set
-    // |this.node_| to the back button and call |moveForward|.
-    const backButtonNode = this.backButtonManager_.backButtonNode();
-    if (backButtonNode)
-      this.node_ = backButtonNode;
-    else
-      this.node_ = this.scope_;
-    this.moveForward();
-  }
-
-  /**
-   * Checks if this.node_ is valid. If so, do nothing.
-   *
-   * If this.node_ is not valid, set this.node_ to a valid scope. Will check the
-   * current scope and past scopes until a valid scope is found. this.node_
-   * is set to that valid scope.
-   *
-   * @private
-   */
-  startAtValidNode_() {
-    if (this.node_.role)
-      return;
-
-    // Current node is invalid, but current scope is still valid, so set node
-    // to the current scope.
-    if (this.scope_.role)
-      this.node_ = this.scope_;
-
-    // Current node and current scope are invalid, so set both to a valid scope
-    // from the scope stack. The stack here always has at least one valid scope
-    // (i.e., the desktop node).
-    while (!this.node_.role && this.scopeStack_.length > 0) {
-      this.node_ = this.scopeStack_.pop();
-      this.scope_ = this.node_;
-    }
-  }
-
-  /**
-   * Get the youngest descendant of |node|, if it has one within the current
-   * scope.
-   *
-   * @param {!chrome.automation.AutomationNode} node
-   * @return {!chrome.automation.AutomationNode}
-   * @private
-   */
-  youngestDescendant_(node) {
-    const leaf = SwitchAccessPredicate.leaf(this.scope_);
-    const visit = SwitchAccessPredicate.visit(this.scope_);
-
-    const result = this.youngestDescendantHelper_(node, leaf, visit);
-    if (!result)
-      return this.scope_;
-    return result;
-  }
-
-  /**
-   * @param {!chrome.automation.AutomationNode} node
-   * @param {function(!chrome.automation.AutomationNode): boolean} leaf
-   * @param {function(!chrome.automation.AutomationNode): boolean} visit
-   * @return {chrome.automation.AutomationNode}
-   * @private
-   */
-  youngestDescendantHelper_(node, leaf, visit) {
-    if (!node)
-      return null;
-
-    if (leaf(node))
-      return visit(node) ? node : null;
-
-    const reverseChildren = node.children.reverse();
-    for (const child of reverseChildren) {
-      const youngest = this.youngestDescendantHelper_(child, leaf, visit);
-      if (youngest)
-        return youngest;
-    }
-
-    return visit(node) ? node : null;
-  }
-
-  // ----------------------Debugging Methods------------------------
-
-  /**
-   * Prints a debug version of the accessibility tree with annotations of
-   * various SwitchAccess properties.
-   *
-   * To use, got to the console for SwitchAccess and run
-   *    switchAccess.automationManager_.printDebugSwitchAccessTree()
-   *
-   * @param {NavigationManager.DisplayMode} opt_displayMode - an optional
-   *     parameter that controls which nodes are printed. Default is
-   *     INTERESTING_NODE.
-   * @return {SwitchAccessDebugNode|undefined}
-   */
-  printDebugSwitchAccessTree(
-      opt_displayMode = NavigationManager.DisplayMode.INTERESTING_NODE) {
-    let allNodes = opt_displayMode === NavigationManager.DisplayMode.ALL;
-    let debugRoot =
-        NavigationManager.switchAccessDebugTree_(this.desktop_, allNodes);
-    if (debugRoot)
-      NavigationManager.printDebugNode_(debugRoot, 0, opt_displayMode);
-    return debugRoot;
-  }
-  /**
-   * creates a tree for debugging the SwitchAccess predicates, rooted at
-   * node, based on the Accessibility tree.
-   *
-   * @param {!chrome.automation.AutomationNode} node
-   * @param {boolean} allNodes
-   * @return {SwitchAccessDebugNode|undefined}
-   * @private
-   */
-  static switchAccessDebugTree_(node, allNodes) {
-    let debugNode = this.createAnnotatedDebugNode_(node, allNodes);
-    if (!debugNode)
-      return;
-
-    for (let child of node.children) {
-      let dChild = this.switchAccessDebugTree_(child, allNodes);
-      if (dChild)
-        debugNode.children.push(dChild);
-    }
-    return debugNode;
-  }
-
-  /**
-   * Creates a debug node from the given automation node, with annotations of
-   * various SwitchAccess properties.
-   *
-   * @param {!chrome.automation.AutomationNode} node
-   * @param {boolean} allNodes
-   * @return {SwitchAccessDebugNode|undefined}
-   * @private
-   */
-  static createAnnotatedDebugNode_(node, allNodes) {
-    if (!allNodes && !SwitchAccessPredicate.isInterestingSubtree(node))
-      return;
-
-    let debugNode = {};
-    if (node.role)
-      debugNode.role = node.role;
-    if (node.name)
-      debugNode.name = node.name;
-
-    debugNode.isActionable = SwitchAccessPredicate.isActionable(node);
-    debugNode.isGroup = SwitchAccessPredicate.isGroup(node, node);
-    debugNode.isInterestingSubtree =
-        SwitchAccessPredicate.isInterestingSubtree(node);
-
-    debugNode.children = [];
-    debugNode.baseNode = node;
-    return debugNode;
-  }
-
-  /**
-   * Prints the debug subtree rooted at |node| in pre-order.
-   *
-   * @param {SwitchAccessDebugNode} node
-   * @param {!number} indent
-   * @param {NavigationManager.DisplayMode} displayMode
-   * @private
-   */
-  static printDebugNode_(node, indent, displayMode) {
-    if (!node)
-      return;
-
-    let result = ' '.repeat(indent);
-    if (node.role)
-      result += 'role:' + node.role + ' ';
-    if (node.name)
-      result += 'name:' + node.name + ' ';
-    result += 'isActionable? ' + node.isActionable;
-    result += ', isGroup? ' + node.isGroup;
-    result += ', isInterestingSubtree? ' + node.isInterestingSubtree;
-
-    switch (displayMode) {
-      case NavigationManager.DisplayMode.ALL:
-        console.log(result);
-        break;
-      case NavigationManager.DisplayMode.INTERESTING_SUBTREE:
-        if (node.isInterestingSubtree)
-          console.log(result);
-        break;
-      case NavigationManager.DisplayMode.INTERESTING_NODE:
-      default:
-        if (node.isActionable || node.isGroup)
-          console.log(result);
-        break;
-    }
-
-    let children = node.children || [];
-    for (let child of children)
-      this.printDebugNode_(child, indent + 2, displayMode);
+    this.node_.onFocus();
+    this.focusRingManager_.setFocusNodes(this.node_, this.group_);
+    window.switchAccess.restartAutoScan();
   }
 }
-
-/**
- * Display modes for debugging tree.
- *
- * @enum {string}
- * @const
- */
-NavigationManager.DisplayMode = {
-  ALL: 'all',
-  INTERESTING_SUBTREE: 'interestingSubtree',
-  INTERESTING_NODE: 'interestingNode'
-};
-
-/**
- * @typedef {{role: (string|undefined),
- *            name: (string|undefined),
- *            isActionable: boolean,
- *            isGroup: boolean,
- *            isInterestingSubtree: boolean,
- *            children: Array<SwitchAccessDebugNode>,
- *            baseNode: chrome.automation.AutomationNode}}
- */
-let SwitchAccessDebugNode;
diff --git a/chrome/browser/resources/chromeos/switch_access/navigation_manager_test.extjs b/chrome/browser/resources/chromeos/switch_access/navigation_manager_test.extjs
index 655d1af..5ee64b8a 100644
--- a/chrome/browser/resources/chromeos/switch_access/navigation_manager_test.extjs
+++ b/chrome/browser/resources/chromeos/switch_access/navigation_manager_test.extjs
@@ -16,17 +16,20 @@
   __proto__: SwitchAccessE2ETest.prototype
 }
 
-function navigateToWebpage(desktop) {
-  assertTrue(desktop != null);
-  const node = new AutomationTreeWalker(desktop, constants.Dir.FORWARD,
-      { visit: (node) => node.role === chrome.automation.RoleType.ROOT_WEB_AREA
-                      && SwitchAccessPredicate.isInterestingSubtree(node, node)
-      }
-  ).next().node;
+function moveToPageContents() {
+  const navigator = switchAccess.navigationManager_;
+  // Start from the desktop node.
+  navigator.group_ = RootNodeWrapper.buildDesktopTree(navigator.desktop_);
+  navigator.node_ = navigator.group_.firstChild;
 
-  assertTrue(node != null);
-  switchAccess.navigationManager_.node_ = node;
-  switchAccess.moveForward();
+  // The first item should be the browser window.
+  navigator.selectCurrentNode();
+
+  // The third item in the browser window is the page contents.
+  // TODO(anastasi): find the browser window dynamically.
+  navigator.moveForward();
+  navigator.moveForward();
+  navigator.selectCurrentNode();
 }
 
 function currentNode() {
@@ -34,154 +37,30 @@
 }
 
 TEST_F('SwitchAccessNavigationManagerTest', 'SelectButton', function() {
-  const website = `data:text/html;charset=utf-8,
-                  <button id="test" aria-pressed="false"></button>
-                  <script>
-                    var state = false;
-                    var button = document.getElementById("test");
-                    button.onclick = () => {
-                      state = !state;
-                      button.setAttribute("aria-pressed", state);
-                    };
-                  </script>`;
+  const website =
+      `data:text/html;charset=utf-8,
+      <button id="test" aria-pressed="false">First Button</button>
+      <button>Second Button</button>
+      <script>
+        var state = false;
+        var button = document.getElementById("test");
+        button.onclick = () => {
+          state = !state;
+          button.setAttribute("aria-pressed", state);
+        };
+      </script>`;
 
   this.runWithLoadedTree(website, function(desktop) {
-    navigateToWebpage(desktop);
+    moveToPageContents();
 
-    currentNode().addEventListener(
-        chrome.automation.EventType.CHECKED_STATE_CHANGED,
-        this.newCallback(function(event) {
-          assertEquals(this.node.name, event.target.name);
-        }.bind({node: currentNode()}))
-    );
+    let node = currentNode().automationNode;
+    assertTrue(!!node);
+    assertEquals(node.name, "First Button");
+
+    node.addEventListener(
+      chrome.automation.EventType.CHECKED_STATE_CHANGED,
+      this.newCallback((event) => assertEquals(node.name, event.target.name)));
 
     switchAccess.selectCurrentNode();
   });
 });
-
-// The back button is inconsistently loaded before this test runs.
-// TODO(anastasi): Fix flakiness and re-enable the tests.
-TEST_F('SwitchAccessNavigationManagerTest', 'DISABLED_MoveForward', function() {
-  const website = `data:text/html;charset=utf-8,
-                  <button>button1</button>
-                  <button>button2</button>
-                  <button>button3</button>`;
-
-  this.runWithLoadedTree(website, function(desktop) {
-    navigateToWebpage(desktop);
-
-    const initialScope = currentNode();
-    // We need to enter the container containing the buttons.
-    assertEquals(initialScope.role, chrome.automation.RoleType.GENERIC_CONTAINER);
-    switchAccess.selectCurrentNode();
-
-    const button1 = currentNode();
-    assertEquals('button1', button1.name);
-
-    switchAccess.moveForward();
-
-    const button2 = currentNode();
-    assertEquals('button2', button2.name);
-
-    switchAccess.moveForward();
-
-    const button3 = currentNode();
-    assertEquals('button3', button3.name);
-
-    switchAccess.moveForward();
-
-    // check that the initialScope is the final element.
-    assertEquals(currentNode(), initialScope);
-
-    switchAccess.moveForward();
-
-    // check that we loop around again.
-    assertEquals(currentNode(), button1);
-  });
-});
-
-// The back button is inconsistently loaded before this test runs.
-// TODO(anastasi): Fix flakiness and re-enable the tests.
-TEST_F('SwitchAccessNavigationManagerTest', 'DISABLED_MoveBackward', function() {
-  const website = `data:text/html;charset=utf-8,
-                  <button>button1</button>
-                  <button>button2</button>
-                  <button>button3</button>`;
-
-  this.runWithLoadedTree(website, function(desktop) {
-    navigateToWebpage(desktop);
-
-    const initialScope = currentNode();
-    // We need to enter the container containing the buttons.
-    assertEquals(initialScope.role, chrome.automation.RoleType.GENERIC_CONTAINER);
-    switchAccess.selectCurrentNode();
-
-    const button1 = currentNode();
-    assertEquals('button1', button1.name);
-
-    switchAccess.moveBackward();
-
-    // Moving backwards from the first button should take us to the initialScope.
-    assertEquals(currentNode(), initialScope);
-
-    switchAccess.moveBackward();
-
-    // Moving backwards from the initialScope should take us to the last button.
-    const button3 = currentNode();
-    assertEquals('button3', button3.name);
-
-    switchAccess.moveBackward();
-
-    const button2 = currentNode();
-    assertEquals('button2', button2.name);
-
-    switchAccess.moveBackward();
-
-    // We should be back at buttton 1.
-    assertEquals(currentNode(), button1);
-  });
-});
-
-// The back button is inconsistently loaded before this test runs.
-// TODO(anastasi): Fix flakiness and re-enable the tests.
-TEST_F('SwitchAccessNavigationManagerTest', 'DISABLED_MoveBackAndForth', function() {
-  const website = `data:text/html;charset=utf-8,
-                  <button>button1</button>
-                  <button>button2</button>
-                  <button>button3</button>`;
-
-  this.runWithLoadedTree(website, function(desktop) {
-    navigateToWebpage(desktop);
-
-    const initialScope = currentNode();
-    // We need to enter the container containing the buttons.
-    switchAccess.selectCurrentNode();
-
-    const button1 = currentNode();
-    assertEquals('button1', button1.name);
-
-    switchAccess.moveBackward();
-
-    // Moving backwards from the first button should take us to the initialScope.
-    assertEquals(currentNode(), initialScope);
-
-    switchAccess.moveBackward();
-
-    // Moving backwards from the initialScope should take us to the last button.
-    const button3 = currentNode();
-    assertEquals('button3', button3.name);
-
-    switchAccess.moveForward();
-
-    assertEquals(currentNode(), initialScope);
-
-    switchAccess.moveForward();
-
-    assertEquals(currentNode(), button1);
-
-    switchAccess.moveForward();
-
-    const button2 = currentNode();
-    assertEquals('button2', button2.name);
-  });
-});
diff --git a/chrome/browser/resources/chromeos/switch_access/nodes/back_button_node.js b/chrome/browser/resources/chromeos/switch_access/nodes/back_button_node.js
new file mode 100644
index 0000000..8843f63
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/nodes/back_button_node.js
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * This class handles the behavior of the back button.
+ */
+class BackButtonNode extends SAChildNode {
+  /**
+   * @param {!SARootNode} group
+   */
+  constructor(group) {
+    super(false /* isGroup */);
+    /**
+     * The group that the back button is shown for.
+     * @private {!SARootNode}
+     */
+    this.group_ = group;
+
+    /** @private {chrome.automation.AutomationNode} */
+    this.node_ = window.switchAccess.getBackButtonAutomationNode();
+  }
+
+  /** @override */
+  equals(other) {
+    return other instanceof BackButtonNode;
+  }
+
+  /** @override */
+  get role() {
+    return chrome.automation.RoleType.BUTTON;
+  }
+
+  /** @override */
+  get location() {
+    if (this.node_) return this.node_.location;
+  }
+
+  /** @override */
+  get automationNode() {
+    return this.node_;
+  }
+
+  /** @override */
+  get actions() {
+    return [SAConstants.MenuAction.SELECT];
+  }
+
+  /** @override */
+  performAction(action) {
+    if (action !== SAConstants.MenuAction.SELECT) return false;
+
+    if (this.node_) this.node_.doDefault();
+    return true;
+  }
+
+  /** @override */
+  isEquivalentTo(node) {
+    return this.node_ === node;
+  }
+
+  /** @override */
+  asRootNode() {
+    return null;
+  }
+
+  /** @override */
+  onFocus() {
+    chrome.accessibilityPrivate.setSwitchAccessMenuState(
+        true, this.group_.location, 0 /* num_actions */);
+  }
+
+  /** @override */
+  onUnfocus() {
+    chrome.accessibilityPrivate.setSwitchAccessMenuState(
+        false, RectHelper.ZERO_RECT, 0 /* num_actions */);
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/nodes/keyboard_node.js b/chrome/browser/resources/chromeos/switch_access/nodes/keyboard_node.js
new file mode 100644
index 0000000..8d80382
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/nodes/keyboard_node.js
@@ -0,0 +1,115 @@
+// 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.
+
+/**
+ * This class handles the behavior of keyboard nodes directly associated with a
+ * single AutomationNode.
+ */
+class KeyboardNode extends NodeWrapper {
+  /**
+   * @param {!chrome.automation.AutomationNode} node
+   * @param {!SARootNode} parent
+   */
+  constructor(node, parent) {
+    super(node, parent);
+  }
+
+  /** @override */
+  get actions() {
+    if (this.isGroup()) return [];
+    return [SAConstants.MenuAction.SELECT];
+  }
+
+  /** @override */
+  performAction(action) {
+    if (this.isGroup()) return false;
+    if (action !== SAConstants.MenuAction.SELECT) return false;
+    let keyLocation = this.location;
+    if (!keyLocation) return false;
+
+    // doDefault() does nothing on Virtual Keyboard buttons, so we must
+    // simulate a mouse click.
+    const center = RectHelper.center(keyLocation);
+    EventHelper.simulateMouseClick(
+        center.x, center.y, SAConstants.VK_KEY_PRESS_DURATION_MS);
+
+    return true;
+  }
+
+  /** @override */
+  asRootNode() {
+    if (!this.isGroup()) return null;
+
+    const node = this.automationNode;
+    if (!node) {
+      throw new TypeError('Keyboard nodes must have an automation node.');
+    }
+
+    return KeyboardNode.buildTree_(node);
+  }
+
+  /**
+   * Builds a tree of KeyboardNodes.
+   * @param {!chrome.automation.AutomationNode} automationNode
+   * @return {!SARootNode}
+   * @private
+   */
+  static buildTree_(automationNode) {
+    const root = new RootNodeWrapper(automationNode);
+    const childConstructor = (node) => new KeyboardNode(node, root);
+
+    RootNodeWrapper.buildHelper(root, childConstructor);
+    return root;
+  }
+}
+
+/**
+ * This class handles the top-level Keyboard node, as well as the construction
+ * of the Keyboard tree.
+ */
+class KeyboardRootNode extends RootNodeWrapper {
+  /**
+   * @param {!chrome.automation.AutomationNode} keyboard
+   * @private
+   */
+  constructor(keyboard) {
+    super(keyboard);
+  }
+
+  /** @override */
+  onExit() {
+    chrome.accessibilityPrivate.setVirtualKeyboardVisible(false);
+  }
+
+  /**
+   * Custom logic when entering the node.
+   */
+  onEnter_() {
+    chrome.accessibilityPrivate.setVirtualKeyboardVisible(true);
+  }
+
+  /**
+   * Creates the tree structure for the system menu.
+   * @param {!chrome.automation.AutomationNode} desktop
+   * @return {!KeyboardRootNode}
+   */
+  static buildTree(desktop) {
+    const keyboardContainer =
+        desktop.find({role: chrome.automation.RoleType.KEYBOARD});
+    const keyboard =
+        new AutomationTreeWalker(keyboardContainer, constants.Dir.FORWARD, {
+          visit: (node) => SwitchAccessPredicate.isGroup(node, null),
+          root: (node) => node === keyboardContainer
+        })
+            .next()
+            .node;
+
+    const root = new KeyboardRootNode(keyboard);
+    root.onEnter_();
+    const childConstructor = (node) => new KeyboardNode(node, root);
+
+    RootNodeWrapper.buildHelper(root, childConstructor);
+    return root;
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/nodes/node_wrapper.js b/chrome/browser/resources/chromeos/switch_access/nodes/node_wrapper.js
new file mode 100644
index 0000000..07cebfb
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/nodes/node_wrapper.js
@@ -0,0 +1,257 @@
+// 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.
+
+const AutomationNode = chrome.automation.AutomationNode;
+
+/**
+ * This class handles interactions with an onscreen element based on a single
+ * AutomationNode.
+ */
+class NodeWrapper extends SAChildNode {
+  /**
+   * @param {!AutomationNode} baseNode
+   * @param {?SARootNode} parent
+   */
+  constructor(baseNode, parent) {
+    super(SwitchAccessPredicate.isGroup(baseNode, parent));
+    /** @private {!AutomationNode} */
+    this.baseNode_ = baseNode;
+  }
+
+  /** @override */
+  equals(other) {
+    if (!other || !(other instanceof NodeWrapper)) return false;
+
+    other = /** @type {!NodeWrapper} */ (other);
+    return other.baseNode_ === this.baseNode_;
+  }
+
+  /** @override */
+  get role() {
+    return this.baseNode_.role;
+  }
+
+  /** @override */
+  get location() {
+    return this.baseNode_.location;
+  }
+
+  /** @override */
+  get automationNode() {
+    return this.baseNode_;
+  }
+
+  /** @override */
+  get actions() {
+    let actions = [];
+    if (SwitchAccessPredicate.isTextInput(this.baseNode_)) {
+      actions.push(SAConstants.MenuAction.OPEN_KEYBOARD);
+      actions.push(SAConstants.MenuAction.DICTATION);
+    } else {
+      actions.push(SAConstants.MenuAction.SELECT);
+    }
+
+    const ancestor = this.getScrollableAncestor_();
+    if (ancestor.scrollable) {
+      if (ancestor.scrollX > ancestor.scrollXMin) {
+        actions.push(SAConstants.MenuAction.SCROLL_LEFT);
+      }
+      if (ancestor.scrollX < ancestor.scrollXMax) {
+        actions.push(SAConstants.MenuAction.SCROLL_RIGHT);
+      }
+      if (ancestor.scrollY > ancestor.scrollYMin) {
+        actions.push(SAConstants.MenuAction.SCROLL_UP);
+      }
+      if (ancestor.scrollY < ancestor.scrollYMax) {
+        actions.push(SAConstants.MenuAction.SCROLL_DOWN);
+      }
+    }
+    const standardActions = /** @type {!Array<!SAConstants.MenuAction>} */ (
+        this.baseNode_.standardActions.filter(
+            action => Object.values(SAConstants.MenuAction).includes(action)));
+
+    return actions.concat(standardActions);
+  }
+
+  /** @override */
+  performAction(action) {
+    let ancestor;
+    switch (action) {
+      case SAConstants.MenuAction.OPEN_KEYBOARD:
+        this.baseNode_.focus();
+        return true;
+      case SAConstants.MenuAction.SELECT:
+        this.baseNode_.doDefault();
+        return true;
+      case SAConstants.MenuAction.DICTATION:
+        chrome.accessibilityPrivate.toggleDictation();
+        return true;
+      case SAConstants.MenuAction.SCROLL_DOWN:
+        ancestor = this.getScrollableAncestor_();
+        if (ancestor.scrollable) ancestor.scrollDown(() => {});
+        return true;
+      case SAConstants.MenuAction.SCROLL_UP:
+        ancestor = this.getScrollableAncestor_();
+        if (ancestor.scrollable) ancestor.scrollUp(() => {});
+        return true;
+      case SAConstants.MenuAction.SCROLL_RIGHT:
+        ancestor = this.getScrollableAncestor_();
+        if (ancestor.scrollable) ancestor.scrollRight(() => {});
+        return true;
+      case SAConstants.MenuAction.SCROLL_LEFT:
+        ancestor = this.getScrollableAncestor_();
+        if (ancestor.scrollable) ancestor.scrollLeft(() => {});
+        return true;
+      default:
+        if (Object.values(chrome.automation.ActionType).includes(action)) {
+          this.baseNode_.performStandardAction(
+              /** @type {chrome.automation.ActionType} */ (action));
+        }
+        return true;
+    }
+  }
+
+  /**
+   * @return {AutomationNode}
+   * @protected
+   */
+  getScrollableAncestor_() {
+    let ancestor = this.baseNode_;
+    while (!ancestor.scrollable && ancestor.parent)
+      ancestor = ancestor.parent;
+    return ancestor;
+  }
+
+  /** @override */
+  isEquivalentTo(node) {
+    return this.baseNode_ === node;
+  }
+
+  /** @override */
+  asRootNode() {
+    if (!this.isGroup()) return null;
+    return RootNodeWrapper.buildTree(this.baseNode_);
+  }
+}
+
+/**
+ * This class handles constructing and traversing a group of onscreen elements
+ * based on all the interesting descendants of a single AutomationNode.
+ */
+class RootNodeWrapper extends SARootNode {
+  /**
+   * @param {!AutomationNode} baseNode
+   */
+  constructor(baseNode) {
+    super();
+
+    /** @private {!AutomationNode} */
+    this.baseNode_ = baseNode;
+  }
+
+  /** @override */
+  equals(other) {
+    if (!(other instanceof RootNodeWrapper)) return false;
+
+    other = /** @type {!RootNodeWrapper} */ (other);
+    return super.equals(other) && this.baseNode_ === other.baseNode_;
+  }
+
+  /** @override */
+  get location() {
+    return this.baseNode_.location || super.location;
+  }
+
+  /** @override */
+  isValid() {
+    return !!this.baseNode_.role;
+  }
+
+  /** @override */
+  isEquivalentTo(automationNode) {
+    return this.baseNode_ === automationNode;
+  }
+
+  /** @override */
+  get automationNode() {
+    return this.baseNode_;
+  }
+
+  /**
+   * @param {!AutomationNode} rootNode
+   * @return {!RootNodeWrapper}
+   */
+  static buildTree(rootNode) {
+    const root = new RootNodeWrapper(rootNode);
+    const childConstructor = (node) => new NodeWrapper(node, root);
+
+    RootNodeWrapper.buildHelper(root, childConstructor);
+    return root;
+  }
+
+  /**
+   * Helper function to connect tree elements, given constructors for the root
+   * and child types.
+   * @param {!RootNodeWrapper} root
+   * @param {function(!AutomationNode): !SAChildNode} childConstructor
+   *     Constructs a child node from an automation node.
+   */
+  static buildHelper(root, childConstructor) {
+    const interestingChildren = RootNodeWrapper.getInterestingChildren(root);
+
+    if (interestingChildren.length < 1) {
+      throw new Error('Root node must have at least 1 interesting child.');
+    }
+
+    const backButton = new BackButtonNode(root);
+
+    let children = interestingChildren.map(childConstructor);
+    children.push(backButton);
+
+    SARootNode.connectChildren(children);
+    root.setChildren_(children);
+
+    return;
+  }
+
+  /**
+   * @param {!AutomationNode} desktop
+   * @return {!RootNodeWrapper}
+   */
+  static buildDesktopTree(desktop) {
+    const root = new RootNodeWrapper(desktop);
+    const interestingChildren = RootNodeWrapper.getInterestingChildren(root);
+
+    if (interestingChildren.length < 1) {
+      throw new Error('Desktop node must have at least 1 interesting child.');
+    }
+
+    const childConstructor = (autoNode) => new NodeWrapper(autoNode, root);
+    let children = interestingChildren.map(childConstructor);
+
+    SARootNode.connectChildren(children);
+    root.setChildren_(children);
+
+    return root;
+  }
+
+  /**
+   * @param {!RootNodeWrapper} root
+   * @return {!Array<!AutomationNode>}
+   */
+  static getInterestingChildren(root) {
+    let interestingChildren = [];
+    let treeWalker = new AutomationTreeWalker(
+        root.baseNode_, constants.Dir.FORWARD,
+        SwitchAccessPredicate.restrictions(root));
+    let node = treeWalker.next().node;
+
+    while (node) {
+      interestingChildren.push(node);
+      node = treeWalker.next().node;
+    }
+
+    return interestingChildren;
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/nodes/switch_access_node.js b/chrome/browser/resources/chromeos/switch_access/nodes/switch_access_node.js
new file mode 100644
index 0000000..79aa903
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/nodes/switch_access_node.js
@@ -0,0 +1,254 @@
+// 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.
+
+/**
+ * This interface represents some object or group of objects on screen
+ *     that Switch Access may be interested in interacting with.
+ *
+ * There is no guarantee of uniqueness; two distinct SAChildNodes may refer
+ *     to the same object. However, it is expected that any pair of
+ *     SAChildNodes referring to the same interesting object are equal
+ *     (calling .equals() returns true).
+ * @abstract
+ */
+class SAChildNode {
+  /**
+   * @param {boolean} isGroup
+   */
+  constructor(isGroup) {
+    this.isGroup_ = isGroup;
+
+    /** @private {?SAChildNode} */
+    this.previous_ = null;
+
+    /** @private {?SAChildNode} */
+    this.next_ = null;
+  }
+
+  /**
+   * @param {!SAChildNode} previous
+   * @protected
+   */
+  setPrevious_(previous) {
+    this.previous_ = previous;
+  }
+
+  /**
+   * @param {!SAChildNode} next
+   * @protected
+   */
+  setNext_(next) {
+    this.next_ = next;
+  }
+
+  /**
+   * @param {SAChildNode} other
+   * @return {boolean}
+   * @abstract
+   */
+  equals(other) {}
+
+  /**
+   * @return {chrome.automation.RoleType|undefined}
+   * @abstract
+   */
+  get role() {}
+
+  /**
+   * @return {chrome.accessibilityPrivate.ScreenRect|undefined}
+   * @abstract
+   */
+  get location() {}
+
+  /**
+   * Returns the underlying automation node, if one exists.
+   * @return {chrome.automation.AutomationNode}
+   * @abstract
+   */
+  get automationNode() {}
+
+  /**
+   * Returns whether this node should be displayed as a group.
+   * @return {boolean}
+   */
+  isGroup() {
+    return this.isGroup_;
+  }
+
+  /**
+   * Returns a list of all the actions available for this node.
+   * @return {!Array<SAConstants.MenuAction>}
+   * @abstract
+   */
+  get actions() {}
+
+  /**
+   * Given a menu action, returns whether it can be performed on this node.
+   * @param {SAConstants.MenuAction} action
+   * @return {boolean}
+   */
+  hasAction(action) {
+    return this.actions.includes(action);
+  }
+
+  /**
+   * Performs the specified action on the node, if it is available.
+   * @param {SAConstants.MenuAction} action
+   * @return {boolean} Whether to close the menu. True if the menu should close,
+   *     false otherwise.
+   * @abstract
+   */
+  performAction(action) {}
+
+  /**
+   * @param {!chrome.automation.AutomationNode} node
+   * @return {boolean}
+   * @abstract
+   */
+  isEquivalentTo(node) {}
+
+  /**
+   * Returns the next node in pre-order traversal.
+   * @return {!SAChildNode}
+   */
+  get next() {
+    if (!this.next_) {
+      throw new Error(
+          'Next node must be set on all SAChildNodes before navigating');
+    }
+    return this.next_;
+  }
+
+  /**
+   * Returns the previous node in pre-order traversal.
+   * @return {!SAChildNode}
+   */
+  get previous() {
+    if (!this.previous_) {
+      throw new Error(
+          'Previous node must be set on all SAChildNodes before navigating');
+    }
+    return this.previous_;
+  }
+
+  /**
+   * If this node is a group, returns the analogous SARootNode.
+   * @return {SARootNode}
+   * @abstract
+   */
+  asRootNode() {}
+
+  /**
+   * Called when a node becomes the primary highlighted node.
+   */
+  onFocus() {}
+
+  /**
+   * Called when a node stops being the primary highlighted node.
+   */
+  onUnfocus() {}
+}
+
+/**
+ * This class represents the root node of a Switch Access traversal group.
+ */
+class SARootNode {
+  constructor() {
+    /** @private {!Array<!SAChildNode>} */
+    this.children_ = [];
+  }
+
+  /**
+   * @param {!Array<!SAChildNode>} children
+   * @protected
+   */
+  setChildren_(children) {
+    this.children_ = children;
+  }
+
+  /**
+   * @param {SARootNode} other
+   * @return {boolean}
+   */
+  equals(other) {
+    if (!other) return false;
+    if (this.children_.length !== other.children_.length) return false;
+
+    let result = true;
+    for (let i = 0; i < this.children_.length; i++) {
+      if (!this.children_[i]) throw new Error('Child cannot be null.');
+      result = result && this.children_[i].equals(other.children_[i]);
+    }
+
+    return result;
+  }
+
+  /** @return {!Array<!SAChildNode>} */
+  get children() {
+    return this.children_;
+  }
+
+  /** @return {!SAChildNode} */
+  get firstChild() {
+    if (this.children_.length > 0) {
+      return this.children_[0];
+    } else {
+      throw new Error('Root nodes must contain children.');
+    }
+  }
+
+  /** @return {!SAChildNode} */
+  get lastChild() {
+    if (this.children_.length > 0) {
+      return this.children_[this.children_.length - 1];
+    } else {
+      throw new Error('Root nodes must contain children.');
+    }
+  }
+
+  /** @return {boolean} */
+  isValid() {
+    return true;
+  }
+
+  /** @return {!chrome.accessibilityPrivate.ScreenRect} */
+  get location() {
+    let childLocations = this.children_.map((c) => c.location);
+    return RectHelper.unionAll(childLocations);
+  }
+
+  /**
+   * @param {AutomationNode} automationNode
+   * @return {boolean}
+   */
+  isEquivalentTo(automationNode) {
+    return false;
+  }
+
+  /** @return {AutomationNode} */
+  get automationNode() {}
+
+  /** Called when a group is exiting. */
+  onExit() {}
+
+  /**
+   * Helper function to connect children.
+   * @param {!Array<!SAChildNode>} children
+   */
+  static connectChildren(children) {
+    if (children.length < 1) {
+      throw new Error('Root node must have at least 1 interesting child.');
+    }
+
+    let previous = children[children.length - 1];
+
+    for (let i = 0; i < children.length; i++) {
+      let current = children[i];
+      previous.setNext_(current);
+      current.setPrevious_(previous);
+
+      previous = current;
+    }
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/nodes/system_menu_node.js b/chrome/browser/resources/chromeos/switch_access/nodes/system_menu_node.js
new file mode 100644
index 0000000..2debe3d
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/nodes/system_menu_node.js
@@ -0,0 +1,35 @@
+// 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.
+
+/**
+ * This class represents the group rooted at the system menu.
+ */
+class SystemMenuRootNode extends RootNodeWrapper {
+  /**
+   * @param {!chrome.automation.AutomationNode} menuNode
+   * @private
+   */
+  constructor(menuNode) {
+    super(menuNode);
+  }
+
+  /** @override */
+  onExit() {
+    // To close a system menu, we need to send an escape key event.
+    EventHelper.simulateKeyPress(EventHelper.KeyCode.ESC);
+  }
+
+  /**
+   * Creates the tree structure for the system menu.
+   * @param {!chrome.automation.AutomationNode} menuNode
+   * @return {!SystemMenuRootNode}
+   */
+  static buildTree(menuNode) {
+    const root = new SystemMenuRootNode(menuNode);
+    const childConstructor = (node) => new NodeWrapper(node, root);
+
+    RootNodeWrapper.buildHelper(root, childConstructor);
+    return root;
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access.js b/chrome/browser/resources/chromeos/switch_access/switch_access.js
index 7af61d9..7395eb6e 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access.js
@@ -54,6 +54,18 @@
      */
     this.enableImprovedTextInput_ = false;
 
+    /**
+     * The automation node for the back button.
+     * @private {chrome.automation.AutomationNode}
+     */
+    this.backButtonAutomationNode_;
+
+    /**
+     * The desktop node.
+     * @private {chrome.automation.AutomationNode}
+     */
+    this.desktop_;
+
     this.init_();
   }
 
@@ -73,10 +85,11 @@
         new SwitchAccessPreferences(this, this.onPrefsReady_.bind(this));
 
     chrome.automation.getDesktop(function(desktop) {
-      this.navigationManager_ = new NavigationManager(desktop, this);
+      this.navigationManager_ = new NavigationManager(desktop);
+      this.desktop_ = desktop;
+      this.findBackButtonNode_();
 
-      if (this.navReadyCallback_)
-        this.navReadyCallback_();
+      if (this.navReadyCallback_) this.navReadyCallback_();
     }.bind(this));
   }
 
@@ -85,8 +98,7 @@
    * @override
    */
   enterMenu() {
-    if (this.navigationManager_)
-      this.navigationManager_.enterMenu();
+    if (this.navigationManager_) this.navigationManager_.enterMenu();
   }
 
   /**
@@ -94,8 +106,7 @@
    * @override
    */
   moveForward() {
-    if (this.navigationManager_)
-      this.navigationManager_.moveForward();
+    if (this.navigationManager_) this.navigationManager_.moveForward();
     this.onMoveForwardForTesting_ && this.onMoveForwardForTesting_();
   }
 
@@ -104,8 +115,7 @@
    * @override
    */
   moveBackward() {
-    if (this.navigationManager_)
-      this.navigationManager_.moveBackward();
+    if (this.navigationManager_) this.navigationManager_.moveBackward();
   }
 
   /**
@@ -113,8 +123,7 @@
    * @override
    */
   selectCurrentNode() {
-    if (this.navigationManager_)
-      this.navigationManager_.selectCurrentNode();
+    if (this.navigationManager_) this.navigationManager_.selectCurrentNode();
   }
 
   /**
@@ -242,8 +251,9 @@
   connectMenuPanel(menuPanel) {
     // Because this may be called before init_(), check if navigationManager_
     // is initialized.
-    if (this.navigationManager_)
+    if (this.navigationManager_) {
       return this.navigationManager_.connectMenuPanel(menuPanel);
+    }
 
     // If not, set navReadyCallback_ to have the menuPanel try again.
     this.navReadyCallback_ = menuPanel.connectToBackground.bind(menuPanel);
@@ -255,8 +265,30 @@
    */
   onPrefsReady_() {
     this.autoScanManager_.onPrefsReady();
-    if (this.navigationManager_) {
-      this.navigationManager_.focusRingManager.onPrefsReady();
+    if (this.navigationManager_) this.navigationManager_.onPrefsReady();
+  }
+
+  /** @return {chrome.automation.AutomationNode} */
+  getBackButtonAutomationNode() {
+    if (!this.backButtonAutomationNode_) {
+      this.findBackButtonNode_();
+      if (!this.backButtonAutomationNode_) {
+        console.log('Error: unable to find back button');
+      }
     }
+    return this.backButtonAutomationNode_;
+  }
+
+  /**
+   * Looks for the back button node.
+   */
+  findBackButtonNode_() {
+    if (!this.desktop_) return;
+    this.backButtonAutomationNode_ =
+        new AutomationTreeWalker(
+            this.desktop_, constants.Dir.FORWARD,
+            {visit: (node) => node.htmlAttributes.id === SAConstants.BACK_ID})
+            .next()
+            .node;
   }
 }
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js b/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
index ed37b67..1c35f94b 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
@@ -124,7 +124,7 @@
   // Move text caret to the end of the text field.
   JUMP_TO_END_OF_TEXT: 'jumpToEndOfText',
   // Open and jump to the virtual keyboard
-  KEYBOARD: 'keyboard',
+  OPEN_KEYBOARD: 'keyboard',
   // Move text caret one character backward.
   MOVE_BACKWARD_ONE_CHAR_OF_TEXT: 'moveBackwardOneCharOfText',
   // Move text caret one word backward.
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
index f17ffc6a..cb24c3c8 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
@@ -38,7 +38,6 @@
    */
   setInKeyboard(inKeyboard) {}
 
-
   /**
    * Check whether or not the feature flag
    * for improved text input is enabled.
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_predicate.js b/chrome/browser/resources/chromeos/switch_access/switch_access_predicate.js
index c2fec34..4d8f1a2 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_predicate.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_predicate.js
@@ -40,31 +40,33 @@
 
     // Skip things that are offscreen or invisible.
     if (state[StateType.OFFSCREEN] || loc.top < 0 || loc.left < 0 ||
-        state[StateType.INVISIBLE])
+        state[StateType.INVISIBLE]) {
       return false;
+    }
 
     // Skip things that are disabled.
-    if (node.restriction === chrome.automation.Restriction.DISABLED)
+    if (node.restriction === chrome.automation.Restriction.DISABLED) {
       return false;
+    }
 
     // These web containers are not directly actionable.
-    if (role === RoleType.WEB_VIEW || role === RoleType.ROOT_WEB_AREA)
+    if (role === RoleType.WEB_VIEW || role === RoleType.ROOT_WEB_AREA) {
       return false;
+    }
 
     if (parent) {
       // crbug.com/710559
       // Work around for browser tabs.
       if (role === RoleType.TAB && parent.role === RoleType.TAB_LIST &&
-          root.role === RoleType.DESKTOP)
+          root.role === RoleType.DESKTOP) {
         return true;
+      }
     }
 
     // Check various indicators that the node is actionable.
-    if (role === RoleType.BUTTON || role === RoleType.SLIDER)
-      return true;
+    if (role === RoleType.BUTTON || role === RoleType.SLIDER) return true;
 
-    if (SwitchAccessPredicate.isTextInput(node))
-      return true;
+    if (SwitchAccessPredicate.isTextInput(node)) return true;
 
     if (defaultActionVerb &&
         (defaultActionVerb === DefaultActionVerb.ACTIVATE ||
@@ -85,9 +87,9 @@
     // should menu items.
     // Current heuristic is to show as actionble any focusable item where no
     // child is an interesting subtree.
-    if (state[StateType.FOCUSABLE] || role === RoleType.MENU_ITEM)
+    if (state[StateType.FOCUSABLE] || role === RoleType.MENU_ITEM) {
       return !node.children.some(SwitchAccessPredicate.isInterestingSubtree);
-
+    }
     return false;
   },
 
@@ -100,23 +102,26 @@
    * box as its scope.
    *
    * @param {!chrome.automation.AutomationNode} node
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {SARootNode} scope
    * @return {boolean}
    */
   isGroup: (node, scope) => {
-    if (node !== scope && RectHelper.areEqual(node.location, scope.location))
+    if (scope && !scope.isEquivalentTo(node) &&
+        RectHelper.areEqual(node.location, scope.location)) {
       return false;
-    if (node.state[StateType.INVISIBLE])
-      return false;
+    }
+    if (node.state[StateType.INVISIBLE]) return false;
 
     let interestingBranchesCount =
         SwitchAccessPredicate.isActionable(node) ? 1 : 0;
     let child = node.firstChild;
     while (child) {
-      if (SwitchAccessPredicate.isInterestingSubtree(child))
+      if (SwitchAccessPredicate.isInterestingSubtree(child)) {
         interestingBranchesCount += 1;
-      if (interestingBranchesCount >= GROUP_INTERESTING_CHILD_THRESHOLD)
+      }
+      if (interestingBranchesCount >= GROUP_INTERESTING_CHILD_THRESHOLD) {
         return true;
+      }
       child = child.nextSibling;
     }
     return false;
@@ -127,7 +132,7 @@
    * is either actionable or a group.
    *
    * @param {!chrome.automation.AutomationNode} node
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {!SARootNode} scope
    * @return {boolean}
    */
   isInteresting: (node, scope) => SwitchAccessPredicate.isActionable(node) ||
@@ -149,10 +154,10 @@
 
   /**
    * Returns true if |node| is an element that contains editable text.
-   * @param {!chrome.automation.AutomationNode} node
+   * @param {chrome.automation.AutomationNode} node
    * @return {boolean}
    */
-  isTextInput: (node) => !!node.state[StateType.EDITABLE],
+  isTextInput: (node) => !!node && !!node.state[StateType.EDITABLE],
 
   /**
    * Returns true if |node| does not have a role of desktop, window, web view,
@@ -175,7 +180,7 @@
   /**
    * Returns a Restrictions object ready to be passed to AutomationTreeWalker.
    *
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {!SARootNode} scope
    * @return {!AutomationTreeWalkerRestriction}
    */
   restrictions: (scope) => {
@@ -190,12 +195,13 @@
    * Creates a function that confirms if |node| is a terminal leaf node of a
    * SwitchAccess scope tree when |scope| is the root.
    *
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {!SARootNode} scope
    * @return {function(!chrome.automation.AutomationNode): boolean}
    */
   leaf: function(scope) {
     return (node) => node.state[StateType.INVISIBLE] ||
-        (node !== scope && SwitchAccessPredicate.isInteresting(node, scope)) ||
+        (!scope.isEquivalentTo(node) &&
+         SwitchAccessPredicate.isInteresting(node, scope)) ||
         !SwitchAccessPredicate.isInterestingSubtree(node);
   },
 
@@ -203,18 +209,18 @@
    * Creates a function that confirms if |node| is the root of a SwitchAccess
    * scope tree when |scope| is the root.
    *
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {!SARootNode} scope
    * @return {function(!chrome.automation.AutomationNode): boolean}
    */
   root: function(scope) {
-    return (node) => node === scope;
+    return (node) => scope.isEquivalentTo(node);
   },
 
   /**
    * Creates a function that determines whether |node| is to be visited in the
    * SwitchAccess scope tree with |scope| as the root.
    *
-   * @param {!chrome.automation.AutomationNode} scope
+   * @param {!SARootNode} scope
    * @return {function(!chrome.automation.AutomationNode): boolean}
    */
   visit: function(scope) {
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_predicate_test.extjs b/chrome/browser/resources/chromeos/switch_access/switch_access_predicate_test.extjs
index 73ceedc..f5f6b09 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_predicate_test.extjs
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_predicate_test.extjs
@@ -101,19 +101,22 @@
       function(desktop) {
         const t = getTree(desktop);
 
-        assertTrue(SwitchAccessPredicate.isInteresting(t.root, t.root));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.upper1, t.root));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.upper2, t.root));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.lower1, t.upper1));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.lower2, t.upper1));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.lower3, t.root));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf1, t.lower1));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf2, t.lower1));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf3, t.lower1));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf4, t.upper1));
-        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf5, t.upper1));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf6, t.lower3));
-        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf7, t.lower3));
+        // The scope is only used to verify the locations are not the same, and
+        // since the buildTree function depends on isInteresting, pass in null
+        // for the scope.
+        assertTrue(SwitchAccessPredicate.isInteresting(t.root, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.upper1, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.upper2, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.lower1, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.lower2, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.lower3, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf1, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf2, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf3, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf4, null));
+        assertTrue(SwitchAccessPredicate.isInteresting(t.leaf5, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf6, null));
+        assertFalse(SwitchAccessPredicate.isInteresting(t.leaf7, null));
       }
   );
 });
@@ -123,19 +126,22 @@
       function(desktop) {
         const t = getTree(desktop);
 
-        assertTrue(SwitchAccessPredicate.isGroup(t.root, t.root));
-        assertTrue(SwitchAccessPredicate.isGroup(t.upper1, t.root));
-        assertFalse(SwitchAccessPredicate.isGroup(t.upper2, t.root));
-        assertTrue(SwitchAccessPredicate.isGroup(t.lower1, t.upper1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.lower2, t.upper1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.lower3, t.root));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf1, t.lower1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf2, t.lower1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf3, t.lower1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf4, t.upper1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf5, t.upper1));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf6, t.lower3));
-        assertFalse(SwitchAccessPredicate.isGroup(t.leaf7, t.lower3));
+        // The scope is only used to verify the locations are not the same, and
+        // since the buildTree function depends on isGroup, pass in null for
+        // the scope.
+        assertTrue(SwitchAccessPredicate.isGroup(t.root, null));
+        assertTrue(SwitchAccessPredicate.isGroup(t.upper1, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.upper2, null));
+        assertTrue(SwitchAccessPredicate.isGroup(t.lower1, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.lower2, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.lower3, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf1, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf2, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf3, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf4, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf5, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf6, null));
+        assertFalse(SwitchAccessPredicate.isGroup(t.leaf7, null));
       }
   );
 });
@@ -275,20 +281,21 @@
         const t = getTree(desktop);
 
         // Start with root as scope
-        let leaf = SwitchAccessPredicate.leaf(t.root);
+        let leaf =
+              SwitchAccessPredicate.leaf(RootNodeWrapper.buildTree(t.root));
         assertFalse(leaf(t.root));
         assertTrue(leaf(t.upper1));
         assertTrue(leaf(t.upper2));
 
         // Set upper1 as scope
-        leaf = SwitchAccessPredicate.leaf(t.upper1);
+        leaf = SwitchAccessPredicate.leaf(RootNodeWrapper.buildTree(t.upper1));
         assertFalse(leaf(t.upper1));
         assertTrue(leaf(t.lower1));
         assertTrue(leaf(t.leaf4));
         assertTrue(leaf(t.leaf5));
 
         // Set lower1 as scope
-        leaf = SwitchAccessPredicate.leaf(t.lower1);
+        leaf = SwitchAccessPredicate.leaf(RootNodeWrapper.buildTree(t.lower1));
         assertFalse(leaf(t.lower1));
         assertTrue(leaf(t.leaf1));
         assertTrue(leaf(t.leaf2));
@@ -303,19 +310,20 @@
         const t = getTree(desktop);
 
         // Start with root as scope
-        let root = SwitchAccessPredicate.root(t.root);
+        let root =
+              SwitchAccessPredicate.root(RootNodeWrapper.buildTree(t.root));
         assertTrue(root(t.root));
         assertFalse(root(t.upper1));
         assertFalse(root(t.upper2));
 
         // Set upper1 as scope
-        root = SwitchAccessPredicate.root(t.upper1);
+        root = SwitchAccessPredicate.root(RootNodeWrapper.buildTree(t.upper1));
         assertTrue(root(t.upper1));
         assertFalse(root(t.lower1));
         assertFalse(root(t.lower2));
 
         // Set lower1 as scope
-        root = SwitchAccessPredicate.root(t.lower1);
+        root = SwitchAccessPredicate.root(RootNodeWrapper.buildTree(t.lower1));
         assertTrue(root(t.lower1));
         assertFalse(root(t.leaf1));
         assertFalse(root(t.leaf2));
@@ -330,13 +338,15 @@
         const t = getTree(desktop);
 
         // Start with root as scope
-        let visit = SwitchAccessPredicate.visit(t.root);
+        let visit =
+              SwitchAccessPredicate.visit(RootNodeWrapper.buildTree(t.root));
         assertTrue(visit(t.root));
         assertTrue(visit(t.upper1));
         assertTrue(visit(t.upper2));
 
         // Set upper1 as scope
-        visit = SwitchAccessPredicate.visit(t.upper1);
+        visit =
+            SwitchAccessPredicate.visit(RootNodeWrapper.buildTree(t.upper1));
         assertTrue(visit(t.upper1));
         assertTrue(visit(t.lower1));
         assertFalse(visit(t.lower2));
@@ -344,7 +354,8 @@
         assertTrue(visit(t.leaf5));
 
         // Set lower1 as scope
-        visit = SwitchAccessPredicate.visit(t.lower1);
+        visit =
+            SwitchAccessPredicate.visit(RootNodeWrapper.buildTree(t.lower1));
         assertTrue(visit(t.lower1));
         assertTrue(visit(t.leaf1));
         assertFalse(visit(t.leaf2));
diff --git a/chrome/browser/resources/chromeos/switch_access/text_input_manager.js b/chrome/browser/resources/chromeos/switch_access/text_input_manager.js
deleted file mode 100644
index a44cd12..0000000
--- a/chrome/browser/resources/chromeos/switch_access/text_input_manager.js
+++ /dev/null
@@ -1,114 +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.
-
-/**
- * Class to handle text input for improved accuracy and efficiency.
- */
-
-class TextInputManager {
-  /** @param {!NavigationManager} navigationManager */
-  constructor(navigationManager) {
-    /** @private {chrome.automation.AutomationNode} */
-    this.node_;
-
-    /** @private {!NavigationManager} */
-    this.navigationManager_ = navigationManager;
-  }
-
-  /**
-   * Enters the keyboard.
-   * @param {!chrome.automation.AutomationNode} node
-   */
-  enterKeyboard(node) {
-    if (!SwitchAccessPredicate.isTextInput(node))
-      return false;
-
-    this.node_ = node;
-    return true;
-  }
-
-  /** Resets the focus in |navigationManager_|. */
-  returnToTextFocus() {
-    if (!this.node_)
-      return;
-    this.navigationManager_.exitCurrentScope(this.node_);
-    this.node_ = null;
-  }
-
-  /**
-   * Sends a keyEvent to click the center of the provided node.
-   * @param {!chrome.automation.AutomationNode} node
-   * @return {boolean} Whether a key was pressed.
-   */
-  pressKey(node) {
-    if (node.role !== chrome.automation.RoleType.BUTTON)
-      return false;
-    if (!this.inVirtualKeyboard(node))
-      return false;
-
-    const x = node.location.left + Math.round(node.location.width / 2);
-    const y = node.location.top + Math.round(node.location.height / 2);
-
-    EventHelper.simulateMouseClick(x, y, SAConstants.VK_KEY_PRESS_DURATION_MS);
-    return true;
-  }
-
-  /**
-   * Returns the container with a role of keyboard.
-   * @param {!chrome.automation.AutomationNode} desktop
-   * @return {chrome.automation.AutomationNode}
-   */
-  getKeyboard(desktop) {
-    let treeWalker = new AutomationTreeWalker(
-        desktop, constants.Dir.FORWARD,
-        {visit: (node) => node.role === chrome.automation.RoleType.KEYBOARD});
-    const keyboardContainer = treeWalker.next().node;
-    treeWalker =
-        new AutomationTreeWalker(keyboardContainer, constants.Dir.FORWARD, {
-          visit: (node) => SwitchAccessPredicate.isGroup(node, node),
-          root: (node) => node === keyboardContainer
-        });
-    return treeWalker.next().node;
-  }
-
-  /**
-   * Checks if |node| is in the virtual keyboard.
-   * @param {!chrome.automation.AutomationNode} node
-   * @return {boolean}
-   */
-  inVirtualKeyboard(node) {
-    if (node.role === chrome.automation.RoleType.KEYBOARD)
-      return true;
-    if (node.parent)
-      return this.inVirtualKeyboard(node.parent);
-    return false;
-  }
-
-  /**
-   * Cuts currently selected text using a keyboard shortcut via synthetic keys.
-   * If there's no selected text, doesn't do anything.
-   * @public
-   */
-  cut() {
-    EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true});
-  }
-
-  /**
-   * Copies currently selected text using a keyboard shortcut via synthetic
-   * keys. If there's no selected text, doesn't do anything.
-   * @public
-   */
-  copy() {
-    EventHelper.simulateKeyPress(EventHelper.KeyCode.C, {ctrl: true});
-  }
-
-  /**
-   * Pastes text from the clipboard using a keyboard shortcut via synthetic
-   * keys. If there's nothing in the clipboard, doesn't do anything.
-   * @public
-   */
-  paste() {
-    EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true});
-  }
-}
diff --git a/chrome/browser/resources/chromeos/switch_access/text_navigation_manager.js b/chrome/browser/resources/chromeos/switch_access/text_navigation_manager.js
index 034ddbf1..dcf209b 100644
--- a/chrome/browser/resources/chromeos/switch_access/text_navigation_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/text_navigation_manager.js
@@ -10,11 +10,7 @@
  * navigation and selection in editable text fields is supported.
  */
 class TextNavigationManager {
-  /** @param {!NavigationManager} navigationManager */
-  constructor(navigationManager) {
-    /** @private {!NavigationManager} */
-    this.navigationManager_ = navigationManager;
-
+  constructor() {
     /** @private {number} */
     this.selectionStartIndex_ = NO_SELECT_INDEX;
 
@@ -37,7 +33,6 @@
   /**
    * Jumps to the beginning of the text field (does nothing
    * if already at the beginning).
-   * @public
    */
   jumpToBeginning() {
     if (this.currentlySelecting_)
@@ -48,7 +43,6 @@
   /**
    * Jumps to the end of the text field (does nothing if
    * already at the end).
-   * @public
    */
   jumpToEnd() {
     if (this.currentlySelecting_)
@@ -60,7 +54,6 @@
    * Moves the text caret one character back (does nothing
    * if there are no more characters preceding the current
    * location of the caret).
-   * @public
    */
   moveBackwardOneChar() {
     if (this.currentlySelecting_)
@@ -72,7 +65,6 @@
    * Moves the text caret one character forward (does nothing
    * if there are no more characters following the current
    * location of the caret).
-   * @public
    */
   moveForwardOneChar() {
     if (this.currentlySelecting_)
@@ -85,7 +77,6 @@
    * if already at the beginning of the field). If the
    * text caret is in the middle of a word, moves the caret
    * to the beginning of that word.
-   * @public
    */
   moveBackwardOneWord() {
     if (this.currentlySelecting_)
@@ -98,7 +89,6 @@
    * already at the end of the field). If the text caret is
    * in the middle of a word, moves the caret to the end of
    * that word.
-   * @public
    */
   moveForwardOneWord() {
     if (this.currentlySelecting_)
@@ -110,7 +100,6 @@
    * Moves the text caret one line up (does nothing
    * if there are no lines above the current location of
    * the caret).
-   * @public
    */
   moveUpOneLine() {
     if (this.currentlySelecting_)
@@ -122,7 +111,6 @@
    * Moves the text caret one line down (does nothing
    * if there are no lines below the current location of
    * the caret).
-   * @public
    */
   moveDownOneLine() {
     if (this.currentlySelecting_)
@@ -184,7 +172,6 @@
   /**
    * Returns the selection end index.
    * @return {number}
-   * @public
    */
   getSelEndIndex() {
     return this.selectionEndIndex_;
@@ -200,7 +187,6 @@
   /**
    * Returns the selection start index.
    * @return {number}
-   * @public
    */
   getSelStartIndex() {
     return this.selectionStartIndex_;
@@ -210,7 +196,6 @@
    * Sets the selection start index.
    * @param {number} startIndex
    * @param {!chrome.automation.AutomationNode} textNode
-   * @public
    */
   setSelStartIndexAndNode(startIndex, textNode) {
     this.selectionStartIndex_ = startIndex;
@@ -220,7 +205,6 @@
   /**
    * Returns if the selection start index is set in the current node.
    * @return {boolean}
-   * @public
    */
   currentlySelecting() {
     return (
@@ -253,7 +237,6 @@
   /**
    * Sets the selectionStart variable based on the selection of the current
    * node. Also sets the currently selecting boolean to true.
-   * @public
    */
   saveSelectStart() {
     chrome.automation.getFocus((focusedNode) => {
@@ -297,7 +280,6 @@
   /**
    * Reset the currentlySelecting variable to false, reset the selection
    * indices, and remove the listener on navigation.
-   * @public
    */
   resetCurrentlySelecting() {
     this.currentlySelecting_ = false;
@@ -308,7 +290,6 @@
 
   /**
    * Sets the selectionEnd variable based on the selection of the current node.
-   * @public
    */
   saveSelectEnd() {
     chrome.automation.getFocus((focusedNode) => {
diff --git a/chrome/browser/resources/chromeos/switch_access/text_navigation_manager_test.extjs b/chrome/browser/resources/chromeos/switch_access/text_navigation_manager_test.extjs
index 3aa924e..8a2b9192 100644
--- a/chrome/browser/resources/chromeos/switch_access/text_navigation_manager_test.extjs
+++ b/chrome/browser/resources/chromeos/switch_access/text_navigation_manager_test.extjs
@@ -19,7 +19,7 @@
       /** @override */
       setUp: function() {
         this.textNavigationManager =
-            switchAccess.navigationManager_.textNavigationManager_;
+            switchAccess.navigationManager_.menuManager_.textNavigationManager_;
         this.navigationManager =
             switchAccess.navigationManager_;
       }
@@ -61,7 +61,8 @@
     assertNotEquals(inputNode, null);
 
     setUpCursorChangeListener(
-        testHelper, inputNode, initialTextIndex, targetTextIndex, targetTextIndex);
+        testHelper, inputNode, initialTextIndex, targetTextIndex,
+        targetTextIndex);
 
     textNavigationAction();
   });
@@ -119,14 +120,16 @@
     assertNotEquals(inputNode, null);
     checkNodeIsFocused(inputNode);
     let callback = testHelper.newCallback(function() {
-      setUpCursorChangeListener(testHelper, inputNode, targetTextEndIndex, targetTextStartIndex, targetTextEndIndex);
+      setUpCursorChangeListener(testHelper, inputNode, targetTextEndIndex,
+                                targetTextStartIndex, targetTextEndIndex);
       testHelper.textNavigationManager.saveSelectEnd();
     });
 
     testHelper.textNavigationManager.saveSelectStart();
 
     setUpCursorChangeListener(
-        testHelper, inputNode, initialTextIndex, navigationTargetIndex, navigationTargetIndex, callback);
+        testHelper, inputNode, initialTextIndex, navigationTargetIndex,
+        navigationTargetIndex, callback);
 
     textNavigationAction();
   });
@@ -307,7 +310,7 @@
     cols: 8,
     wrap: 'hard',
     navigationAction: () => {
-      this.textNavigationManager.moveUpOneLine()
+      this.textNavigationManager.moveUpOneLine();
     }
   });
 });
diff --git a/chrome/browser/resources/downloads/BUILD.gn b/chrome/browser/resources/downloads/BUILD.gn
index 7fceff2..b661362 100644
--- a/chrome/browser/resources/downloads/BUILD.gn
+++ b/chrome/browser/resources/downloads/BUILD.gn
@@ -23,7 +23,6 @@
       "downloads.mojom-lite.js",
       "downloads.mojom-lite.html",
     ]
-    replace_for_html_imports_polyfill = "crisper.js"
 
     deps = [
       ":unpak",
@@ -94,7 +93,6 @@
     ":manager",
     "//ui/webui/resources/js:cr",
   ]
-  externs_list = [ "$externs_path/html_imports.js" ]
 }
 
 js_library("icon_loader") {
diff --git a/chrome/browser/resources/downloads/downloads.html b/chrome/browser/resources/downloads/downloads.html
index ad88272..437c6414 100644
--- a/chrome/browser/resources/downloads/downloads.html
+++ b/chrome/browser/resources/downloads/downloads.html
@@ -44,8 +44,6 @@
   <command id="clear-all-command" shortcut="Alt|c">
   <command id="undo-command" shortcut="Ctrl|z">
 </if>
-  <script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js">
-  </script>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="import" href="chrome://resources/html/cr.html">
   <link rel="import" href="chrome://resources/html/polymer.html">
diff --git a/chrome/browser/resources/downloads/downloads.js b/chrome/browser/resources/downloads/downloads.js
index 6bad17b..fe3834c0 100644
--- a/chrome/browser/resources/downloads/downloads.js
+++ b/chrome/browser/resources/downloads/downloads.js
@@ -2,21 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-function loadDownloads() {
-  return HTMLImports.whenReady(function() {
-    downloads.Manager.onLoad().then(function() {
-      requestIdleCallback(function() {
-        chrome.send(
-            'metricsHandler:recordTime',
-            ['Download.ResultsRenderedTime', window.performance.now()]);
-        document.fonts.load('bold 12px Roboto');
-      });
+window.addEventListener('load', function() {
+  downloads.Manager.onLoad().then(function() {
+    requestIdleCallback(function() {
+      chrome.send(
+          'metricsHandler:recordTime',
+          ['Download.ResultsRenderedTime', window.performance.now()]);
+      document.fonts.load('bold 12px Roboto');
     });
   });
-}
-
-if (document.readyState === 'complete') {
-  loadDownloads();
-} else {
-  window.addEventListener('load', loadDownloads);
-}
+});
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index 734470fd..0714637 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -18,7 +18,6 @@
     insert_in_head = "<base href=\"chrome://extensions\">"
     input = rebase_path("$target_gen_dir/$unpak_folder", root_build_dir)
     js_out_files = [ "crisper.js" ]
-    replace_for_html_imports_polyfill = "crisper.js"
 
     deps = [
       ":unpak",
diff --git a/chrome/browser/resources/extensions/extensions.html b/chrome/browser/resources/extensions/extensions.html
index 8324340..5ffa83776 100644
--- a/chrome/browser/resources/extensions/extensions.html
+++ b/chrome/browser/resources/extensions/extensions.html
@@ -57,8 +57,6 @@
   </style>
 </head>
 <body>
-  <script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js">
-  </script>
   <extensions-manager></extensions-manager>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="import" href="manager.html">
diff --git a/chrome/browser/resources/optimize_webui.gni b/chrome/browser/resources/optimize_webui.gni
index 42a37a1..1b506ca 100644
--- a/chrome/browser/resources/optimize_webui.gni
+++ b/chrome/browser/resources/optimize_webui.gni
@@ -74,13 +74,6 @@
         invoker.insert_in_head,
       ]
     }
-
-    if (defined(invoker.replace_for_html_imports_polyfill)) {
-      args += [
-        "--replace-for-html-imports-polyfill",
-        invoker.replace_for_html_imports_polyfill,
-      ]
-    }
   }
 }
 
diff --git a/chrome/browser/resources/optimize_webui.py b/chrome/browser/resources/optimize_webui.py
index 3776afb..e9207c6 100755
--- a/chrome/browser/resources/optimize_webui.py
+++ b/chrome/browser/resources/optimize_webui.py
@@ -58,8 +58,6 @@
 _VULCANIZE_BASE_ARGS = [
   # These files are already combined and minified.
   '--exclude', 'chrome://resources/html/polymer.html',
-  '--exclude', 'chrome://resources/polymer/v1_0/html-imports/' +
-      'html-imports.min.js',
   '--exclude', 'chrome://resources/polymer/v1_0/polymer/polymer.html',
   '--exclude', 'chrome://resources/polymer/v1_0/polymer/polymer-micro.html',
   '--exclude', 'chrome://resources/polymer/v1_0/polymer/polymer-mini.html',
@@ -206,28 +204,6 @@
                    '--html', crisper_html_out_paths[index],
                    '--js', os.path.join(tmp_out_dir, js_out_file)])
 
-      if args.replace_for_html_imports_polyfill == js_out_file:
-        # Replace the output file with a loader script, to wait until HTML
-        # imports are ready before loading.
-        with open(crisper_html_out_paths[index], 'r') as f:
-          output = f.read()
-          output = output.replace(js_out_file + '"',
-                                  'chrome://resources/js/crisper_loader.js"' + \
-                                  ' data-script-name="' + js_out_file + '"')
-
-          # Preload the final script, even though it will not be evaluated
-          # until after crisper_loader.js executes.
-          output = output.replace('<head>',
-                                  '<head><link rel="preload" href="' + \
-                                        js_out_file + '" as="script">')
-          f.close()
-
-        # Open file again with 'w' such that the previous contents are
-        # overwritten.
-        with open(crisper_html_out_paths[index], 'w') as f:
-          f.write(output)
-          f.close()
-
       # Pass the JS file through Uglify and write the output to its final
       # destination.
       node.RunNode([node_modules.PathToUglify(),
@@ -259,7 +235,6 @@
   parser.add_argument('--insert_in_head')
   parser.add_argument('--js_out_files', nargs='*', required=True)
   parser.add_argument('--out_folder', required=True)
-  parser.add_argument('--replace-for-html-imports-polyfill')
   args = parser.parse_args(argv)
 
   # NOTE(dbeam): on Windows, GN can send dirs/like/this. When joined, you might
diff --git a/chrome/browser/resources/optimize_webui_test.py b/chrome/browser/resources/optimize_webui_test.py
index 70ed6d2..6bf26a5 100755
--- a/chrome/browser/resources/optimize_webui_test.py
+++ b/chrome/browser/resources/optimize_webui_test.py
@@ -44,8 +44,7 @@
     assert self._out_folder
     return open(os.path.join(self._out_folder, file_name), 'r').read()
 
-  def _run_optimize(self, depfile, html_in_file, html_out_file, js_out_file,
-                    replace_for_html_imports_polyfill):
+  def _run_optimize(self, depfile, html_in_file, html_out_file, js_out_file):
     # TODO(dbeam): make it possible to _run_optimize twice? Is that useful?
     assert not self._out_folder
     self._out_folder = self._create_tmp_dir()
@@ -57,7 +56,6 @@
       '--input', self._tmp_src_dir,
       '--js_out_files', js_out_file,
       '--out_folder', self._out_folder,
-      '--replace-for-html-imports-polyfill', replace_for_html_imports_polyfill,
     ])
 
   def _write_files_to_src_dir(self):
@@ -99,35 +97,11 @@
     self._run_optimize(depfile='depfile.d',
                        html_in_file='ui.html',
                        html_out_file='fast.html',
-                       js_out_file='fast.js',
-                       replace_for_html_imports_polyfill='')
+                       js_out_file='fast.js')
 
     fast_html = self._read_out_file('fast.html')
     self._check_output_html(fast_html)
     self.assertIn('<script src="fast.js"></script>', fast_html)
-    self.assertNotIn(
-        '<script src="chrome://resources/js/crisper_loader.js"></script>',
-        fast_html)
-    self._check_output_js()
-    self._check_output_depfile()
-
-  def testSimpleOptimizeWithHTMLImportsPolyfill(self):
-    self._write_files_to_src_dir()
-    self._run_optimize(depfile='depfile.d',
-                       html_in_file='ui.html',
-                       html_out_file='fast.html',
-                       js_out_file='fast.js',
-                       replace_for_html_imports_polyfill='fast.js')
-
-    fast_html = self._read_out_file('fast.html')
-    self._check_output_html(fast_html)
-    self.assertNotIn('<script src="fast.js"></script>', fast_html)
-    self.assertIn(
-        '<script src="chrome://resources/js/crisper_loader.js" ' + \
-            'data-script-name="fast.js"></script>',
-        fast_html)
-    self.assertIn('<link rel="preload" href="fast.js" as="script">',
-                  fast_html)
     self._check_output_js()
     self._check_output_depfile()
 
diff --git a/chrome/browser/resources/pdf/BUILD.gn b/chrome/browser/resources/pdf/BUILD.gn
index 095c13b..88ac35c 100644
--- a/chrome/browser/resources/pdf/BUILD.gn
+++ b/chrome/browser/resources/pdf/BUILD.gn
@@ -67,6 +67,9 @@
 }
 
 js_library("metrics") {
+  deps = [
+    ":pdf_fitting_type",
+  ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
@@ -81,6 +84,7 @@
   deps = [
     "elements:viewer-pdf-toolbar",
     "elements:viewer-zoom-toolbar",
+    "//ui/webui/resources/js:util",
   ]
 }
 
@@ -97,9 +101,12 @@
 js_library("pdf_viewer") {
   deps = [
     ":controller",
+    ":metrics",
     ":navigator",
+    ":pdf_scripting_api",
     ":toolbar_manager",
     ":viewport",
+    ":viewport_scroller",
     "elements:viewer-bookmark",
     "elements:viewer-error-screen",
     "elements:viewer-page-indicator",
@@ -112,12 +119,21 @@
   externs_list = [ "$externs_path/resources_private.js" ]
 }
 
+js_library("main") {
+  deps = [
+    ":browser_api",
+    ":pdf_viewer",
+  ]
+}
+
 js_type_check("pdf_resources") {
   is_polymer3 = true
   deps = [
+    ":annotation_tool",
     ":browser_api",
     ":controller",
     ":gesture_detector",
+    ":main",
     ":metrics",
     ":navigator",
     ":open_pdf_params_parser",
diff --git a/chrome/browser/resources/pdf/browser_api.js b/chrome/browser/resources/pdf/browser_api.js
index 6127ce4..e44d9b4 100644
--- a/chrome/browser/resources/pdf/browser_api.js
+++ b/chrome/browser/resources/pdf/browser_api.js
@@ -161,8 +161,8 @@
 /**
  * Creates a BrowserApi for an extension running as a mime handler.
  *
- * @return {Promise<BrowserApi>} A promise to a BrowserApi instance constructed
- *     using the mimeHandlerPrivate API.
+ * @return {!Promise<!BrowserApi>} A promise to a BrowserApi instance
+ *     constructed using the mimeHandlerPrivate API.
  */
 function createBrowserApiForMimeHandlerView() {
   return new Promise(function(resolve, reject) {
@@ -198,8 +198,8 @@
 /**
  * Creates a BrowserApi instance for an extension not running as a mime handler.
  *
- * @return {Promise<BrowserApi>} A promise to a BrowserApi instance constructed
- *     from the URL.
+ * @return {!Promise<!BrowserApi>} A promise to a BrowserApi instance
+ *     constructed from the URL.
  */
 function createBrowserApiForPrintPreview() {
   const url = window.location.search.substring(1);
@@ -227,7 +227,7 @@
 }
 
 /**
- * @return {Promise<BrowserApi>} A promise to a BrowserApi instance for the
+ * @return {!Promise<!BrowserApi>} A promise to a BrowserApi instance for the
  *     current environment.
  */
 function createBrowserApi() {
diff --git a/chrome/browser/resources/pdf/main.js b/chrome/browser/resources/pdf/main.js
index cf8f569..427a1d5 100644
--- a/chrome/browser/resources/pdf/main.js
+++ b/chrome/browser/resources/pdf/main.js
@@ -42,30 +42,29 @@
 /**
  * Initialize the global PDFViewer and pass any outstanding messages to it.
  *
- * @param {Promise<BrowserApi>} browserApi A promise resolving to an API
- *     to the browser.
+ * @param {!BrowserApi} browserApi
  */
 function initViewer(browserApi) {
   // PDFViewer will handle any messages after it is created.
   window.removeEventListener('message', handleScriptingMessage, false);
-  viewer = new PDFViewer(browserApi);
+  window.viewer = new PDFViewer(browserApi);
   while (pendingMessages.length > 0) {
-    viewer.handleScriptingMessage(pendingMessages.shift());
+    window.viewer.handleScriptingMessage(pendingMessages.shift());
   }
 }
 
 /**
  * Determine if the content settings allow PDFs to execute javascript.
  *
- * @param {Promise<BrowserApi>} browserApi A promise resolving to an API
- *     to the browser.
+ * @param {!BrowserApi} browserApi
+ * @return {!Promise<!BrowserApi>}
  */
 function configureJavaScriptContentSetting(browserApi) {
   return new Promise((resolve, reject) => {
     chrome.contentSettings.javascript.get(
         {
           'primaryUrl': browserApi.getStreamInfo().originalUrl,
-          'secondaryUrl': window.origin
+          'secondaryUrl': window.location.origin
         },
         (result) => {
           browserApi.getStreamInfo().javascript = result.setting;
@@ -89,7 +88,7 @@
     chain = chain.then(configureJavaScriptContentSetting);
   }
 
-  chain = chain.then(initViewer);
+  chain.then(initViewer);
 }
 
 main();
diff --git a/chrome/browser/resources/print_preview/BUILD.gn b/chrome/browser/resources/print_preview/BUILD.gn
index 0bb1822b..2c304a0 100644
--- a/chrome/browser/resources/print_preview/BUILD.gn
+++ b/chrome/browser/resources/print_preview/BUILD.gn
@@ -18,7 +18,6 @@
     insert_in_head = "<base href=\"chrome://print\">"
     input = rebase_path("$target_gen_dir/$unpak_folder", root_build_dir)
     js_out_files = [ "crisper.js" ]
-    replace_for_html_imports_polyfill = "crisper.js"
 
     excludes = [ "pdf/pdf_scripting_api.js" ]
 
diff --git a/chrome/browser/resources/print_preview/print_preview.html b/chrome/browser/resources/print_preview/print_preview.html
index 9712cf5..51305f3 100644
--- a/chrome/browser/resources/print_preview/print_preview.html
+++ b/chrome/browser/resources/print_preview/print_preview.html
@@ -52,8 +52,6 @@
   </style>
 </head>
 <body>
-  <script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js">
-  </script>
   <print-preview-app></print-preview-app>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
diff --git a/chrome/browser/resources/safety_tips/BUILD.gn b/chrome/browser/resources/safety_tips/BUILD.gn
deleted file mode 100644
index d28e9c1..0000000
--- a/chrome/browser/resources/safety_tips/BUILD.gn
+++ /dev/null
@@ -1,50 +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.
-
-# Generate the binary proto form of "safety_tips" from the ascii proto.
-#
-# TODO(meacer): Remove this from the build once we have the script to push the
-# generated protobuf ready. This will most likely be a manual process and won't
-# be part of the build.
-#
-action("make_safety_tips_protobuf") {
-  script = "gen_safety_tips_proto.py"
-
-  # The output goes in $target_gen_dir since that's where
-  # chrome/browser/browser_resources.grd will look for it.
-
-  input_filename = "safety_tips.asciipb"
-  output_dir = target_gen_dir
-  output_basename = "safety_tips.pb"
-  python_path_root = "$root_out_dir/pyproto"
-  python_path_safety_tips =
-      "$python_path_root/chrome/browser/lookalikes/safety_tips/"
-
-  inputs = [
-    input_filename,
-  ]
-
-  deps = [
-    "//chrome/browser/lookalikes/safety_tips:proto",
-    "//third_party/protobuf:py_proto",
-  ]
-
-  outputs = [
-    "$output_dir/$output_basename",
-  ]
-
-  args = [
-    "-w",
-    "-i",
-    rebase_path(input_filename, root_build_dir),
-    "-d",
-    rebase_path(output_dir, root_build_dir),
-    "-o",
-    output_basename,
-    "-p",
-    rebase_path(python_path_root, root_build_dir),
-    "-p",
-    rebase_path(python_path_safety_tips, root_build_dir),
-  ]
-}
diff --git a/chrome/browser/resources/safety_tips/OWNERS b/chrome/browser/resources/safety_tips/OWNERS
deleted file mode 100644
index f050018fa..0000000
--- a/chrome/browser/resources/safety_tips/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://chrome/browser/lookalikes/OWNERS
diff --git a/chrome/browser/resources/safety_tips/PRESUBMIT.py b/chrome/browser/resources/safety_tips/PRESUBMIT.py
deleted file mode 100644
index e7eae89..0000000
--- a/chrome/browser/resources/safety_tips/PRESUBMIT.py
+++ /dev/null
@@ -1,51 +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.
-
-"""Presubmit checks for Safety Tips proto for the component updater.
-"""
-
-"""Returns true if any line in changed_contents contains the string |s|.
-|changed_contents| is a tuple containing (line number, line text) pairs.
-"""
-def ContainsLine(changed_contents, s):
-  for _, line in changed_contents:
-    if line.strip().startswith(s):
-      return True
-  return False
-
-def CheckVersionUpdatedInProto(input_api, output_api):
-  def IsSafetyTipProto(x):
-    return (input_api.os_path.basename(x.LocalPath()) ==
-            'safety_tips.asciipb')
-
-  safety_tips_proto = input_api.AffectedFiles(file_filter=IsSafetyTipProto)
-  if not safety_tips_proto:
-    return []
-
-  contents = safety_tips_proto[0].ChangedContents()
-  # Must not have any changes containing flagged_page or allowed_pattern:
-  if (ContainsLine(contents, 'flagged_page:') or
-      ContainsLine(contents, 'allowed_pattern:')):
-      return [output_api.PresubmitError(
-      'Do not check in the full safety_tips.asciipb proto. '
-      'Only increment |version_id|.')]
-
-  # It's enticing to do something fancy like checking whether the ID was in fact
-  # incremented or whether this is a whitespace-only or comment-only change.
-  # However, currently deleted lines don't show up in ChangedContents() and
-  # attempting to parse the asciipb file any more than we are doing above is
-  # likely not worth the trouble.
-  #
-  # At worst, the submitter can skip the presubmit check on upload if it isn't
-  # correct.
-  if not ContainsLine(contents, 'version_id:'):
-    return [output_api.PresubmitError(
-        'Increment |version_id| in safety_tips.asciipb if you are '
-        'updating the file types proto.')]
-
-  return []
-
-
-def CheckChangeOnUpload(input_api, output_api):
-  return CheckVersionUpdatedInProto(input_api, output_api)
diff --git a/chrome/browser/resources/safety_tips/README.md b/chrome/browser/resources/safety_tips/README.md
deleted file mode 100644
index b02c20d..0000000
--- a/chrome/browser/resources/safety_tips/README.md
+++ /dev/null
@@ -1 +0,0 @@
-See go/safety-tips-component-update for Safety Tips component update playbook.
diff --git a/chrome/browser/resources/safety_tips/gen_safety_tips_proto.py b/chrome/browser/resources/safety_tips/gen_safety_tips_proto.py
deleted file mode 100755
index 75f7e34..0000000
--- a/chrome/browser/resources/safety_tips/gen_safety_tips_proto.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/python
-# 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.
-
-"""
- Convert the ASCII safety_tips.asciipb proto into a binary resource.
-"""
-
-import os
-import sys
-
-# Disable warnings for "safety_tips_pb2" which is dynamically imported.
-# pylint: disable=undefined-variable
-
-# Subdirectory to be copied to Google Cloud Storage. Contains a copy of the
-# generated proto under a versioned directory.
-# TODO(meacer): Remove this. Safety tips does not read the proto from a local
-# resource bundle, it only uses the proto passed from component updater. It does
-# not need two copies of the file.
-GS_COPY_DIR = "gs_copy"
-
-# Import the binary proto generator. Walks up to the root of the source tree
-# which is five directories above, and finds the protobufs directory from there.
-proto_generator_path = os.path.normpath(os.path.join(os.path.abspath(__file__),
-    *[os.path.pardir] * 5 + ['chrome/browser/resources/protobufs']))
-sys.path.insert(0, proto_generator_path)
-from binary_proto_generator import BinaryProtoGenerator
-
-def MakeSubDirs(outfile):
-  """ Make the subdirectories needed to create file |outfile| """
-  dirname = os.path.dirname(outfile)
-  if not os.path.exists(dirname):
-    os.makedirs(dirname)
-
-class SafetyTipsProtoGenerator(BinaryProtoGenerator):
-  def ImportProtoModule(self):
-    import safety_tips_pb2
-    globals()['safety_tips_pb2'] = safety_tips_pb2
-
-  def EmptyProtoInstance(self):
-    return safety_tips_pb2.SafetyTipsConfig()
-
-  def ValidatePb(self, opts, pb):
-    assert pb.version_id > 0
-
-    for flagged_page in pb.flagged_page:
-      assert flagged_page.pattern
-      assert flagged_page.type != safety_tips_pb2.FlaggedPage.UNKNOWN
-
-    for allowed_pattern in pb.allowed_pattern:
-      assert allowed_pattern.pattern
-
-    flagged_patterns = [p.pattern for p in pb.flagged_page]
-    assert sorted(flagged_patterns) == flagged_patterns, (
-        "Please sort flagged_page entries by pattern.")
-
-    allowed_patterns = [p.pattern for p in pb.allowed_pattern]
-    assert sorted(allowed_patterns) == allowed_patterns, (
-        "Please sort allowed_pattern entries by pattern.")
-
-  def ProcessPb(self, opts, pb):
-    binary_pb_str = pb.SerializeToString()
-    outfile = os.path.join(opts.outdir, opts.outbasename)
-
-    # Write two copies of the proto:
-    # 1. Under the root of the gen directory for .grd files to refer to
-    #    (./safety_tips/safety_tips.pb)
-    # 2. Under a versioned directory for the proto pusher to refer to
-    #    (./safety_tips/gs_copy/<version>/all/safety_tips.pb)
-    outfile = os.path.join(opts.outdir, opts.outbasename)
-    with open(outfile, 'wb') as f:
-      f.write(binary_pb_str)
-
-    outfile_copy = os.path.join(opts.outdir, GS_COPY_DIR, str(pb.version_id),
-                                "all", opts.outbasename)
-    MakeSubDirs(outfile_copy)
-    with open(outfile_copy, 'wb') as f:
-      f.write(binary_pb_str)
-
-
-def main():
-  return SafetyTipsProtoGenerator().Run()
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/browser/resources/safety_tips/push_proto.py b/chrome/browser/resources/safety_tips/push_proto.py
deleted file mode 100755
index 2404cb9..0000000
--- a/chrome/browser/resources/safety_tips/push_proto.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/python
-# 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.
-
-# Build and push the {vers}/all/ssl_error_assistant.pb file to GCS so
-# that the component update system will pick it up and push it to users.
-# See README.md before running this.
-#
-# Requires ninja and gsutil to be in the user's path.
-
-import optparse
-import os
-import shutil
-import subprocess
-import sys
-
-
-DEST_BUCKET = 'gs://chrome-components-safety-tips'
-RESOURCE_SUBDIR = 'chrome/browser/resources/safety_tips'
-
-# Subdirectory to be copied to Google Cloud Storage. Contains a copy of the
-# generated proto under a versioned directory.
-GS_COPY_DIR = "gs_copy"
-
-# TODO(meacer): This is pretty much a duplicate of
-#               chrome/browser/safe_browsing/push_file_type_proto.py. Consider
-#               refactoring and reusing code.
-def main():
-  parser = optparse.OptionParser()
-  parser.add_option('-d', '--dir',
-                    help='An up-to-date GN/Ninja build directory, '
-                    'such as ./out/Debug')
-
-  (opts, _) = parser.parse_args()
-  if opts.dir is None:
-    parser.print_help()
-    return 1
-
-  # Clear out the target dir before we build so we can be sure we've got
-  # the freshest version.
-  target_dir = os.path.join(opts.dir, "gen", RESOURCE_SUBDIR)
-  if os.path.isdir(target_dir):
-    shutil.rmtree(target_dir)
-
-  gn_command = ['ninja',
-                '-C', opts.dir,
-                RESOURCE_SUBDIR + ':make_safety_tips_protobuf']
-  print "Running the following"
-  print "   " + (' '.join(gn_command))
-  if subprocess.call(gn_command):
-    print "Ninja failed."
-    return 1
-
-  # Use the versioned files under the copy directory to push to the GCS bucket.
-  copy_dir = os.path.join(target_dir, GS_COPY_DIR)
-  os.chdir(copy_dir)
-
-  # Sanity check that there is a versioned copy under the directory.
-  dirs = os.listdir('.')
-  assert len(dirs) == 1 and dirs[0].isdigit(), (
-      "There must be a single versioned dir under " + copy_dir)
-
-  # Push the files with their directories, in the form
-  #   {vers}/{platform}/download_file_types.pb
-  # Don't overwrite existing files, in case we forgot to increment the
-  # version.
-  version_dir = dirs[0]
-  command = ['gsutil', 'cp', '-Rn', version_dir, DEST_BUCKET]
-
-  print '\nGoing to run the following command'
-  print '   ', ' '.join(command)
-  print '\nIn directory'
-  print '   ', copy_dir
-  print '\nWhich should push the following files'
-  expected_files = [os.path.join(dp, f) for dp, _, fn in
-                    os.walk(version_dir) for f in fn]
-  for f in expected_files:
-    print '   ', f
-
-  shall = raw_input('\nAre you sure (y/N) ').lower() == 'y'
-  if not shall:
-    print 'aborting'
-    return 1
-  return subprocess.call(command)
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/chrome/browser/resources/safety_tips/safety_tips.asciipb b/chrome/browser/resources/safety_tips/safety_tips.asciipb
deleted file mode 100644
index a1536bd..0000000
--- a/chrome/browser/resources/safety_tips/safety_tips.asciipb
+++ /dev/null
@@ -1,27 +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.
-
-# Placeholder for list of known bad patterns.
-# This file must be kept as-is except for version_id. The full list must not be
-# checked in to Chromium source. When you want to update it, overwrite it
-# locally, generate and push the protobuffer to the storage bucket but do not
-# check in the changes.
-
-version_id: 5
-
-# See chrome/browser/lookalikes/safety_tips.proto for the full format.
-# flagged_page entries must be sorted alphabetically by pattern.
-
-# Example entry:
-# flagged_page {
-#   pattern: "bad.test/test-path-for-safety-tips/test.html"
-#   type: BAD_REP
-# }
-
-# A page that's marked to not show a UI for any heuristic (including lookalike
-# or edit distance).
-# allowed_pattern entries must be sorted alphabetically by pattern.
-# allowed_pattern:  {
-#   pattern: "good.test/some/path.html"
-# }
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index a13e7384..d51f1a7e 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -819,11 +819,13 @@
 }
 
 void LocalNtpSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   std::string stripped_path = StripParameters(path);
   if (stripped_path == kConfigDataFilename) {
     std::string config_data_js = search_config_provider_->config_data_js();
diff --git a/chrome/browser/search/local_ntp_source.h b/chrome/browser/search/local_ntp_source.h
index ad6ecead..765e28a 100644
--- a/chrome/browser/search/local_ntp_source.h
+++ b/chrome/browser/search/local_ntp_source.h
@@ -72,7 +72,7 @@
   // Overridden from content::URLDataSource:
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/search/most_visited_iframe_source.cc b/chrome/browser/search/most_visited_iframe_source.cc
index 4243f74e..4d4314e 100644
--- a/chrome/browser/search/most_visited_iframe_source.cc
+++ b/chrome/browser/search/most_visited_iframe_source.cc
@@ -61,11 +61,11 @@
 }
 
 void MostVisitedIframeSource::StartDataRequest(
-    const std::string& path_and_query,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
-  GURL url(chrome::kChromeSearchMostVisitedUrl + path_and_query);
-  std::string path(url.path());
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path(url.path());
 
   if (path == kSingleHTMLPath) {
     SendResource(IDR_MOST_VISITED_SINGLE_HTML, callback);
diff --git a/chrome/browser/search/most_visited_iframe_source.h b/chrome/browser/search/most_visited_iframe_source.h
index 12af692..6f97034 100644
--- a/chrome/browser/search/most_visited_iframe_source.h
+++ b/chrome/browser/search/most_visited_iframe_source.h
@@ -23,7 +23,7 @@
   // content::URLDataSource:
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path_and_query,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path_and_query) override;
diff --git a/chrome/browser/search/most_visited_iframe_source_unittest.cc b/chrome/browser/search/most_visited_iframe_source_unittest.cc
index 250ca24..bcfc079 100644
--- a/chrome/browser/search/most_visited_iframe_source_unittest.cc
+++ b/chrome/browser/search/most_visited_iframe_source_unittest.cc
@@ -46,7 +46,7 @@
   }
 
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override {}
 
diff --git a/chrome/browser/search/ntp_icon_source.cc b/chrome/browser/search/ntp_icon_source.cc
index 12d820426..e3aa430 100644
--- a/chrome/browser/search/ntp_icon_source.cc
+++ b/chrome/browser/search/ntp_icon_source.cc
@@ -260,14 +260,15 @@
 }
 
 void NtpIconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   favicon::FaviconService* favicon_service =
       FaviconServiceFactory::GetForProfile(profile_,
                                            ServiceAccessType::EXPLICIT_ACCESS);
 
-  const ParsedNtpIconPath parsed = ParseNtpIconPath(path);
+  const ParsedNtpIconPath parsed =
+      ParseNtpIconPath(content::URLDataSource::URLToRequestPath(url));
 
   if (parsed.url.is_valid()) {
     int icon_size_in_pixels =
diff --git a/chrome/browser/search/ntp_icon_source.h b/chrome/browser/search/ntp_icon_source.h
index 3148d2f..314b757 100644
--- a/chrome/browser/search/ntp_icon_source.h
+++ b/chrome/browser/search/ntp_icon_source.h
@@ -37,7 +37,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/search/suggestions/suggestions_ui.cc b/chrome/browser/search/suggestions/suggestions_ui.cc
index a038da4..524599df 100644
--- a/chrome/browser/search/suggestions/suggestions_ui.cc
+++ b/chrome/browser/search/suggestions/suggestions_ui.cc
@@ -27,7 +27,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
@@ -50,10 +50,11 @@
 }
 
 void SuggestionsSourceWrapper::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
-  suggestions_source_.StartDataRequest(path, callback);
+  suggestions_source_.StartDataRequest(
+      content::URLDataSource::URLToRequestPath(url), callback);
 }
 
 std::string SuggestionsSourceWrapper::GetMimeType(const std::string& path) {
diff --git a/chrome/browser/sync/test/integration/passwords_helper.cc b/chrome/browser/sync/test/integration/passwords_helper.cc
index 0dfd08d..570ea96 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.cc
+++ b/chrome/browser/sync/test/integration/passwords_helper.cc
@@ -164,6 +164,18 @@
   wait_event.Wait();
 }
 
+void UpdateLoginWithPrimaryKey(PasswordStore* store,
+                               const PasswordForm& new_form,
+                               const PasswordForm& old_form) {
+  ASSERT_TRUE(store);
+  base::WaitableEvent wait_event(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  store->UpdateLoginWithPrimaryKey(new_form, old_form);
+  store->ScheduleTask(base::BindOnce(&PasswordStoreCallback, &wait_event));
+  wait_event.Wait();
+}
+
 std::vector<std::unique_ptr<PasswordForm>> GetLogins(PasswordStore* store) {
   EXPECT_TRUE(store);
   password_manager::PasswordStore::FormDigest matcher_form = {
diff --git a/chrome/browser/sync/test/integration/passwords_helper.h b/chrome/browser/sync/test/integration/passwords_helper.h
index 082693db..94c7f5a 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.h
+++ b/chrome/browser/sync/test/integration/passwords_helper.h
@@ -36,6 +36,12 @@
 void UpdateLogin(password_manager::PasswordStore* store,
                  const autofill::PasswordForm& form);
 
+// Removes |old_form| from password store |store| and immediately adds
+// |new_form|. This method blocks until the operation is complete.
+void UpdateLoginWithPrimaryKey(password_manager::PasswordStore* store,
+                               const autofill::PasswordForm& new_form,
+                               const autofill::PasswordForm& old_form);
+
 // Returns all logins from |store| matching a fake signon realm (see
 // CreateTestPasswordForm()).
 // TODO(treib): Rename this to make clear how specific it is.
diff --git a/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc b/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
index 2255dad..a68ebdb 100644
--- a/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_passwords_sync_test.cc
@@ -35,6 +35,7 @@
 using passwords_helper::RemoveLogin;
 using passwords_helper::RemoveLogins;
 using passwords_helper::UpdateLogin;
+using passwords_helper::UpdateLoginWithPrimaryKey;
 
 using autofill::PasswordForm;
 
@@ -335,6 +336,25 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(TwoClientPasswordsSyncTest, AddImmediatelyAfterDelete) {
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  ASSERT_TRUE(AllProfilesContainSamePasswordFormsAsVerifier());
+
+  PasswordForm form0 = CreateTestPasswordForm(0);
+  AddLogin(GetVerifierPasswordStore(), form0);
+  AddLogin(GetPasswordStore(0), form0);
+
+  ASSERT_TRUE(SamePasswordFormsAsVerifierChecker(1).Wait());
+  ASSERT_TRUE(AllProfilesContainSamePasswordFormsAsVerifier());
+
+  PasswordForm form1 = CreateTestPasswordForm(1);
+  UpdateLoginWithPrimaryKey(GetVerifierPasswordStore(), form1, form0);
+  UpdateLoginWithPrimaryKey(GetPasswordStore(0), form1, form0);
+
+  ASSERT_TRUE(SamePasswordFormsAsVerifierChecker(1).Wait());
+  ASSERT_TRUE(AllProfilesContainSamePasswordFormsAsVerifier());
+}
+
 INSTANTIATE_TEST_SUITE_P(USS,
                          TwoClientPasswordsSyncTest,
                          ::testing::Values(false, true));
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a566771..23a188b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1993,6 +1993,10 @@
 
   if (is_win || is_mac || is_desktop_linux || is_chromeos) {
     sources += [
+      "autofill/payments/verify_pending_dialog_controller.h",
+      "autofill/payments/verify_pending_dialog_controller_impl.cc",
+      "autofill/payments/verify_pending_dialog_controller_impl.h",
+      "autofill/payments/verify_pending_dialog_view.h",
       "autofill/payments/webauthn_offer_dialog_controller.h",
       "autofill/payments/webauthn_offer_dialog_controller_impl.cc",
       "autofill/payments/webauthn_offer_dialog_controller_impl.h",
@@ -2004,6 +2008,8 @@
       "frame/window_frame_util.h",
       "tab_contents/chrome_web_contents_view_handle_drop.cc",
       "tab_contents/chrome_web_contents_view_handle_drop.h",
+      "views/autofill/payments/verify_pending_dialog_view_impl.cc",
+      "views/autofill/payments/verify_pending_dialog_view_impl.h",
       "views/autofill/payments/webauthn_offer_dialog_view_impl.cc",
       "views/autofill/payments/webauthn_offer_dialog_view_impl.h",
       "views/close_bubble_on_tab_activation_helper.cc",
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index b9e52141..decb462 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -82,6 +82,8 @@
 #include "ui/android/window_android.h"
 #else  // !OS_ANDROID
 #include "chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h"
 #include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h"
 #include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h"
 #include "chrome/browser/ui/browser.h"
@@ -268,6 +270,26 @@
 #endif
 }
 
+#if !defined(OS_ANDROID)
+void ChromeAutofillClient::ShowVerifyPendingDialog(
+    base::OnceClosure cancel_card_verification_callback) {
+  autofill::VerifyPendingDialogControllerImpl::CreateForWebContents(
+      web_contents());
+  autofill::VerifyPendingDialogControllerImpl::FromWebContents(web_contents())
+      ->ShowDialog(std::move(cancel_card_verification_callback));
+}
+
+void ChromeAutofillClient::CloseVerifyPendingDialog() {
+  VerifyPendingDialogControllerImpl* controller =
+      autofill::VerifyPendingDialogControllerImpl::FromWebContents(
+          web_contents());
+  if (!controller)
+    return;
+
+  controller->OnCardVerificationCompleted();
+}
+#endif
+
 void ChromeAutofillClient::ShowWebauthnOfferDialog(
     WebauthnOfferDialogCallback callback) {
 #if !defined(OS_ANDROID)
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index a5dc10c..e080c73 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -86,6 +86,11 @@
       const base::string16& tip_message,
       const std::vector<MigratableCreditCard>& migratable_credit_cards,
       MigrationDeleteCardCallback delete_local_card_callback) override;
+#if !defined(OS_ANDROID)
+  void ShowVerifyPendingDialog(
+      base::OnceClosure cancel_card_verification_callback) override;
+  void CloseVerifyPendingDialog() override;
+#endif  // !defined(OS_ANDROID)
   void ShowWebauthnOfferDialog(WebauthnOfferDialogCallback callback) override;
   bool CloseWebauthnOfferDialog() override;
   void UpdateWebauthnOfferDialogWithError() override;
diff --git a/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller.h b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller.h
new file mode 100644
index 0000000..68cb9c0
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller.h
@@ -0,0 +1,32 @@
+// 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_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+namespace autofill {
+
+// An interface that exposes necessary controller functionality to
+// VerifyPendingDialogView.
+class VerifyPendingDialogController {
+ public:
+  VerifyPendingDialogController() = default;
+  virtual ~VerifyPendingDialogController() = default;
+
+  virtual base::string16 GetDialogTitle() const = 0;
+  virtual base::string16 GetCancelButtonLabel() const = 0;
+
+  virtual void OnCancel() = 0;
+  virtual void OnDialogClosed() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(VerifyPendingDialogController);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.cc b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.cc
new file mode 100644
index 0000000..a6fec50d
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.cc
@@ -0,0 +1,64 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h"
+
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+VerifyPendingDialogControllerImpl::VerifyPendingDialogControllerImpl(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+VerifyPendingDialogControllerImpl::~VerifyPendingDialogControllerImpl() {
+  // If browser tab is closed when dialog is visible, the controller is
+  // destroyed before the view is, so need to reset view's reference to
+  // controller.
+  if (dialog_view_)
+    dialog_view_->Hide();
+}
+
+void VerifyPendingDialogControllerImpl::ShowDialog(
+    base::OnceClosure cancel_card_verification_callback) {
+  DCHECK(!dialog_view_);
+
+  cancel_card_verification_callback_ =
+      std::move(cancel_card_verification_callback);
+  dialog_view_ =
+      VerifyPendingDialogView::CreateDialogAndShow(this, web_contents());
+}
+
+void VerifyPendingDialogControllerImpl::OnCardVerificationCompleted() {
+  if (!dialog_view_)
+    return;
+
+  cancel_card_verification_callback_.Reset();
+  dialog_view_->Hide();
+}
+
+base::string16 VerifyPendingDialogControllerImpl::GetDialogTitle() const {
+  return l10n_util::GetStringUTF16(IDS_AUTOFILL_VERIFY_PENDING_DIALOG_TITLE);
+}
+
+base::string16 VerifyPendingDialogControllerImpl::GetCancelButtonLabel() const {
+  return l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_VERIFY_PENDING_DIALOG_CANCEL_BUTTON_LABEL);
+}
+
+void VerifyPendingDialogControllerImpl::OnCancel() {
+  if (cancel_card_verification_callback_)
+    std::move(cancel_card_verification_callback_).Run();
+}
+
+void VerifyPendingDialogControllerImpl::OnDialogClosed() {
+  dialog_view_ = nullptr;
+  cancel_card_verification_callback_.Reset();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(VerifyPendingDialogControllerImpl)
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h
new file mode 100644
index 0000000..4bb3bdf
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_IMPL_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_controller.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace autofill {
+
+class VerifyPendingDialogView;
+
+// Implementation of the per-tab controller to control the
+// VerifyPendingDialogView. Lazily initialized when used.
+class VerifyPendingDialogControllerImpl
+    : public VerifyPendingDialogController,
+      public content::WebContentsObserver,
+      public content::WebContentsUserData<VerifyPendingDialogControllerImpl> {
+ public:
+  ~VerifyPendingDialogControllerImpl() override;
+
+  void ShowDialog(base::OnceClosure cancel_card_verification_callback);
+
+  // Close the dialog when card verification is completed.
+  void OnCardVerificationCompleted();
+
+  // VerifyPendingDialogController:
+  base::string16 GetDialogTitle() const override;
+  base::string16 GetCancelButtonLabel() const override;
+  void OnCancel() override;
+  void OnDialogClosed() override;
+
+  VerifyPendingDialogView* dialog_view() { return dialog_view_; }
+
+ protected:
+  explicit VerifyPendingDialogControllerImpl(
+      content::WebContents* web_contents);
+
+ private:
+  friend class content::WebContentsUserData<VerifyPendingDialogControllerImpl>;
+
+  // Callback invoked when the cancel button in the dialog is clicked. Will
+  // cancel the card verification in progress.
+  base::OnceClosure cancel_card_verification_callback_;
+
+  VerifyPendingDialogView* dialog_view_ = nullptr;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  DISALLOW_COPY_AND_ASSIGN(VerifyPendingDialogControllerImpl);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h b/chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h
new file mode 100644
index 0000000..77df5de
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h
@@ -0,0 +1,30 @@
+// 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_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_H_
+
+namespace content {
+class WebContents;
+}
+
+namespace autofill {
+
+class VerifyPendingDialogController;
+
+// The dialog to show card verification is in progress.
+class VerifyPendingDialogView {
+ public:
+  // Factory function implemented by dialog's view implementation.
+  static VerifyPendingDialogView* CreateDialogAndShow(
+      VerifyPendingDialogController* controller,
+      content::WebContents* web_contents);
+
+  // Close the dialog and prevent callbacks being invoked.
+  virtual void Hide() = 0;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_browsertest.cc b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_browsertest.cc
new file mode 100644
index 0000000..1ab6d3d
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_browsertest.cc
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.h"
+#include "ui/views/window/dialog_client_view.h"
+
+namespace autofill {
+
+class VerifyPendingDialogViewBrowserTest : public DialogBrowserTest {
+ public:
+  VerifyPendingDialogViewBrowserTest() = default;
+  // DialogBrowserTest:
+  void ShowUi(const std::string& name) override {
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    // Do lazy initialization of VerifyPendingDialogControllerImpl.
+    VerifyPendingDialogControllerImpl::CreateForWebContents(web_contents);
+    controller_ =
+        VerifyPendingDialogControllerImpl::FromWebContents(web_contents);
+    DCHECK(controller_);
+    controller_->ShowDialog(base::DoNothing());
+  }
+
+  VerifyPendingDialogViewImpl* GetVerifyPendingDialog() {
+    if (!controller_)
+      return nullptr;
+    VerifyPendingDialogView* verify_pending_dialog_view =
+        controller_->dialog_view();
+    if (!verify_pending_dialog_view)
+      return nullptr;
+    return static_cast<VerifyPendingDialogViewImpl*>(
+        verify_pending_dialog_view);
+  }
+
+  VerifyPendingDialogControllerImpl* controller() { return controller_; }
+
+ private:
+  VerifyPendingDialogControllerImpl* controller_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(VerifyPendingDialogViewBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(VerifyPendingDialogViewBrowserTest, InvokeUi_default) {
+  ShowAndVerifyUi();
+}
+
+// This test ensures an edge case (closing tab while dialog being visible) is
+// correctly handled, otherwise this test will crash during web contents being
+// closed.
+IN_PROC_BROWSER_TEST_F(VerifyPendingDialogViewBrowserTest,
+                       CanCloseTabWhileDialogShowing) {
+  ShowUi(std::string());
+  VerifyUi();
+  browser()->tab_strip_model()->GetActiveWebContents()->Close();
+  base::RunLoop().RunUntilIdle();
+}
+
+// Ensures closing browser while dialog being visible is correctly handled.
+IN_PROC_BROWSER_TEST_F(VerifyPendingDialogViewBrowserTest,
+                       CanCloseBrowserWhileDialogShowing) {
+  ShowUi(std::string());
+  VerifyUi();
+  browser()->window()->Close();
+  base::RunLoop().RunUntilIdle();
+}
+
+// Ensures dialog is closed when cancel button is clicked.
+IN_PROC_BROWSER_TEST_F(VerifyPendingDialogViewBrowserTest, ClickCancelButton) {
+  ShowUi(std::string());
+  VerifyUi();
+  GetVerifyPendingDialog()->GetDialogClientView()->CancelWindow();
+  base::RunLoop().RunUntilIdle();
+}
+
+// TODO(crbug.com/991037): Add more browser tests:
+// 1. Make sure callback runs if cancel button clicked.
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.cc b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.cc
new file mode 100644
index 0000000..4523e9f3
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.cc
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.h"
+
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_controller.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/window/dialog_client_view.h"
+
+namespace autofill {
+
+VerifyPendingDialogViewImpl::VerifyPendingDialogViewImpl(
+    VerifyPendingDialogController* controller)
+    : controller_(controller) {
+  // TODO(crbug.com/1014278): Should get correct width automatically when
+  // snapping.
+  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
+  // TODO(crbug.com/1014334): Investigate why CalculatePreferredSize can not be
+  // overridden when there is no layout manager.
+  SetPreferredSize(gfx::Size(width, GetHeightForWidth(width)));
+}
+
+VerifyPendingDialogViewImpl::~VerifyPendingDialogViewImpl() {
+  if (controller_) {
+    controller_->OnDialogClosed();
+    controller_ = nullptr;
+  }
+}
+
+VerifyPendingDialogView* VerifyPendingDialogView::CreateDialogAndShow(
+    VerifyPendingDialogController* controller,
+    content::WebContents* web_contents) {
+  VerifyPendingDialogViewImpl* dialog =
+      new VerifyPendingDialogViewImpl(controller);
+  constrained_window::ShowWebModalDialogViews(dialog, web_contents);
+  return dialog;
+}
+
+void VerifyPendingDialogViewImpl::Hide() {
+  if (controller_) {
+    controller_->OnDialogClosed();
+    controller_ = nullptr;
+  }
+  GetWidget()->Close();
+}
+
+void VerifyPendingDialogViewImpl::AddedToWidget() {
+  GetBubbleFrameView()->SetProgress(/*Infinite animation*/ -1);
+}
+
+bool VerifyPendingDialogViewImpl::Cancel() {
+  controller_->OnCancel();
+  return true;
+}
+
+int VerifyPendingDialogViewImpl::GetDialogButtons() const {
+  return ui::DIALOG_BUTTON_CANCEL;
+}
+
+base::string16 VerifyPendingDialogViewImpl::GetDialogButtonLabel(
+    ui::DialogButton button) const {
+  DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL);
+  return controller_->GetCancelButtonLabel();
+}
+
+ui::ModalType VerifyPendingDialogViewImpl::GetModalType() const {
+  return ui::MODAL_TYPE_CHILD;
+}
+
+base::string16 VerifyPendingDialogViewImpl::GetWindowTitle() const {
+  return controller_->GetDialogTitle();
+}
+
+bool VerifyPendingDialogViewImpl::ShouldShowCloseButton() const {
+  return false;
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.h b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.h
new file mode 100644
index 0000000..b09b8b6
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/payments/verify_pending_dialog_view_impl.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_IMPL_H_
+#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_IMPL_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/autofill/payments/verify_pending_dialog_view.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace autofill {
+
+class VerifyPendingDialogController;
+
+// The Views implementation of the dialog that shows the card verification is in
+// progress. It is shown when card verification process starts only if WebAuthn
+// has been enabled and opted in.
+class VerifyPendingDialogViewImpl : public VerifyPendingDialogView,
+                                    public views::DialogDelegateView {
+ public:
+  explicit VerifyPendingDialogViewImpl(
+      VerifyPendingDialogController* controller);
+  ~VerifyPendingDialogViewImpl() override;
+
+  // VerifyPendingDialogView:
+  void Hide() override;
+
+  // views::DialogDelegateView:
+  void AddedToWidget() override;
+  bool Cancel() override;
+  int GetDialogButtons() const override;
+  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+  ui::ModalType GetModalType() const override;
+  base::string16 GetWindowTitle() const override;
+  bool ShouldShowCloseButton() const override;
+
+ private:
+  VerifyPendingDialogController* controller_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(VerifyPendingDialogViewImpl);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_VERIFY_PENDING_DIALOG_VIEW_IMPL_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 1ca3914..382885b 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -289,14 +289,12 @@
 
   size_t index = it - model_->pinned_action_ids().cbegin();
 
-  ToolbarActionViewController* view_controller =
-      (GetViewForId(*it))->view_controller();
-  data->provider().SetDragImage(
-      view_controller->GetIcon(GetCurrentWebContents(), GetToolbarActionSize())
-          .AsImageSkia(),
-      press_pt.OffsetFromOrigin());
+  ToolbarActionView* extension_view = GetViewForId(*it);
+  data->provider().SetDragImage(GetExtensionIcon(extension_view),
+                                press_pt.OffsetFromOrigin());
   // Fill in the remaining info.
-  BrowserActionDragData drag_data(view_controller->GetId(), index);
+  BrowserActionDragData drag_data(extension_view->view_controller()->GetId(),
+                                  index);
   drag_data.Write(browser_->profile(), data);
 }
 
@@ -352,6 +350,7 @@
 
   if (!drop_info_.get() || drop_info_->index != before_icon) {
     drop_info_ = std::make_unique<DropInfo>(data.id(), before_icon);
+    SetExtensionIconVisibility(drop_info_->action_id, false);
     ReorderViews();
   }
 
@@ -359,8 +358,14 @@
 }
 
 void ExtensionsToolbarContainer::OnDragExited() {
+  const ToolbarActionsModel::ActionId dragged_extension_id =
+      drop_info_->action_id;
   drop_info_.reset();
   ReorderViews();
+  static_cast<views::AnimatingLayoutManager*>(GetLayoutManager())
+      ->RunOrQueueAction(base::BindOnce(
+          &ExtensionsToolbarContainer::SetExtensionIconVisibility,
+          weak_ptr_factory_.GetWeakPtr(), dragged_extension_id, true));
 }
 
 int ExtensionsToolbarContainer::OnPerformDrop(
@@ -408,6 +413,27 @@
   return std::min(unclamped_count, actions_.size());
 }
 
+gfx::ImageSkia ExtensionsToolbarContainer::GetExtensionIcon(
+    ToolbarActionView* extension_view) {
+  return extension_view->view_controller()
+      ->GetIcon(GetCurrentWebContents(), GetToolbarActionSize())
+      .AsImageSkia();
+}
+
+void ExtensionsToolbarContainer::SetExtensionIconVisibility(
+    ToolbarActionsModel::ActionId id,
+    bool visible) {
+  auto it = std::find_if(model_->pinned_action_ids().cbegin(),
+                         model_->pinned_action_ids().cend(),
+                         [this, id](const std::string& action_id) {
+                           return GetViewForId(action_id) == GetViewForId(id);
+                         });
+  ToolbarActionView* extension_view = GetViewForId(*it);
+  extension_view->SetImage(
+      views::Button::STATE_NORMAL,
+      visible ? GetExtensionIcon(extension_view) : gfx::ImageSkia());
+}
+
 void ExtensionsToolbarContainer::ShowActiveBubble(
     views::View* anchor_view,
     std::unique_ptr<ToolbarActionsBarBubbleDelegate> controller) {
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 2b81f8d..054e7c9 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -76,6 +76,12 @@
   // Utility function for going from width to icon counts.
   size_t WidthToIconCount(int x_offset);
 
+  gfx::ImageSkia GetExtensionIcon(ToolbarActionView* extension_view);
+
+  // Sets a pinned extension button's image to be shown/hidden.
+  void SetExtensionIconVisibility(ToolbarActionsModel::ActionId id,
+                                  bool visible);
+
   // ExtensionsContainer:
   ToolbarActionViewController* GetActionForId(
       const std::string& action_id) override;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index ec378e16..09fa41d 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -233,6 +233,12 @@
   if (button == suggestion_tab_switch_button_) {
     OpenMatch(WindowOpenDisposition::SWITCH_TO_TAB, event.time_stamp());
   } else if (button == remove_suggestion_button_) {
+    if (!base::FeatureList::IsEnabled(
+            omnibox::kConfirmOmniboxSuggestionRemovals)) {
+      RemoveSuggestion();
+      return;
+    }
+
     // Temporarily inhibit the popup closing on blur while we open the remove
     // suggestion confirmation bubble.
     popup_contents_view_->model()->set_popup_closes_on_blur(false);
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index bbd4555..8b822cc 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -1308,8 +1308,15 @@
   // TODO(tommycli): This does not seem like it should be necessary.
   // Investigate why it's needed and see if we can remove it.
   model()->ResetDisplayTexts();
+
+  bool suppress = suppress_on_focus_suggestions_;
+  if (GetFocusManager() &&
+      GetFocusManager()->focus_change_reason() !=
+          views::FocusManager::FocusChangeReason::kDirectFocusChange) {
+    suppress = true;
+  }
   // TODO(oshima): Get control key state.
-  model()->OnSetFocus(false, suppress_on_focus_suggestions_);
+  model()->OnSetFocus(false, suppress);
   // Don't call controller()->OnSetFocus, this view has already acquired focus.
 
   // Restore the selection we saved in OnBlur() if it's still valid.
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_container_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_container_view.cc
index 12119d4..1d41c0b5 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_container_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_container_view.cc
@@ -30,6 +30,11 @@
 PageActionIconContainerView::Params::Params() = default;
 PageActionIconContainerView::Params::~Params() = default;
 
+// static
+const char
+    PageActionIconContainerView::kPageActionIconContainerViewClassName[] =
+        "PageActionIconContainerView";
+
 PageActionIconContainerView::PageActionIconContainerView(const Params& params)
     : zoom_observer_(this) {
   DCHECK(params.page_action_icon_delegate);
@@ -247,6 +252,10 @@
     zoom_icon_->ZoomChangedForActiveTab(can_show_bubble);
 }
 
+const char* PageActionIconContainerView::GetClassName() const {
+  return kPageActionIconContainerViewClassName;
+}
+
 void PageActionIconContainerView::ChildPreferredSizeChanged(
     views::View* child) {
   PreferredSizeChanged();
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_container_view.h b/chrome/browser/ui/views/page_action/page_action_icon_container_view.h
index 99b1d673..a41f10d 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_container_view.h
+++ b/chrome/browser/ui/views/page_action/page_action_icon_container_view.h
@@ -91,6 +91,11 @@
   // See comment in browser_window.h for more info.
   void ZoomChangedForActiveTab(bool can_show_bubble);
 
+  // views::View:
+  const char* GetClassName() const override;
+
+  static const char kPageActionIconContainerViewClassName[];
+
  private:
   // views::View:
   void ChildPreferredSizeChanged(views::View* child) override;
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index a75cb13..b15ff03d 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -304,6 +304,21 @@
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
 }
 
+// Ensure normal sites with low engagement are not blocked in incognito.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       NoShowOnLowEngagementIncognito) {
+  Browser* incognito_browser = new Browser(Browser::CreateParams(
+      browser()->profile()->GetOffTheRecordProfile(), true));
+  auto kNavigatedUrl = GetURL("site1.com");
+  SetEngagementScore(incognito_browser, kNavigatedUrl, kLowEngagement);
+  NavigateToURL(incognito_browser, kNavigatedUrl,
+                WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+
+  ASSERT_NO_FATAL_FAILURE(
+      CheckPageInfoDoesNotShowSafetyTipInfo(incognito_browser));
+}
+
 // Ensure blocked sites with high engagement are not blocked.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
                        NoShowOnHighEngagement) {
@@ -317,6 +332,23 @@
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
 }
 
+// Ensure blocked sites with high engagement are not blocked in incognito.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       NoShowOnHighEngagementIncognito) {
+  Browser* incognito_browser = new Browser(Browser::CreateParams(
+      browser()->profile()->GetOffTheRecordProfile(), true));
+  auto kNavigatedUrl = GetURL("site1.com");
+  SetSafetyTipBadRepPatterns({"site1.com/"});
+
+  SetEngagementScore(incognito_browser, kNavigatedUrl, kHighEngagement);
+  NavigateToURL(incognito_browser, kNavigatedUrl,
+                WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+
+  ASSERT_NO_FATAL_FAILURE(
+      CheckPageInfoDoesNotShowSafetyTipInfo(incognito_browser));
+}
+
 // Ensure blocked sites get blocked.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest, ShowOnBlock) {
   auto kNavigatedUrl = GetURL("site1.com");
@@ -329,6 +361,23 @@
       browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
 }
 
+// Ensure blocked sites get blocked in incognito.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       ShowOnBlockIncognito) {
+  Browser* incognito_browser = new Browser(Browser::CreateParams(
+      browser()->profile()->GetOffTheRecordProfile(), true));
+  auto kNavigatedUrl = GetURL("site1.com");
+  SetSafetyTipBadRepPatterns({"site1.com/"});
+
+  NavigateToURL(incognito_browser, kNavigatedUrl,
+                WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_TRUE(IsUIShowingIfEnabled());
+
+  ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
+      incognito_browser, security_state::SafetyTipStatus::kBadReputation,
+      GURL()));
+}
+
 // Ensure explicitly-allowed sites don't get blocked when the site is otherwise
 // blocked server-side.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
diff --git a/chrome/browser/ui/views/passwords/credential_leak_dialog_view.cc b/chrome/browser/ui/views/passwords/credential_leak_dialog_view.cc
index 0f55f7a8e..945ce16 100644
--- a/chrome/browser/ui/views/passwords/credential_leak_dialog_view.cc
+++ b/chrome/browser/ui/views/passwords/credential_leak_dialog_view.cc
@@ -102,6 +102,11 @@
     : controller_(controller), web_contents_(web_contents) {
   DCHECK(controller);
   DCHECK(web_contents);
+
+  DialogDelegate::set_button_label(ui::DIALOG_BUTTON_OK,
+                                   controller_->GetAcceptButtonLabel());
+  DialogDelegate::set_button_label(ui::DIALOG_BUTTON_CANCEL,
+                                   controller_->GetCancelButtonLabel());
 }
 
 CredentialLeakDialogView::~CredentialLeakDialogView() = default;
@@ -128,12 +133,6 @@
   return gfx::Size(width, GetHeightForWidth(width));
 }
 
-base::string16 CredentialLeakDialogView::GetDialogButtonLabel(
-    ui::DialogButton button) const {
-  return button == ui::DIALOG_BUTTON_OK ? controller_->GetAcceptButtonLabel()
-                                        : controller_->GetCancelButtonLabel();
-}
-
 bool CredentialLeakDialogView::Cancel() {
   if (controller_)
     // Since OnCancelDialog() synchronously invokes Close() on this instance, we
diff --git a/chrome/browser/ui/views/passwords/credential_leak_dialog_view.h b/chrome/browser/ui/views/passwords/credential_leak_dialog_view.h
index 1f089978..d2d08646 100644
--- a/chrome/browser/ui/views/passwords/credential_leak_dialog_view.h
+++ b/chrome/browser/ui/views/passwords/credential_leak_dialog_view.h
@@ -31,7 +31,6 @@
   // views::DialogDelegateView:
   ui::ModalType GetModalType() const override;
   gfx::Size CalculatePreferredSize() const override;
-  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
   bool Cancel() override;
   bool Accept() override;
   bool Close() override;
diff --git a/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
index 9f61c3d..ec1b4d3 100644
--- a/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
@@ -469,6 +469,28 @@
   }
 }
 
+// Test can cancel payment with 'basic-card' payment method from alicepay.
+IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest, PayWithBasicCardCancel) {
+  InstallAlicePayForMethod("basic-card");
+  {
+    SetDownloaderAndIgnorePortInOriginComparisonForTesting();
+    NavigateTo(
+        "/payment_request_bobpay_and_basic_card_with_modifiers_test.html");
+    InvokePaymentRequestUI();
+    ClickOnCancel();
+    ExpectBodyContains({"User closed the Payment Request UI."});
+  }
+  // Repeat should have identical results.
+  {
+    SetDownloaderAndIgnorePortInOriginComparisonForTesting();
+    NavigateTo(
+        "/payment_request_bobpay_and_basic_card_with_modifiers_test.html");
+    InvokePaymentRequestUI();
+    ClickOnCancel();
+    ExpectBodyContains({"User closed the Payment Request UI."});
+  }
+}
+
 class PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip
     : public PaymentRequestPaymentAppTest {
  public:
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
index db52ede..b453a59 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -112,6 +112,10 @@
 
 }  // namespace
 
+// static
+const char AvatarToolbarButton::kAvatarToolbarButtonClassName[] =
+    "AvatarToolbarButton";
+
 AvatarToolbarButton::AvatarToolbarButton(Browser* browser)
     : AvatarToolbarButton(browser, nullptr) {}
 
@@ -288,6 +292,10 @@
   observer_list_.RemoveObserver(observer);
 }
 
+const char* AvatarToolbarButton::GetClassName() const {
+  return kAvatarToolbarButtonClassName;
+}
+
 void AvatarToolbarButton::NotifyClick(const ui::Event& event) {
   Button::NotifyClick(event);
   MaybeHideIdentityAnimation();
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
index bfa8ac3..60638f8 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
@@ -51,6 +51,11 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+  // views::View:
+  const char* GetClassName() const override;
+
+  static const char kAvatarToolbarButtonClassName[];
+
  private:
   FRIEND_TEST_ALL_PREFIXES(AvatarToolbarButtonTest,
                            HighlightMeetsMinimumContrast);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
index 1eb66b4..791c8c1 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.cc
@@ -20,6 +20,11 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/widget/widget.h"
 
+// static
+const char ToolbarAccountIconContainerView::
+    kToolbarAccountIconContainerViewClassName[] =
+        "ToolbarAccountIconContainerView";
+
 ToolbarAccountIconContainerView::ToolbarAccountIconContainerView(
     Browser* browser)
     : ToolbarIconContainerView(
@@ -73,6 +78,10 @@
   UpdateAllIcons();
 }
 
+const char* ToolbarAccountIconContainerView::GetClassName() const {
+  return kToolbarAccountIconContainerViewClassName;
+}
+
 SkColor ToolbarAccountIconContainerView::GetIconColor() const {
   return GetThemeProvider()->GetColor(
       ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
index a0ce1a6..c6ae35e 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_account_icon_container_view.h
@@ -32,12 +32,15 @@
 
   // views::View:
   void OnThemeChanged() override;
+  const char* GetClassName() const override;
 
   PageActionIconContainerView* page_action_icon_container() {
     return page_action_icon_container_view_;
   }
   AvatarToolbarButton* avatar_button() { return avatar_; }
 
+  static const char kToolbarAccountIconContainerViewClassName[];
+
  private:
   SkColor GetIconColor() const;
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.cc b/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.cc
index 247feb2..c2922a3 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.cc
@@ -16,6 +16,10 @@
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/view_class_properties.h"
 
+// static
+const char ToolbarIconContainerView::kToolbarIconContainerViewClassName[] =
+    "ToolbarIconContainerView";
+
 ToolbarIconContainerView::ToolbarIconContainerView(bool uses_highlight)
     : uses_highlight_(uses_highlight) {
   views::AnimatingLayoutManager* animating_layout =
@@ -98,6 +102,10 @@
   return gfx::Insets();
 }
 
+const char* ToolbarIconContainerView::GetClassName() const {
+  return kToolbarIconContainerViewClassName;
+}
+
 bool ToolbarIconContainerView::ShouldDisplayHighlight() {
   if (!uses_highlight_)
     return false;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h b/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h
index 755e16c..40a910b 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h
@@ -49,6 +49,8 @@
 
   bool uses_highlight() { return uses_highlight_; }
 
+  static const char kToolbarIconContainerViewClassName[];
+
  private:
   friend class ToolbarAccountIconContainerViewBrowserTest;
 
@@ -57,6 +59,7 @@
   void OnMouseExited(const ui::MouseEvent& event) override;
   void ChildPreferredSizeChanged(views::View* child) override;
   gfx::Insets GetInsets() const override;
+  const char* GetClassName() const override;
 
   // gfx::AnimationDelegate:
   void AnimationProgressed(const gfx::Animation* animation) override;
diff --git a/chrome/browser/ui/webui/about_ui.cc b/chrome/browser/ui/webui/about_ui.cc
index 560b170d..0fc5196 100644
--- a/chrome/browser/ui/webui/about_ui.cc
+++ b/chrome/browser/ui/webui/about_ui.cc
@@ -595,9 +595,11 @@
 }
 
 void AboutUIHTMLSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   std::string response;
   // Add your data source here, in alphabetical order.
   if (source_name_ == chrome::kChromeUIChromeURLsHost) {
diff --git a/chrome/browser/ui/webui/about_ui.h b/chrome/browser/ui/webui/about_ui.h
index 0f2ae712..485fa5b 100644
--- a/chrome/browser/ui/webui/about_ui.h
+++ b/chrome/browser/ui/webui/about_ui.h
@@ -25,7 +25,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/ui/webui/about_ui_unittest.cc b/chrome/browser/ui/webui/about_ui_unittest.cc
index 66904b30..118ca6c 100644
--- a/chrome/browser/ui/webui/about_ui_unittest.cc
+++ b/chrome/browser/ui/webui/about_ui_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
@@ -124,7 +125,9 @@
                     TestDataReceiver* data_receiver) {
     content::WebContents::Getter wc_getter;
     tested_html_source_->StartDataRequest(
-        request_url, std::move(wc_getter),
+        GURL(base::StrCat(
+            {"chrome://", chrome::kChromeUITermsHost, "/", request_url})),
+        std::move(wc_getter),
         base::BindRepeating(&TestDataReceiver::OnDataReceived,
                             base::Unretained(data_receiver)));
     task_environment_.RunUntilIdle();
diff --git a/chrome/browser/ui/webui/app_launcher_page_ui.cc b/chrome/browser/ui/webui/app_launcher_page_ui.cc
index 521d94e..553d3f2 100644
--- a/chrome/browser/ui/webui/app_launcher_page_ui.cc
+++ b/chrome/browser/ui/webui/app_launcher_page_ui.cc
@@ -99,7 +99,7 @@
 }
 
 void AppLauncherPageUI::HTMLSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/chrome/browser/ui/webui/app_launcher_page_ui.h b/chrome/browser/ui/webui/app_launcher_page_ui.h
index b5ad2e1..011f5579 100644
--- a/chrome/browser/ui/webui/app_launcher_page_ui.h
+++ b/chrome/browser/ui/webui/app_launcher_page_ui.h
@@ -39,7 +39,7 @@
     // content::URLDataSource implementation.
     std::string GetSource() override;
     void StartDataRequest(
-        const std::string& path,
+        const GURL& url,
         const content::WebContents::Getter& wc_getter,
         const content::URLDataSource::GotDataCallback& callback) override;
     std::string GetMimeType(const std::string&) override;
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
index 6ac7d19..2d32689 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/mobile_setup_ui.cc
@@ -177,7 +177,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string&) override { return "text/html"; }
@@ -269,9 +269,10 @@
 }
 
 void MobileSetupUIHTMLSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   // Sanity checks that activation was requested for an appropriate network.
   const NetworkState* network =
       NetworkHandler::Get()->network_state_handler()->GetNetworkState(path);
diff --git a/chrome/browser/ui/webui/chromeos/image_source.cc b/chrome/browser/ui/webui/chromeos/image_source.cc
index 1a9ff11..3110ff0 100644
--- a/chrome/browser/ui/webui/chromeos/image_source.cc
+++ b/chrome/browser/ui/webui/chromeos/image_source.cc
@@ -54,9 +54,10 @@
 }
 
 void ImageSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& got_data_callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   if (!IsWhitelisted(path)) {
     got_data_callback.Run(nullptr);
     return;
diff --git a/chrome/browser/ui/webui/chromeos/image_source.h b/chrome/browser/ui/webui/chromeos/image_source.h
index 4bf09ea..6848d0a 100644
--- a/chrome/browser/ui/webui/chromeos/image_source.h
+++ b/chrome/browser/ui/webui/chromeos/image_source.h
@@ -27,7 +27,7 @@
 
   // content::URLDataSource implementation.
   std::string GetSource() override;
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const content::WebContents::Getter& wc_getter,
                         const content::URLDataSource::GotDataCallback&
                             got_data_callback) override;
diff --git a/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.cc b/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.cc
index e86ea51..2d4544f 100644
--- a/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.cc
+++ b/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.cc
@@ -34,7 +34,7 @@
 }
 
 void ScreenlockIconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   if (!icon_provider_) {
@@ -42,7 +42,8 @@
     return;
   }
 
-  GURL url(chrome::kChromeUIScreenlockIconURL + path);
+  // TODO(crbug/1009127): Make sure |url| matches
+  // |chrome::kChromeUIScreenlockIconURL| now that |url| is available.
   std::string username =
       net::UnescapeBinaryURLComponent(url.path_piece().substr(1));
 
diff --git a/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.h b/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.h
index 44b13c4..0293300 100644
--- a/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.h
+++ b/chrome/browser/ui/webui/chromeos/login/screenlock_icon_source.h
@@ -23,7 +23,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() const override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/chromeos/slow_trace_ui.cc b/chrome/browser/ui/webui/chromeos/slow_trace_ui.cc
index c5c1b2c..1231c9c9 100644
--- a/chrome/browser/ui/webui/chromeos/slow_trace_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/slow_trace_ui.cc
@@ -33,10 +33,12 @@
 }
 
 void SlowTraceSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   int trace_id = 0;
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   size_t pos = path.find('#');
   TracingManager* manager = TracingManager::Get();
   if (!manager ||
diff --git a/chrome/browser/ui/webui/chromeos/slow_trace_ui.h b/chrome/browser/ui/webui/chromeos/slow_trace_ui.h
index 8d000798..78ebae81 100644
--- a/chrome/browser/ui/webui/chromeos/slow_trace_ui.h
+++ b/chrome/browser/ui/webui/chromeos/slow_trace_ui.h
@@ -31,7 +31,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/ui/webui/chromeos/terminal/terminal_source.cc b/chrome/browser/ui/webui/chromeos/terminal/terminal_source.cc
index 1bc97a1d..2f96140 100644
--- a/chrome/browser/ui/webui/chromeos/terminal/terminal_source.cc
+++ b/chrome/browser/ui/webui/chromeos/terminal/terminal_source.cc
@@ -49,9 +49,11 @@
 #endif
 
 void TerminalSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   // Reparse path to strip any query or fragment, skip first '/' in path.
   std::string reparsed =
       GURL(chrome::kChromeUITerminalURL + path).path().substr(1);
diff --git a/chrome/browser/ui/webui/chromeos/terminal/terminal_source.h b/chrome/browser/ui/webui/chromeos/terminal/terminal_source.h
index 28e1fb9a..10f3d0d5 100644
--- a/chrome/browser/ui/webui/chromeos/terminal/terminal_source.h
+++ b/chrome/browser/ui/webui/chromeos/terminal/terminal_source.h
@@ -25,7 +25,7 @@
 #endif
 
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/chromeos/user_image_source.cc b/chrome/browser/ui/webui/chromeos/user_image_source.cc
index e8ca63d..50d6e7a 100644
--- a/chrome/browser/ui/webui/chromeos/user_image_source.cc
+++ b/chrome/browser/ui/webui/chromeos/user_image_source.cc
@@ -178,12 +178,14 @@
 }
 
 void UserImageSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): Make sure |url| matches
+  // |chrome::kChromeUIUserImageURL| now that |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   std::string email;
   int frame = -1;
-  GURL url(chrome::kChromeUIUserImageURL + path);
   ParseRequest(url, &email, &frame);
   const AccountId account_id(AccountId::FromUserEmail(email));
   callback.Run(GetUserImageInternal(account_id, frame));
diff --git a/chrome/browser/ui/webui/chromeos/user_image_source.h b/chrome/browser/ui/webui/chromeos/user_image_source.h
index f20af23..85c68ad 100644
--- a/chrome/browser/ui/webui/chromeos/user_image_source.h
+++ b/chrome/browser/ui/webui/chromeos/user_image_source.h
@@ -32,7 +32,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/ui/webui/chromeos/video_source.cc b/chrome/browser/ui/webui/chromeos/video_source.cc
index 4b56024a..e8d5262 100644
--- a/chrome/browser/ui/webui/chromeos/video_source.cc
+++ b/chrome/browser/ui/webui/chromeos/video_source.cc
@@ -67,9 +67,10 @@
 }
 
 void VideoSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& got_data_callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   if (!IsWhitelisted(path)) {
     got_data_callback.Run(nullptr);
     return;
diff --git a/chrome/browser/ui/webui/chromeos/video_source.h b/chrome/browser/ui/webui/chromeos/video_source.h
index c8f6cae1..e113f3f 100644
--- a/chrome/browser/ui/webui/chromeos/video_source.h
+++ b/chrome/browser/ui/webui/chromeos/video_source.h
@@ -29,7 +29,7 @@
 
   // content::URLDataSource:
   std::string GetSource() override;
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const content::WebContents::Getter& wc_getter,
                         const content::URLDataSource::GotDataCallback&
                             got_data_callback) override;
diff --git a/chrome/browser/ui/webui/devtools_ui_data_source.cc b/chrome/browser/ui/webui/devtools_ui_data_source.cc
index 53b632a..4e70373 100644
--- a/chrome/browser/ui/webui/devtools_ui_data_source.cc
+++ b/chrome/browser/ui/webui/devtools_ui_data_source.cc
@@ -95,10 +95,12 @@
 }
 
 void DevToolsDataSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const GotDataCallback& callback) {
   // Serve request to devtools://bundled/ from local bundle.
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   std::string bundled_path_prefix(chrome::kChromeUIDevToolsBundledPath);
   bundled_path_prefix += "/";
   if (base::StartsWith(path, bundled_path_prefix,
diff --git a/chrome/browser/ui/webui/devtools_ui_data_source.h b/chrome/browser/ui/webui/devtools_ui_data_source.h
index 6ee0377..04a5c00 100644
--- a/chrome/browser/ui/webui/devtools_ui_data_source.h
+++ b/chrome/browser/ui/webui/devtools_ui_data_source.h
@@ -39,7 +39,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
 
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const content::WebContents::Getter& wc_getter,
                         const GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/devtools_ui_data_source_unittest.cc b/chrome/browser/ui/webui/devtools_ui_data_source_unittest.cc
index f756944b..07ccf1d0 100644
--- a/chrome/browser/ui/webui/devtools_ui_data_source_unittest.cc
+++ b/chrome/browser/ui/webui/devtools_ui_data_source_unittest.cc
@@ -80,13 +80,14 @@
 
   std::string data() const { return data_; }
 
+  // TODO(crbug/1009127): pass in GURL instead.
   void StartRequest(const std::string& path) {
     data_received_ = false;
     data_.clear();
     std::string trimmed_path = path.substr(1);
     content::WebContents::Getter wc_getter;
     data_source()->StartDataRequest(
-        trimmed_path, std::move(wc_getter),
+        GURL("chrome://any-host/" + trimmed_path), std::move(wc_getter),
         base::BindRepeating(&DevToolsUIDataSourceTest::OnDataReceived,
                             base::Unretained(this)));
   }
diff --git a/chrome/browser/ui/webui/downloads/downloads_ui.cc b/chrome/browser/ui/webui/downloads/downloads_ui.cc
index 66fba9f..07df435 100644
--- a/chrome/browser/ui/webui/downloads/downloads_ui.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_ui.cc
@@ -44,7 +44,6 @@
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/resources/grit/webui_resources.h"
 
 using content::BrowserContext;
 using content::DownloadManager;
@@ -159,9 +158,6 @@
     source->AddResourcePath(kDownloadsResources[i].name,
                             kDownloadsResources[i].value);
   }
-  // Add the subpage loader, to load subpages in non-optimized builds.
-  source->AddResourcePath("subpage_loader.html", IDR_WEBUI_HTML_SUBPAGE_LOADER);
-  source->AddResourcePath("subpage_loader.js", IDR_WEBUI_JS_SUBPAGE_LOADER);
   source->SetDefaultResource(IDR_DOWNLOADS_DOWNLOADS_HTML);
 #endif
 
diff --git a/chrome/browser/ui/webui/extensions/extension_icon_source.cc b/chrome/browser/ui/webui/extensions/extension_icon_source.cc
index ea745a1b..ee1b12dd 100644
--- a/chrome/browser/ui/webui/extensions/extension_icon_source.cc
+++ b/chrome/browser/ui/webui/extensions/extension_icon_source.cc
@@ -113,9 +113,10 @@
 }
 
 void ExtensionIconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   // This is where everything gets started. First, parse the request and make
   // the request data available for later.
   static int next_id = 0;
diff --git a/chrome/browser/ui/webui/extensions/extension_icon_source.h b/chrome/browser/ui/webui/extensions/extension_icon_source.h
index 0737257..c3e1d66 100644
--- a/chrome/browser/ui/webui/extensions/extension_icon_source.h
+++ b/chrome/browser/ui/webui/extensions/extension_icon_source.h
@@ -71,7 +71,7 @@
   std::string GetSource() override;
   std::string GetMimeType(const std::string&) override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   bool AllowCaching() override;
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
index 835595a5..6c73a39 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
@@ -436,7 +436,7 @@
 }
 
 void ExtensionsInternalsSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   std::string json = WriteToString();
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.h b/chrome/browser/ui/webui/extensions/extensions_internals_source.h
index 0b2840ae..ca3121c 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.h
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.h
@@ -21,7 +21,7 @@
   std::string GetSource() override;
   std::string GetMimeType(const std::string& path) override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/extensions/extensions_ui.cc b/chrome/browser/ui/webui/extensions/extensions_ui.cc
index 18b00d53..cb994e2 100644
--- a/chrome/browser/ui/webui/extensions/extensions_ui.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_ui.cc
@@ -36,7 +36,6 @@
 #include "extensions/common/extension_urls.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/resources/grit/webui_resources.h"
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
@@ -304,9 +303,6 @@
     source->AddResourcePath(kExtensionsResources[i].name,
                             kExtensionsResources[i].value);
   }
-  // Add the subpage loader, to load subpages in non-optimized builds.
-  source->AddResourcePath("subpage_loader.html", IDR_WEBUI_HTML_SUBPAGE_LOADER);
-  source->AddResourcePath("subpage_loader.js", IDR_WEBUI_JS_SUBPAGE_LOADER);
   source->SetDefaultResource(IDR_EXTENSIONS_EXTENSIONS_HTML);
 #endif
 
diff --git a/chrome/browser/ui/webui/favicon_source.cc b/chrome/browser/ui/webui/favicon_source.cc
index 498c9de..d62e7ae 100644
--- a/chrome/browser/ui/webui/favicon_source.cc
+++ b/chrome/browser/ui/webui/favicon_source.cc
@@ -75,9 +75,10 @@
 }
 
 void FaviconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   favicon::FaviconService* favicon_service =
       FaviconServiceFactory::GetForProfile(profile_,
                                            ServiceAccessType::EXPLICIT_ACCESS);
diff --git a/chrome/browser/ui/webui/favicon_source.h b/chrome/browser/ui/webui/favicon_source.h
index 35e52ba..5e419abf 100644
--- a/chrome/browser/ui/webui/favicon_source.h
+++ b/chrome/browser/ui/webui/favicon_source.h
@@ -44,7 +44,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string&) override;
diff --git a/chrome/browser/ui/webui/favicon_source_unittest.cc b/chrome/browser/ui/webui/favicon_source_unittest.cc
index 95b83fc..43a30679 100644
--- a/chrome/browser/ui/webui/favicon_source_unittest.cc
+++ b/chrome/browser/ui/webui/favicon_source_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/strings/strcat.h"
 #include "base/test/bind_test_util.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/favicon/history_ui_favicon_request_handler_factory.h"
@@ -34,6 +35,7 @@
 namespace {
 
 const int kDummyTaskId = 1;
+const char kDummyPrefix[] = "chrome://any-host/";
 
 }  // namespace
 
@@ -165,14 +167,14 @@
 TEST_F(FaviconSourceTestWithLegacyFormat, DarkDefault) {
   SetDarkMode(true);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
-  source()->StartDataRequest(std::string(), test_web_contents_getter_,
+  source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
                              base::BindRepeating(&Noop));
 }
 
 TEST_F(FaviconSourceTestWithLegacyFormat, LightDefault) {
   SetDarkMode(false);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
-  source()->StartDataRequest(std::string(), test_web_contents_getter_,
+  source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
                              base::BindRepeating(&Noop));
 }
 
@@ -185,22 +187,22 @@
               GetRawFaviconForPageURL)
       .Times(0);
 
-  source()->StartDataRequest("size/16@1x/https://www.google.com",
-                             test_web_contents_getter_,
-                             base::BindRepeating(&Noop));
+  source()->StartDataRequest(
+      GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
+      test_web_contents_getter_, base::BindRepeating(&Noop));
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format, DarkDefault) {
   SetDarkMode(true);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
-  source()->StartDataRequest(std::string(), test_web_contents_getter_,
+  source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
                              base::BindRepeating(&Noop));
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format, LightDefault) {
   SetDarkMode(false);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
-  source()->StartDataRequest(std::string(), test_web_contents_getter_,
+  source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
                              base::BindRepeating(&Noop));
 }
 
@@ -214,8 +216,10 @@
       .Times(0);
 
   source()->StartDataRequest(
-      "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
-      "com&allow_google_server_fallback=0",
+      GURL(base::StrCat(
+          {kDummyPrefix,
+           "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
+           "com&allow_google_server_fallback=0"})),
       test_web_contents_getter_, base::BindRepeating(&Noop));
 }
 
@@ -229,8 +233,10 @@
       .Times(0);
 
   source()->StartDataRequest(
-      "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
-      "com&allow_google_server_fallback=1",
+      GURL(base::StrCat(
+          {kDummyPrefix,
+           "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
+           "com&allow_google_server_fallback=1"})),
       test_web_contents_getter_, base::BindRepeating(&Noop));
 }
 
@@ -246,7 +252,9 @@
       .Times(1);
 
   source()->StartDataRequest(
-      "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
-      "com&allow_google_server_fallback=1",
+      GURL(base::StrCat(
+          {kDummyPrefix,
+           "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
+           "com&allow_google_server_fallback=1"})),
       test_web_contents_getter_, base::BindRepeating(&Noop));
 }
diff --git a/chrome/browser/ui/webui/fileicon_source.cc b/chrome/browser/ui/webui/fileicon_source.cc
index a9e963a..909745a 100644
--- a/chrome/browser/ui/webui/fileicon_source.cc
+++ b/chrome/browser/ui/webui/fileicon_source.cc
@@ -113,12 +113,14 @@
 }
 
 void FileIconSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   base::FilePath file_path;
   IconLoader::IconSize icon_size = IconLoader::NORMAL;
   float scale_factor = 1.0f;
+  // TODO(crbug/1009127): Make ParseQueryParams take GURL.
   ParseQueryParams(path, &file_path, &scale_factor, &icon_size);
   FetchFileIcon(file_path, scale_factor, icon_size, callback);
 }
diff --git a/chrome/browser/ui/webui/fileicon_source.h b/chrome/browser/ui/webui/fileicon_source.h
index d607cf1..c7599d7 100644
--- a/chrome/browser/ui/webui/fileicon_source.h
+++ b/chrome/browser/ui/webui/fileicon_source.h
@@ -27,7 +27,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string&) override;
diff --git a/chrome/browser/ui/webui/fileicon_source_unittest.cc b/chrome/browser/ui/webui/fileicon_source_unittest.cc
index b4da377..f56d2b6 100644
--- a/chrome/browser/ui/webui/fileicon_source_unittest.cc
+++ b/chrome/browser/ui/webui/fileicon_source_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ref_counted_memory.h"
 #include "base/stl_util.h"
+#include "base/strings/strcat.h"
 #include "build/build_config.h"
 #include "chrome/browser/icon_manager.h"
 #include "chrome/browser/profiles/profile.h"
@@ -119,7 +120,9 @@
                     base::FilePath(kBasicExpectations[i].unescaped_path),
                     kBasicExpectations[i].scale_factor,
                     kBasicExpectations[i].size, CallbackIsNull()));
-    source->StartDataRequest(kBasicExpectations[i].request_path,
-                             content::WebContents::Getter(), callback);
+    source->StartDataRequest(
+        GURL(base::StrCat(
+            {"chrome://any-host/", kBasicExpectations[i].request_path})),
+        content::WebContents::Getter(), callback);
   }
 }
diff --git a/chrome/browser/ui/webui/history_ui.cc b/chrome/browser/ui/webui/history_ui.cc
index 4826cb8..6c46321 100644
--- a/chrome/browser/ui/webui/history_ui.cc
+++ b/chrome/browser/ui/webui/history_ui.cc
@@ -113,11 +113,10 @@
 
   source->AddBoolean(kIsUserSignedInKey, IsUserSignedIn(profile));
 
-  struct UncompressedResource {
+  const struct {
     const char* path;
     int idr;
-  };
-  const UncompressedResource uncompressed_resources[] = {
+  } unbundled_resources[] = {
     {"constants.html", IDR_HISTORY_CONSTANTS_HTML},
     {"constants.js", IDR_HISTORY_CONSTANTS_JS},
     {"history.js", IDR_HISTORY_HISTORY_JS},
@@ -154,7 +153,7 @@
 #endif
   };
 
-  for (const auto& resource : uncompressed_resources) {
+  for (const auto& resource : unbundled_resources) {
     source->AddResourcePath(resource.path, resource.idr);
   }
 
diff --git a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
index 239071f4..52aab72 100644
--- a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
+++ b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
@@ -93,7 +93,7 @@
   std::string GetContentSecurityPolicyStyleSrc() override;
   std::string GetContentSecurityPolicyImgSrc() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
@@ -468,9 +468,13 @@
 }
 
 void InterstitialHTMLSource::StartDataRequest(
-    const std::string& path,
+    const GURL& request_url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): Simplify usages of |path| since |request_url| is
+  // available.
+  const std::string path =
+      content::URLDataSource::URLToRequestPath(request_url);
   content::WebContents* web_contents = wc_getter.Run();
   if (!web_contents) {
     // When browser-side navigation is enabled, web_contents can be null if
diff --git a/chrome/browser/ui/webui/ntp/new_tab_ui.cc b/chrome/browser/ui/webui/ntp/new_tab_ui.cc
index 1743ef94..50eff0a 100644
--- a/chrome/browser/ui/webui/ntp/new_tab_ui.cc
+++ b/chrome/browser/ui/webui/ntp/new_tab_ui.cc
@@ -152,11 +152,12 @@
 }
 
 void NewTabUI::NewTabHTMLSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   if (!path.empty() && path[0] != '#') {
     // A path under new-tab was requested; it's likely a bad relative
     // URL from the new tab page, but in any case it's an error.
diff --git a/chrome/browser/ui/webui/ntp/new_tab_ui.h b/chrome/browser/ui/webui/ntp/new_tab_ui.h
index 01d668a..66f6c4d 100644
--- a/chrome/browser/ui/webui/ntp/new_tab_ui.h
+++ b/chrome/browser/ui/webui/ntp/new_tab_ui.h
@@ -56,7 +56,7 @@
     // content::URLDataSource implementation.
     std::string GetSource() override;
     void StartDataRequest(
-        const std::string& path,
+        const GURL& url,
         const content::WebContents::Getter& wc_getter,
         const content::URLDataSource::GotDataCallback& callback) override;
     std::string GetMimeType(const std::string&) override;
diff --git a/chrome/browser/ui/webui/prefs_internals_source.cc b/chrome/browser/ui/webui/prefs_internals_source.cc
index 152af75..d4ba24aa 100644
--- a/chrome/browser/ui/webui/prefs_internals_source.cc
+++ b/chrome/browser/ui/webui/prefs_internals_source.cc
@@ -28,7 +28,7 @@
 }
 
 void PrefsInternalsSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/ui/webui/prefs_internals_source.h b/chrome/browser/ui/webui/prefs_internals_source.h
index 9223026..8aad42fa 100644
--- a/chrome/browser/ui/webui/prefs_internals_source.h
+++ b/chrome/browser/ui/webui/prefs_internals_source.h
@@ -20,7 +20,7 @@
   std::string GetSource() override;
   std::string GetMimeType(const std::string& path) override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index 619e2bd..1ccaf62 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -57,7 +57,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/resources/grit/webui_resources.h"
 #include "ui/web_dialogs/web_dialog_delegate.h"
 #include "ui/web_dialogs/web_dialog_ui.h"
 
@@ -400,9 +399,6 @@
     source->AddResourcePath(kPrintPreviewResources[i].name,
                             kPrintPreviewResources[i].value);
   }
-  // Add the subpage loader, to load subpages in non-optimized builds.
-  source->AddResourcePath("subpage_loader.html", IDR_WEBUI_HTML_SUBPAGE_LOADER);
-  source->AddResourcePath("subpage_loader.js", IDR_WEBUI_JS_SUBPAGE_LOADER);
   source->SetDefaultResource(IDR_PRINT_PREVIEW_HTML);
 #endif
   SetupPrintPreviewPlugin(source);
diff --git a/chrome/browser/ui/webui/test_data_source.cc b/chrome/browser/ui/webui/test_data_source.cc
index 1fbe673..ed53f337 100644
--- a/chrome/browser/ui/webui/test_data_source.cc
+++ b/chrome/browser/ui/webui/test_data_source.cc
@@ -43,9 +43,11 @@
 }
 
 void TestDataSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
+  // TODO(crbug/1009127): Make ReadFile take GURL().
   if (path == "strings.m.js") {
     std::string output = "import {loadTimeData} from ";
     output.append("'chrome://resources/js/load_time_data.m.js';\n");
diff --git a/chrome/browser/ui/webui/test_data_source.h b/chrome/browser/ui/webui/test_data_source.h
index e4c6ea6..b34e8808 100644
--- a/chrome/browser/ui/webui/test_data_source.h
+++ b/chrome/browser/ui/webui/test_data_source.h
@@ -20,7 +20,7 @@
 
  private:
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
 
diff --git a/chrome/browser/ui/webui/theme_source.cc b/chrome/browser/ui/webui/theme_source.cc
index 80b40ae..31b07bc 100644
--- a/chrome/browser/ui/webui/theme_source.cc
+++ b/chrome/browser/ui/webui/theme_source.cc
@@ -82,9 +82,11 @@
 }
 
 void ThemeSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): Simplify usages of |path| since |url| is available.
+  const std::string path = content::URLDataSource::URLToRequestPath(url);
   // Default scale factor if not specified.
   float scale = 1.0f;
   // All frames by default if not specified.
diff --git a/chrome/browser/ui/webui/theme_source.h b/chrome/browser/ui/webui/theme_source.h
index 4ebcc70..b862455 100644
--- a/chrome/browser/ui/webui/theme_source.h
+++ b/chrome/browser/ui/webui/theme_source.h
@@ -23,7 +23,7 @@
   // content::URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/chrome/browser/ui/webui/theme_source_unittest.cc b/chrome/browser/ui/webui/theme_source_unittest.cc
index a17b7b4..489540a1 100644
--- a/chrome/browser/ui/webui/theme_source_unittest.cc
+++ b/chrome/browser/ui/webui/theme_source_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/run_loop.h"
+#include "base/strings/strcat.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/theme_source.h"
 #include "chrome/common/url_constants.h"
@@ -24,8 +25,10 @@
   size_t result_data_size() const { return result_data_size_; }
 
   void StartDataRequest(const std::string& source) {
-    theme_source()->StartDataRequest(source, content::WebContents::Getter(),
-                                     callback_);
+    theme_source()->StartDataRequest(
+        GURL(base::StrCat({content::kChromeUIScheme, "://",
+                           chrome::kChromeUIThemeHost, "/", source})),
+        content::WebContents::Getter(), callback_);
   }
 
   size_t result_data_size_;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index cd9f5de..b76ef10 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1271,6 +1271,7 @@
       "../browser/ui/toolbar/browser_actions_bar_browsertest.h",
       "../browser/ui/update_chrome_dialog_browsertest.cc",
       "../browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc",
+      "../browser/ui/views/autofill/payments/verify_pending_dialog_view_browsertest.cc",
       "../browser/ui/views/bookmarks/bookmark_bar_view_browsertest.cc",
       "../browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc",
       "../browser/ui/views/chrome_cleaner_reboot_dialog_browsertest_win.cc",
@@ -3098,6 +3099,7 @@
     "../browser/page_load_metrics/observers/previews_ukm_observer_unittest.cc",
     "../browser/page_load_metrics/observers/protocol_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/scheme_page_load_metrics_observer_unittest.cc",
+    "../browser/page_load_metrics/observers/security_state_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/service_worker_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/signed_exchange_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/subresource_loading_page_load_metrics_observer_unittest.cc",
@@ -5607,7 +5609,6 @@
     # Runtime dependencies
     data_deps = [
       "//ppapi:ppapi_tests",
-      "//testing/buildbot/filters:interactive_ui_tests_filters",
       "//third_party/mesa_headers",
     ]
 
diff --git a/chrome/test/base/browser_tests_main_chromeos.cc b/chrome/test/base/browser_tests_main_chromeos.cc
index 5426703..55106b29 100644
--- a/chrome/test/base/browser_tests_main_chromeos.cc
+++ b/chrome/test/base/browser_tests_main_chromeos.cc
@@ -30,10 +30,8 @@
  public:
   int RunTestSuite(int argc, char** argv) override {
     BrowserTestSuiteChromeOS test_suite(argc, argv);
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals.
     test_suite.DisableCheckForLeakedGlobals();
-    test_suite.DisableCheckForThreadPriorityAtTestEnd();
     return test_suite.Run();
   }
 };
diff --git a/chrome/test/base/chrome_test_launcher.cc b/chrome/test/base/chrome_test_launcher.cc
index 346c4b5a..5e3c3d32 100644
--- a/chrome/test/base/chrome_test_launcher.cc
+++ b/chrome/test/base/chrome_test_launcher.cc
@@ -77,10 +77,8 @@
 
 int ChromeTestSuiteRunner::RunTestSuite(int argc, char** argv) {
   ChromeTestSuite test_suite(argc, argv);
-  // Browser tests are expected not to tear-down various globals and may
-  // complete with the thread priority being above NORMAL.
+  // Browser tests are expected not to tear-down various globals.
   test_suite.DisableCheckForLeakedGlobals();
-  test_suite.DisableCheckForThreadPriorityAtTestEnd();
 #if defined(OS_ANDROID)
   // Android browser tests run child processes as threads instead.
   content::ContentTestSuiteBase::RegisterInProcessThreads();
diff --git a/chrome/test/base/interactive_ui_tests_main.cc b/chrome/test/base/interactive_ui_tests_main.cc
index abc8ab2..051d4b3 100644
--- a/chrome/test/base/interactive_ui_tests_main.cc
+++ b/chrome/test/base/interactive_ui_tests_main.cc
@@ -41,10 +41,8 @@
  protected:
   // ChromeTestSuite overrides:
   void Initialize() override {
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals.
     base::TestSuite::DisableCheckForLeakedGlobals();
-    base::TestSuite::DisableCheckForThreadPriorityAtTestEnd();
 
     ChromeTestSuite::Initialize();
 
diff --git a/chrome/test/base/js2gtest.js b/chrome/test/base/js2gtest.js
index 668d04b1..93772a9 100644
--- a/chrome/test/base/js2gtest.js
+++ b/chrome/test/base/js2gtest.js
@@ -385,7 +385,6 @@
       this[testFixture].prototype.isAsync + ',\n          ';
   var testShouldFail = this[testFixture].prototype.testShouldFail;
   var testPredicate = testShouldFail ? 'ASSERT_FALSE' : 'ASSERT_TRUE';
-  var loaderFile = this[testFixture].prototype.loaderFile;
   var webuiHost = this[testFixture].prototype.webuiHost;
   var extraLibraries = genIncludes.concat(
       this[testFixture].prototype.extraLibraries.map(includeFileToPath),
@@ -526,10 +525,6 @@
     output(`
   set_use_mojo_lite_bindings();`);
   }
-  if (loaderFile) {
-    output(`
-  set_loader_file("${loaderFile}");`);
-  }
   if (webuiHost) {
     output(`
   set_webui_host("${webuiHost}");`);
diff --git a/chrome/test/base/mojo_web_ui_browser_test.cc b/chrome/test/base/mojo_web_ui_browser_test.cc
index b2e3657..1cc713b 100644
--- a/chrome/test/base/mojo_web_ui_browser_test.cc
+++ b/chrome/test/base/mojo_web_ui_browser_test.cc
@@ -110,22 +110,9 @@
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   if (use_mojo_lite_bindings_) {
-    base::string16 file_content =
-        l10n_util::GetStringUTF16(IDR_WEB_UI_TEST_MOJO_LITE_JS);
-    // The generated script assumes that mojo has already been imported by the
-    // page. This is not the case when native HTML imports are disabled. If
-    // the polyfill is in place, wait for HTMLImports.whenReady().
-    base::string16 wrapped_file_content =
-        base::UTF8ToUTF16(
-            "const promise = typeof HTMLImports === 'undefined' ? "
-            "Promise.resolve() : "
-            "new Promise(resolve => { "
-            "HTMLImports.whenReady(resolve); "
-            "}); "
-            "promise.then(() => {") +
-        file_content + base::UTF8ToUTF16("});");
     web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
-        wrapped_file_content, base::NullCallback());
+        l10n_util::GetStringUTF16(IDR_WEB_UI_TEST_MOJO_LITE_JS),
+        base::NullCallback());
   } else {
     web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
         l10n_util::GetStringUTF16(IDR_WEB_UI_TEST_MOJO_JS),
diff --git a/chrome/test/base/web_ui_browser_test.cc b/chrome/test/base/web_ui_browser_test.cc
index 611dd7d..c8ff3d50 100644
--- a/chrome/test/base/web_ui_browser_test.cc
+++ b/chrome/test/base/web_ui_browser_test.cc
@@ -271,18 +271,7 @@
       web_contents, this, preload_test_fixture_, preload_test_name_);
   content::TestNavigationObserver navigation_observer(web_contents);
 
-  GURL browse_to_final(browse_to);
-  std::string path = browse_to.path();
-  if (!loader_file_.empty() && path.length() > 1) {
-    GURL::Replacements replace_url;
-    replace_url.SetPathStr(loader_file_);
-    // Remove the leading '/', and use file=<rest of the path> as the query.
-    std::string query = "file=" + path.substr(1);
-    replace_url.SetQueryStr(query);
-    browse_to_final = browse_to_final.ReplaceComponents(replace_url);
-  }
-
-  NavigateParams params(browser(), browse_to_final, ui::PAGE_TRANSITION_TYPED);
+  NavigateParams params(browser(), browse_to, ui::PAGE_TRANSITION_TYPED);
   params.disposition = WindowOpenDisposition::CURRENT_TAB;
 
   Navigate(&params);
@@ -373,10 +362,6 @@
   preload_test_name_ = preload_test_name;
 }
 
-void BaseWebUIBrowserTest::set_loader_file(const std::string& loader_file) {
-  loader_file_ = loader_file;
-}
-
 void BaseWebUIBrowserTest::set_webui_host(const std::string& webui_host) {
   test_factory_->set_webui_host(webui_host);
 }
@@ -396,7 +381,7 @@
   std::string GetSource() override { return "dummyurl"; }
 
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override {
     std::string dummy_html = "<html><body>Dummy</body></html>";
diff --git a/chrome/test/base/web_ui_browser_test.h b/chrome/test/base/web_ui_browser_test.h
index cd7f0805..c038a47 100644
--- a/chrome/test/base/web_ui_browser_test.h
+++ b/chrome/test/base/web_ui_browser_test.h
@@ -109,7 +109,6 @@
   void set_preload_test_fixture(const std::string& preload_test_fixture);
   void set_preload_test_name(const std::string& preload_test_name);
 
-  void set_loader_file(const std::string& loader_file);
   void set_webui_host(const std::string& webui_host);
 
   // Enable command line flags for test.
@@ -167,11 +166,6 @@
   std::string preload_test_fixture_;
   std::string preload_test_name_;
 
-  // When this is non-empty, this is the file to be substituted for the file in
-  // browsePreload, to load the HTML Imports polyfill and then load the real
-  // file after the polyfill is ready.
-  std::string loader_file_;
-
   // When this is non-NULL, this is The WebUI instance used for testing.
   // Otherwise the selected tab's web_ui is used.
   content::WebUI* override_selected_web_ui_;
diff --git a/chrome/test/chromedriver/BUILD.gn b/chrome/test/chromedriver/BUILD.gn
index d74f331..bd06651c 100644
--- a/chrome/test/chromedriver/BUILD.gn
+++ b/chrome/test/chromedriver/BUILD.gn
@@ -234,6 +234,7 @@
     "command_listener_proxy.h",
     "commands.cc",
     "commands.h",
+    "constants/version.h",
     "devtools_events_logger.cc",
     "devtools_events_logger.h",
     "element_commands.cc",
@@ -258,8 +259,6 @@
     "session_thread_map.h",
     "util.cc",
     "util.h",
-    "version.cc",
-    "version.h",
     "webauthn_commands.cc",
     "webauthn_commands.h",
     "window_commands.cc",
@@ -282,8 +281,10 @@
     ":automation_client_lib",
     "//base",
     "//base/third_party/dynamic_annotations",
+    "//build:branding_buildflags",
     "//chrome/common:constants",
     "//chrome/common:version_header",
+    "//chrome/test/chromedriver/constants:version_header",
     "//components/crx_file",
     "//components/version_info:generate_version_info",
     "//crypto",
diff --git a/chrome/test/chromedriver/capabilities.cc b/chrome/test/chromedriver/capabilities.cc
index a65175b..23dcd77 100644
--- a/chrome/test/chromedriver/capabilities.cc
+++ b/chrome/test/chromedriver/capabilities.cc
@@ -23,6 +23,8 @@
 #include "chrome/test/chromedriver/chrome/mobile_device.h"
 #include "chrome/test/chromedriver/chrome/page_load_strategy.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/chrome/version.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/logging.h"
 #include "chrome/test/chromedriver/session.h"
 #include "chrome/test/chromedriver/util.h"
@@ -100,7 +102,8 @@
     const char* option_name,
     const base::Value& option,
     Capabilities* capabilities) {
-  LOG(WARNING) << "Deprecated chrome option is ignored: " << option_name;
+  LOG(WARNING) << "Deprecated " << base::ToLowerASCII(kBrowserShortName)
+               << " option is ignored: " << option_name;
   return Status(kOk);
 }
 
@@ -584,8 +587,11 @@
   for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
        it.Advance()) {
     if (parser_map.find(it.key()) == parser_map.end()) {
-      return Status(kInvalidArgument,
-                    "unrecognized chrome option: " + it.key());
+      return Status(
+          kInvalidArgument,
+          base::StringPrintf("unrecognized %s option: %s",
+                             base::ToLowerASCII(kBrowserShortName).c_str(),
+                             it.key().c_str()));
     }
     Status status = parser_map[it.key()].Run(it.value(), capabilities);
     if (status.IsError())
@@ -615,6 +621,14 @@
 }
 }  // namespace
 
+bool GetChromeOptionsDictionary(const base::DictionaryValue& params,
+                                const base::DictionaryValue** out) {
+  if (params.GetDictionary(kChromeDriverOptionsKeyPrefixed, out)) {
+    return true;
+  }
+  return params.GetDictionary(kChromeDriverOptionsKey, out);
+}
+
 Switches::Switches() {}
 
 Switches::Switches(const Switches& other) = default;
@@ -777,29 +791,35 @@
       base::BindRepeating(&ParseUnhandledPromptBehavior);
 
   // ChromeDriver specific capabilities.
-  // goog:chromeOptions is the current spec conformance, but chromeOptions is
+  // Vendor-prefixed is the current spec conformance, but unprefixed is
   // still supported in legacy mode.
   if (w3c_compliant ||
-      desired_caps.GetDictionary("goog:chromeOptions", nullptr)) {
-    parser_map["goog:chromeOptions"] = base::BindRepeating(&ParseChromeOptions);
+      desired_caps.GetDictionary(kChromeDriverOptionsKeyPrefixed, nullptr)) {
+    parser_map[kChromeDriverOptionsKeyPrefixed] =
+        base::BindRepeating(&ParseChromeOptions);
   } else {
-    parser_map["chromeOptions"] = base::BindRepeating(&ParseChromeOptions);
+    parser_map[kChromeDriverOptionsKey] =
+        base::BindRepeating(&ParseChromeOptions);
   }
   // se:options.loggingPrefs and goog:loggingPrefs is spec-compliant name,
   // but loggingPrefs is still supported in legacy mode.
+  const std::string prefixedLoggingPrefsKey =
+      base::StringPrintf("%s:loggingPrefs", kChromeDriverCompanyPrefix);
   if (desired_caps.GetDictionary("se:options.loggingPrefs", nullptr)) {
     parser_map["se:options"] = base::BindRepeating(&ParseSeleniumOptions);
   } else if (w3c_compliant ||
-             desired_caps.GetDictionary("goog:loggingPrefs", nullptr)) {
-    parser_map["goog:loggingPrefs"] = base::BindRepeating(&ParseLoggingPrefs);
+             desired_caps.GetDictionary(prefixedLoggingPrefsKey, nullptr)) {
+    parser_map[prefixedLoggingPrefsKey] =
+        base::BindRepeating(&ParseLoggingPrefs);
   } else {
     parser_map["loggingPrefs"] = base::BindRepeating(&ParseLoggingPrefs);
   }
   // Network emulation requires device mode, which is only enabled when
   // mobile emulation is on.
-  if (desired_caps.GetDictionary("goog:chromeOptions.mobileEmulation",
-                                 nullptr) ||
-      desired_caps.GetDictionary("chromeOptions.mobileEmulation", nullptr)) {
+
+  const base::DictionaryValue* chrome_options = nullptr;
+  if (GetChromeOptionsDictionary(desired_caps, &chrome_options) &&
+      chrome_options->GetDictionary("mobileEmulation", nullptr)) {
     parser_map["networkConnectionEnabled"] =
         base::BindRepeating(&ParseBoolean, &network_emulation_enabled);
   }
@@ -828,9 +848,8 @@
   LoggingPrefs::const_iterator iter = logging_prefs.find(
       WebDriverLog::kPerformanceType);
   if (iter == logging_prefs.end() || iter->second == Log::kOff) {
-    const base::DictionaryValue* chrome_options = NULL;
-    if ((desired_caps.GetDictionary("goog:chromeOptions", &chrome_options) ||
-         desired_caps.GetDictionary("chromeOptions", &chrome_options)) &&
+    const base::DictionaryValue* chrome_options = nullptr;
+    if (GetChromeOptionsDictionary(desired_caps, &chrome_options) &&
         chrome_options->HasKey("perfLoggingPrefs")) {
       return Status(kInvalidArgument,
                     "perfLoggingPrefs specified, "
@@ -841,9 +860,8 @@
       WebDriverLog::kDevToolsType);
   if (dt_events_logging_iter == logging_prefs.end()
       || dt_events_logging_iter->second == Log::kOff) {
-    const base::DictionaryValue* chrome_options = NULL;
-    if ((desired_caps.GetDictionary("goog:chromeOptions", &chrome_options) ||
-         desired_caps.GetDictionary("chromeOptions", &chrome_options)) &&
+    const base::DictionaryValue* chrome_options = nullptr;
+    if (GetChromeOptionsDictionary(desired_caps, &chrome_options) &&
         chrome_options->HasKey("devToolsEventsToLog")) {
       return Status(kInvalidArgument,
                     "devToolsEventsToLog specified, "
diff --git a/chrome/test/chromedriver/capabilities.h b/chrome/test/chromedriver/capabilities.h
index 14e0b2a..38e31e1 100644
--- a/chrome/test/chromedriver/capabilities.h
+++ b/chrome/test/chromedriver/capabilities.h
@@ -188,4 +188,7 @@
   bool use_automation_extension;
 };
 
+bool GetChromeOptionsDictionary(const base::DictionaryValue& params,
+                                const base::DictionaryValue** out);
+
 #endif  // CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_
diff --git a/chrome/test/chromedriver/chrome/DEPS b/chrome/test/chromedriver/chrome/DEPS
index efb11fcb..d0dbe19 100644
--- a/chrome/test/chromedriver/chrome/DEPS
+++ b/chrome/test/chromedriver/chrome/DEPS
@@ -3,6 +3,7 @@
   "-chrome/test/chromedriver",
 
   "+chrome/test/chromedriver/chrome",
+  "+chrome/test/chromedriver/constants",
   "+chrome/test/chromedriver/extension",
   "+chrome/test/chromedriver/js",
   "+chrome/test/chromedriver/net",
diff --git a/chrome/test/chromedriver/chrome/adb_impl.cc b/chrome/test/chromedriver/chrome/adb_impl.cc
index 9fffca3..5a2b501 100644
--- a/chrome/test/chromedriver/chrome/adb_impl.cc
+++ b/chrome/test/chromedriver/chrome/adb_impl.cc
@@ -23,6 +23,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/time/time.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/net/adb_client_socket.h"
 #include "net/base/net_errors.h"
 
@@ -155,12 +156,13 @@
   if (*local_port_output == 0) {
     return Status(
         kUnknownError,
-        "Failed to forward ports to device " + device_serial +
-            ". No port chosen: " + response +
-            ". Perhaps your adb version is out of date. "
-            "ChromeDriver 2.39 and newer require adb version 1.0.38 or newer. "
-            "Run 'adb version' in your terminal of the host device to find "
-            "your version of adb.");
+        base::StringPrintf(
+            "Failed to forward ports to device %s. No port chosen: %s. Perhaps "
+            "your adb version is out of date. %s 2.39 and newer require adb "
+            "version 1.0.38 or newer. Run 'adb version' in your terminal of "
+            "the host device to find your version of adb.",
+            device_serial.c_str(), response.c_str(),
+            kChromeDriverProductFullName));
   }
 
   return Status(kOk);
diff --git a/chrome/test/chromedriver/chrome/browser_info.cc b/chrome/test/chromedriver/chrome/browser_info.cc
index b1cb0a9..8db6e42 100644
--- a/chrome/test/chromedriver/chrome/browser_info.cc
+++ b/chrome/test/chromedriver/chrome/browser_info.cc
@@ -12,17 +12,9 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/values.h"
-
-namespace {
-
-const char kVersionPrefix[] = "Chrome/";
-const size_t kVersionPrefixLen = sizeof(kVersionPrefix) - 1;
-
-const char kHeadlessVersionPrefix[] = "HeadlessChrome/";
-const size_t kHeadlessVersionPrefixLen = sizeof(kHeadlessVersionPrefix) - 1;
-
-}  // namespace
+#include "chrome/test/chromedriver/constants/version.h"
 
 BrowserInfo::BrowserInfo()
     : major_version(0),
@@ -79,16 +71,21 @@
     return Status(kOk);
   }
 
+  static const std::string kVersionPrefix =
+      std::string(kUserAgentProductName) + "/";
+  static const std::string kHeadlessVersionPrefix =
+      std::string(kHeadlessUserAgentProductName) + "/";
+
   int build_no = 0;
   if (base::StartsWith(browser_string, kVersionPrefix,
                        base::CompareCase::SENSITIVE) ||
       base::StartsWith(browser_string, kHeadlessVersionPrefix,
                        base::CompareCase::SENSITIVE)) {
-    std::string version = browser_string.substr(kVersionPrefixLen);
+    std::string version = browser_string.substr(kVersionPrefix.length());
     bool headless = false;
     if (base::StartsWith(browser_string, kHeadlessVersionPrefix,
                          base::CompareCase::SENSITIVE)) {
-      version = browser_string.substr(kHeadlessVersionPrefixLen);
+      version = browser_string.substr(kHeadlessVersionPrefix.length());
       headless = true;
     }
 
@@ -99,10 +96,11 @@
 
     if (build_no != 0) {
       if (headless) {
-        browser_info->browser_name = "headless chrome";
+        browser_info->browser_name =
+            base::StringPrintf("headless %s", kBrowserCapabilityName);
         browser_info->is_headless = true;
       } else {
-        browser_info->browser_name = "chrome";
+        browser_info->browser_name = kBrowserCapabilityName;
       }
       browser_info->browser_version = version;
       browser_info->build_no = build_no;
@@ -116,7 +114,7 @@
     if (pos != std::string::npos) {
       browser_info->browser_name = "webview";
       browser_info->browser_version =
-          browser_string.substr(pos + kVersionPrefixLen);
+          browser_string.substr(pos + kVersionPrefix.length());
       browser_info->is_android = true;
       return ParseBrowserVersionString(browser_info->browser_version,
                                        &browser_info->major_version, &build_no);
@@ -125,7 +123,8 @@
   }
 
   return Status(kUnknownError,
-                "unrecognized Chrome version: " + browser_string);
+                base::StringPrintf("unrecognized %s version: %s",
+                                   kBrowserShortName, browser_string.c_str()));
 }
 
 Status ParseBrowserVersionString(const std::string& browser_version,
diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
index ce685bc..cf9655be 100644
--- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
@@ -12,6 +12,7 @@
 #include "base/posix/eintr_wrapper.h"
 #include "base/process/kill.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
@@ -21,7 +22,9 @@
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/chrome/version.h"
 #include "chrome/test/chromedriver/chrome/web_view_impl.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/net/timeout.h"
 
 #if defined(OS_POSIX)
@@ -99,12 +102,15 @@
   if (!quit_) {
     base::FilePath user_data_dir = user_data_dir_.Take();
     base::FilePath extension_dir = extension_dir_.Take();
-    LOG(WARNING) << "chrome quit unexpectedly, leaving behind temporary "
-        "directories for debugging:";
+    LOG(WARNING) << kBrowserShortName
+                 << " quit unexpectedly, leaving behind temporary directories"
+                    "for debugging:";
     if (user_data_dir_.IsValid())
-      LOG(WARNING) << "chrome user data directory: " << user_data_dir.value();
+      LOG(WARNING) << kBrowserShortName
+                   << " user data directory: " << user_data_dir.value();
     if (extension_dir_.IsValid())
-      LOG(WARNING) << "chromedriver automation extension directory: "
+      LOG(WARNING) << kChromeDriverProductShortName
+                   << " automation extension directory: "
                    << extension_dir.value();
   }
 }
@@ -230,7 +236,8 @@
   // to allow Chrome to write out all the net logs to the log path.
   kill_gracefully |= command_.HasSwitch("log-net-log");
   if (!KillProcess(process_, kill_gracefully))
-    return Status(kUnknownError, "cannot kill Chrome");
+    return Status(kUnknownError,
+                  base::StringPrintf("cannot kill %s", kBrowserShortName));
   return Status(kOk);
 }
 
diff --git a/chrome/test/chromedriver/chrome/chrome_finder.cc b/chrome/test/chromedriver/chrome/chrome_finder.cc
index acf43084..6cfcf9ab 100644
--- a/chrome/test/chromedriver/chrome/chrome_finder.cc
+++ b/chrome/test/chromedriver/chrome/chrome_finder.cc
@@ -19,7 +19,9 @@
 #include "base/stl_util.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/branding_buildflags.h"
 #include "build/build_config.h"
+#include "chrome/common/chrome_constants.h"
 
 #if defined(OS_WIN)
 #include "base/base_paths_win.h"
@@ -60,6 +62,7 @@
   locations->push_back(base::FilePath("/bin"));
   // Lastly, try the default installation location.
   locations->push_back(base::FilePath("/opt/google/chrome"));
+  locations->push_back(base::FilePath("/opt/chromium.org/chromium"));
 }
 #elif defined(OS_ANDROID)
 void GetApplicationDirs(std::vector<base::FilePath>* locations) {
@@ -129,17 +132,16 @@
 #endif
 
 bool FindChrome(base::FilePath* browser_exe) {
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
   base::FilePath browser_exes_array[] = {
-#if defined(OS_WIN)
-      base::FilePath(L"chrome.exe")
-#elif defined(OS_MACOSX)
-      base::FilePath("Google Chrome.app/Contents/MacOS/Google Chrome"),
-      base::FilePath("Chromium.app/Contents/MacOS/Chromium")
-#elif defined(OS_LINUX)
-      base::FilePath("google-chrome"),
-      base::FilePath("chrome"),
-      base::FilePath("chromium"),
-      base::FilePath("chromium-browser")
+    base::FilePath(chrome::kBrowserProcessExecutablePath),
+    base::FilePath(chrome::kBrowserProcessExecutablePathChromium),
+#if defined(OS_LINUX)
+    base::FilePath("google-chrome"),
+    base::FilePath("chrome"),
+    base::FilePath("chromium"),
+    base::FilePath("chromium-browser")
+#endif
 #else
       // it will compile but won't work on other OSes
       base::FilePath()
diff --git a/chrome/test/chromedriver/chrome/version.cc b/chrome/test/chromedriver/chrome/version.cc
index 2cd6152..7d9d4799 100644
--- a/chrome/test/chromedriver/chrome/version.cc
+++ b/chrome/test/chromedriver/chrome/version.cc
@@ -9,8 +9,8 @@
 
 namespace {
 
-const int kSupportedChromeVersion[] = {CHROME_VERSION};
+const int kSupportedBrowserVersion[] = {CHROME_VERSION};
 
 }  // namespace
 
-const int kSupportedChromeMajorVersion = kSupportedChromeVersion[0];
+const int kSupportedBrowserMajorVersion = kSupportedBrowserVersion[0];
diff --git a/chrome/test/chromedriver/chrome/version.h b/chrome/test/chromedriver/chrome/version.h
index 3248984..85436b6 100644
--- a/chrome/test/chromedriver/chrome/version.h
+++ b/chrome/test/chromedriver/chrome/version.h
@@ -7,6 +7,6 @@
 
 #include <string>
 
-extern const int kSupportedChromeMajorVersion;
+extern const int kSupportedBrowserMajorVersion;
 
 #endif  // CHROME_TEST_CHROMEDRIVER_CHROME_VERSION_H_
diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc
index 49c23fa..048f670 100644
--- a/chrome/test/chromedriver/chrome_launcher.cc
+++ b/chrome/test/chromedriver/chrome_launcher.cc
@@ -49,6 +49,7 @@
 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
 #include "chrome/test/chromedriver/chrome/version.h"
 #include "chrome/test/chromedriver/chrome/web_view.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/log_replay/chrome_replay_impl.h"
 #include "chrome/test/chromedriver/log_replay/replay_http_client.h"
 #include "chrome/test/chromedriver/net/net_util.h"
@@ -134,11 +135,14 @@
   base::FilePath program = capabilities.binary;
   if (program.empty()) {
     if (!FindChrome(&program))
-      return Status(kUnknownError, "cannot find Chrome binary");
+      return Status(kUnknownError, base::StringPrintf("cannot find %s binary",
+                                                      kBrowserShortName));
   } else if (!base::PathExists(program)) {
-    return Status(kUnknownError,
-                  base::StringPrintf("no chrome binary at %" PRFilePath,
-                                     program.value().c_str()));
+    return Status(
+        kUnknownError,
+        base::StringPrintf("no %s binary at %" PRFilePath,
+                           base::ToLowerASCII(kBrowserShortName).c_str(),
+                           program.value().c_str()));
   }
   base::CommandLine command(program);
   Switches switches;
@@ -258,27 +262,28 @@
     LOG(WARNING) << "You are using an unsupported command-line switch: "
                     "--disable-build-check. Please don't report bugs that "
                     "cannot be reproduced with this switch removed.";
-  } else if (browser_info->major_version != kSupportedChromeMajorVersion) {
+  } else if (browser_info->major_version != kSupportedBrowserMajorVersion) {
     if (browser_info->major_version == 0) {
       // TODO(https://crbug.com/932013): Content Shell doesn't report a version
       // number. Skip version checking with a warning.
-      LOG(WARNING) << "Unable to retrieve Chrome version. "
-                      "Unable to verify browser compatibility.";
+      LOG(WARNING) << "Unable to retrieve " << kBrowserShortName
+                   << " version. Unable to verify browser compatibility.";
     } else if (browser_info->major_version ==
-               kSupportedChromeMajorVersion + 1) {
+               kSupportedBrowserMajorVersion + 1) {
       // TODO(https://crbug.com/chromedriver/2656): Since we don't currently
       // release ChromeDriver for dev or canary channels, allow using
       // ChromeDriver version n (e.g., Beta) with Chrome version n+1 (e.g., Dev
       // or Canary), with a warning.
-      LOG(WARNING) << "This version of ChromeDriver has not been tested with "
-                   << "Chrome version " << browser_info->major_version << ".";
+      LOG(WARNING) << "This version of " << kChromeDriverProductFullName
+                   << " has not been tested with " << kBrowserShortName
+                   << " version " << browser_info->major_version << ".";
     } else {
       *retry = false;
       return Status(
           kSessionNotCreated,
-          base::StringPrintf(
-              "This version of ChromeDriver only supports Chrome version %d",
-              kSupportedChromeMajorVersion));
+          base::StringPrintf("This version of %s only supports %s version %d",
+                             kChromeDriverProductFullName, kBrowserShortName,
+                             kSupportedBrowserMajorVersion));
     }
   }
 
@@ -351,9 +356,12 @@
       capabilities.debugger_address, factory, socket_factory, &capabilities, 60,
       &devtools_http_client, &retry);
   if (status.IsError()) {
-    return Status(kUnknownError, "cannot connect to chrome at " +
-                      capabilities.debugger_address.ToString(),
-                  status);
+    return Status(
+        kUnknownError,
+        base::StringPrintf("cannot connect to %s at %s",
+                           base::ToLowerASCII(kBrowserShortName).c_str(),
+                           capabilities.debugger_address.ToString().c_str()),
+        status);
   }
 
   std::unique_ptr<DevToolsClient> devtools_websocket_client;
@@ -463,10 +471,13 @@
 #else
   std::string command_string = command.GetCommandLineString();
 #endif
-  VLOG(0) << "Launching chrome: " << command_string;
+  VLOG(0) << "Launching " << base::ToLowerASCII(kBrowserShortName) << ": "
+          << command_string;
   base::Process process = base::LaunchProcess(command, options);
   if (!process.IsValid())
-    return Status(kUnknownError, "Failed to create a Chrome process.");
+    return Status(
+        kUnknownError,
+        base::StringPrintf("Failed to create %s process.", kBrowserShortName));
 
   // Attempt to connect to devtools in order to send commands to Chrome. If
   // attempts fail, check if Chrome has crashed and return error.
@@ -509,8 +520,10 @@
                       "argument, or don't use --user-data-dir");
       std::string termination_reason =
           internal::GetTerminationReason(chrome_status);
-      Status failure_status = Status(
-          kUnknownError, "Chrome failed to start: " + termination_reason);
+      Status failure_status =
+          Status(kUnknownError, base::StringPrintf("%s failed to start: %s.",
+                                                   kBrowserShortName,
+                                                   termination_reason.c_str()));
       failure_status.AddDetails(status.message());
       // There is a use case of someone passing a path to a binary to us in
       // capabilities that is not an actual Chrome binary but a script that
@@ -523,23 +536,27 @@
       // the Chrome binary at the end of your script, you must find a way to
       // properly handle our termination signal so that you don't have zombie
       // Chrome processes running after the test is completed.
-      failure_status.AddDetails("The process started from chrome location " +
-                                command.GetProgram().AsUTF8Unsafe() +
-                                " is no longer running, so ChromeDriver is "
-                                "assuming that Chrome has "
-                                "crashed.");
+      failure_status.AddDetails(base::StringPrintf(
+          "The process started from %s location %s is no longer running, "
+          "so %s is assuming that %s has crashed.",
+          base::ToLowerASCII(kBrowserShortName).c_str(),
+          command.GetProgram().AsUTF8Unsafe().c_str(),
+          kChromeDriverProductShortName, kBrowserShortName));
       return failure_status;
     }
     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
   }
 
   if (status.IsError()) {
-    VLOG(0) << "Failed to connect to Chrome. Attempting to kill it.";
+    VLOG(0) << "Failed to connect to " << kBrowserShortName
+            << ". Attempting to kill it.";
     if (!process.Terminate(0, true)) {
       int exit_code;
       if (base::GetTerminationStatus(process.Handle(), &exit_code) ==
           base::TERMINATION_STATUS_STILL_RUNNING)
-        return Status(kUnknownError, "cannot kill Chrome", status);
+        return Status(kUnknownError,
+                      base::StringPrintf("cannot kill %s", kBrowserShortName),
+                      status);
     }
     return status;
   }
@@ -1074,11 +1091,11 @@
   }
   return Status(
       kUnknownError,
-      std::string("Could not remove old devtools port file. Perhaps "
-                  "the given user-data-dir at ") +
-          user_data_dir.AsUTF8Unsafe() +
-          std::string(" is still attached to a running Chrome or Chromium "
-                      "process."));
+      base::StringPrintf(
+          "Could not remove old devtools port file. Perhaps the given "
+          "user-data-dir at %s is still attached to a running %s or "
+          "Chromium process",
+          user_data_dir.AsUTF8Unsafe().c_str(), kBrowserShortName));
 }
 
 std::string GetTerminationReason(base::TerminationStatus status) {
diff --git a/chrome/test/chromedriver/commands.cc b/chrome/test/chromedriver/commands.cc
index 0cc8bd8..65cf981 100644
--- a/chrome/test/chromedriver/commands.cc
+++ b/chrome/test/chromedriver/commands.cc
@@ -19,6 +19,7 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
@@ -26,12 +27,12 @@
 #include "chrome/test/chromedriver/chrome/browser_info.h"
 #include "chrome/test/chromedriver/chrome/chrome.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/logging.h"
 #include "chrome/test/chromedriver/session.h"
 #include "chrome/test/chromedriver/session_commands.h"
 #include "chrome/test/chromedriver/session_thread_map.h"
 #include "chrome/test/chromedriver/util.h"
-#include "chrome/test/chromedriver/version.h"
 
 void ExecuteGetStatus(
     const base::DictionaryValue& params,
@@ -42,7 +43,8 @@
   // so we are always ready.
   base::DictionaryValue info;
   info.SetBoolean("ready", true);
-  info.SetString("message", "ChromeDriver ready for new sessions.");
+  info.SetString("message", base::StringPrintf("%s ready for new sessions.",
+                                               kChromeDriverProductShortName));
 
   // ChromeDriver specific data:
   base::DictionaryValue build;
diff --git a/chrome/test/chromedriver/constants/BRANDING b/chrome/test/chromedriver/constants/BRANDING
new file mode 100644
index 0000000..c95834f
--- /dev/null
+++ b/chrome/test/chromedriver/constants/BRANDING
@@ -0,0 +1,10 @@
+COMPANY_FULLNAME=The Chromium Authors

+COMPANY_SHORTNAME=The Chromium Authors

+COMPANY_PREFIX=goog

+PRODUCT_FULLNAME=ChromeDriver

+PRODUCT_SHORTNAME=ChromeDriver

+BROWSER_OPTIONS_KEY=chromeOptions

+BROWSER_SHORTNAME=Chrome

+BROWSER_CAPABILITYNAME=chrome

+USER_AGENT_PRODUCT_NAME=Chrome

+HEADLESS_USER_AGENT_PRODUCT_NAME=HeadlessChrome

diff --git a/chrome/test/chromedriver/constants/BUILD.gn b/chrome/test/chromedriver/constants/BUILD.gn
new file mode 100644
index 0000000..318b7c9
--- /dev/null
+++ b/chrome/test/chromedriver/constants/BUILD.gn
@@ -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.
+
+import("//build/util/lastchange.gni")
+import("//build/util/process_version.gni")
+
+# Nothing outside //chrome/test/chromedriver can depend on these targets.
+visibility = [ "//chrome/test/chromedriver/*" ]
+
+process_version("version_header") {
+  template_file = "version.cc.in"
+  sources = [
+    "//chrome/VERSION",
+    "BRANDING",
+    lastchange_file,
+  ]
+  output = "$target_gen_dir/version.cc"
+}
diff --git a/chrome/test/chromedriver/constants/version.cc.in b/chrome/test/chromedriver/constants/version.cc.in
new file mode 100644
index 0000000..6e0cbb0
--- /dev/null
+++ b/chrome/test/chromedriver/constants/version.cc.in
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.cc is generated from version.cc.in. Edit the source!
+
+#include "chrome/test/chromedriver/constants/version.h"
+
+// Version Information
+
+const char kChromeDriverVersion[] = "@MAJOR@.@MINOR@.@BUILD@.@PATCH@ (@LASTCHANGE@)";
+
+// Branding Information
+
+const char kChromeDriverProductFullName[] = "@PRODUCT_FULLNAME@";
+const char kChromeDriverProductShortName[] = "@PRODUCT_SHORTNAME@";
+const char kChromeDriverCompanyPrefix[] = "@COMPANY_PREFIX@";
+
+const char kChromeDriverOptionsKey[] = "@BROWSER_OPTIONS_KEY@";
+const char kChromeDriverOptionsKeyPrefixed[] = "@COMPANY_PREFIX@:@BROWSER_OPTIONS_KEY@";
+
+const char kBrowserShortName[] = "@BROWSER_SHORTNAME@";
+const char kBrowserCapabilityName[] = "@BROWSER_CAPABILITYNAME@";
+const char kUserAgentProductName[] = "@USER_AGENT_PRODUCT_NAME@";
+const char kHeadlessUserAgentProductName[] = "@HEADLESS_USER_AGENT_PRODUCT_NAME@";
diff --git a/chrome/test/chromedriver/constants/version.h b/chrome/test/chromedriver/constants/version.h
new file mode 100644
index 0000000..0437e860
--- /dev/null
+++ b/chrome/test/chromedriver/constants/version.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_TEST_CHROMEDRIVER_CONSTANTS_VERSION_H_
+#define CHROME_TEST_CHROMEDRIVER_CONSTANTS_VERSION_H_
+
+// Version Information
+
+extern const char kChromeDriverVersion[];
+
+// Branding Information
+
+extern const char kChromeDriverCompanyPrefix[];
+extern const char kChromeDriverProductFullName[];
+extern const char kChromeDriverProductShortName[];
+
+extern const char kChromeDriverOptionsKey[];
+extern const char kChromeDriverOptionsKeyPrefixed[];
+
+extern const char kBrowserShortName[];
+extern const char kBrowserCapabilityName[];
+extern const char kUserAgentProductName[];
+extern const char kHeadlessUserAgentProductName[];
+
+#endif  // CHROME_TEST_CHROMEDRIVER_CONSTANTS_VERSION_H_
diff --git a/chrome/test/chromedriver/element_commands.cc b/chrome/test/chromedriver/element_commands.cc
index a83edcf..1e6ae810 100644
--- a/chrome/test/chromedriver/element_commands.cc
+++ b/chrome/test/chromedriver/element_commands.cc
@@ -26,6 +26,7 @@
 #include "chrome/test/chromedriver/chrome/status.h"
 #include "chrome/test/chromedriver/chrome/ui_events.h"
 #include "chrome/test/chromedriver/chrome/web_view.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/element_util.h"
 #include "chrome/test/chromedriver/session.h"
 #include "chrome/test/chromedriver/util.h"
@@ -392,12 +393,11 @@
   }
   static bool isClearWarningNotified = false;
   if (!isClearWarningNotified) {
-    std::string messageClearWarning =
-        "\n\t=== NOTE: ===\n"
-        "\tThe Clear command in ChromeDriver 2.43 and above\n"
-        "\thas been updated to conform to the current standard,\n"
-        "\tincluding raising blur event after clearing.\n";
-    VLOG(0) << messageClearWarning;
+    VLOG(0) << "\n\t=== NOTE: ===\n"
+            << "\tThe Clear command in " << kChromeDriverProductShortName
+            << " 2.43 and above\n"
+            << "\thas been updated to conform to the current standard,\n"
+            << "\tincluding raising blur event after clearing.\n";
     isClearWarningNotified = true;
   }
   base::ListValue args;
diff --git a/chrome/test/chromedriver/logging.cc b/chrome/test/chromedriver/logging.cc
index 21393d4f..dbea7fe 100644
--- a/chrome/test/chromedriver/logging.cc
+++ b/chrome/test/chromedriver/logging.cc
@@ -22,16 +22,23 @@
 #include "chrome/test/chromedriver/chrome/console_logger.h"
 #include "chrome/test/chromedriver/chrome/status.h"
 #include "chrome/test/chromedriver/command_listener_proxy.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/devtools_events_logger.h"
 #include "chrome/test/chromedriver/performance_logger.h"
 #include "chrome/test/chromedriver/session.h"
-#include "chrome/test/chromedriver/version.h"
 
 #if defined(OS_POSIX)
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
+const char* GetPortProtectionMessage() {
+  static std::string kPortProtectionMessage = base::StringPrintf(
+      "Please protect ports used by %s and related test frameworks to "
+      "prevent access by malicious code.",
+      kChromeDriverProductShortName);
+  return kPortProtectionMessage.c_str();
+}
 
 namespace {
 
@@ -340,8 +347,9 @@
       logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
   bool res = logging::InitLogging(logging_settings);
   if (cmd_line->HasSwitch("log-path") && res) {
-    VLOG(0) << "Starting ChromeDriver " << kChromeDriverVersion;
-    VLOG(0) << kPortProtectionMessage;
+    VLOG(0) << "Starting " << kChromeDriverProductFullName << " "
+            << kChromeDriverVersion;
+    VLOG(0) << GetPortProtectionMessage();
   }
   return res;
 }
diff --git a/chrome/test/chromedriver/logging.h b/chrome/test/chromedriver/logging.h
index a13ae5e..afc52b9 100644
--- a/chrome/test/chromedriver/logging.h
+++ b/chrome/test/chromedriver/logging.h
@@ -91,8 +91,6 @@
     std::vector<std::unique_ptr<DevToolsEventListener>>* out_devtools_listeners,
     std::vector<std::unique_ptr<CommandListener>>* out_command_listeners);
 
-const char* const kPortProtectionMessage =
-    "Please protect ports used by ChromeDriver and related test frameworks to "
-    "prevent access by malicious code.";
+const char* GetPortProtectionMessage();
 
 #endif  // CHROME_TEST_CHROMEDRIVER_LOGGING_H_
diff --git a/chrome/test/chromedriver/net/DEPS b/chrome/test/chromedriver/net/DEPS
index 29efa71..3758f6e 100644
--- a/chrome/test/chromedriver/net/DEPS
+++ b/chrome/test/chromedriver/net/DEPS
@@ -3,6 +3,7 @@
   "-chrome/test/chromedriver",
 
   "+chrome/test/chromedriver/chrome/status.h",
+  "+chrome/test/chromedriver/constants",
   "+chrome/test/chromedriver/net",
   # LogReplaySocket is a mock out of a chromedriver/net impelementation
   # that logically belongs in chromedriver/log_replay but must be included
diff --git a/chrome/test/chromedriver/net/url_request_context_getter.cc b/chrome/test/chromedriver/net/url_request_context_getter.cc
index 34c9a98e..9e4d253 100644
--- a/chrome/test/chromedriver/net/url_request_context_getter.cc
+++ b/chrome/test/chromedriver/net/url_request_context_getter.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/single_thread_task_runner.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "net/proxy_resolution/proxy_config_service_fixed.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_builder.h"
@@ -22,7 +23,7 @@
   if (!url_request_context_) {
     net::URLRequestContextBuilder builder;
     // net::HttpServer fails to parse headers if user-agent header is blank.
-    builder.set_user_agent("chromedriver");
+    builder.set_user_agent(base::ToLowerASCII(kChromeDriverProductShortName));
     builder.DisableHttpCache();
     builder.set_proxy_config_service(
         std::make_unique<net::ProxyConfigServiceFixed>(
diff --git a/chrome/test/chromedriver/server/chromedriver_server.cc b/chrome/test/chromedriver/server/chromedriver_server.cc
index aa7469f..e666804d 100644
--- a/chrome/test/chromedriver/server/chromedriver_server.cc
+++ b/chrome/test/chromedriver/server/chromedriver_server.cc
@@ -36,9 +36,9 @@
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/logging.h"
 #include "chrome/test/chromedriver/server/http_handler.h"
-#include "chrome/test/chromedriver/version.h"
 #include "mojo/core/embedder/embedder.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
@@ -382,8 +382,8 @@
     // https://chromium.googlesource.com/chromium/src/+/69.0.3464.0/net/socket/socket_descriptor.cc#28.
     need_ipv4 = NeedIPv4::NOT_NEEDED;
 #else
-    LOG(WARNING)
-        << "Running on a platform not officially supported by ChromeDriver.";
+    LOG(WARNING) << "Running on a platform not officially supported by "
+                 << kChromeDriverProductFullName << ".";
     need_ipv4 = NeedIPv4::UNKNOWN;
 #endif
   }
@@ -417,7 +417,8 @@
                const std::vector<net::IPAddress>& whitelisted_ips,
                const std::string& url_base,
                int adb_port) {
-  base::Thread io_thread("ChromeDriver IO");
+  base::Thread io_thread(
+      base::StringPrintf("%s IO", kChromeDriverProductShortName));
   CHECK(io_thread.StartWithOptions(
       base::Thread::Options(base::MessagePumpType::IO, 0)));
 
@@ -490,9 +491,6 @@
             "print the version number and exit",
         "url-base",
             "base URL path prefix for commands, e.g. wd/url",
-        "whitelisted-ips",
-            "comma-separated whitelist of remote IP addresses "
-            "which are allowed to connect to ChromeDriver",
         "readable-timestamp",
             "add readable timestamps to log",
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
@@ -506,12 +504,20 @@
           "  --%-30s%s\n",
           kOptionAndDescriptions[i], kOptionAndDescriptions[i + 1]);
     }
+
+    // Add helper info for whitelisted-ips since the product name may be
+    // different.
+    options += base::StringPrintf(
+        "  --%-30scomma-separated whitelist of remote IP addresses which are "
+        "allowed to connect to %s\n",
+        "whitelisted-ips", kChromeDriverProductShortName);
+
     printf("Usage: %s [OPTIONS]\n\nOptions\n%s", argv[0], options.c_str());
     return 0;
   }
   bool early_exit = false;
   if (cmd_line->HasSwitch("v") || cmd_line->HasSwitch("version")) {
-    printf("ChromeDriver %s\n", kChromeDriverVersion);
+    printf("%s %s\n", kChromeDriverProductFullName, kChromeDriverVersion);
     early_exit = true;
   }
   if (early_exit)
@@ -573,7 +579,8 @@
   }
   if (!cmd_line->HasSwitch("silent") &&
       cmd_line->GetSwitchValueASCII("log-level") != "OFF") {
-    printf("Starting ChromeDriver %s on port %u\n", kChromeDriverVersion, port);
+    printf("Starting %s %s on port %u\n", kChromeDriverProductShortName,
+           kChromeDriverVersion, port);
     if (!allow_remote) {
       printf("Only local connections are allowed.\n");
     } else if (!whitelisted_ips.empty()) {
@@ -582,7 +589,7 @@
     } else {
       printf("All remote connections are allowed. Use a whitelist instead!\n");
     }
-    printf("%s\n", kPortProtectionMessage);
+    printf("%s\n", GetPortProtectionMessage());
     fflush(stdout);
   }
 
@@ -597,7 +604,8 @@
 
   mojo::core::Init();
 
-  base::ThreadPoolInstance::CreateAndStartWithDefaultParams("ChromeDriver");
+  base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
+      kChromeDriverProductShortName);
 
   RunServer(port, allow_remote, whitelisted_ips, url_base, adb_port);
 
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 56e3b8dc..b8505d595 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -28,11 +28,11 @@
 #include "chrome/test/chromedriver/chrome/adb_impl.h"
 #include "chrome/test/chromedriver/chrome/device_manager.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
 #include "chrome/test/chromedriver/session.h"
 #include "chrome/test/chromedriver/session_thread_map.h"
 #include "chrome/test/chromedriver/util.h"
-#include "chrome/test/chromedriver/version.h"
 #include "chrome/test/chromedriver/webauthn_commands.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "net/server/http_server_request_info.h"
@@ -120,6 +120,15 @@
 
 CommandMapping::~CommandMapping() {}
 
+// Create a command mapping with a prefixed HTTP path (.e.g goog/).
+CommandMapping VendorPrefixedCommandMapping(HttpMethod method,
+                                            const char* path_pattern,
+                                            const Command& command) {
+  return CommandMapping(
+      method, base::StringPrintf(path_pattern, kChromeDriverCompanyPrefix),
+      command);
+}
+
 HttpHandler::HttpHandler(const std::string& url_base)
     : url_base_(url_base),
       received_shutdown_(false),
@@ -872,35 +881,37 @@
       CommandMapping(kPost, "session/:sessionId/chromium/send_command",
                      WrapToCommand("SendCommand",
                                    base::BindRepeating(&ExecuteSendCommand))),
-      CommandMapping(
-          kPost, "session/:sessionId/goog/cdp/execute",
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/cdp/execute",
           WrapToCommand("ExecuteCDP",
                         base::BindRepeating(&ExecuteSendCommandAndGetResult))),
       CommandMapping(
           kPost, "session/:sessionId/chromium/send_command_and_get_result",
           WrapToCommand("SendCommandAndGetResult",
                         base::BindRepeating(&ExecuteSendCommandAndGetResult))),
-      CommandMapping(
-          kPost, "session/:sessionId/goog/page/freeze",
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/page/freeze",
           WrapToCommand("Freeze", base::BindRepeating(&ExecuteFreeze))),
-      CommandMapping(
-          kPost, "session/:sessionId/goog/page/resume",
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/page/resume",
           WrapToCommand("Resume", base::BindRepeating(&ExecuteResume))),
-      CommandMapping(kPost, "session/:sessionId/goog/cast/set_sink_to_use",
-                     WrapToCommand("SetSinkToUse",
-                                   base::BindRepeating(&ExecuteSetSinkToUse))),
-      CommandMapping(
-          kPost, "session/:sessionId/goog/cast/start_tab_mirroring",
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/cast/set_sink_to_use",
+          WrapToCommand("SetSinkToUse",
+                        base::BindRepeating(&ExecuteSetSinkToUse))),
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/cast/start_tab_mirroring",
           WrapToCommand("StartTabMirroring",
                         base::BindRepeating(&ExecuteStartTabMirroring))),
-      CommandMapping(kPost, "session/:sessionId/goog/cast/stop_casting",
-                     WrapToCommand("StopCasting",
-                                   base::BindRepeating(&ExecuteStopCasting))),
-      CommandMapping(
-          kGet, "session/:sessionId/goog/cast/get_sinks",
+      VendorPrefixedCommandMapping(
+          kPost, "session/:sessionId/%s/cast/stop_casting",
+          WrapToCommand("StopCasting",
+                        base::BindRepeating(&ExecuteStopCasting))),
+      VendorPrefixedCommandMapping(
+          kGet, "session/:sessionId/%s/cast/get_sinks",
           WrapToCommand("GetSinks", base::BindRepeating(&ExecuteGetSinks))),
-      CommandMapping(
-          kGet, "session/:sessionId/goog/cast/get_issue_message",
+      VendorPrefixedCommandMapping(
+          kGet, "session/:sessionId/%s/cast/get_issue_message",
           WrapToCommand("GetIssueMessage",
                         base::BindRepeating(&ExecuteGetIssueMessage))),
 
@@ -1082,9 +1093,9 @@
   if (status.IsError()) {
     Status full_status(status);
     full_status.AddDetails(base::StringPrintf(
-        "Driver info: chromedriver=%s,platform=%s %s %s",
-        kChromeDriverVersion,
-        base::SysInfo::OperatingSystemName().c_str(),
+        "Driver info: %s=%s,platform=%s %s %s",
+        base::ToLowerASCII(kChromeDriverProductShortName).c_str(),
+        kChromeDriverVersion, base::SysInfo::OperatingSystemName().c_str(),
         base::SysInfo::OperatingSystemVersion().c_str(),
         base::SysInfo::OperatingSystemArchitecture().c_str()));
     std::unique_ptr<base::DictionaryValue> error(new base::DictionaryValue());
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index a054c5c..80ee128 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -31,13 +31,14 @@
 #include "chrome/test/chromedriver/chrome/geoposition.h"
 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/chrome/version.h"
 #include "chrome/test/chromedriver/chrome/web_view.h"
 #include "chrome/test/chromedriver/chrome_launcher.h"
 #include "chrome/test/chromedriver/command_listener.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/logging.h"
 #include "chrome/test/chromedriver/session.h"
 #include "chrome/test/chromedriver/util.h"
-#include "chrome/test/chromedriver/version.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
 namespace {
@@ -87,26 +88,27 @@
 bool GetW3CSetting(const base::DictionaryValue& params) {
   bool w3c;
   const base::ListValue* list;
-  const base::DictionaryValue* dict;
+  const base::DictionaryValue* caps_dict;
+  const base::DictionaryValue* options_dict;
 
-  if (params.GetDictionary("capabilities.alwaysMatch", &dict)) {
-    if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
-        dict->GetBoolean("chromeOptions.w3c", &w3c)) {
+  if (params.GetDictionary("capabilities.alwaysMatch", &caps_dict)) {
+    if (GetChromeOptionsDictionary(*caps_dict, &options_dict) &&
+        options_dict->GetBoolean("w3c", &w3c)) {
       return w3c;
     }
   }
 
   if (params.GetList("capabilities.firstMatch", &list) &&
-      list->GetDictionary(0, &dict)) {
-    if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
-        dict->GetBoolean("chromeOptions.w3c", &w3c)) {
+      list->GetDictionary(0, &caps_dict)) {
+    if (GetChromeOptionsDictionary(*caps_dict, &options_dict) &&
+        options_dict->GetBoolean("w3c", &w3c)) {
       return w3c;
     }
   }
 
-  if (params.GetDictionary("desiredCapabilities", &dict)) {
-    if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
-        dict->GetBoolean("chromeOptions.w3c", &w3c)) {
+  if (params.GetDictionary("desiredCapabilities", &caps_dict)) {
+    if (GetChromeOptionsDictionary(*caps_dict, &options_dict) &&
+        options_dict->GetBoolean("w3c", &w3c)) {
       return w3c;
     }
   }
@@ -131,7 +133,7 @@
 
   // Capabilities defined by W3C. Some of these capabilities have different
   // names in legacy mode.
-  caps->SetString("browserName", "chrome");
+  caps->SetString("browserName", base::ToLowerASCII(kBrowserShortName));
   caps->SetString(session->w3c_compliant ? "browserVersion" : "version",
                   session->chrome->GetBrowserInfo()->browser_version);
   std::string operatingSystemName = session->chrome->GetOperatingSystemName();
@@ -171,14 +173,21 @@
                   session->unhandled_prompt_behavior);
 
   // Chrome-specific extensions.
-  caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion);
+  const std::string chromedriverVersionKey = base::StringPrintf(
+      "%s.%sVersion", base::ToLowerASCII(kBrowserShortName).c_str(),
+      base::ToLowerASCII(kChromeDriverProductShortName).c_str());
+  caps->SetString(chromedriverVersionKey, kChromeDriverVersion);
+  const std::string debuggerAddressKey =
+      base::StringPrintf("%s.debuggerAddress", kChromeDriverOptionsKeyPrefixed);
   caps->SetString(
-      "goog:chromeOptions.debuggerAddress",
+      debuggerAddressKey,
       session->chrome->GetBrowserInfo()->debugger_address.ToString());
   ChromeDesktopImpl* desktop = NULL;
   Status status = session->chrome->GetAsDesktop(&desktop);
   if (status.IsOk()) {
-    caps->SetString("chrome.userDataDir",
+    const std::string userDataDirKey = base::StringPrintf(
+        "%s.userDataDir", base::ToLowerASCII(kBrowserShortName).c_str());
+    caps->SetString(userDataDirKey,
                     desktop->command().GetSwitchValueNative("user-data-dir"));
     caps->SetBoolean("networkConnectionEnabled",
                      desktop->IsNetworkConnectionEnabled());
@@ -407,7 +416,7 @@
 bool MatchCapabilities(const base::DictionaryValue* capabilities) {
   const base::Value* name;
   if (capabilities->Get("browserName", &name) && !name->is_none()) {
-    if (!(name->is_string() && name->GetString() == "chrome"))
+    if (!(name->is_string() && name->GetString() == kBrowserCapabilityName))
       return false;
   }
 
@@ -424,10 +433,14 @@
       std::string actual_first_token =
         actual_platform_name.substr(0, actual_platform_name.find(' '));
 
-      bool is_android = capabilities->FindPath(
-                            "goog:chromeOptions.androidPackage") != nullptr;
-      bool is_remote = capabilities->FindPath(
-                           "goog:chromeOptions.debuggerAddress") != nullptr;
+      const base::DictionaryValue* chrome_options;
+      const bool has_chrome_options =
+          GetChromeOptionsDictionary(*capabilities, &chrome_options);
+
+      bool is_android = has_chrome_options && chrome_options->FindStringKey(
+                                                  "androidPackage") != nullptr;
+      bool is_remote = has_chrome_options && chrome_options->FindStringKey(
+                                                 "debuggerAddress") != nullptr;
       if (requested_platform_name == "any" || is_remote ||
           (is_android && requested_platform_name == "android")) {
         // "any" can be used as a wild card for platformName.
diff --git a/chrome/test/chromedriver/util.cc b/chrome/test/chromedriver/util.cc
index 3e135e2..710cb73 100644
--- a/chrome/test/chromedriver/util.cc
+++ b/chrome/test/chromedriver/util.cc
@@ -24,6 +24,7 @@
 #include "chrome/test/chromedriver/chrome/ui_events.h"
 #include "chrome/test/chromedriver/chrome/web_view.h"
 #include "chrome/test/chromedriver/command_listener.h"
+#include "chrome/test/chromedriver/constants/version.h"
 #include "chrome/test/chromedriver/key_converter.h"
 #include "chrome/test/chromedriver/session.h"
 #include "third_party/zlib/google/zip.h"
@@ -46,8 +47,10 @@
       return Status(kUnknownError, "keys should be a string");
     for (size_t j = 0; j < keys_list_part.size(); ++j) {
       if (CBU16_IS_SURROGATE(keys_list_part[j])) {
-        return Status(kUnknownError,
-                      "ChromeDriver only supports characters in the BMP");
+        return Status(
+            kUnknownError,
+            base::StringPrintf("%s only supports characters in the BMP",
+                               kChromeDriverProductShortName));
       }
     }
     keys.append(keys_list_part);
diff --git a/chrome/test/chromedriver/version.cc b/chrome/test/chromedriver/version.cc
deleted file mode 100644
index ec66dbf..0000000
--- a/chrome/test/chromedriver/version.cc
+++ /dev/null
@@ -1,8 +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/test/chromedriver/version.h"
-#include "components/version_info/version_info_values.h"
-
-const char kChromeDriverVersion[] = PRODUCT_VERSION " (" LAST_CHANGE ")";
diff --git a/chrome/test/chromedriver/version.h b/chrome/test/chromedriver/version.h
deleted file mode 100644
index 73195142..0000000
--- a/chrome/test/chromedriver/version.h
+++ /dev/null
@@ -1,10 +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_TEST_CHROMEDRIVER_VERSION_H_
-#define CHROME_TEST_CHROMEDRIVER_VERSION_H_
-
-extern const char kChromeDriverVersion[];
-
-#endif  // CHROME_TEST_CHROMEDRIVER_VERSION_H_
diff --git a/chrome/test/data/webui/downloads/downloads_browsertest.js b/chrome/test/data/webui/downloads/downloads_browsertest.js
index 74ce4547..169c777 100644
--- a/chrome/test/data/webui/downloads/downloads_browsertest.js
+++ b/chrome/test/data/webui/downloads/downloads_browsertest.js
@@ -23,21 +23,6 @@
   },
 
   /** @override */
-  loaderFile: 'subpage_loader.html',
-
-  // The name of the custom element under test. Should be overridden by
-  // subclasses that are loading the URL of a non-element.
-  get customElementName() {
-    const r = /chrome\:\/\/downloads\/([a-zA-Z-_]+)\.html/;
-    const result = r.exec(this.browsePreload);
-    if (!result || result.length < 1) {
-      // Loading the main page, so wait for downloads manager.
-      return 'downloads-manager';
-    }
-    return 'downloads-' + result[1].replace(/_/gi, '-');
-  },
-
-  /** @override */
   runAccessibilityChecks: true,
 };
 
@@ -125,14 +110,6 @@
 
   /** @override */
   browsePreload: 'chrome://downloads/a/b/',
-
-  /** @override */
-  loaderFile: '',
-
-  /** @override */
-  get customElementName() {
-    return null;
-  }
 };
 
 TEST_F('DownloadsUrlTest', 'All', function() {
diff --git a/chrome/test/data/webui/extensions/cr_extensions_browsertest.js b/chrome/test/data/webui/extensions/cr_extensions_browsertest.js
index 8acaa03..672e5865 100644
--- a/chrome/test/data/webui/extensions/cr_extensions_browsertest.js
+++ b/chrome/test/data/webui/extensions/cr_extensions_browsertest.js
@@ -45,25 +45,6 @@
     return null;
   }
 
-  /** @override */
-  get loaderFile() {
-    return 'subpage_loader.html';
-  }
-
-  // The name of the custom element under test. Should be overridden by
-  // subclasses that are loading the URL of a non-element.
-  get customElementName() {
-    const r = /chrome\:\/\/extensions\/(([a-zA-Z-_]+)\/)?([a-zA-Z-_]+)\.html/;
-    const result = r.exec(this.browsePreload);
-    if (result && result.length > 3) {
-      const element = result[3].replace(/_/gi, '-');
-      return result[2] === undefined ? 'extensions-' + element : element;
-    }
-
-    // Loading the main page, return extensions manager.
-    return 'extensions-manager';
-  }
-
   /** @param {string} testName The name of the test to run. */
   runMochaTest(testName) {
     runMochaTest(this.suiteName, testName);
@@ -230,12 +211,6 @@
       'activity_log_test.js',
     ]);
   }
-
-  /** @override */
-  get customElementName() {
-    // This element's naming scheme is unusual.
-    return 'extensions-activity-log';
-  }
 };
 
 TEST_F('CrExtensionsActivityLogTest', 'All', () => {
@@ -846,12 +821,6 @@
   get suiteName() {
     return extension_navigation_helper_tests.suiteName;
   }
-
-  /** @override */
-  get customElementName() {
-    // This test is verifying a class, not a custom element.
-    return null;
-  }
 };
 
 TEST_F('CrExtensionsNavigationHelperTest', 'Basic', function() {
diff --git a/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js b/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js
index 6f0fc156..24329b5a 100644
--- a/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js
+++ b/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js
@@ -27,11 +27,6 @@
       '../test_util.js',
     ];
   }
-
-  /** @override */
-  get loaderFile() {
-    return 'subpage_loader.html';
-  }
 };
 
 
@@ -51,12 +46,6 @@
   }
 
   /** @override */
-  get customElementName() {
-    // Wait for the manager since this test is loading the main page.
-    return 'extensions-manager';
-  }
-
-  /** @override */
   testGenPreamble() {
     GEN('  InstallExtensionWithInPageOptions();');
   }
diff --git a/chrome/test/data/webui/polymer_browser_test_base.js b/chrome/test/data/webui/polymer_browser_test_base.js
index 1cd593c..e53eaff 100644
--- a/chrome/test/data/webui/polymer_browser_test_base.js
+++ b/chrome/test/data/webui/polymer_browser_test_base.js
@@ -24,14 +24,6 @@
   browsePreload: 'chrome://chrome-urls/',
 
   /**
-   * The name of the custom element under test. Should be overridden by
-   * subclasses that are using the HTML imports polyfill and need to wait for
-   * a custom element to be defined before starting the test.
-   * @type {?string}
-   */
-  customElementName: null,
-
-  /**
    * The mocha adapter assumes all tests are async.
    * @override
    * @final
@@ -93,20 +85,6 @@
         throw e;
       }
     };
-
-    if (typeof HTMLImports !== 'undefined') {
-      suiteSetup(() => {
-        return new Promise(resolve => {
-                 HTMLImports.whenReady(resolve);
-               })
-            .then(() => {
-              const customElementName = this.customElementName;
-              if (customElementName) {
-                return customElements.whenDefined(customElementName);
-              }
-            });
-      });
-    }
   },
 };
 
diff --git a/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js b/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
index ef6b8102..d2e5142 100644
--- a/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
+++ b/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
@@ -21,24 +21,11 @@
     ];
   }
 
-  /** @override */
-  get loaderFile() {
-    return 'subpage_loader.html';
-  }
-
   // The name of the mocha suite. Should be overridden by subclasses.
   get suiteName() {
     return null;
   }
 
-  // The name of the custom element under test. Should be overridden by
-  // subclasses that are not directly loading the URL of a custom element.
-  get customElementName() {
-    const r = /chrome\:\/\/print\/([a-zA-Z-_]+)\/([a-zA-Z-_]+)\.html/;
-    const result = r.exec(this.browsePreload);
-    return 'print-preview-' + result[2].replace(/_/gi, '-');
-  }
-
   /** @param {string} testName The name of the test to run. */
   runMochaTest(testName) {
     runMochaTest(this.suiteName, testName);
diff --git a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
index b45c76c..74a2f146 100644
--- a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
@@ -20,24 +20,11 @@
     ];
   }
 
-  /** @override */
-  get loaderFile() {
-    return 'subpage_loader.html';
-  }
-
   // The name of the mocha suite. Should be overridden by subclasses.
   get suiteName() {
     return null;
   }
 
-  // The name of the custom element under test. Should be overridden by
-  // subclasses that are not directly loading the URL of a custom element.
-  get customElementName() {
-    const r = /chrome\:\/\/print\/([a-zA-Z-_]+)\/([a-zA-Z-_]+)\.html/;
-    const result = r.exec(this.browsePreload);
-    return 'print-preview-' + result[2].replace(/_/gi, '-');
-  }
-
   /** @param {string} testName The name of the test to run. */
   runMochaTest(testName) {
     runMochaTest(this.suiteName, testName);
@@ -228,12 +215,6 @@
   get suiteName() {
     return select_behavior_test.suiteName;
   }
-
-  /** @override */
-  get customElementName() {
-    // This test is loading a behavior, not an element.
-    return null;
-  }
 };
 
 TEST_F('PrintPreviewSelectBehaviorTest', 'CallProcessSelectChange', function() {
@@ -646,12 +627,6 @@
   get suiteName() {
     return destination_store_test.suiteName;
   }
-
-  /** @override */
-  get customElementName() {
-    // This test is loading a data class, not an element.
-    return null;
-  }
 };
 
 TEST_F(
diff --git a/chromecast/app/cast_test_launcher.cc b/chromecast/app/cast_test_launcher.cc
index ca0544c..186c3147f2 100644
--- a/chromecast/app/cast_test_launcher.cc
+++ b/chromecast/app/cast_test_launcher.cc
@@ -24,10 +24,8 @@
 
   int RunTestSuite(int argc, char** argv) override {
     base::TestSuite test_suite(argc, argv);
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals.
     test_suite.DisableCheckForLeakedGlobals();
-    test_suite.DisableCheckForThreadPriorityAtTestEnd();
     return test_suite.Run();
   }
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index b7ab16c..5eed845 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -447,8 +447,10 @@
                      /*assistant_tree=*/nullptr));
 }
 
-void AssistantManagerServiceImpl::StartTextInteraction(const std::string& query,
-                                                       bool allow_tts) {
+void AssistantManagerServiceImpl::StartTextInteraction(
+    const std::string& query,
+    mojom::AssistantQuerySource source,
+    bool allow_tts) {
   assistant_client::VoicelessOptions options;
   options.is_user_initiated = true;
 
@@ -462,7 +464,7 @@
   options.conversation_turn_id = base::NumberToString(next_interaction_id_++);
   pending_interactions_[options.conversation_turn_id] =
       mojom::AssistantInteractionMetadata::New(
-          /*type=*/mojom::AssistantInteractionType::kText, /*query=*/query);
+          mojom::AssistantInteractionType::kText, source, query);
 
   if (base::FeatureList::IsEnabled(
           assistant::features::kEnableTextQueriesWithClientDiscourseContext) &&
@@ -1259,6 +1261,7 @@
     metadata_ptr->type = metadata.is_mic_open
                              ? mojom::AssistantInteractionType::kVoice
                              : mojom::AssistantInteractionType::kText;
+    metadata_ptr->source = mojom::AssistantQuerySource::kLibAssistantInitiated;
   }
 
   for (auto& it : interaction_subscribers_)
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 9b33a14..4e63f6b 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -128,7 +128,9 @@
   void StartCachedScreenContextInteraction() override;
   void StartEditReminderInteraction(const std::string& client_id) override;
   void StartMetalayerInteraction(const gfx::Rect& region) override;
-  void StartTextInteraction(const std::string& query, bool allow_tts) override;
+  void StartTextInteraction(const std::string& query,
+                            mojom::AssistantQuerySource source,
+                            bool allow_tts) override;
   void StartVoiceInteraction() override;
   void StartWarmerWelcomeInteraction(int num_warmer_welcome_triggered,
                                      bool allow_tts) override;
diff --git a/chromeos/services/assistant/fake_assistant_manager_service_impl.cc b/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
index b98cc134..39c3971 100644
--- a/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
@@ -67,6 +67,7 @@
 
 void FakeAssistantManagerServiceImpl::StartTextInteraction(
     const std::string& query,
+    mojom::AssistantQuerySource source,
     bool allow_tts) {}
 
 void FakeAssistantManagerServiceImpl::StartVoiceInteraction() {}
diff --git a/chromeos/services/assistant/fake_assistant_manager_service_impl.h b/chromeos/services/assistant/fake_assistant_manager_service_impl.h
index 303b1c1..be1a659 100644
--- a/chromeos/services/assistant/fake_assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/fake_assistant_manager_service_impl.h
@@ -49,7 +49,9 @@
   void StartCachedScreenContextInteraction() override;
   void StartEditReminderInteraction(const std::string& client_id) override;
   void StartMetalayerInteraction(const gfx::Rect& region) override;
-  void StartTextInteraction(const std::string& query, bool allow_tts) override;
+  void StartTextInteraction(const std::string& query,
+                            mojom::AssistantQuerySource source,
+                            bool allow_tts) override;
   void StartVoiceInteraction() override;
   void StartWarmerWelcomeInteraction(int num_warmer_welcome_triggered,
                                      bool allow_tts) override;
diff --git a/chromeos/services/assistant/public/mojom/assistant.mojom b/chromeos/services/assistant/public/mojom/assistant.mojom
index a145c512a..a060992 100644
--- a/chromeos/services/assistant/public/mojom/assistant.mojom
+++ b/chromeos/services/assistant/public/mojom/assistant.mojom
@@ -80,7 +80,8 @@
   // result will contain TTS. Otherwise TTS will not be present in the
   // generated server response. Results will be returned through registered
   // |AssistantInteractionSubscriber|.
-  StartTextInteraction(string query, bool allow_tts);
+  StartTextInteraction(
+    string query, AssistantQuerySource source, bool allow_tts);
 
   // Starts a new Assistant voice interaction.
   StartVoiceInteraction();
@@ -141,6 +142,7 @@
 // Describes an Assistant interaction.
 struct AssistantInteractionMetadata {
   AssistantInteractionType type;
+  AssistantQuerySource source;
   string query;
 };
 
@@ -150,6 +152,20 @@
   kVoice,
 };
 
+// Enumeration of possible Assistant query sources. These values are persisted
+// to logs. Entries should not be renumbered and numeric values should never
+// be reused. Append new values to the end.
+enum AssistantQuerySource {
+  kUnspecified = 0,
+  kDeepLink = 1,
+  kDialogPlateTextField = 2,
+  kStylus = 3,
+  kSuggestionChip = 4,
+  kVoiceInput = 5,
+  kProactiveSuggestions = 6,
+  kLibAssistantInitiated = 7,
+};
+
 // Subscribes to Assistant's interaction event. These events are server driven
 // in response to the user's direct interaction with the assistant. Responses
 // from the assistant may contain untrusted third-party content. Subscriber
diff --git a/chromeos/services/assistant/test_support/mock_assistant.h b/chromeos/services/assistant/test_support/mock_assistant.h
index 4b7df36..af504ef 100644
--- a/chromeos/services/assistant/test_support/mock_assistant.h
+++ b/chromeos/services/assistant/test_support/mock_assistant.h
@@ -28,7 +28,8 @@
 
   MOCK_METHOD1(StartMetalayerInteraction, void(const gfx::Rect&));
 
-  MOCK_METHOD2(StartTextInteraction, void(const std::string&, bool));
+  MOCK_METHOD3(StartTextInteraction,
+               void(const std::string&, mojom::AssistantQuerySource, bool));
 
   MOCK_METHOD0(StartVoiceInteraction, void());
 
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 7749f7b..2ed56cfc 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -306,9 +306,21 @@
       const std::vector<MigratableCreditCard>& migratable_credit_cards,
       MigrationDeleteCardCallback delete_local_card_callback) = 0;
 
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+  // Will show a dialog indicating the card verification is in progress. It is
+  // shown after verification starts only if the WebAuthn is enabled.
+  // Implemented only on desktop.
+  virtual void ShowVerifyPendingDialog(
+      base::OnceClosure cancel_card_verification_callback) = 0;
+
+  // Close the verify pending dialog once the card verificiation is completed or
+  // verification falls back to CVC.
+  virtual void CloseVerifyPendingDialog() = 0;
+#endif
+
   // Will show a dialog offering the option to use device's platform
   // authenticator in the future instead of CVC to verify the card being
-  // unmasked. Runs |callback| is the OK button or the cancel button in the
+  // unmasked. Runs |callback| if the OK button or the cancel button in the
   // dialog is clicked. This is only implemented on desktop.
   virtual void ShowWebauthnOfferDialog(
       WebauthnOfferDialogCallback callback) = 0;
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index af5007f..dded674f 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -25,6 +25,7 @@
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_tick_clock.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -306,6 +307,11 @@
   }
 #endif
   if (AuthenticationRequiresUnmaskDetails() && !get_unmask_details_returned) {
+    // On desktop, shows the verify pending dialog.
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+    ShowVerifyPendingDialog();
+#endif
+
     // Wait for |ready_to_start_authentication_| to be signaled by
     // OnDidGetUnmaskDetails() or until timeout before calling Authenticate().
     base::PostTaskAndReplyWithResult(
@@ -353,14 +359,24 @@
       get_unmask_details_returned && unmask_details_.unmask_auth_method ==
                                          AutofillClient::UnmaskAuthMethod::FIDO;
 
-  // Do not use FIDO if card is not listed in unmask details, as each Card must
-  // be CVC authed at least once per device. Logging decision.
-  bool card_is_eligible_for_fido =
+  bool card_is_authorized_for_fido =
       fido_auth_suggested &&
       unmask_details_.fido_eligible_card_ids.find(card_->server_id()) !=
           unmask_details_.fido_eligible_card_ids.end();
 
-  if (fido_auth_suggested) {
+  // If FIDO authentication was suggested, but card is not in authorized list,
+  // must authenticate with CVC followed by FIDO in order to authorize this card
+  // for future FIDO use.
+  should_follow_up_cvc_with_fido_auth_ =
+      fido_auth_suggested && !card_is_authorized_for_fido;
+
+  // Only use FIDO if card is authorized and not expired.
+  bool card_is_eligible_for_fido =
+      card_is_authorized_for_fido && !card_->IsExpired(AutofillClock::Now());
+
+  // If FIDO auth was suggested, logging which authentication method was
+  // actually used.
+  if (fido_auth_suggested && !card_->IsExpired(AutofillClock::Now())) {
     AutofillMetrics::LogCardUnmaskTypeDecision(
         card_is_eligible_for_fido
             ? AutofillMetrics::CardUnmaskTypeDecisionMetric::kFidoOnly
@@ -377,6 +393,11 @@
         std::move(unmask_details_.fido_request_options));
 #endif
   } else {
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+    // Close the verify pending dialog if it enters CVC authentication flow
+    // since the card unmask prompt will pop up.
+    client_->CloseVerifyPendingDialog();
+#endif
     GetOrCreateCVCAuthenticator()->Authenticate(
         card_, weak_ptr_factory_.GetWeakPtr(), personal_data_manager_,
         form_parsed_timestamp_);
@@ -443,7 +464,8 @@
   } else if (should_offer_fido_auth) {
     GetOrCreateFIDOAuthenticator()->ShowWebauthnOfferDialog(
         response.card_authorization_token);
-  } else if (unmask_details_.fido_request_options.is_dict()) {
+  } else if (should_follow_up_cvc_with_fido_auth_) {
+    DCHECK(unmask_details_.fido_request_options.is_dict());
     GetOrCreateFIDOAuthenticator()->Authorize(
         response.card_authorization_token,
         std::move(unmask_details_.fido_request_options));
@@ -455,6 +477,13 @@
 void CreditCardAccessManager::OnFIDOAuthenticationComplete(
     bool did_succeed,
     const CreditCard* card) {
+#if !defined(OS_ANDROID)
+  // Close the verify pending dialog. If FIDO authentication succeeded, card is
+  // filled to the form, otherwise fall back to CVC authentication which does
+  // not need the verify pending dialog either.
+  client_->CloseVerifyPendingDialog();
+#endif
+
   if (did_succeed) {
     is_authentication_in_progress_ = false;
     accessor_->OnCreditCardFetched(did_succeed, card);
@@ -469,6 +498,10 @@
 }
 #endif
 
+bool CreditCardAccessManager::IsLocalCard(const CreditCard* card) {
+  return card && card->record_type() == CreditCard::LOCAL_CARD;
+}
+
 bool CreditCardAccessManager::AuthenticationRequiresUnmaskDetails() {
 #if defined(OS_IOS)
   return false;
@@ -478,8 +511,18 @@
 #endif
 }
 
-bool CreditCardAccessManager::IsLocalCard(const CreditCard* card) {
-  return card && card->record_type() == CreditCard::LOCAL_CARD;
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+void CreditCardAccessManager::ShowVerifyPendingDialog() {
+  client_->ShowVerifyPendingDialog(
+      base::BindOnce(&CreditCardAccessManager::OnDidCancelCardVerification,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
+void CreditCardAccessManager::OnDidCancelCardVerification() {
+  payments_client_->CancelRequest();
+  unmask_details_request_in_progress_ = false;
+  is_authentication_in_progress_ = false;
+}
+#endif
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.h b/components/autofill/core/browser/payments/credit_card_access_manager.h
index bf3dca51..7c6868a 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.h
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.h
@@ -161,10 +161,25 @@
   // immediately.
   bool AuthenticationRequiresUnmaskDetails();
 
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+  // After card verification starts, shows the verify pending dialog if WebAuthn
+  // is enabled, indicating some verification steps are in progress.
+  void ShowVerifyPendingDialog();
+
+  // The callback function invoked when the cancel button in the verify pending
+  // dialog is clicked. Will cancel the attempt to fetch unmask details.
+  void OnDidCancelCardVerification();
+#endif
+
   // Is set to true only when waiting for the callback to
   // OnCVCAuthenticationComplete() to be executed.
   bool is_authentication_in_progress_ = false;
 
+  // Set to true if the card selected needs to be authenticated through CVC
+  // first, and then FIDO. This happens when a user is opted-in but has not
+  // previously authenticated this card with CVC on this device.
+  bool should_follow_up_cvc_with_fido_auth_ = false;
+
   // The associated autofill driver. Weak reference.
   AutofillDriver* const driver_;
 
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
index d553e0e..fe44ff7b 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -836,6 +836,29 @@
       AutofillMetrics::WebauthnResultMetric::kSuccess, 1);
 }
 
+// Ensures expired cards always invoke a CVC prompt instead of WebAuthn.
+TEST_F(CreditCardAccessManagerTest, FetchExpiredServerCardInvokesCvcPrompt) {
+  // Creating an expired server card and opting the user in with authorized
+  // card.
+  CreateServerCard(kTestGUID, kTestNumber);
+  CreditCard* card = credit_card_access_manager_->GetCreditCard(kTestGUID);
+  card->SetExpirationYearFromString(base::UTF8ToUTF16("2010"));
+  GetFIDOAuthenticator()->SetUserVerifiable(true);
+  SetUserOptedIn(true);
+  payments_client_->AddFidoEligibleCard(card->server_id(), kCredentialId,
+                                        kGooglePaymentsRpid);
+
+  credit_card_access_manager_->PrepareToFetchCreditCard();
+  WaitForCallbacks();
+
+  credit_card_access_manager_->FetchCreditCard(card, accessor_->GetWeakPtr());
+  WaitForCallbacks();
+
+  // Expect CVC prompt to be invoked.
+  EXPECT_TRUE(GetRealPanForCVCAuth(AutofillClient::SUCCESS, kTestNumber));
+  EXPECT_EQ(ASCIIToUTF16(kTestNumber), accessor_->number());
+}
+
 #if defined(OS_ANDROID)
 // Ensures that the WebAuthn enrollment prompt is invoked after user opts in.
 TEST_F(CreditCardAccessManagerTest, FIDOEnrollmentSuccess_Android) {
diff --git a/components/autofill/core/browser/test_autofill_client.cc b/components/autofill/core/browser/test_autofill_client.cc
index 5e96551..a04561e 100644
--- a/components/autofill/core/browser/test_autofill_client.cc
+++ b/components/autofill/core/browser/test_autofill_client.cc
@@ -107,6 +107,13 @@
     const std::vector<MigratableCreditCard>& migratable_credit_cards,
     MigrationDeleteCardCallback delete_local_card_callback) {}
 
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+void TestAutofillClient::ShowVerifyPendingDialog(
+    base::OnceClosure cancel_card_verification_callback) {}
+
+void TestAutofillClient::CloseVerifyPendingDialog() {}
+#endif
+
 void TestAutofillClient::ShowWebauthnOfferDialog(
     WebauthnOfferDialogCallback callback) {}
 
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index 7352b0b..92b7019 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -65,6 +65,11 @@
       const base::string16& tip_message,
       const std::vector<MigratableCreditCard>& migratable_credit_cards,
       MigrationDeleteCardCallback delete_local_card_callback) override;
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+  void ShowVerifyPendingDialog(
+      base::OnceClosure cancel_card_verification_callback) override;
+  void CloseVerifyPendingDialog() override;
+#endif
   void ShowWebauthnOfferDialog(WebauthnOfferDialogCallback callback) override;
   bool CloseWebauthnOfferDialog() override;
   void ConfirmSaveAutofillProfile(const AutofillProfile& profile,
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index ee3276b..cb01a618 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -502,6 +502,17 @@
     </message>
   </if>
 
+
+  <!-- Verify pending dialog (Desktop only) -->
+  <if expr="not is_ios and not is_android">
+    <message name="IDS_AUTOFILL_VERIFY_PENDING_DIALOG_TITLE" desc="Headline showing the card verification is in progress.">
+        Verifying your identity...
+    </message>
+    <message name="IDS_AUTOFILL_VERIFY_PENDING_DIALOG_CANCEL_BUTTON_LABEL" desc="Button that allows the user to cancel the card verification.">
+        Cancel
+    </message>
+  </if>
+
   <message name="IDS_AUTOFILL_WALLET_MANAGEMENT_LINK_TEXT" desc="Text for link that allows users to see and edit their Wallet information." formatter_data="android_java">
     Edit
   </message>
diff --git a/components/dom_distiller/content/browser/dom_distiller_viewer_source.cc b/components/dom_distiller/content/browser/dom_distiller_viewer_source.cc
index 5ef694e..d9208b5 100644
--- a/components/dom_distiller/content/browser/dom_distiller_viewer_source.cc
+++ b/components/dom_distiller/content/browser/dom_distiller_viewer_source.cc
@@ -236,9 +236,11 @@
 }
 
 void DomDistillerViewerSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const content::WebContents::Getter& wc_getter,
     const content::URLDataSource::GotDataCallback& callback) {
+  // TODO(crbug/1009127): simplify path matching.
+  const std::string path = URLDataSource::URLToRequestPath(url);
   content::WebContents* web_contents = wc_getter.Run();
   if (!web_contents)
     return;
diff --git a/components/dom_distiller/content/browser/dom_distiller_viewer_source.h b/components/dom_distiller/content/browser/dom_distiller_viewer_source.h
index 1f984bb..85f1899 100644
--- a/components/dom_distiller/content/browser/dom_distiller_viewer_source.h
+++ b/components/dom_distiller/content/browser/dom_distiller_viewer_source.h
@@ -32,7 +32,7 @@
   // Overridden from content::URLDataSource:
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const content::WebContents::Getter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
   std::string GetMimeType(const std::string& path) override;
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 45e5574..3c965a8 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -1192,11 +1192,13 @@
     // we should revert the temporary text same as what pressing escape would
     // have done.
     //
-    // This doesn't apply for on-focus suggestions, for which the first result
-    // can be completely distinct from the omnibox contents. We enforce that
-    // via user_input_in_progress_, which is false for ZeroSuggest.
+    // Reverting, however, does not make sense for on-focus suggestions
+    // (user_input_in_progress_ is false) unless the first result is a
+    // verbatim match of the omnibox input (on-focus query refinements on SERP).
     const size_t line_no = GetNewSelectedLine(count);
-    if (has_temporary_text_ && line_no == 0 && user_input_in_progress_) {
+    if (has_temporary_text_ && line_no == 0 &&
+        (user_input_in_progress_ ||
+         result().default_match()->IsVerbatimType())) {
       RevertTemporaryTextAndPopup();
     } else {
       popup_model()->MoveTo(line_no);
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 2ec98aa6..dc2c669 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -305,4 +305,9 @@
 const base::Feature kOnDeviceHeadProvider{"OmniboxOnDeviceHeadProvider",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+// If enabled, shows a confirm dialog before removing search suggestions from
+// the omnibox. See ConfirmNtpSuggestionRemovals for the NTP equivalent.
+const base::Feature kConfirmOmniboxSuggestionRemovals{
+    "ConfirmOmniboxSuggestionRemovals", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace omnibox
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 70e5d1b..18aa3fb8 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -9,7 +9,9 @@
 
 namespace omnibox {
 
-// TODO(dbeam): why is this list not sorted alphabetically?
+// Please do not add more features to this "big blob" list.
+// Instead, use the categorized and alphabetized lists below this "big blob".
+// You can create a new category if none of the existing ones fit.
 extern const base::Feature kHideFileUrlScheme;
 extern const base::Feature kHideSteadyStateUrlScheme;
 extern const base::Feature kHideSteadyStateUrlTrivialSubdomains;
@@ -58,6 +60,9 @@
 extern const base::Feature kZeroSuggestionsOnNTPRealbox;
 extern const base::Feature kZeroSuggestionsOnSERP;
 
+// Suggestions UI - these affect the UI or function of the suggestions popup.
+extern const base::Feature kConfirmOmniboxSuggestionRemovals;
+
 }  // namespace omnibox
 
 #endif  // COMPONENTS_OMNIBOX_COMMON_OMNIBOX_FEATURES_H_
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index ca2a223..11b45b8 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -470,6 +470,7 @@
     "//components/test/data/password_manager/login_db_v23.sql",
     "//components/test/data/password_manager/login_db_v24.sql",
     "//components/test/data/password_manager/login_db_v25.sql",
+    "//components/test/data/password_manager/login_db_v26.sql",
     "//components/test/data/password_manager/login_db_v2_broken.sql",
     "//components/test/data/password_manager/login_db_v3.sql",
     "//components/test/data/password_manager/login_db_v3_broken.sql",
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_database.cc b/components/password_manager/core/browser/android_affiliation/affiliation_database.cc
index 840cae4..ccb0ba47 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_database.cc
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_database.cc
@@ -260,7 +260,7 @@
     SQLTableBuilder* eq_classes_builder,
     SQLTableBuilder* eq_class_members_builder) {
   // Version 1 of the affiliation database.
-  eq_classes_builder->AddColumnToPrimaryKey("id", "INTEGER");
+  eq_classes_builder->AddPrimaryKeyColumn("id");
   eq_classes_builder->AddColumn("last_update_time", "INTEGER");
   // The first call to |SealVersion| sets the version to 0, that's why it is
   // repeated.
@@ -268,7 +268,7 @@
   unsigned eq_classes_version = eq_classes_builder->SealVersion();
   DCHECK_EQ(1u, eq_classes_version);
 
-  eq_class_members_builder->AddColumnToPrimaryKey("id", "INTEGER");
+  eq_class_members_builder->AddPrimaryKeyColumn("id");
   eq_class_members_builder->AddColumnToUniqueKey("facet_uri",
                                                  "LONGVARCHAR NOT NULL");
   eq_class_members_builder->AddColumn(
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index b8d868b..6534d99 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -55,7 +55,7 @@
 namespace password_manager {
 
 // The current version number of the login database schema.
-const int kCurrentVersionNumber = 25;
+const int kCurrentVersionNumber = 26;
 // The oldest version of the schema such that a legacy Chrome client using that
 // version can still read/write the current database.
 const int kCompatibleVersionNumber = 19;
@@ -491,14 +491,13 @@
   SealVersion(builders, /*expected_version=*/19u);
 
   // Version 20.
-  builders.logins->AddColumnToPrimaryKey("id", "INTEGER");
+  builders.logins->AddPrimaryKeyColumn("id");
   SealVersion(builders, /*expected_version=*/20u);
 
   // Version 21.
-  builders.sync_entities_metadata->AddColumnToPrimaryKey("storage_key",
-                                                         "INTEGER");
+  builders.sync_entities_metadata->AddPrimaryKeyColumn("storage_key");
   builders.sync_entities_metadata->AddColumn("metadata", "VARCHAR NOT NULL");
-  builders.sync_model_metadata->AddColumnToPrimaryKey("id", "INTEGER");
+  builders.sync_model_metadata->AddPrimaryKeyColumn("id");
   builders.sync_model_metadata->AddColumn("model_metadata", "VARCHAR NOT NULL");
   SealVersion(builders, /*expected_version=*/21u);
 
@@ -520,6 +519,9 @@
   builders.logins->AddColumn("date_last_used", "INTEGER NOT NULL DEFAULT 0");
   SealVersion(builders, /*expected_version=*/25u);
 
+  // Version 26 is the first version where the id is AUTOINCREMENT.
+  SealVersion(builders, /*expected_version=*/26u);
+
   DCHECK_EQ(static_cast<size_t>(COLUMN_NUM), builders.logins->NumberOfColumns())
       << "Adjust LoginDatabaseTableColumns if you change column definitions "
          "here.";
@@ -561,7 +563,7 @@
 
   // Sync Metadata tables have been introduced in version 21. It is enough to
   // drop all data because Sync would populate the tables properly at startup.
-  if (current_version == 21 || current_version == 22 || current_version == 23) {
+  if (current_version >= 21 && current_version < 26) {
     if (!ClearAllSyncMetadata(db))
       return false;
   }
@@ -586,6 +588,45 @@
       return false;
   }
 
+  // In version 26, the primary key of the logins table became an AUTOINCREMENT
+  // field. Since SQLite doesn't allow changing the column type, the only way is
+  // to actually create a temp table with the primary key propely set as an
+  // AUTOINCREMENT field, and move the data there. The code has been adjusted
+  // such that newly created tables have the primary key properly set as
+  // AUTOINCREMENT.
+  if (current_version < 26) {
+    // This statement creates the logins database similar to version 26 with the
+    // primary key column set to AUTOINCREMENT.
+    std::string temp_table_create_statement_version_26 =
+        "CREATE TABLE logins_temp (origin_url VARCHAR NOT NULL,action_url "
+        "VARCHAR,username_element VARCHAR,username_value "
+        "VARCHAR,password_element VARCHAR,password_value BLOB,submit_element "
+        "VARCHAR,signon_realm VARCHAR NOT NULL,preferred INTEGER NOT "
+        "NULL,date_created INTEGER NOT NULL,blacklisted_by_user INTEGER NOT "
+        "NULL,scheme INTEGER NOT NULL,password_type INTEGER,times_used "
+        "INTEGER,form_data BLOB,date_synced INTEGER,display_name "
+        "VARCHAR,icon_url VARCHAR,federation_url VARCHAR,skip_zero_click "
+        "INTEGER,generation_upload_status INTEGER,possible_username_pairs "
+        "BLOB,id INTEGER PRIMARY KEY AUTOINCREMENT,date_last_used "
+        "INTEGER,UNIQUE (origin_url, username_element, username_value, "
+        "password_element, signon_realm))";
+    std::string move_data_statement =
+        "INSERT INTO logins_temp SELECT * from logins";
+    std::string drop_table_statement = "DROP TABLE logins";
+    std::string rename_table_statement =
+        "ALTER TABLE logins_temp RENAME TO logins";
+
+    sql::Transaction transaction(db);
+    if (!(transaction.Begin() &&
+          db->Execute(temp_table_create_statement_version_26.c_str()) &&
+          db->Execute(move_data_statement.c_str()) &&
+          db->Execute(drop_table_statement.c_str()) &&
+          db->Execute(rename_table_statement.c_str()) &&
+          transaction.Commit())) {
+      return false;
+    }
+  }
+
   return true;
 }
 
diff --git a/components/password_manager/core/browser/login_database_unittest.cc b/components/password_manager/core/browser/login_database_unittest.cc
index aed9a70..e34e6e21 100644
--- a/components/password_manager/core/browser/login_database_unittest.cc
+++ b/components/password_manager/core/browser/login_database_unittest.cc
@@ -452,6 +452,27 @@
   EXPECT_EQ(0U, result.size());
 }
 
+TEST_F(LoginDatabaseTest, ShouldNotRecyclePrimaryKeys) {
+  std::vector<std::unique_ptr<PasswordForm>> result;
+
+  // Example password form.
+  PasswordForm form;
+  GenerateExamplePasswordForm(&form);
+
+  // Add the form.
+  PasswordStoreChangeList change_list = db().AddLogin(form);
+  ASSERT_EQ(1U, change_list.size());
+  int primary_key1 = change_list[0].primary_key();
+  change_list.clear();
+  // Delete the form
+  EXPECT_TRUE(db().RemoveLoginByPrimaryKey(primary_key1, &change_list));
+  ASSERT_EQ(1U, change_list.size());
+  // Add it again.
+  change_list = db().AddLogin(form);
+  ASSERT_EQ(1U, change_list.size());
+  EXPECT_NE(primary_key1, change_list[0].primary_key());
+}
+
 TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatching) {
   std::vector<std::unique_ptr<PasswordForm>> result;
 
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc
index 8d1d0027..0537d21 100644
--- a/components/password_manager/core/browser/password_form_manager.cc
+++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -165,6 +165,9 @@
                           metrics_recorder,
                           PasswordStore::FormDigest(observed_form)) {
   driver_ = driver;
+  if (driver_)
+    driver_id_ = driver->GetId();
+
   observed_form_ = observed_form;
   metrics_recorder_->RecordFormSignature(CalculateFormSignature(observed_form));
   // Do not fetch saved credentials for Chrome sync form, since nor filling nor
diff --git a/components/password_manager/core/browser/password_form_manager.h b/components/password_manager/core/browser/password_form_manager.h
index f49e866..d2b3929 100644
--- a/components/password_manager/core/browser/password_form_manager.h
+++ b/components/password_manager/core/browser/password_form_manager.h
@@ -169,6 +169,8 @@
   base::WeakPtr<PasswordManagerDriver> GetDriver() const;
   const autofill::PasswordForm* GetSubmittedForm() const;
 
+  int driver_id() { return driver_id_; }
+
 #if defined(OS_IOS)
   // Presaves the form with |generated_password|. This function is called once
   // when the user accepts the generated password. The password was generated in
@@ -283,6 +285,10 @@
 
   base::WeakPtr<PasswordManagerDriver> driver_;
 
+  // Id of |driver_|. Cached since |driver_| might become null when frame is
+  // close..
+  int driver_id_ = 0;
+
   // TODO(https://crbug.com/943045): use std::variant for keeping
   // |observed_form_| and |observed_not_web_form_digest_|.
   autofill::FormData observed_form_;
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index a726bfe1..0d226a6 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -751,6 +751,13 @@
     return;
   }
 
+  if (!driver->IsMainFrame() &&
+      submitted_manager->driver_id() != driver->GetId()) {
+    // Frames different from the main frame and the frame of the submitted form
+    // are unlikely relevant to success of submission.
+    return;
+  }
+
   // If we see the login form again, then the login failed.
   if (submitted_manager->GetPendingCredentials().scheme ==
       PasswordForm::Scheme::kHtml) {
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index 701cea08..2c38420 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -187,11 +187,18 @@
 
 class MockPasswordManagerDriver : public StubPasswordManagerDriver {
  public:
+  MockPasswordManagerDriver() {
+    ON_CALL(*this, GetId()).WillByDefault(Return(0));
+    ON_CALL(*this, IsMainFrame()).WillByDefault(Return(true));
+  }
+
+  MOCK_CONST_METHOD0(GetId, int());
   MOCK_METHOD1(FormEligibleForGenerationFound,
                void(const autofill::PasswordFormGenerationData&));
   MOCK_METHOD1(FillPasswordForm, void(const autofill::PasswordFormFillData&));
   MOCK_METHOD0(GetPasswordManager, PasswordManager*());
   MOCK_METHOD0(GetPasswordAutofillManager, PasswordAutofillManager*());
+  MOCK_CONST_METHOD0(IsMainFrame, bool());
   MOCK_CONST_METHOD0(GetLastCommittedURL, const GURL&());
 };
 
@@ -3295,4 +3302,77 @@
   EXPECT_EQ(password, saved_form.password_value);
 }
 
+TEST_F(PasswordManagerTest, FormSubmittedOnMainFrame) {
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  EXPECT_CALL(*store_, GetLogins(_, _))
+      .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms()));
+  PasswordForm form(MakeSimpleForm());
+
+  // Submit |form| on a main frame.
+  manager()->OnPasswordFormsParsed(&driver_, {form} /* observed */);
+  manager()->OnPasswordFormSubmitted(&driver_, form);
+
+  // Simulate finish loading of some iframe.
+  MockPasswordManagerDriver iframe_driver;
+  EXPECT_CALL(iframe_driver, IsMainFrame()).WillRepeatedly(Return(false));
+  EXPECT_CALL(iframe_driver, GetId()).WillRepeatedly(Return(123));
+  manager()->OnPasswordFormsRendered(&iframe_driver, {} /* observed */, true);
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0);
+  Mock::VerifyAndClearExpectations(&client_);
+
+  // Simulate finish loading of some iframe. Check that the prompt is shown.
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_));
+  manager()->OnPasswordFormsRendered(&driver_, {} /* observed */,
+                                     true /* did stop loading */);
+}
+
+TEST_F(PasswordManagerTest, FormSubmittedOnIFrame) {
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  EXPECT_CALL(*store_, GetLogins(_, _))
+      .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms()));
+  PasswordForm form(MakeSimpleForm());
+
+  // Submit |form| on an iframe.
+  MockPasswordManagerDriver iframe_driver;
+  ON_CALL(iframe_driver, IsMainFrame()).WillByDefault(Return(false));
+  ON_CALL(iframe_driver, GetId()).WillByDefault(Return(123));
+  manager()->OnPasswordFormsParsed(&iframe_driver, {form} /* observed */);
+  manager()->OnPasswordFormSubmitted(&iframe_driver, form);
+
+  // Simulate finish loading of another iframe.
+  MockPasswordManagerDriver another_iframe_driver;
+  EXPECT_CALL(another_iframe_driver, IsMainFrame())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(another_iframe_driver, GetId()).WillRepeatedly(Return(456));
+  manager()->OnPasswordFormsRendered(&another_iframe_driver, {} /* observed */,
+                                     true);
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0);
+  Mock::VerifyAndClearExpectations(&client_);
+
+  // Simulate finish loading of the submitted form iframe. Check that the prompt
+  // is shown.
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_));
+  manager()->OnPasswordFormsRendered(&iframe_driver, {} /* observed */,
+                                     true /* did stop loading */);
+}
+
+TEST_F(PasswordManagerTest, FormSubmittedOnIFrameMainFrameLoaded) {
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  EXPECT_CALL(*store_, GetLogins(_, _))
+      .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms()));
+  PasswordForm form(MakeSimpleForm());
+
+  // Simulate a form submission on an iframe.
+  MockPasswordManagerDriver iframe_driver;
+  ON_CALL(iframe_driver, IsMainFrame()).WillByDefault(Return(false));
+  ON_CALL(iframe_driver, GetId()).WillByDefault(Return(123));
+  manager()->OnPasswordFormsParsed(&iframe_driver, {form} /* observed */);
+  manager()->OnPasswordFormSubmitted(&iframe_driver, form);
+
+  // Simulate finish loading of the main frame. Check that the prompt is shown.
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_));
+  manager()->OnPasswordFormsRendered(&driver_, {} /* observed */,
+                                     true /* did stop loading */);
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/sql_table_builder.cc b/components/password_manager/core/browser/sql_table_builder.cc
index 39cc2b1..36455d6 100644
--- a/components/password_manager/core/browser/sql_table_builder.cc
+++ b/components/password_manager/core/browser/sql_table_builder.cc
@@ -35,8 +35,8 @@
 struct SQLTableBuilder::Column {
   std::string name;
   std::string type;
-  // Whether this column is part of the table's PRIMARY KEY constraint.
-  bool part_of_primary_key;
+  // Whether this column is the table's PRIMARY KEY.
+  bool is_primary_key;
   // Whether this column is part of the table's UNIQUE constraint.
   bool part_of_unique_key;
   // The first version this column is part of.
@@ -76,10 +76,12 @@
                       sealed_version_ + 1, kInvalidVersion, false});
 }
 
-void SQLTableBuilder::AddColumnToPrimaryKey(std::string name,
-                                            std::string type) {
-  AddColumn(std::move(name), std::move(type));
-  columns_.back().part_of_primary_key = true;
+void SQLTableBuilder::AddPrimaryKeyColumn(std::string name) {
+  for (const Column& column : columns_) {
+    DCHECK(!column.is_primary_key);
+  }
+  AddColumn(std::move(name), "INTEGER");
+  columns_.back().is_primary_key = true;
 }
 
 void SQLTableBuilder::AddColumnToUniqueKey(std::string name, std::string type) {
@@ -108,7 +110,7 @@
     // just replaced, it needs to be kept for generating the migration code.
     Column new_column = {new_name,
                          old_column->type,
-                         old_column->part_of_primary_key,
+                         old_column->is_primary_key,
                          old_column->part_of_unique_key,
                          sealed_version_ + 1,
                          kInvalidVersion,
@@ -173,7 +175,6 @@
 }
 
 std::string SQLTableBuilder::ComputeConstraints(unsigned version) const {
-  std::string primary_key;
   std::string unique_key;
   for (const Column& column : columns_) {
     // Ignore dropped columns.
@@ -182,14 +183,10 @@
     // Ignore columns columns from future versions.
     if (column.min_version > version)
       continue;
-    if (column.part_of_primary_key)
-      Append(column.name, &primary_key);
     if (column.part_of_unique_key)
       Append(column.name, &unique_key);
   }
   std::string constraints;
-  if (!primary_key.empty())
-    Append("PRIMARY KEY (" + primary_key + ")", &constraints);
   if (!unique_key.empty())
     Append("UNIQUE (" + unique_key + ")", &constraints);
   return constraints;
@@ -213,13 +210,21 @@
   DCHECK(IsVersionLastAndSealed(sealed_version_));
   if (db->DoesTableExist(table_name_.c_str()))
     return true;
+
   std::string constraints = ComputeConstraints(sealed_version_);
-  DCHECK(!constraints.empty());
+  DCHECK(!constraints.empty() || std::any_of(columns_.begin(), columns_.end(),
+                                             [](const Column& column) {
+                                               return column.is_primary_key;
+                                             }));
 
   std::string names;  // Names and types of the current columns.
   for (const Column& column : columns_) {
-    if (IsColumnInLastVersion(column))
-      Append(column.name + " " + column.type, &names);
+    if (IsColumnInLastVersion(column)) {
+      std::string suffix;
+      if (column.is_primary_key)
+        suffix = " PRIMARY KEY AUTOINCREMENT";
+      Append(column.name + " " + column.type + suffix, &names);
+    }
   }
 
   std::vector<std::string>
@@ -231,12 +236,16 @@
           base::JoinString(index.columns, ", ").c_str()));
     }
   }
+
+  std::string create_table_statement =
+      constraints.empty()
+          ? base::StringPrintf("CREATE TABLE %s (%s)", table_name_.c_str(),
+                               names.c_str())
+          : base::StringPrintf("CREATE TABLE %s (%s, %s)", table_name_.c_str(),
+                               names.c_str(), constraints.c_str());
+
   sql::Transaction transaction(db);
-  return transaction.Begin() &&
-         db->Execute(base::StringPrintf("CREATE TABLE %s (%s, %s)",
-                                        table_name_.c_str(), names.c_str(),
-                                        constraints.c_str())
-                         .c_str()) &&
+  return transaction.Begin() && db->Execute(create_table_statement.c_str()) &&
          std::all_of(create_index_sqls.begin(), create_index_sqls.end(),
                      [&db](const std::string& sql) {
                        return db->Execute(sql.c_str());
@@ -259,7 +268,7 @@
   std::string result;
   for (const Column& column : columns_) {
     if (IsColumnInLastVersion(column) &&
-        !(column.part_of_primary_key || column.part_of_unique_key))
+        !(column.is_primary_key || column.part_of_unique_key))
       Append(column.name + "=?", &result);
   }
   return result;
@@ -283,10 +292,11 @@
   std::vector<base::StringPiece> result;
   result.reserve(columns_.size());
   for (const Column& column : columns_) {
-    if (IsColumnInLastVersion(column) && column.part_of_primary_key) {
+    if (IsColumnInLastVersion(column) && column.is_primary_key) {
       result.emplace_back(column.name);
     }
   }
+  DCHECK(result.size() < 2);
   return result;
 }
 
@@ -318,6 +328,8 @@
   // because that is not supported by a single SQLite command.
   bool needs_temp_table = false;
 
+  bool has_primary_key = false;
+
   for (auto column = columns_.begin(); column != columns_.end(); ++column) {
     if (column->max_version == old_version) {
       // This column was deleted after |old_version|. It can have two reasons:
@@ -337,15 +349,27 @@
       }
     } else if (column->min_version == old_version + 1) {
       // This column was added after old_version.
-      if (column->part_of_primary_key || column->part_of_unique_key)
+      if (column->is_primary_key || column->part_of_unique_key)
         needs_temp_table = true;
-      names_of_new_columns_list.push_back(column->name + " " + column->type);
+      std::string suffix;
+      if (column->is_primary_key) {
+        suffix = " PRIMARY KEY AUTOINCREMENT";
+        has_primary_key = true;
+      }
+      names_of_new_columns_list.push_back(column->name + " " + column->type +
+                                          suffix);
     } else if (column->min_version <= old_version &&
                (column->max_version == kInvalidVersion ||
                 column->max_version > old_version)) {
+      std::string suffix;
+      if (column->is_primary_key) {
+        suffix = " PRIMARY KEY AUTOINCREMENT";
+        has_primary_key = true;
+      }
       // This column stays.
       Append(column->name, &old_names_of_existing_columns_without_types);
-      Append(column->name + " " + column->type, &new_names_of_existing_columns);
+      Append(column->name + " " + column->type + suffix,
+             &new_names_of_existing_columns);
       Append(column->name, &new_names_of_existing_columns_without_types);
     }
   }
@@ -365,7 +389,7 @@
     // one.
 
     std::string constraints = ComputeConstraints(old_version + 1);
-    DCHECK(!constraints.empty());
+    DCHECK(has_primary_key || !constraints.empty());
 
     // Foreign key constraints are not enabled for the login database, so no
     // PRAGMA foreign_keys=off needed.
@@ -376,12 +400,17 @@
       Append(new_column, &names_of_all_columns);
     }
 
+    std::string create_table_statement =
+        constraints.empty()
+            ? base::StringPrintf("CREATE TABLE %s (%s)",
+                                 temp_table_name.c_str(),
+                                 names_of_all_columns.c_str())
+            : base::StringPrintf(
+                  "CREATE TABLE %s (%s, %s)", temp_table_name.c_str(),
+                  names_of_all_columns.c_str(), constraints.c_str());
+
     sql::Transaction transaction(db);
-    if (!(transaction.Begin() &&
-          db->Execute(base::StringPrintf(
-                          "CREATE TABLE %s (%s, %s)", temp_table_name.c_str(),
-                          names_of_all_columns.c_str(), constraints.c_str())
-                          .c_str()) &&
+    if (!(transaction.Begin() && db->Execute(create_table_statement.c_str()) &&
           db->Execute(base::StringPrintf(
                           "INSERT OR REPLACE INTO %s (%s) SELECT %s FROM %s",
                           temp_table_name.c_str(),
diff --git a/components/password_manager/core/browser/sql_table_builder.h b/components/password_manager/core/browser/sql_table_builder.h
index 653ae21..1c8f46c6 100644
--- a/components/password_manager/core/browser/sql_table_builder.h
+++ b/components/password_manager/core/browser/sql_table_builder.h
@@ -56,7 +56,9 @@
   void AddColumn(std::string name, std::string type);
 
   // As AddColumn but also adds column |name| to the primary key of the table.
-  void AddColumnToPrimaryKey(std::string name, std::string type);
+  // The column must be of type "INTEGER" and will be AUTO INCREMENT. This
+  // method can be called only once.
+  void AddPrimaryKeyColumn(std::string name);
 
   // As AddColumn but also adds column |name| to the unique key of the table.
   void AddColumnToUniqueKey(std::string name, std::string type);
diff --git a/components/password_manager/core/browser/sql_table_builder_unittest.cc b/components/password_manager/core/browser/sql_table_builder_unittest.cc
index be440e0..4c32240 100644
--- a/components/password_manager/core/browser/sql_table_builder_unittest.cc
+++ b/components/password_manager/core/browser/sql_table_builder_unittest.cc
@@ -229,7 +229,7 @@
 }
 
 TEST_F(SQLTableBuilderTest, MigrateFrom_RenameAndAddColumns) {
-  builder()->AddColumnToPrimaryKey("id", "INTEGER");
+  builder()->AddPrimaryKeyColumn("id");
   builder()->AddColumn("old_name", "INTEGER");
   EXPECT_EQ(0u, builder()->SealVersion());
 
@@ -258,8 +258,7 @@
 }
 
 TEST_F(SQLTableBuilderTest, MigrateFrom_RenameAndAddAndDropColumns) {
-  builder()->AddColumnToPrimaryKey("pk_1", "VARCHAR NOT NULL");
-  builder()->AddColumnToPrimaryKey("pk_2", "VARCHAR NOT NULL");
+  builder()->AddPrimaryKeyColumn("pk_1");
   builder()->AddColumnToUniqueKey("uni", "VARCHAR NOT NULL");
   builder()->AddColumn("old_name", "INTEGER");
   EXPECT_EQ(0u, builder()->SealVersion());
@@ -279,18 +278,16 @@
   EXPECT_FALSE(db()->DoesColumnExist("my_logins_table", "old_name"));
   EXPECT_FALSE(db()->DoesColumnExist("my_logins_table", "added"));
   EXPECT_TRUE(db()->DoesColumnExist("my_logins_table", "pk_1"));
-  EXPECT_TRUE(db()->DoesColumnExist("my_logins_table", "pk_2"));
   EXPECT_TRUE(db()->DoesColumnExist("my_logins_table", "uni"));
   EXPECT_TRUE(db()->DoesColumnExist("my_logins_table", "new_name"));
   EXPECT_TRUE(IsColumnOfType("new_name", "INTEGER"));
-  EXPECT_EQ(5u, builder()->NumberOfColumns());
-  EXPECT_EQ("signon_realm, pk_1, pk_2, uni, new_name",
+  EXPECT_EQ(4u, builder()->NumberOfColumns());
+  EXPECT_EQ("signon_realm, pk_1, uni, new_name",
             builder()->ListAllColumnNames());
   EXPECT_EQ("new_name=?", builder()->ListAllNonuniqueKeyNames());
   EXPECT_EQ("signon_realm=? AND uni=?", builder()->ListAllUniqueKeyNames());
 
-  EXPECT_THAT(builder()->AllPrimaryKeyNames(),
-              UnorderedElementsAre("pk_1", "pk_2"));
+  EXPECT_THAT(builder()->AllPrimaryKeyNames(), UnorderedElementsAre("pk_1"));
 }
 
 TEST_F(SQLTableBuilderTest, MigrateFrom_AddPrimaryKey) {
@@ -298,7 +295,7 @@
   EXPECT_EQ(0u, builder()->SealVersion());
   EXPECT_TRUE(builder()->CreateTable(db()));
 
-  builder()->AddColumnToPrimaryKey("pk_1", "VARCHAR NOT NULL");
+  builder()->AddPrimaryKeyColumn("pk_1");
   EXPECT_EQ(1u, builder()->SealVersion());
 
   EXPECT_FALSE(db()->DoesColumnExist("my_logins_table", "pk_1"));
@@ -308,8 +305,9 @@
   EXPECT_TRUE(builder()->MigrateFrom(0, db()));
 
   EXPECT_TRUE(db()->DoesColumnExist("my_logins_table", "pk_1"));
-  EXPECT_TRUE(db()->GetSchema().find("PRIMARY KEY (pk_1)") !=
-              std::string::npos);
+  EXPECT_TRUE(
+      db()->GetSchema().find("pk_1 INTEGER PRIMARY KEY AUTOINCREMENT") !=
+      std::string::npos);
 }
 
 }  // namespace password_manager
diff --git a/components/test/data/password_manager/login_db_v26.sql b/components/test/data/password_manager/login_db_v26.sql
new file mode 100644
index 0000000..5476193
--- /dev/null
+++ b/components/test/data/password_manager/login_db_v26.sql
@@ -0,0 +1,123 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO "meta" VALUES('last_compatible_version','19');
+INSERT INTO "meta" VALUES('version','25');
+CREATE TABLE logins (
+origin_url VARCHAR NOT NULL,
+action_url VARCHAR,
+username_element VARCHAR,
+username_value VARCHAR,
+password_element VARCHAR,
+password_value BLOB,
+submit_element VARCHAR,
+signon_realm VARCHAR NOT NULL,
+preferred INTEGER NOT NULL,
+date_created INTEGER NOT NULL,
+blacklisted_by_user INTEGER NOT NULL,
+scheme INTEGER NOT NULL,
+password_type INTEGER,
+times_used INTEGER,
+form_data BLOB,
+date_synced INTEGER,
+display_name VARCHAR,
+icon_url VARCHAR,
+federation_url VARCHAR,
+skip_zero_click INTEGER,
+generation_upload_status INTEGER,
+possible_username_pairs BLOB,
+id INTEGER PRIMARY KEY AUTOINCREMENT,
+date_last_used INTEGER,
+UNIQUE (origin_url, username_element, username_value, password_element, signon_realm));
+INSERT INTO "logins" (origin_url,action_url,username_element,username_value,password_element,password_value,submit_element,signon_realm,preferred,date_created,blacklisted_by_user,scheme,password_type,times_used,form_data,date_synced,display_name,icon_url,federation_url,skip_zero_click,generation_upload_status,possible_username_pairs,date_last_used) VALUES(
+'https://accounts.google.com/ServiceLogin', /* origin_url */
+'https://accounts.google.com/ServiceLoginAuth', /* action_url */
+'Email', /* username_element */
+'theerikchen', /* username_value */
+'Passwd', /* password_element */
+X'', /* password_value */
+'', /* submit_element */
+'https://accounts.google.com/', /* signon_realm */
+1, /* preferred */
+13047429345000000, /* date_created */
+0, /* blacklisted_by_user */
+0, /* scheme */
+0, /* password_type */
+1, /* times_used */
+X'18000000020000000000000000000000000000000000000000000000', /* form_data */
+0, /* date_synced */
+'', /* display_name */
+'', /* icon_url */
+'', /* federation_url */
+1,  /* skip_zero_click */
+0,  /* generation_upload_status */
+X'00000000', /* possible_username_pairs */
+0 /* date_last_used */
+);
+INSERT INTO "logins" (origin_url,action_url,username_element,username_value,password_element,password_value,submit_element,signon_realm,preferred,date_created,blacklisted_by_user,scheme,password_type,times_used,form_data,date_synced,display_name,icon_url,federation_url,skip_zero_click,generation_upload_status,possible_username_pairs,date_last_used) VALUES(
+'https://accounts.google.com/ServiceLogin', /* origin_url */
+'https://accounts.google.com/ServiceLoginAuth', /* action_url */
+'Email', /* username_element */
+'theerikchen2', /* username_value */
+'Passwd', /* password_element */
+X'', /* password_value */
+'non-empty', /* submit_element */
+'https://accounts.google.com/', /* signon_realm */
+1, /* preferred */
+13047423600000000, /* date_created */
+0, /* blacklisted_by_user */
+0, /* scheme */
+0, /* password_type */
+1, /* times_used */
+X'18000000020000000000000000000000000000000000000000000000', /* form_data */
+0, /* date_synced */
+'', /* display_name */
+'https://www.google.com/icon', /* icon_url */
+'', /* federation_url */
+1,  /* skip_zero_click */
+0,  /* generation_upload_status */
+X'00000000', /* possible_username_pairs */
+0 /* date_last_used */
+);
+INSERT INTO "logins" (origin_url,action_url,username_element,username_value,password_element,password_value,submit_element,signon_realm,preferred,date_created,blacklisted_by_user,scheme,password_type,times_used,form_data,date_synced,display_name,icon_url,federation_url,skip_zero_click,generation_upload_status,possible_username_pairs,date_last_used) VALUES(
+'http://example.com', /* origin_url */
+'http://example.com/landing', /* action_url */
+'', /* username_element */
+'user', /* username_value */
+'', /* password_element */
+X'', /* password_value */
+'non-empty', /* submit_element */
+'http://example.com', /* signon_realm */
+1, /* preferred */
+13047423600000000, /* date_created */
+0, /* blacklisted_by_user */
+1, /* scheme */
+0, /* password_type */
+1, /* times_used */
+X'18000000020000000000000000000000000000000000000000000000', /* form_data */
+0, /* date_synced */
+'', /* display_name */
+'https://www.google.com/icon', /* icon_url */
+'', /* federation_url */
+1,  /* skip_zero_click */
+0,  /* generation_upload_status */
+X'00000000', /* possible_username_pairs */
+0 /* date_last_used */
+);
+CREATE INDEX logins_signon ON logins (signon_realm);
+CREATE TABLE stats (
+origin_domain VARCHAR NOT NULL,
+username_value VARCHAR,
+dismissal_count INTEGER,
+update_time INTEGER NOT NULL,
+UNIQUE(origin_domain, username_value));
+CREATE INDEX stats_origin ON stats(origin_domain);
+CREATE TABLE sync_entities_metadata (
+  storage_key INTEGER PRIMARY KEY AUTOINCREMENT,
+  metadata VARCHAR NOT NULL
+);
+CREATE TABLE sync_model_metadata (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  metadata VARCHAR NOT NULL
+);
+COMMIT;
diff --git a/components/test/data/payments/bobpay_and_basic_card_with_modifiers.js b/components/test/data/payments/bobpay_and_basic_card_with_modifiers.js
index a71d887..00e9547 100644
--- a/components/test/data/payments/bobpay_and_basic_card_with_modifiers.js
+++ b/components/test/data/payments/bobpay_and_basic_card_with_modifiers.js
@@ -10,7 +10,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for basic-card.
  */
-function buy() {  // eslint-disable-line no-unused-vars
+function buy() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -54,7 +54,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for Bob Pay.
  */
-function buyWithBobPayDiscount() {  // eslint-disable-line no-unused-vars
+function buyWithBobPayDiscount() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -98,7 +98,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for basic-card with "credit" type
  */
-function creditSupportedType() {  // eslint-disable-line no-unused-vars
+function creditSupportedType() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -145,7 +145,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for basic-card with "debit" type
  */
-function debitSupportedType() {  // eslint-disable-line no-unused-vars
+function debitSupportedType() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -192,7 +192,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for basic-card with "credit" type and "visa" network
  */
-function visaSupportedNetwork() {  // eslint-disable-line no-unused-vars
+function visaSupportedNetwork() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -241,7 +241,7 @@
  * methods and a modifier for basic-card with "credit" type and " mastercard"
  * network
  */
-function mastercardSupportedNetwork() {  // eslint-disable-line no-unused-vars
+function mastercardSupportedNetwork() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -289,7 +289,7 @@
  * Launches the PaymentRequest UI with Bob Pay and basic-card as payment
  * methods and a modifier for basic-card with "mastercard" network.
  */
-function mastercardAnySupportedType() {  // eslint-disable-line no-unused-vars
+function mastercardAnySupportedType() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest(
         [
@@ -337,7 +337,7 @@
  * modifier for basic-card with "mastercard" network, but the modifier does not
  * have a total specified.
  */
-function noTotal() {  // eslint-disable-line no-unused-vars
+function noTotal() { // eslint-disable-line no-unused-vars
   try {
     new PaymentRequest([{supportedMethods: 'basic-card'}], {
       total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}},
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 99b18d1..364c9bec 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -568,6 +568,22 @@
     RenderPassId remapped_pass_id = RemapPassId(source.id, surface_id);
 
     gfx::Rect output_rect = source.output_rect;
+
+    // TODO(ericrk): This is incorrect in the non de-jelly case as well, but we
+    // restrict the fix to de-jelly for merge safety. Implement a full fix.
+    // crbug.com/1016677
+    if (de_jelly_enabled_) {
+      if (referenced_passes[j] == render_pass_list.back()) {
+        DCHECK(!merge_pass);
+        // We are processing the root RenderPass. If this does not produce the
+        // full SurfaceDrawQuad, we will end up with errors in the
+        // !|merge_pass| path below. Expand the RenderPass.
+        gfx::Rect scaled_rect(gfx::ScaleToEnclosingRect(
+            source_rect, layer_to_content_scale_x, layer_to_content_scale_y));
+        output_rect.Union(scaled_rect);
+      }
+    }
+
     if (max_texture_size_ > 0) {
       output_rect.set_width(std::min(output_rect.width(), max_texture_size_));
       output_rect.set_height(std::min(output_rect.height(), max_texture_size_));
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index c654fea0..3d393cff 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1314,6 +1314,8 @@
     "picture_in_picture/picture_in_picture_window_controller_impl.h",
     "portal/portal.cc",
     "portal/portal.h",
+    "portal/portal_navigation_throttle.cc",
+    "portal/portal_navigation_throttle.h",
     "presentation/presentation_service_impl.cc",
     "presentation/presentation_service_impl.h",
     "process_internals/process_internals_handler_impl.cc",
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 8525910e..1975a79 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -1253,14 +1253,6 @@
     if (actual_origin_lock == expected_origin_lock)
       return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
 
-    // Allow about: pages to commit in a process that does not match the opaque
-    // origin's precursor information.
-    // TODO(acolwell): Remove this once process selection for about: URLs has
-    // been fixed to always match the precursor info.
-    if (url_origin.opaque() && (url.IsAboutBlank() || url.IsAboutSrcdoc()) &&
-        !actual_origin_lock.is_empty()) {
-      return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
-    }
     return CanCommitStatus::CANNOT_COMMIT_URL;
   }
 
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
index b2ffbf8..ba6ce71 100644
--- a/content/browser/devtools/protocol/emulation_handler.cc
+++ b/content/browser/devtools/protocol/emulation_handler.cc
@@ -336,11 +336,15 @@
 void EmulationHandler::SetDeviceEmulationParams(
     const blink::WebDeviceEmulationParams& params) {
   bool enabled = params != blink::WebDeviceEmulationParams();
-  if (params != device_emulation_params_) {
-    device_emulation_enabled_ = enabled;
-    device_emulation_params_ = params;
-    UpdateDeviceEmulationState();
-  }
+  bool enable_changed = enabled != device_emulation_enabled_;
+  bool params_changed = params != device_emulation_params_;
+  if (!device_emulation_enabled_ && !enable_changed)
+    return;  // Still disabled.
+  if (!enable_changed && !params_changed)
+    return;  // Nothing changed.
+  device_emulation_enabled_ = enabled;
+  device_emulation_params_ = params;
+  UpdateDeviceEmulationState();
 }
 
 WebContentsImpl* EmulationHandler::GetWebContents() {
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
index fad4d2a..a00fa28 100644
--- a/content/browser/frame_host/frame_tree_node.cc
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -640,7 +640,7 @@
     return true;
   return render_manager_.current_frame_host()
       ->GetRenderWidgetHost()
-      ->ConsumePendingUserActivationIfAllowed();
+      ->RemovePendingUserActivationIfAvailable();
 }
 
 bool FrameTreeNode::UpdateUserActivationState(
diff --git a/content/browser/frame_host/navigation_throttle_runner.cc b/content/browser/frame_host/navigation_throttle_runner.cc
index 8727dc0..972ba307 100644
--- a/content/browser/frame_host/navigation_throttle_runner.cc
+++ b/content/browser/frame_host/navigation_throttle_runner.cc
@@ -14,6 +14,7 @@
 #include "content/browser/frame_host/navigator_delegate.h"
 #include "content/browser/frame_host/origin_policy_throttle.h"
 #include "content/browser/frame_host/webui_navigation_throttle.h"
+#include "content/browser/portal/portal_navigation_throttle.h"
 #include "content/public/browser/navigation_handle.h"
 
 namespace content {
@@ -116,6 +117,9 @@
   // Handle Origin Policy (if enabled)
   AddThrottle(OriginPolicyThrottle::MaybeCreateThrottleFor(handle_));
 
+  // Block certain requests that are not permitted for portals.
+  AddThrottle(PortalNavigationThrottle::MaybeCreateThrottleFor(handle_));
+
   for (auto& throttle :
        devtools_instrumentation::CreateNavigationThrottles(handle_)) {
     AddThrottle(std::move(throttle));
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 9d4a5c3..1b60ff7 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1678,8 +1678,6 @@
                         OnUpdateUserActivationState)
     IPC_MESSAGE_HANDLER(FrameHostMsg_SetHasReceivedUserGestureBeforeNavigation,
                         OnSetHasReceivedUserGestureBeforeNavigation)
-    IPC_MESSAGE_HANDLER(FrameHostMsg_SetNeedsOcclusionTracking,
-                        OnSetNeedsOcclusionTracking);
     IPC_MESSAGE_HANDLER(FrameHostMsg_ScrollRectToVisibleInParentFrame,
                         OnScrollRectToVisibleInParentFrame)
     IPC_MESSAGE_HANDLER(FrameHostMsg_BubbleLogicalScrollInParentFrame,
@@ -3519,6 +3517,24 @@
   delegate_->DidContainInsecureFormAction();
 }
 
+void RenderFrameHostImpl::SetNeedsOcclusionTracking(bool needs_tracking) {
+  // Don't process the IPC if this RFH is pending deletion.  See also
+  // https://crbug.com/972566.
+  if (!is_active())
+    return;
+
+  RenderFrameProxyHost* proxy =
+      frame_tree_node()->render_manager()->GetProxyToParent();
+  if (!proxy) {
+    bad_message::ReceivedBadMessage(GetProcess(),
+                                    bad_message::RFH_NO_PROXY_TO_PARENT);
+    return;
+  }
+
+  proxy->Send(new FrameMsg_SetNeedsOcclusionTracking(proxy->GetRoutingID(),
+                                                     needs_tracking));
+}
+
 void RenderFrameHostImpl::LifecycleStateChanged(
     blink::mojom::FrameLifecycleState state) {
   frame_lifecycle_state_ = state;
@@ -4051,24 +4067,6 @@
   frame_tree_node_->OnSetHasReceivedUserGestureBeforeNavigation(value);
 }
 
-void RenderFrameHostImpl::OnSetNeedsOcclusionTracking(bool needs_tracking) {
-  // Don't process the IPC if this RFH is pending deletion.  See also
-  // https://crbug.com/972566.
-  if (!is_active())
-    return;
-
-  RenderFrameProxyHost* proxy =
-      frame_tree_node()->render_manager()->GetProxyToParent();
-  if (!proxy) {
-    bad_message::ReceivedBadMessage(GetProcess(),
-                                    bad_message::RFH_NO_PROXY_TO_PARENT);
-    return;
-  }
-
-  proxy->Send(new FrameMsg_SetNeedsOcclusionTracking(proxy->GetRoutingID(),
-                                                     needs_tracking));
-}
-
 void RenderFrameHostImpl::OnScrollRectToVisibleInParentFrame(
     const gfx::Rect& rect_to_scroll,
     const blink::WebScrollIntoViewParams& params) {
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index ac28986..e8b6b30 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1216,6 +1216,7 @@
                                  bool user_gesture) override;
   void DidDisplayInsecureContent() override;
   void DidContainInsecureFormAction() override;
+  void SetNeedsOcclusionTracking(bool needs_tracking) override;
 
  protected:
   friend class RenderFrameHostFactory;
diff --git a/content/browser/network_service_browsertest.cc b/content/browser/network_service_browsertest.cc
index 0055fa4b..f2631ab 100644
--- a/content/browser/network_service_browsertest.cc
+++ b/content/browser/network_service_browsertest.cc
@@ -80,7 +80,7 @@
   std::string GetSource() override { return "webui"; }
 
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const WebContents::Getter& wc_getter,
       const URLDataSource::GotDataCallback& callback) override {
     std::string dummy_html = "<html><body>Foo</body></html>";
diff --git a/content/browser/portal/portal_navigation_throttle.cc b/content/browser/portal/portal_navigation_throttle.cc
new file mode 100644
index 0000000..200203c
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle.cc
@@ -0,0 +1,70 @@
+// 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/portal/portal_navigation_throttle.h"
+
+#include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/portal/portal.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/navigation_handle.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+// static
+std::unique_ptr<PortalNavigationThrottle>
+PortalNavigationThrottle::MaybeCreateThrottleFor(
+    NavigationHandle* navigation_handle) {
+  if (!IsEnabled() || !navigation_handle->IsInMainFrame())
+    return nullptr;
+
+  return base::WrapUnique(new PortalNavigationThrottle(navigation_handle));
+}
+
+// static
+bool PortalNavigationThrottle::IsEnabled() {
+  return Portal::IsEnabled() &&
+         !base::FeatureList::IsEnabled(blink::features::kPortalsCrossOrigin);
+}
+
+PortalNavigationThrottle::PortalNavigationThrottle(
+    NavigationHandle* navigation_handle)
+    : NavigationThrottle(navigation_handle) {}
+
+PortalNavigationThrottle::~PortalNavigationThrottle() = default;
+
+const char* PortalNavigationThrottle::GetNameForLogging() {
+  return "PortalNavigationThrottle";
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillStartRequest() {
+  return WillStartOrRedirectRequest();
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillRedirectRequest() {
+  return WillStartOrRedirectRequest();
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillStartOrRedirectRequest() {
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(navigation_handle()->GetWebContents());
+  Portal* portal = web_contents->portal();
+  if (!portal)
+    return PROCEED;
+
+  url::Origin origin = url::Origin::Create(navigation_handle()->GetURL());
+  url::Origin first_party_origin =
+      portal->owner_render_frame_host()->GetLastCommittedOrigin();
+
+  return origin == first_party_origin ? PROCEED : BLOCK_REQUEST;
+}
+
+}  // namespace content
diff --git a/content/browser/portal/portal_navigation_throttle.h b/content/browser/portal/portal_navigation_throttle.h
new file mode 100644
index 0000000..50572869
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle.h
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
+#define CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
+
+#include <memory>
+
+#include "base/auto_reset.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/navigation_throttle.h"
+
+namespace content {
+
+// When enabled, restricts navigation within a portal main frame to only the
+// origin of its host. This allows a more limited testing mode of the portals
+// feature, in which third-party (cross-origin) content cannot be loaded.
+//
+// This throttle is enabled only when portals are enabled but third-party
+// portals are not, and provides enforcement of that state.
+//
+// Note: This has complicated interactions with portal activation, which are not
+// yet resolved. See https://crbug.com/1013389.
+class CONTENT_EXPORT PortalNavigationThrottle : public NavigationThrottle {
+ public:
+  static std::unique_ptr<PortalNavigationThrottle> MaybeCreateThrottleFor(
+      NavigationHandle* navigation_handle);
+
+  ~PortalNavigationThrottle() override;
+
+  // NavigationThrottle
+  const char* GetNameForLogging() override;
+  ThrottleCheckResult WillStartRequest() override;
+  ThrottleCheckResult WillRedirectRequest() override;
+
+ private:
+  static bool IsEnabled();
+
+  PortalNavigationThrottle(NavigationHandle* navigation_handle);
+
+  ThrottleCheckResult WillStartOrRedirectRequest();
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
diff --git a/content/browser/portal/portal_navigation_throttle_browsertest.cc b/content/browser/portal/portal_navigation_throttle_browsertest.cc
new file mode 100644
index 0000000..04caffb
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle_browsertest.cc
@@ -0,0 +1,251 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/portal/portal.h"
+#include "content/browser/portal/portal_created_observer.h"
+#include "content/browser/portal/portal_navigation_throttle.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+// TODO(jbroman): Perhaps this would be a useful utility generally.
+GURL GetServerRedirectURL(const net::EmbeddedTestServer* server,
+                          const std::string& hostname,
+                          const GURL& destination) {
+  return server->GetURL(
+      hostname,
+      "/server-redirect?" +
+          net::EscapeQueryParamValue(destination.spec(), /*use_plus=*/false));
+}
+
+class PortalNavigationThrottleBrowserTest : public ContentBrowserTest {
+ protected:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{blink::features::kPortals},
+        /*disabled_features=*/{blink::features::kPortalsCrossOrigin});
+    ContentBrowserTest::SetUp();
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ContentBrowserTest::SetUpOnMainThread();
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &net::test_server::HandlePrefixedRequest, "/notreached",
+        base::BindRepeating(
+            [](const net::test_server::HttpRequest& r)
+                -> std::unique_ptr<net::test_server::HttpResponse> {
+              ADD_FAILURE() << "/notreached was requested";
+              return nullptr;
+            })));
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  WebContentsImpl* GetWebContents() {
+    return static_cast<WebContentsImpl*>(shell()->web_contents());
+  }
+  RenderFrameHostImpl* GetMainFrame() {
+    return GetWebContents()->GetMainFrame();
+  }
+
+  Portal* InsertAndWaitForPortal(const GURL& url,
+                                 bool expected_to_succeed = true) {
+    TestNavigationObserver navigation_observer(url);
+    navigation_observer.StartWatchingNewWebContents();
+    PortalCreatedObserver portal_created_observer(GetMainFrame());
+    EXPECT_TRUE(ExecJs(
+        GetMainFrame(),
+        base::StringPrintf("var portal = document.createElement('portal');\n"
+                           "portal.src = '%s';\n"
+                           "document.body.appendChild(portal);",
+                           url.spec().c_str())));
+    Portal* portal = portal_created_observer.WaitUntilPortalCreated();
+    navigation_observer.StopWatchingNewWebContents();
+    navigation_observer.Wait();
+    EXPECT_EQ(navigation_observer.last_navigation_succeeded(),
+              expected_to_succeed);
+    return portal;
+  }
+
+  bool NavigatePortalViaSrcAttribute(Portal* portal,
+                                     const GURL& url,
+                                     int number_of_navigations) {
+    TestNavigationObserver navigation_observer(portal->GetPortalContents(),
+                                               number_of_navigations);
+    EXPECT_TRUE(
+        ExecJs(GetMainFrame(),
+               base::StringPrintf(
+                   "document.querySelector('body > portal').src = '%s';",
+                   url.spec().c_str())));
+    navigation_observer.WaitForNavigationFinished();
+    return navigation_observer.last_navigation_succeeded();
+  }
+
+  bool NavigatePortalViaLocationHref(Portal* portal,
+                                     const GURL& url,
+                                     int number_of_navigations) {
+    TestNavigationObserver navigation_observer(portal->GetPortalContents(),
+                                               number_of_navigations);
+    EXPECT_TRUE(ExecJs(
+        portal->GetPortalContents(),
+        base::StringPrintf("location.href = '%s';", url.spec().c_str())));
+    navigation_observer.WaitForNavigationFinished();
+    return navigation_observer.last_navigation_succeeded();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       SameOriginInitialNavigation) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+  EXPECT_NE(portal, nullptr);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       CrossOriginInitialNavigation) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("not.portal.test", "/title2.html"),
+      /*expected_to_succeed=*/false);
+  EXPECT_NE(portal, nullptr);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       SameOriginNavigation) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("portal.test", "/title3.html");
+  EXPECT_TRUE(NavigatePortalViaSrcAttribute(portal, destination_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       SameOriginNavigationTriggeredByPortal) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("portal.test", "/title3.html");
+  EXPECT_TRUE(NavigatePortalViaLocationHref(portal, destination_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       SameOriginNavigationWithServerRedirect) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("portal.test", "/title3.html");
+  GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+                                           "portal.test", destination_url);
+  EXPECT_TRUE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       CrossOriginNavigation) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("not.portal.test", "/notreached");
+  EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, destination_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       CrossOriginNavigationTriggeredByPortal) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("not.portal.test", "/notreached");
+  EXPECT_FALSE(NavigatePortalViaLocationHref(portal, destination_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       CrossOriginNavigationViaRedirect) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("not.portal.test", "/notreached");
+  GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+                                           "portal.test", destination_url);
+  EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+            destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+                       CrossOriginRedirectLeadingBack) {
+  ASSERT_TRUE(NavigateToURL(
+      GetWebContents(),
+      embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  Portal* portal = InsertAndWaitForPortal(
+      embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+  GURL destination_url =
+      embedded_test_server()->GetURL("portal.test", "/notreached");
+  GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+                                           "not.portal.test", destination_url);
+  EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+  EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(), redirect_url);
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 5855462..b85fe2d 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2736,7 +2736,7 @@
   pending_user_activation_timer_.Stop();
 }
 
-bool RenderWidgetHostImpl::ConsumePendingUserActivationIfAllowed() {
+bool RenderWidgetHostImpl::RemovePendingUserActivationIfAvailable() {
   if (pending_user_activation_counter_ > 0) {
     pending_user_activation_counter_--;
     return true;
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 889284ba..87b88bb 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -780,12 +780,11 @@
   // Marks all views in the frame tree as evicted.
   std::vector<viz::SurfaceId> CollectSurfaceIdsForEviction();
 
-  // This function validates a renderer's attempt to enable user activation on a
-  // frame. Gaining user activation is allowed if this widget had previously
-  // seen an input event (e.g., mousedown or keydown) that may lead to user
-  // activation; in this case, this "pending user activation" is consumed and
-  // this function returns true.  Otherwise, this function returns false.
-  bool ConsumePendingUserActivationIfAllowed();
+  // This function validates a renderer's attempt to activate frames. It
+  // removes one pending user activation if available and returns true;
+  // otherwise, it returns false.  See comments on
+  // Add/ClearPendingUserActivation() for details.
+  bool RemovePendingUserActivationIfAvailable();
 
  protected:
   // ---------------------------------------------------------------------------
@@ -1019,11 +1018,11 @@
 
   // The following functions are used to keep track of pending user activation
   // events, which are input events (e.g., mousedown or keydown) that allow a
-  // renderer to gain user activation.  AddPendingUserActivation() increments a
-  // counter of such events and sets a timer, which allows the renderer to claim
-  // user activation within |kActivationNotificationExpireTime| ms.
-  // ClearPendingUserActivation() clears the counter and is called after
-  // navigations or timeouts
+  // renderer to gain user activation.  AddPendingUserActivation() increments
+  // |pending_user_activation_counter_| and sets a timer, which allows the
+  // renderer to claim user activation within
+  // |kActivationNotificationExpireTime| ms.  ClearPendingUserActivation()
+  // clears the counter and is called after navigations or timeouts.
   void AddPendingUserActivation(const blink::WebInputEvent& event);
   void ClearPendingUserActivation();
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 9cb00ac9..18b38b7 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -2055,27 +2055,27 @@
 
   // One event allows one activation notification.
   SimulateMouseEvent(WebInputEvent::kMouseDown);
-  EXPECT_TRUE(host_->ConsumePendingUserActivationIfAllowed());
-  EXPECT_FALSE(host_->ConsumePendingUserActivationIfAllowed());
+  EXPECT_TRUE(host_->RemovePendingUserActivationIfAvailable());
+  EXPECT_FALSE(host_->RemovePendingUserActivationIfAvailable());
 
   // Mouse move and up does not increase pending user activation counter.
   SimulateMouseEvent(WebInputEvent::kMouseMove);
   SimulateMouseEvent(WebInputEvent::kMouseUp);
-  EXPECT_FALSE(host_->ConsumePendingUserActivationIfAllowed());
+  EXPECT_FALSE(host_->RemovePendingUserActivationIfAvailable());
 
   // 2 events allow 2 activation notifications.
   SimulateMouseEvent(WebInputEvent::kMouseDown);
   SimulateKeyboardEvent(WebInputEvent::kKeyDown);
-  EXPECT_TRUE(host_->ConsumePendingUserActivationIfAllowed());
-  EXPECT_TRUE(host_->ConsumePendingUserActivationIfAllowed());
-  EXPECT_FALSE(host_->ConsumePendingUserActivationIfAllowed());
+  EXPECT_TRUE(host_->RemovePendingUserActivationIfAvailable());
+  EXPECT_TRUE(host_->RemovePendingUserActivationIfAvailable());
+  EXPECT_FALSE(host_->RemovePendingUserActivationIfAvailable());
 
   // Timer reset the pending activation.
   SimulateMouseEvent(WebInputEvent::kMouseDown);
   SimulateMouseEvent(WebInputEvent::kMouseDown);
   task_environment_.FastForwardBy(
       RenderWidgetHostImpl::kActivationNotificationExpireTime);
-  EXPECT_FALSE(host_->ConsumePendingUserActivationIfAllowed());
+  EXPECT_FALSE(host_->RemovePendingUserActivationIfAvailable());
 }
 
 // Tests that fling events are not dispatched when the wheel event is consumed.
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 546acdf3..ef7f537 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -6616,13 +6616,13 @@
   // notification, and it has user activation.
   EXPECT_FALSE(root->current_frame_host()
                    ->GetRenderWidgetHost()
-                   ->ConsumePendingUserActivationIfAllowed());
+                   ->RemovePendingUserActivationIfAvailable());
   EXPECT_TRUE(root->HasTransientUserActivation());
   // Child frame doesn't have allowed_activation state set, and does not have
   // user activation.
   EXPECT_FALSE(child->current_frame_host()
                    ->GetRenderWidgetHost()
-                   ->ConsumePendingUserActivationIfAllowed());
+                   ->RemovePendingUserActivationIfAvailable());
   EXPECT_FALSE(child->HasTransientUserActivation());
 
   // Clear the activation state.
@@ -6641,13 +6641,13 @@
   // the activation notification, and it has user activation.
   EXPECT_FALSE(child->current_frame_host()
                    ->GetRenderWidgetHost()
-                   ->ConsumePendingUserActivationIfAllowed());
+                   ->RemovePendingUserActivationIfAvailable());
   EXPECT_TRUE(child->HasTransientUserActivation());
   // Root frame doesn't have allowed_activation state set, but has user
   // activation because with UAv2, ancestor frames get activated as well.
   EXPECT_FALSE(root->current_frame_host()
                    ->GetRenderWidgetHost()
-                   ->ConsumePendingUserActivationIfAllowed());
+                   ->RemovePendingUserActivationIfAvailable());
   EXPECT_TRUE(root->HasTransientUserActivation());
 }
 
diff --git a/content/browser/webui/shared_resources_data_source.cc b/content/browser/webui/shared_resources_data_source.cc
index 35db9c8..114de1e 100644
--- a/content/browser/webui/shared_resources_data_source.cc
+++ b/content/browser/webui/shared_resources_data_source.cc
@@ -294,9 +294,10 @@
 }
 
 void SharedResourcesDataSource::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const WebContents::Getter& wc_getter,
     const URLDataSource::GotDataCallback& callback) {
+  const std::string path = URLDataSource::URLToRequestPath(url);
   std::string updated_path = path;
 #if defined(OS_CHROMEOS)
   // If this is a Polymer request and multiple Polymer versions are enabled,
diff --git a/content/browser/webui/shared_resources_data_source.h b/content/browser/webui/shared_resources_data_source.h
index 9a7c67d..07a4fed 100644
--- a/content/browser/webui/shared_resources_data_source.h
+++ b/content/browser/webui/shared_resources_data_source.h
@@ -22,7 +22,7 @@
   // URLDataSource implementation.
   std::string GetSource() override;
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const WebContents::Getter& wc_getter,
       const URLDataSource::GotDataCallback& callback) override;
   bool AllowCaching() override;
diff --git a/content/browser/webui/url_data_manager_backend.cc b/content/browser/webui/url_data_manager_backend.cc
index a9f0765..7360544b 100644
--- a/content/browser/webui/url_data_manager_backend.cc
+++ b/content/browser/webui/url_data_manager_backend.cc
@@ -182,17 +182,6 @@
   return true;
 }
 
-void URLDataManagerBackend::URLToRequestPath(const GURL& url,
-                                             std::string* path) {
-  const std::string& spec = url.possibly_invalid_spec();
-  const url::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
-  // + 1 to skip the slash at the beginning of the path.
-  int offset = parsed.CountCharactersBefore(url::Parsed::PATH, false) + 1;
-
-  if (offset < static_cast<int>(spec.size()))
-    path->assign(spec.substr(offset));
-}
-
 bool URLDataManagerBackend::IsValidNetworkErrorCode(int error_code) {
   std::unique_ptr<base::DictionaryValue> error_codes = net::GetNetConstants();
   const base::DictionaryValue* net_error_codes_dict = nullptr;
diff --git a/content/browser/webui/url_data_manager_backend.h b/content/browser/webui/url_data_manager_backend.h
index 0dabd1f..72c92d39 100644
--- a/content/browser/webui/url_data_manager_backend.h
+++ b/content/browser/webui/url_data_manager_backend.h
@@ -65,10 +65,6 @@
   // Returns whether |url| passes some sanity checks and is a valid GURL.
   static bool CheckURLIsValid(const GURL& url);
 
-  // Parse |url| to get the path which will be used to resolve the request. The
-  // path is the remaining portion after the scheme and hostname.
-  static void URLToRequestPath(const GURL& url, std::string* path);
-
   // Check if the given integer is a valid network error code.
   static bool IsValidNetworkErrorCode(int error_code);
 
diff --git a/content/browser/webui/web_ui_data_source_impl.cc b/content/browser/webui/web_ui_data_source_impl.cc
index aca4cf80..a1d40a0 100644
--- a/content/browser/webui/web_ui_data_source_impl.cc
+++ b/content/browser/webui/web_ui_data_source_impl.cc
@@ -68,10 +68,10 @@
     return parent_->GetMimeType(path);
   }
   void StartDataRequest(
-      const std::string& path,
+      const GURL& url,
       const WebContents::Getter& wc_getter,
       const URLDataSource::GotDataCallback& callback) override {
-    return parent_->StartDataRequest(path, wc_getter, callback);
+    return parent_->StartDataRequest(url, wc_getter, callback);
   }
   bool ShouldReplaceExistingSource() override {
     return parent_->replace_existing_source_;
@@ -273,9 +273,10 @@
 }
 
 void WebUIDataSourceImpl::StartDataRequest(
-    const std::string& path,
+    const GURL& url,
     const WebContents::Getter& wc_getter,
     const URLDataSource::GotDataCallback& callback) {
+  const std::string path = URLDataSource::URLToRequestPath(url);
   if (!should_handle_request_callback_.is_null() &&
       should_handle_request_callback_.Run(path)) {
     filter_callback_.Run(path, callback);
diff --git a/content/browser/webui/web_ui_data_source_impl.h b/content/browser/webui/web_ui_data_source_impl.h
index 988f597..f64f9e5 100644
--- a/content/browser/webui/web_ui_data_source_impl.h
+++ b/content/browser/webui/web_ui_data_source_impl.h
@@ -83,7 +83,7 @@
   // Methods that match URLDataSource which are called by
   // InternalDataSource.
   std::string GetMimeType(const std::string& path) const;
-  void StartDataRequest(const std::string& path,
+  void StartDataRequest(const GURL& url,
                         const WebContents::Getter& wc_getter,
                         const URLDataSource::GotDataCallback& callback);
 
diff --git a/content/browser/webui/web_ui_data_source_unittest.cc b/content/browser/webui/web_ui_data_source_unittest.cc
index 63d870c..d330d4e 100644
--- a/content/browser/webui/web_ui_data_source_unittest.cc
+++ b/content/browser/webui/web_ui_data_source_unittest.cc
@@ -55,7 +55,8 @@
 
   void StartDataRequest(const std::string& path,
                         const URLDataSource::GotDataCallback& callback) {
-    source_->StartDataRequest(path, WebContents::Getter(), callback);
+    source_->StartDataRequest(GURL("https://any-host/" + path),
+                              WebContents::Getter(), callback);
   }
 
   std::string GetMimeType(const std::string& path) const {
diff --git a/content/browser/webui/web_ui_url_loader_factory.cc b/content/browser/webui/web_ui_url_loader_factory.cc
index 5c152c58..add56cd 100644
--- a/content/browser/webui/web_ui_url_loader_factory.cc
+++ b/content/browser/webui/web_ui_url_loader_factory.cc
@@ -163,9 +163,7 @@
     return;
   }
 
-  std::string path;
-  URLDataManagerBackend::URLToRequestPath(request.url, &path);
-
+  std::string path = URLDataSource::URLToRequestPath(request.url);
   std::string origin_header;
   request.headers.GetHeader(net::HttpRequestHeaders::kOrigin, &origin_header);
 
@@ -203,7 +201,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> target_runner =
       source->source()->TaskRunnerForRequestPath(path);
   if (!target_runner) {
-    source->source()->StartDataRequest(path, std::move(wc_getter),
+    source->source()->StartDataRequest(request.url, std::move(wc_getter),
                                        std::move(data_available_callback));
     return;
   }
@@ -213,7 +211,7 @@
   target_runner->PostTask(
       FROM_HERE,
       base::BindOnce(&URLDataSource::StartDataRequest,
-                     base::Unretained(source->source()), path,
+                     base::Unretained(source->source()), request.url,
                      std::move(wc_getter), std::move(data_available_callback)));
 }
 
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 594f554..c098924 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -1238,12 +1238,6 @@
                     gfx::Rect /* compositor_visible_rect */,
                     blink::FrameOcclusionState /* occlusion_state */)
 
-// Indicates that a child frame requires its parent frame to send it information
-// about whether it is occluded or has visual effects applied, in order to
-// service IntersectionObserver's that track visibility.
-IPC_MESSAGE_ROUTED1(FrameHostMsg_SetNeedsOcclusionTracking,
-                    bool /* needs_tracking */)
-
 // Informs the child that the frame has changed visibility.
 IPC_MESSAGE_ROUTED1(FrameHostMsg_VisibilityChanged,
                     blink::mojom::FrameVisibility /* visibility */)
diff --git a/content/public/browser/url_data_source.cc b/content/public/browser/url_data_source.cc
index 35a916f..0cc65ec 100644
--- a/content/public/browser/url_data_source.cc
+++ b/content/public/browser/url_data_source.cc
@@ -52,6 +52,19 @@
       std::move(callback));
 }
 
+// static
+std::string URLDataSource::URLToRequestPath(const GURL& url) {
+  const std::string& spec = url.possibly_invalid_spec();
+  const url::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
+  // + 1 to skip the slash at the beginning of the path.
+  int offset = parsed.CountCharactersBefore(url::Parsed::PATH, false) + 1;
+
+  if (offset < static_cast<int>(spec.size()))
+    return spec.substr(offset);
+
+  return std::string();
+}
+
 scoped_refptr<base::SingleThreadTaskRunner>
 URLDataSource::TaskRunnerForRequestPath(const std::string& path) {
   return base::CreateSingleThreadTaskRunner({BrowserThread::UI});
diff --git a/content/public/browser/url_data_source.h b/content/public/browser/url_data_source.h
index e5d6ea8..ed64db6 100644
--- a/content/public/browser/url_data_source.h
+++ b/content/public/browser/url_data_source.h
@@ -43,6 +43,11 @@
                               const GURL& url,
                               base::OnceCallback<void(URLDataSource*)>);
 
+  // Parse |url| to get the path which will be used to resolve the request. The
+  // path is the remaining portion after the scheme and hostname, without the
+  // leading slash.
+  static std::string URLToRequestPath(const GURL& url);
+
   virtual ~URLDataSource() {}
 
   // The name of this source.
@@ -62,14 +67,14 @@
   // Must be called on the task runner specified by TaskRunnerForRequestPath,
   // or the IO thread if TaskRunnerForRequestPath returns nullptr.
   //
-  // Called by URLDataSource to request data at |path|. The string parameter is
-  // the path of the request. The child class should run |callback| when the
-  // data is available or if the request could not be satisfied. This can be
-  // called either in this callback or asynchronously with the response.
-  // |wc_getter| can be called on the UI thread to return the WebContents for
-  // this request if it originates from a render frame. If it originated from a
-  // worker or if the frame has destructed it will return null.
-  virtual void StartDataRequest(const std::string& path,
+  // Called by URLDataSource to request data at |url|. The child class should
+  // run |callback| when the data is available or if the request could not be
+  // satisfied. This can be called either in this callback or asynchronously
+  // with the response. |wc_getter| can be called on the UI thread to return the
+  // WebContents for this request if it originates from a render frame. If it
+  // originated from a worker or if the frame has destructed it will return
+  // null.
+  virtual void StartDataRequest(const GURL& url,
                                 const WebContents::Getter& wc_getter,
                                 const GotDataCallback& callback) = 0;
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 337a80b..1244105 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4648,10 +4648,6 @@
   GetLocalRootRenderWidget()->SetMouseCapture(capture);
 }
 
-void RenderFrameImpl::SetNeedsOcclusionTracking(bool needs_tracking) {
-  Send(new FrameHostMsg_SetNeedsOcclusionTracking(routing_id_, needs_tracking));
-}
-
 void RenderFrameImpl::LifecycleStateChanged(
     blink::mojom::FrameLifecycleState state) {
   GetFrameHost()->LifecycleStateChanged(state);
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 5664362..cf244be 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -722,7 +722,6 @@
   void UpdateUserActivationState(
       blink::UserActivationUpdateType update_type) override;
   void SetHasReceivedUserGestureBeforeNavigation(bool value) override;
-  void SetNeedsOcclusionTracking(bool needs_tracking) override;
   void LifecycleStateChanged(blink::mojom::FrameLifecycleState state) override;
   void SetMouseCapture(bool capture) override;
   bool ShouldReportDetailedMessageForSource(
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 02b95ce..d3066da3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -991,6 +991,7 @@
     "../browser/pointer_lock_browsertest.h",
     "../browser/pointer_lock_browsertest_mac.mm",
     "../browser/portal/portal_browsertest.cc",
+    "../browser/portal/portal_navigation_throttle_browsertest.cc",
     "../browser/power_monitor_browsertest.cc",
     "../browser/process_internals/process_internals_browsertest.cc",
     "../browser/renderer_host/input/autoscroll_browsertest.cc",
diff --git a/content/test/content_test_launcher.cc b/content/test/content_test_launcher.cc
index 7fd5bcb2..37aec6f6 100644
--- a/content/test/content_test_launcher.cc
+++ b/content/test/content_test_launcher.cc
@@ -39,10 +39,9 @@
 
  protected:
   void Initialize() override {
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals. (Must run
+    // before the base class is initialized.)
     base::TestSuite::DisableCheckForLeakedGlobals();
-    base::TestSuite::DisableCheckForThreadPriorityAtTestEnd();
 
     ContentTestSuiteBase::Initialize();
 
diff --git a/docs/android_build_instructions.md b/docs/android_build_instructions.md
index bd226689..2f368e1 100644
--- a/docs/android_build_instructions.md
+++ b/docs/android_build_instructions.md
@@ -363,49 +363,31 @@
    * What it does: Disables ProGuard (slow build step)
 
 #### Incremental Install
-"Incremental install" uses reflection and side-loading to speed up the edit
-& deploy cycle (normally < 10 seconds). The initial launch of the apk will be
-a little slower since updated dex files are installed manually.
+[Incremental Install](/build/android/incremental_install/README.md) uses
+reflection and side-loading to speed up the edit & deploy cycle (normally < 10
+seconds). The initial launch of the apk will be a lot slower on older Android
+versions (pre-N) where the OS needs to pre-optimize the side-loaded files, but
+then be only marginally slower after the first launch.
 
-*   All apk targets have \*`_incremental` targets defined (e.g.
-    `chrome_public_apk_incremental`) except for Webview and Monochrome
+To enable Incremental Install, add the gn args:
 
-Here's an example:
-
-```shell
-autoninja -C out/Default chrome_public_apk_incremental
-out/Default/bin/chrome_public_apk install --incremental --verbose
+```gn
+incremental_install = true
+disable_incremental_isolated_processes = true
 ```
 
-For gunit tests (note that run_*_incremental automatically add
-`--fast-local-dev` when calling `test_runner.py`):
+Some APKs (e.g. WebView) do not work with incremental install, and are
+blacklisted from being built as such via `never_incremental = true`.
 
-```shell
-autoninja -C out/Default base_unittests_incremental
-out/Default/bin/run_base_unittests_incremental
-```
-
-For instrumentation tests:
-
-```shell
-autoninja -C out/Default chrome_public_test_apk_incremental
-out/Default/bin/run_chrome_public_test_apk_incremental
-```
-
-To uninstall:
+**Bug:** Sometimes Android does not notice changes to dex files, and tries to
+use prior versions. If you see impossible sounding dex exceptions, try
+uninstalling then re-installing:
 
 ```shell
 out/Default/bin/chrome_public_apk uninstall
+out/Default/bin/chrome_public_apk run
 ```
 
-To avoid typing `_incremental` when building targets, you can use the GN arg:
-
-```gn
-incremental_apk_by_default = true
-```
-
-This will make `chrome_public_apk` build in incremental mode.
-
 ## Installing and Running Chromium on an Emulator
 
 Running on an emulator is the same as on a device. Refer to
diff --git a/extensions/shell/test/shell_test_launcher_delegate.cc b/extensions/shell/test/shell_test_launcher_delegate.cc
index d0a9430e..2f680e4 100644
--- a/extensions/shell/test/shell_test_launcher_delegate.cc
+++ b/extensions/shell/test/shell_test_launcher_delegate.cc
@@ -13,10 +13,8 @@
 
 int AppShellTestLauncherDelegate::RunTestSuite(int argc, char** argv) {
   base::TestSuite test_suite(argc, argv);
-  // Browser tests are expected not to tear-down various globals and may
-  // complete with the thread priority being above NORMAL.
+  // Browser tests are expected not to tear-down various globals.
   test_suite.DisableCheckForLeakedGlobals();
-  test_suite.DisableCheckForThreadPriorityAtTestEnd();
   return test_suite.Run();
 }
 
diff --git a/fuchsia/engine/test/web_engine_test_launcher.cc b/fuchsia/engine/test/web_engine_test_launcher.cc
index f739f25..169667f 100644
--- a/fuchsia/engine/test/web_engine_test_launcher.cc
+++ b/fuchsia/engine/test/web_engine_test_launcher.cc
@@ -24,10 +24,8 @@
   // content::TestLauncherDelegate implementation:
   int RunTestSuite(int argc, char** argv) override {
     base::TestSuite test_suite(argc, argv);
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals.
     test_suite.DisableCheckForLeakedGlobals();
-    test_suite.DisableCheckForThreadPriorityAtTestEnd();
     return test_suite.Run();
   }
 
diff --git a/gpu/angle_deqp_tests_main.cc b/gpu/angle_deqp_tests_main.cc
index 2c1351a..7567b6a 100644
--- a/gpu/angle_deqp_tests_main.cc
+++ b/gpu/angle_deqp_tests_main.cc
@@ -35,9 +35,8 @@
   angle::InitTestHarness(&argc, argv);
   base::TestSuite test_suite(argc, argv);
 
-  // The process and thread priorities are modified by
-  // StabilizeCPUForBenchmarking()/SetLowPriorityProcess().
-  test_suite.DisableCheckForThreadAndProcessPriority();
+  // The process priority is lowered by the constructor of tcu::ANGLEPlatform().
+  test_suite.DisableCheckForProcessPriority();
 
   int rt = base::LaunchUnitTestsSerially(
       argc, argv, base::BindOnce(&RunHelper, base::Unretained(&test_suite)));
diff --git a/gpu/angle_perftests_main.cc b/gpu/angle_perftests_main.cc
index 09a97d4..341410b 100644
--- a/gpu/angle_perftests_main.cc
+++ b/gpu/angle_perftests_main.cc
@@ -27,10 +27,6 @@
   ANGLEProcessPerfTestArgs(&argc, argv);
 
   base::TestSuite test_suite(argc, argv);
-
-  // The thread priority is modified by StabilizeCPUForBenchmarking().
-  test_suite.DisableCheckForThreadAndProcessPriority();
-
   int rt = base::LaunchUnitTestsSerially(
       argc, argv, base::BindOnce(&RunHelper, base::Unretained(&test_suite)));
   return rt;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
index 86a1c586..4199567 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
@@ -886,7 +886,8 @@
     // A SCANOUT image should not require copy.
     DCHECK(!image || image->ShouldBindOrCopy() == gl::GLImage::BIND);
     if (!image || !image->BindTexImage(target)) {
-      LOG(ERROR) << "CreateSharedImage: Failed to create image";
+      LOG(ERROR) << "CreateSharedImage: Failed to "
+                 << (image ? "bind" : "create") << " image";
       api->glDeleteTexturesFn(1, &service_id);
       return nullptr;
     }
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 0cd6c03..2ff94d5 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -2904,18 +2904,6 @@
       ]
     },
     {
-      "id":275,
-      "cr_bugs": [838725],
-      "description": "Disable AImageReader on ARM GPUs as its buggy.",
-      "os": {
-        "type": "android"
-      },
-      "gl_vendor": "ARM.*",
-      "features": [
-        "disable_aimagereader"
-      ]
-    },
-    {
       "id": 277,
       "description": "Direct composition path is buggy on certain AMD devices/drivers",
       "cr_bugs": [800950],
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 d10e5e2..dd1e489 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
@@ -165,13 +165,15 @@
 #endif
   if (!pixmap.get()) {
     LOG(ERROR) << "Failed to create pixmap " << size.ToString() << ", "
-               << gfx::BufferFormatToString(format);
+               << gfx::BufferFormatToString(format) << ", usage "
+               << gfx::BufferUsageToString(usage);
     return nullptr;
   }
   auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
   if (!image->Initialize(std::move(pixmap))) {
     LOG(ERROR) << "Failed to create GLImage " << size.ToString() << ", "
-               << gfx::BufferFormatToString(format);
+               << gfx::BufferFormatToString(format) << ", usage "
+               << gfx::BufferUsageToString(usage);
     return nullptr;
   }
   *is_cleared = true;
diff --git a/headless/test/headless_test_launcher.cc b/headless/test/headless_test_launcher.cc
index d0b1430..6d8ee1c 100644
--- a/headless/test/headless_test_launcher.cc
+++ b/headless/test/headless_test_launcher.cc
@@ -41,10 +41,8 @@
   // content::TestLauncherDelegate implementation:
   int RunTestSuite(int argc, char** argv) override {
     base::TestSuite test_suite(argc, argv);
-    // Browser tests are expected not to tear-down various globals and may
-    // complete with the thread priority being above NORMAL.
+    // Browser tests are expected not to tear-down various globals.
     test_suite.DisableCheckForLeakedGlobals();
-    test_suite.DisableCheckForThreadPriorityAtTestEnd();
     return test_suite.Run();
   }
 
diff --git a/infra/config/buckets/ci.star b/infra/config/buckets/ci.star
index 3f57a99..ef0c328b 100644
--- a/infra/config/buckets/ci.star
+++ b/infra/config/buckets/ci.star
@@ -337,10 +337,6 @@
 )
 
 android_builder(
-    name = 'android-jumbo-rel',
-)
-
-android_builder(
     name = 'android-kitkat-arm-rel',
     goma_backend = goma.backend.RBE_PROD,
 )
@@ -1063,11 +1059,6 @@
 )
 
 fyi_builder(
-    name = 'Jumbo Linux x64',
-    goma_backend = goma.backend.RBE_PROD,
-)
-
-fyi_builder(
     name = 'Linux Viz',
     goma_backend = goma.backend.RBE_PROD,
 )
@@ -1322,12 +1313,6 @@
   )
 
 fyi_mac_builder(
-    name = 'Jumbo Mac',
-    cores = 4,
-    goma_backend = goma.backend.RBE_PROD,
-)
-
-fyi_mac_builder(
     name = 'Mac deterministic',
     cores = None,
     executable = luci.recipe(name = 'swarming/deterministic_build'),
@@ -1380,10 +1365,6 @@
 )
 
 fyi_windows_builder(
-    name = 'Jumbo Win x64',
-)
-
-fyi_windows_builder(
     name = 'win-annotator-rel',
     execution_timeout = 16 * time.hour,
 )
@@ -1971,10 +1952,6 @@
 )
 
 linux_builder(
-    name = 'linux-jumbo-rel',
-)
-
-linux_builder(
     name = 'linux-ozone-rel',
     goma_backend = goma.backend.RBE_PROD,
 )
@@ -2044,13 +2021,6 @@
     os = os.MAC_10_13,
 )
 
-mac_builder(
-    name = 'mac-jumbo-rel',
-    cores = 4,
-    os = os.MAC_ANY,
-)
-
-
 def mac_ios_builder(*, name, **kwargs):
   return mac_builder(
       name = name,
@@ -2278,11 +2248,6 @@
     execution_timeout = 6 * time.hour,
 )
 
-win_builder(
-    name = 'win-jumbo-rel',
-    os = os.WINDOWS_ANY,
-)
-
 
 def win_celab_builder(*, name, **kwargs):
   return win_builder(
diff --git a/infra/config/buckets/try.star b/infra/config/buckets/try.star
index e5c5afd..0c324b3 100644
--- a/infra/config/buckets/try.star
+++ b/infra/config/buckets/try.star
@@ -1246,10 +1246,6 @@
 )
 
 linux_builder(
-    name = 'linux-jumbo-rel',
-)
-
-linux_builder(
     name = 'linux-libfuzzer-asan-rel',
     executable = luci.recipe(name = 'chromium_libfuzzer_trybot'),
     tryjob = tryjob(),
@@ -1461,11 +1457,6 @@
   )
 
 mac_builder(
-    name = 'mac-jumbo-rel',
-    cores = 4,
-)
-
-mac_builder(
     name = 'mac-osxbeta-rel',
     builderless = True,
     os = os.MAC_DEFAULT,
@@ -1672,10 +1663,6 @@
 )
 
 win_builder(
-    name = 'win-jumbo-rel',
-)
-
-win_builder(
     name = 'win-libfuzzer-asan-rel',
     builderless = False,
     executable = luci.recipe(name = 'chromium_libfuzzer_trybot'),
diff --git a/infra/config/consoles/chromium.android.star b/infra/config/consoles/chromium.android.star
index b850cb6..a261c30a 100644
--- a/infra/config/consoles/chromium.android.star
+++ b/infra/config/consoles/chromium.android.star
@@ -58,10 +58,6 @@
             short_name = 'm',
         ),
         luci.console_view_entry(
-            builder = 'ci/android-jumbo-rel',
-            category = 'builder',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Android arm Builder (dbg)',
             category = 'builder|arm',
             short_name = '32',
diff --git a/infra/config/consoles/chromium.fyi.star b/infra/config/consoles/chromium.fyi.star
index 33d0e60..b423f87 100644
--- a/infra/config/consoles/chromium.fyi.star
+++ b/infra/config/consoles/chromium.fyi.star
@@ -57,16 +57,6 @@
             short_name = 'beta',
         ),
         luci.console_view_entry(
-            builder = 'ci/mac-jumbo-rel',
-            category = 'mac',
-            short_name = 'jmb',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/win-jumbo-rel',
-            category = 'win',
-            short_name = 'jmb',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Mac deterministic',
             category = 'deterministic|mac',
             short_name = 'rel',
@@ -199,18 +189,6 @@
             short_name = 'ios13',
         ),
         luci.console_view_entry(
-            builder = 'ci/Jumbo Linux x64',
-            category = 'jumbo',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Jumbo Mac',
-            category = 'jumbo',
-        ),
-        luci.console_view_entry(
-            builder = 'ci/Jumbo Win x64',
-            category = 'jumbo',
-        ),
-        luci.console_view_entry(
             builder = 'ci/linux-blink-animation-use-time-delta',
             category = 'linux|blink',
             short_name = 'TD',
diff --git a/infra/config/consoles/chromium.goma.migration.star b/infra/config/consoles/chromium.goma.migration.star
index 9b064c4f..cdab71a 100644
--- a/infra/config/consoles/chromium.goma.migration.star
+++ b/infra/config/consoles/chromium.goma.migration.star
@@ -315,11 +315,6 @@
             short_name = 'dbg',
         ),
         luci.console_view_entry(
-            builder = 'ci/Jumbo Linux x64',
-            category = 'week2d|linux',
-            short_name = 'jumbo',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Dawn Mac x64 Builder',
             category = 'week2d|mac|dawn',
         ),
@@ -362,11 +357,6 @@
             short_name = 'dbg',
         ),
         luci.console_view_entry(
-            builder = 'ci/Jumbo Mac',
-            category = 'week2d|mac',
-            short_name = 'jumbo',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Linux Builder',
             category = 'week2.5|linux',
         ),
@@ -498,11 +488,6 @@
             short_name = 'herm',
         ),
         luci.console_view_entry(
-            builder = 'ci/mac-jumbo-rel',
-            category = 'week3c|mac',
-            short_name = 'jumbo',
-        ),
-        luci.console_view_entry(
             builder = 'ci/mac-mojo-rel',
             category = 'week3c|mac',
             short_name = 'mojo',
diff --git a/infra/config/consoles/chromium.linux.star b/infra/config/consoles/chromium.linux.star
index ed2e5dd..cf5c2c0 100644
--- a/infra/config/consoles/chromium.linux.star
+++ b/infra/config/consoles/chromium.linux.star
@@ -19,11 +19,6 @@
             short_name = 'gcc',
         ),
         luci.console_view_entry(
-            builder = 'ci/linux-jumbo-rel',
-            category = 'release',
-            short_name = 'jmb',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Deterministic Linux',
             category = 'release',
             short_name = 'det',
diff --git a/infra/config/consoles/luci.chromium.try.star b/infra/config/consoles/luci.chromium.try.star
index 1685e7b..25cb08b 100644
--- a/infra/config/consoles/luci.chromium.try.star
+++ b/infra/config/consoles/luci.chromium.try.star
@@ -103,7 +103,6 @@
         'try/linux-blink-heap-concurrent-marking-tsan-rel',
         'try/linux-blink-heap-verification-try',
         'try/linux-chromeos-rel',
-        'try/linux-jumbo-rel',
         'try/linux-libfuzzer-asan-rel',
         'try/linux-ozone-rel',
         'try/linux_android_dbg_ng',
@@ -144,7 +143,6 @@
         'try/ios-simulator-xcode-clang',
         'try/ios13-sdk-simulator',
         'try/mac-angle-rel',
-        'try/mac-jumbo-rel',
         'try/mac-rel',
         'try/mac_chromium_10.10',
         'try/mac_chromium_10.12_rel_ng',
@@ -169,7 +167,6 @@
         'try/win-angle-deqp-rel-64',
         'try/win-angle-rel-32',
         'try/win-angle-rel-64',
-        'try/win-jumbo-rel',
         'try/win_archive',
         'try/win_chromium_compile_dbg_ng',
         'try/win_chromium_compile_rel_ng',
diff --git a/infra/config/consoles/main.star b/infra/config/consoles/main.star
index 973feb7f..068167d8 100644
--- a/infra/config/consoles/main.star
+++ b/infra/config/consoles/main.star
@@ -195,11 +195,6 @@
             short_name = 'gcc',
         ),
         luci.console_view_entry(
-            builder = 'ci/linux-jumbo-rel',
-            category = 'chromium.linux|release',
-            short_name = 'jmb',
-        ),
-        luci.console_view_entry(
             builder = 'ci/Deterministic Linux',
             category = 'chromium.linux|release',
             short_name = 'det',
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 409d6f6..b798d3f 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -2649,66 +2649,6 @@
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     >
     builders: <
-      name: "Jumbo Linux x64"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "ssd:0"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.fyi\""
-      >
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
-      name: "Jumbo Mac"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:Jumbo Mac"
-      dimensions: "cores:4"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.13"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.fyi\""
-      >
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
-      name: "Jumbo Win x64"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:Jumbo Win x64"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.fyi\""
-      >
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
       name: "KitKat Phone Tester (dbg)"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -7225,27 +7165,6 @@
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     >
     builders: <
-      name: "android-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "ssd:0"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"jobs\":150}"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.android\""
-      >
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
       name: "android-kitkat-arm-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -8754,27 +8673,6 @@
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     >
     builders: <
-      name: "linux-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "ssd:0"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$build/goma:{\"jobs\":500}"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.linux\""
-      >
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
       name: "linux-oor-cors-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -9094,25 +8992,6 @@
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     >
     builders: <
-      name: "mac-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:mac-jumbo-rel"
-      dimensions: "cores:4"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.mac\""
-      >
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
       name: "mac-mojo-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -9277,25 +9156,6 @@
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     >
     builders: <
-      name: "win-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:win-jumbo-rel"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows"
-      recipe: <
-        name: "chromium"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"chromium.win\""
-      >
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    >
-    builders: <
       name: "win-pixel-builder-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -13812,34 +13672,6 @@
       >
     >
     builders: <
-      name: "linux-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "ssd:0"
-      recipe: <
-        name: "chromium_trybot"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"tryserver.chromium.linux\""
-      >
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      caches: <
-        name: "win_toolchain"
-        path: "win_toolchain"
-      >
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      task_template_canary_percentage: <
-        value: 5
-      >
-    >
-    builders: <
       name: "linux-layout-tests-fragment-item"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -14839,33 +14671,6 @@
       >
     >
     builders: <
-      name: "mac-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builder:mac-jumbo-rel"
-      dimensions: "cores:4"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac"
-      recipe: <
-        name: "chromium_trybot"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"tryserver.chromium.mac\""
-      >
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      caches: <
-        name: "win_toolchain"
-        path: "win_toolchain"
-      >
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      task_template_canary_percentage: <
-        value: 5
-      >
-    >
-    builders: <
       name: "mac-osxbeta-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -15603,34 +15408,6 @@
       >
     >
     builders: <
-      name: "win-jumbo-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "ssd:0"
-      recipe: <
-        name: "chromium_trybot"
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/master"
-        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
-        properties_j: "mastername:\"tryserver.chromium.win\""
-      >
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      caches: <
-        name: "win_toolchain"
-        path: "win_toolchain"
-      >
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      task_template_canary_percentage: <
-        value: 5
-      >
-    >
-    builders: <
       name: "win-libfuzzer-asan-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 27f24c0..1cf2b53 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -400,10 +400,6 @@
     short_name: "m"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/android-jumbo-rel"
-    category: "builder"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Android arm Builder (dbg)"
     category: "builder|arm"
     short_name: "32"
@@ -2611,16 +2607,6 @@
     short_name: "beta"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/mac-jumbo-rel"
-    category: "mac"
-    short_name: "jmb"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/win-jumbo-rel"
-    category: "win"
-    short_name: "jmb"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Mac deterministic"
     category: "deterministic|mac"
     short_name: "rel"
@@ -2753,18 +2739,6 @@
     short_name: "ios13"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Jumbo Linux x64"
-    category: "jumbo"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Jumbo Mac"
-    category: "jumbo"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Jumbo Win x64"
-    category: "jumbo"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/linux-blink-animation-use-time-delta"
     category: "linux|blink"
     short_name: "TD"
@@ -4456,11 +4430,6 @@
     short_name: "dbg"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Jumbo Linux x64"
-    category: "week2d|linux"
-    short_name: "jumbo"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Dawn Mac x64 Builder"
     category: "week2d|mac|dawn"
   >
@@ -4503,11 +4472,6 @@
     short_name: "dbg"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Jumbo Mac"
-    category: "week2d|mac"
-    short_name: "jumbo"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Linux Builder"
     category: "week2.5|linux"
   >
@@ -4639,11 +4603,6 @@
     short_name: "herm"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/mac-jumbo-rel"
-    category: "week3c|mac"
-    short_name: "jumbo"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/mac-mojo-rel"
     category: "week3c|mac"
     short_name: "mojo"
@@ -6030,11 +5989,6 @@
     short_name: "gcc"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/linux-jumbo-rel"
-    category: "release"
-    short_name: "jmb"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Deterministic Linux"
     category: "release"
     short_name: "det"
@@ -8908,9 +8862,6 @@
     name: "buildbucket/luci.chromium.try/linux-chromeos-rel"
   >
   builders: <
-    name: "buildbucket/luci.chromium.try/linux-jumbo-rel"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.try/linux-libfuzzer-asan-rel"
   >
   builders: <
@@ -9031,9 +8982,6 @@
     name: "buildbucket/luci.chromium.try/mac-angle-rel"
   >
   builders: <
-    name: "buildbucket/luci.chromium.try/mac-jumbo-rel"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.try/mac-rel"
   >
   builders: <
@@ -9106,9 +9054,6 @@
     name: "buildbucket/luci.chromium.try/win-angle-rel-64"
   >
   builders: <
-    name: "buildbucket/luci.chromium.try/win-jumbo-rel"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.try/win_archive"
   >
   builders: <
@@ -9337,11 +9282,6 @@
     short_name: "gcc"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/linux-jumbo-rel"
-    category: "chromium.linux|release"
-    short_name: "jmb"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Deterministic Linux"
     category: "chromium.linux|release"
     short_name: "det"
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index fc64b784..166ad7b4 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -178,9 +178,6 @@
   triggers: "GPU Mac Builder"
   triggers: "GPU Win x64 Builder (dbg)"
   triggers: "GPU Win x64 Builder"
-  triggers: "Jumbo Linux x64"
-  triggers: "Jumbo Mac"
-  triggers: "Jumbo Win x64"
   triggers: "Leak Detection Linux"
   triggers: "Libfuzzer Upload Chrome OS ASan"
   triggers: "Libfuzzer Upload Linux32 ASan Debug"
@@ -325,7 +322,6 @@
   triggers: "android-cronet-x86-rel"
   triggers: "android-archive-dbg"
   triggers: "android-incremental-dbg"
-  triggers: "android-jumbo-rel"
   triggers: "android-kitkat-arm-rel"
   triggers: "android-marshmallow-arm64-rel"
   triggers: "android-mojo-webview-rel"
@@ -379,7 +375,6 @@
   triggers: "linux-code-coverage"
   triggers: "linux-archive-dbg"
   triggers: "linux-gcc-rel"
-  triggers: "linux-jumbo-rel"
   triggers: "linux-ozone-rel"
   triggers: "linux-archive-rel"
   triggers: "linux-fieldtrial-rel"
@@ -389,7 +384,6 @@
   triggers: "mac-code-coverage-generation"
   triggers: "mac-archive-dbg"
   triggers: "mac-hermetic-upgrade-rel"
-  triggers: "mac-jumbo-rel"
   triggers: "mac-mojo-rel"
   triggers: "mac-archive-rel"
   triggers: "WebRTC Chromium Android Builder"
@@ -399,7 +393,6 @@
   triggers: "win-annotator-rel"
   triggers: "win-asan"
   triggers: "win-archive-dbg"
-  triggers: "win-jumbo-rel"
   triggers: "win-archive-rel"
   triggers: "win-pixel-builder-rel"
   triggers: "win10-code-coverage" # TODO(crbug.com/1010732) Move when stable.
@@ -890,16 +883,6 @@
 }
 
 job {
-  id: "android-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "android-jumbo-rel"
-  }
-}
-
-job {
   id: "android-kitkat-arm-rel"
   acl_sets: "default"
   buildbucket: {
@@ -1930,16 +1913,6 @@
 }
 
 job {
-  id: "linux-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "linux-jumbo-rel"
-  }
-}
-
-job {
   id: "linux-ozone-rel"
   acl_sets: "default"
   buildbucket: {
@@ -2279,16 +2252,6 @@
 }
 
 job {
-  id: "mac-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "mac-jumbo-rel"
-  }
-}
-
-job {
   id: "mac-mojo-rel"
   acl_sets: "default"
   buildbucket: {
@@ -3224,16 +3187,6 @@
 }
 
 job {
-  id: "win-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "win-jumbo-rel"
-  }
-}
-
-job {
   id: "win-celab-builder-rel"
   acl_sets: "default"
   # Run this every six hours
@@ -3614,36 +3567,6 @@
 }
 
 job {
-  id: "Jumbo Linux x64"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Linux x64"
-  }
-}
-
-job {
-  id: "Jumbo Mac"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Mac"
-  }
-}
-
-job {
-  id: "Jumbo Win x64"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Win x64"
-  }
-}
-
-job {
   id: "Libfuzzer Upload Chrome OS ASan"
   acl_sets: "default"
   buildbucket: {
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index fc64b784..166ad7b4 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -178,9 +178,6 @@
   triggers: "GPU Mac Builder"
   triggers: "GPU Win x64 Builder (dbg)"
   triggers: "GPU Win x64 Builder"
-  triggers: "Jumbo Linux x64"
-  triggers: "Jumbo Mac"
-  triggers: "Jumbo Win x64"
   triggers: "Leak Detection Linux"
   triggers: "Libfuzzer Upload Chrome OS ASan"
   triggers: "Libfuzzer Upload Linux32 ASan Debug"
@@ -325,7 +322,6 @@
   triggers: "android-cronet-x86-rel"
   triggers: "android-archive-dbg"
   triggers: "android-incremental-dbg"
-  triggers: "android-jumbo-rel"
   triggers: "android-kitkat-arm-rel"
   triggers: "android-marshmallow-arm64-rel"
   triggers: "android-mojo-webview-rel"
@@ -379,7 +375,6 @@
   triggers: "linux-code-coverage"
   triggers: "linux-archive-dbg"
   triggers: "linux-gcc-rel"
-  triggers: "linux-jumbo-rel"
   triggers: "linux-ozone-rel"
   triggers: "linux-archive-rel"
   triggers: "linux-fieldtrial-rel"
@@ -389,7 +384,6 @@
   triggers: "mac-code-coverage-generation"
   triggers: "mac-archive-dbg"
   triggers: "mac-hermetic-upgrade-rel"
-  triggers: "mac-jumbo-rel"
   triggers: "mac-mojo-rel"
   triggers: "mac-archive-rel"
   triggers: "WebRTC Chromium Android Builder"
@@ -399,7 +393,6 @@
   triggers: "win-annotator-rel"
   triggers: "win-asan"
   triggers: "win-archive-dbg"
-  triggers: "win-jumbo-rel"
   triggers: "win-archive-rel"
   triggers: "win-pixel-builder-rel"
   triggers: "win10-code-coverage" # TODO(crbug.com/1010732) Move when stable.
@@ -890,16 +883,6 @@
 }
 
 job {
-  id: "android-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "android-jumbo-rel"
-  }
-}
-
-job {
   id: "android-kitkat-arm-rel"
   acl_sets: "default"
   buildbucket: {
@@ -1930,16 +1913,6 @@
 }
 
 job {
-  id: "linux-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "linux-jumbo-rel"
-  }
-}
-
-job {
   id: "linux-ozone-rel"
   acl_sets: "default"
   buildbucket: {
@@ -2279,16 +2252,6 @@
 }
 
 job {
-  id: "mac-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "mac-jumbo-rel"
-  }
-}
-
-job {
   id: "mac-mojo-rel"
   acl_sets: "default"
   buildbucket: {
@@ -3224,16 +3187,6 @@
 }
 
 job {
-  id: "win-jumbo-rel"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "win-jumbo-rel"
-  }
-}
-
-job {
   id: "win-celab-builder-rel"
   acl_sets: "default"
   # Run this every six hours
@@ -3614,36 +3567,6 @@
 }
 
 job {
-  id: "Jumbo Linux x64"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Linux x64"
-  }
-}
-
-job {
-  id: "Jumbo Mac"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Mac"
-  }
-}
-
-job {
-  id: "Jumbo Win x64"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Jumbo Win x64"
-  }
-}
-
-job {
   id: "Libfuzzer Upload Chrome OS ASan"
   acl_sets: "default"
   buildbucket: {
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 4e228dc..e7c7c537 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -486,7 +486,7 @@
 // Enable pooling of SharedImageVideo objects for use by MCVD, to save a hop to
 // the GPU main thread during VideoFrame construction.
 const base::Feature kUsePooledSharedImageVideoProvider{
-    "UsePooledSharedImageVideoProvider", base::FEATURE_DISABLED_BY_DEFAULT};
+    "UsePooledSharedImageVideoProvider", base::FEATURE_ENABLED_BY_DEFAULT};
 
 #endif  // defined(OS_ANDROID)
 
diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc
index f6294341..41265b2 100644
--- a/net/cookies/canonical_cookie_unittest.cc
+++ b/net/cookies/canonical_cookie_unittest.cc
@@ -583,7 +583,7 @@
 TEST(CanonicalCookieTest, IncludeForRequestURL) {
   GURL url("http://www.example.com");
   base::Time creation_time = base::Time::Now();
-  CookieOptions options;
+  CookieOptions options = CookieOptions::MakeAllInclusive();
   base::Optional<base::Time> server_time = base::nullopt;
 
   std::unique_ptr<CanonicalCookie> cookie(
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 39c8326..aa3e89a 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -206,22 +206,22 @@
         cm,
         std::make_unique<CanonicalCookie>(
             "dom_1", "A", ".harvard.edu", "/", base::Time(), base::Time(),
-            base::Time(), false, false, CookieSameSite::NO_RESTRICTION,
+            base::Time(), false, false, CookieSameSite::LAX_MODE,
             COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
     EXPECT_TRUE(this->SetCanonicalCookie(
         cm,
         std::make_unique<CanonicalCookie>(
             "dom_2", "B", ".math.harvard.edu", "/", base::Time(), base::Time(),
-            base::Time(), false, false, CookieSameSite::NO_RESTRICTION,
+            base::Time(), false, false, CookieSameSite::LAX_MODE,
             COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
     EXPECT_TRUE(this->SetCanonicalCookie(
         cm,
         std::make_unique<CanonicalCookie>(
             "dom_3", "C", ".bourbaki.math.harvard.edu", "/", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     // Host cookies
@@ -229,22 +229,22 @@
         cm,
         std::make_unique<CanonicalCookie>(
             "host_1", "A", url_top_level_domain_plus_1, "/", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
     EXPECT_TRUE(this->SetCanonicalCookie(
         cm,
         std::make_unique<CanonicalCookie>(
             "host_2", "B", url_top_level_domain_plus_2, "/", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
     EXPECT_TRUE(this->SetCanonicalCookie(
         cm,
         std::make_unique<CanonicalCookie>(
             "host_3", "C", url_top_level_domain_plus_3, "/", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     // http_only cookie
@@ -252,8 +252,8 @@
         cm,
         std::make_unique<CanonicalCookie>(
             "httpo_check", "A", url_top_level_domain_plus_2, "/", base::Time(),
-            base::Time(), base::Time(), false, true,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, true, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     // same-site cookie
@@ -287,15 +287,15 @@
         cm,
         std::make_unique<CanonicalCookie>(
             "dom_path_1", "A", ".math.harvard.edu", "/dir1", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
     EXPECT_TRUE(this->SetCanonicalCookie(
         cm,
         std::make_unique<CanonicalCookie>(
             "dom_path_2", "B", ".math.harvard.edu", "/dir1/dir2", base::Time(),
-            base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE,
+            COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     // Host path cookies
@@ -304,7 +304,7 @@
         std::make_unique<CanonicalCookie>(
             "host_path_1", "A", url_top_level_domain_plus_2, "/dir1",
             base::Time(), base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     EXPECT_TRUE(this->SetCanonicalCookie(
@@ -312,7 +312,7 @@
         std::make_unique<CanonicalCookie>(
             "host_path_2", "B", url_top_level_domain_plus_2, "/dir1/dir2",
             base::Time(), base::Time(), base::Time(), false, false,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT),
         "http", true /*modify_httponly*/));
 
     EXPECT_EQ(14U, this->GetAllCookies(cm).size());
diff --git a/net/cookies/cookie_store_unittest.h b/net/cookies/cookie_store_unittest.h
index a488c72..b5224aaa 100644
--- a/net/cookies/cookie_store_unittest.h
+++ b/net/cookies/cookie_store_unittest.h
@@ -420,7 +420,7 @@
   std::unique_ptr<CanonicalCookie> cc(CanonicalCookie::CreateSanitizedCookie(
       this->www_foo_foo_.url(), "A", "B", std::string(), "/foo", one_hour_ago,
       one_hour_from_now, base::Time(), false, false,
-      CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT));
+      CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
   ASSERT_TRUE(cc);
   EXPECT_TRUE(this->SetCanonicalCookie(cs, std::move(cc), "https",
                                        true /*modify_httponly*/));
@@ -430,7 +430,7 @@
   cc = CanonicalCookie::CreateSanitizedCookie(
       this->www_foo_bar_.url(), "C", "D", this->www_foo_bar_.domain(), "/bar",
       two_hours_ago, base::Time(), one_hour_ago, false, true,
-      CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT);
+      CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT);
   ASSERT_TRUE(cc);
   EXPECT_TRUE(this->SetCanonicalCookie(cs, std::move(cc), "https",
                                        true /*modify_httponly*/));
@@ -547,7 +547,7 @@
       std::make_unique<CanonicalCookie>(
           "A", "B", foo_foo_host, "/foo", one_hour_ago, one_hour_from_now,
           base::Time(), false /* secure */, false /* httponly */,
-          CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+          CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT),
       "http", true));
   // Note that for the creation time to be set exactly, without modification,
   // it must be different from the one set by the line above.
@@ -555,7 +555,7 @@
       cs,
       std::make_unique<CanonicalCookie>(
           "C", "D", "." + foo_bar_domain, "/bar", two_hours_ago, base::Time(),
-          one_hour_ago, false, true, CookieSameSite::NO_RESTRICTION,
+          one_hour_ago, false, true, CookieSameSite::LAX_MODE,
           COOKIE_PRIORITY_DEFAULT),
       "http", true));
 
@@ -614,7 +614,7 @@
                 std::make_unique<CanonicalCookie>(
                     "G", "H", http_foo_host, "/unique", base::Time(),
                     base::Time(), base::Time(), false /* secure */,
-                    true /* httponly */, CookieSameSite::NO_RESTRICTION,
+                    true /* httponly */, CookieSameSite::LAX_MODE,
                     COOKIE_PRIORITY_DEFAULT),
                 "http", false /* modify_http_only */)
             .HasExclusionReason(
@@ -641,7 +641,7 @@
         std::make_unique<CanonicalCookie>(
             "G", "H", http_foo_host, "/unique", base::Time(), base::Time(),
             base::Time(), false /* secure */, true /* httponly */,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT),
         "http", true /* modify_http_only */));
 
     EXPECT_TRUE(
@@ -650,7 +650,7 @@
                 std::make_unique<CanonicalCookie>(
                     "G", "H", http_foo_host, "/unique", base::Time(),
                     base::Time(), base::Time(), false /* secure */,
-                    true /* httponly */, CookieSameSite::NO_RESTRICTION,
+                    true /* httponly */, CookieSameSite::LAX_MODE,
                     COOKIE_PRIORITY_DEFAULT),
                 "http", false /* modify_http_only */)
             .HasExclusionReason(
@@ -662,7 +662,7 @@
         std::make_unique<CanonicalCookie>(
             "G", "H", http_foo_host, "/unique", base::Time(), base::Time(),
             base::Time(), false /* secure */, true /* httponly */,
-            CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT),
+            CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT),
         "http", true /* modify_http_only */));
   }
 
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
index 3137059..d8a1ff7 100644
--- a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
+++ b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
@@ -1239,7 +1239,7 @@
       GURL("ftp://subdomain.ftperiffic.com/page"), "A=B; max-age=3600",
       base::Time::Now(), base::nullopt /* server_time */);
   cookie_monster->SetCanonicalCookieAsync(
-      std::move(cookie), "ftp", CookieOptions(),
+      std::move(cookie), "ftp", CookieOptions::MakeAllInclusive(),
       base::BindOnce(&ResultSavingCookieCallback<
                          CanonicalCookie::CookieInclusionStatus>::Run,
                      base::Unretained(&set_cookie_callback)));
@@ -1256,7 +1256,7 @@
         "A=B; max-age=3600", base::Time::Now(),
         base::nullopt /* server_time */);
     cookie_monster->SetCanonicalCookieAsync(
-        std::move(canonical_cookie), "http", CookieOptions(),
+        std::move(canonical_cookie), "http", CookieOptions::MakeAllInclusive(),
         base::BindOnce(&ResultSavingCookieCallback<
                            CanonicalCookie::CookieInclusionStatus>::Run,
                        base::Unretained(&set_cookie_callback2)));
@@ -1284,7 +1284,8 @@
   // Now try to get the cookie back.
   GetCookieListCallback get_callback;
   cookie_monster->GetCookieListWithOptionsAsync(
-      GURL("ftp://subdomain.ftperiffic.com/page"), CookieOptions(),
+      GURL("ftp://subdomain.ftperiffic.com/page"),
+      CookieOptions::MakeAllInclusive(),
       base::BindOnce(&GetCookieListCallback::Run,
                      base::Unretained(&get_callback)));
   get_callback.WaitUntilDone();
@@ -1311,7 +1312,7 @@
                                         "A=B; max-age=3600", base::Time::Now(),
                                         base::nullopt /* server_time */);
   cookie_monster->SetCanonicalCookieAsync(
-      std::move(cookie), "http", CookieOptions(),
+      std::move(cookie), "http", CookieOptions::MakeAllInclusive(),
       base::BindOnce(&ResultSavingCookieCallback<
                          CanonicalCookie::CookieInclusionStatus>::Run,
                      base::Unretained(&set_cookie_callback)));
diff --git a/net/reporting/reporting_uploader_unittest.cc b/net/reporting/reporting_uploader_unittest.cc
index f3e0edf..b1fb4ad 100644
--- a/net/reporting/reporting_uploader_unittest.cc
+++ b/net/reporting/reporting_uploader_unittest.cc
@@ -452,7 +452,7 @@
   auto cookie = CanonicalCookie::Create(url, "foo=bar", base::Time::Now(),
                                         base::nullopt /* server_time */);
   context_.cookie_store()->SetCanonicalCookieAsync(
-      std::move(cookie), url.scheme(), CookieOptions(),
+      std::move(cookie), url.scheme(), CookieOptions::MakeAllInclusive(),
       cookie_callback.MakeCallback());
   cookie_callback.WaitUntilDone();
   ASSERT_TRUE(cookie_callback.result().IsInclude());
@@ -485,7 +485,7 @@
 
   GetCookieListCallback cookie_callback;
   context_.cookie_store()->GetCookieListWithOptionsAsync(
-      server_.GetURL("/"), CookieOptions(),
+      server_.GetURL("/"), CookieOptions::MakeAllInclusive(),
       base::BindOnce(&GetCookieListCallback::Run,
                      base::Unretained(&cookie_callback)));
   cookie_callback.WaitUntilDone();
diff --git a/services/image_annotation/BUILD.gn b/services/image_annotation/BUILD.gn
index 67c1c3b..c9aa221 100644
--- a/services/image_annotation/BUILD.gn
+++ b/services/image_annotation/BUILD.gn
@@ -22,7 +22,6 @@
     "//services/data_decoder/public/mojom",
     "//services/image_annotation/public/mojom",
     "//services/network/public/cpp",
-    "//services/service_manager/public/cpp",
     "//url",
   ]
 }
@@ -33,13 +32,12 @@
     "image_annotation_service.h",
   ]
 
-  deps = [
+  public_deps = [
     ":lib",
     "//base",
+    "//services/data_decoder/public/mojom",
     "//services/image_annotation/public/mojom",
     "//services/network/public/cpp",
-    "//services/service_manager/public/cpp",
-    "//services/service_manager/public/mojom",
     "//url",
   ]
 }
@@ -65,6 +63,7 @@
     "//services/network/public/cpp",
     "//services/service_manager/public/cpp",
     "//services/service_manager/public/cpp/test:test_support",
+    "//services/service_manager/public/mojom",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
diff --git a/services/image_annotation/annotator.cc b/services/image_annotation/annotator.cc
index 7184916..cab23c2c 100644
--- a/services/image_annotation/annotator.cc
+++ b/services/image_annotation/annotator.cc
@@ -23,7 +23,6 @@
 #include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/image_annotation/image_annotation_metrics.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
-#include "services/service_manager/public/cpp/connector.h"
 #include "url/gurl.h"
 
 namespace image_annotation {
@@ -348,9 +347,9 @@
     const int batch_size,
     const double min_ocr_confidence,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    service_manager::Connector* const connector)
-    : url_loader_factory_(std::move(url_loader_factory)),
-      connector_(connector),
+    std::unique_ptr<Client> client)
+    : client_(std::move(client)),
+      url_loader_factory_(std::move(url_loader_factory)),
       server_request_timer_(
           FROM_HERE,
           throttle,
@@ -359,9 +358,7 @@
       server_url_(std::move(server_url)),
       api_key_(std::move(api_key)),
       batch_size_(batch_size),
-      min_ocr_confidence_(min_ocr_confidence) {
-  DCHECK(connector_);
-}
+      min_ocr_confidence_(min_ocr_confidence) {}
 
 Annotator::~Annotator() {
   // Report any clients still connected at service shutdown.
@@ -712,11 +709,8 @@
 
 data_decoder::mojom::JsonParser* Annotator::GetJsonParser() {
   if (!json_parser_) {
-    connector_->Connect(data_decoder::mojom::kServiceName,
-                        json_parser_.BindNewPipeAndPassReceiver());
-    json_parser_.set_disconnect_handler(base::BindOnce(
-        [](Annotator* const annotator) { annotator->json_parser_.reset(); },
-        base::Unretained(this)));
+    client_->BindJsonParser(json_parser_.BindNewPipeAndPassReceiver());
+    json_parser_.reset_on_disconnect();
   }
 
   return json_parser_.get();
diff --git a/services/image_annotation/annotator.h b/services/image_annotation/annotator.h
index a49deba..e6fcdd1 100644
--- a/services/image_annotation/annotator.h
+++ b/services/image_annotation/annotator.h
@@ -27,10 +27,6 @@
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "url/gurl.h"
 
-namespace service_manager {
-class Connector;
-}  // namespace service_manager
-
 namespace image_annotation {
 
 // The annotator communicates with the external image annotation server to
@@ -46,6 +42,14 @@
 // images) or image pixels to the external server.
 class Annotator : public mojom::Annotator {
  public:
+  class Client {
+   public:
+    virtual ~Client() {}
+
+    virtual void BindJsonParser(
+        mojo::PendingReceiver<data_decoder::mojom::JsonParser> receiver) = 0;
+  };
+
   // The HTTP request header in which the API key should be transmitted.
   static constexpr char kGoogApiKeyHeader[] = "X-Goog-Api-Key";
 
@@ -76,7 +80,7 @@
             int batch_size,
             double min_ocr_confidence,
             scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-            service_manager::Connector* connector);
+            std::unique_ptr<Client> client);
   ~Annotator() override;
 
   // Start providing behavior for the given Mojo receiver.
@@ -196,6 +200,8 @@
       const std::set<RequestKey>& request_keys,
       const std::map<std::string, mojom::AnnotateImageResultPtr>& results);
 
+  const std::unique_ptr<Client> client_;
+
   // Maps from request key to previously-obtained annotation results.
   // TODO(crbug.com/916420): periodically clear entries from this cache.
   std::map<RequestKey, mojom::AnnotateImageResultPtr> cached_results_;
@@ -232,8 +238,6 @@
 
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 
-  service_manager::Connector* const connector_;
-
   mojo::ReceiverSet<mojom::Annotator> receivers_;
 
   // Should not be used directly; GetJsonParser() should be called instead.
diff --git a/services/image_annotation/annotator_unittest.cc b/services/image_annotation/annotator_unittest.cc
index 7f5b7ef..ed310c1 100644
--- a/services/image_annotation/annotator_unittest.cc
+++ b/services/image_annotation/annotator_unittest.cc
@@ -381,6 +381,22 @@
   }
 }
 
+class TestAnnotatorClient : public Annotator::Client {
+ public:
+  explicit TestAnnotatorClient(service_manager::Connector* connector)
+      : connector_(connector) {}
+  ~TestAnnotatorClient() override = default;
+
+  // Annotator::Client implementation:
+  void BindJsonParser(mojo::PendingReceiver<data_decoder::mojom::JsonParser>
+                          receiver) override {
+    connector_->Connect(data_decoder::mojom::kServiceName, std::move(receiver));
+  }
+
+ private:
+  service_manager::Connector* const connector_;
+};
+
 }  // namespace
 
 // Test that annotation works for one client, and that the cache is populated.
@@ -395,7 +411,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
   TestImageProcessor processor;
 
   // First call performs original image annotation.
@@ -501,7 +518,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
   TestImageProcessor processor;
 
   base::Optional<mojom::AnnotateImageError> error;
@@ -608,7 +626,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
   TestImageProcessor processor;
 
   base::Optional<mojom::AnnotateImageError> error;
@@ -723,7 +742,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -779,7 +799,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -862,7 +883,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -956,7 +978,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -1046,7 +1069,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -1104,7 +1128,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -1179,7 +1204,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -1259,7 +1285,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[2];
   base::Optional<mojom::AnnotateImageError> error[2];
@@ -1328,7 +1355,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -1403,7 +1431,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       3 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -1490,7 +1519,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       3 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[2];
   base::Optional<mojom::AnnotateImageError> error[2];
@@ -1634,7 +1664,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[4];
   base::Optional<mojom::AnnotateImageError> error[4];
@@ -1742,7 +1773,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       3 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -1944,7 +1976,8 @@
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
       3 /* batch_size */, 1.0 /* min_ocr_confidence */,
-      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
+      test_url_factory.AsSharedURLLoaderFactory(),
+      std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -2138,10 +2171,11 @@
     TestServerURLLoaderFactory test_url_factory(
         "https://ia-pa.googleapis.com/v1/");
 
-    Annotator annotator(GURL(kTestServerUrl), "my_api_key", kThrottle,
-                        1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory(),
-                        test_dd_service.connector());
+    Annotator annotator(
+        GURL(kTestServerUrl), "my_api_key", kThrottle, 1 /* batch_size */,
+        1.0 /* min_ocr_confidence */,
+        test_url_factory.AsSharedURLLoaderFactory(),
+        std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, kDescLang, processor.GetPendingRemote(),
@@ -2171,11 +2205,11 @@
     TestServerURLLoaderFactory test_url_factory(
         "http://ia-pa.googleapis.com/v1/");
 
-    Annotator annotator(GURL("http://ia-pa.googleapis.com/v1/annotation"),
-                        "my_api_key", kThrottle, 1 /* batch_size */,
-                        1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory(),
-                        test_dd_service.connector());
+    Annotator annotator(
+        GURL("http://ia-pa.googleapis.com/v1/annotation"), "my_api_key",
+        kThrottle, 1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+        test_url_factory.AsSharedURLLoaderFactory(),
+        std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, kDescLang, processor.GetPendingRemote(),
@@ -2202,11 +2236,11 @@
   {
     TestServerURLLoaderFactory test_url_factory("https://datascraper.com/");
 
-    Annotator annotator(GURL("https://datascraper.com/annotation"),
-                        "my_api_key", kThrottle, 1 /* batch_size */,
-                        1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory(),
-                        test_dd_service.connector());
+    Annotator annotator(
+        GURL("https://datascraper.com/annotation"), "my_api_key", kThrottle,
+        1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+        test_url_factory.AsSharedURLLoaderFactory(),
+        std::make_unique<TestAnnotatorClient>(test_dd_service.connector()));
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, kDescLang, processor.GetPendingRemote(),
diff --git a/services/image_annotation/image_annotation_service.cc b/services/image_annotation/image_annotation_service.cc
index 7977b7c..4c4505f 100644
--- a/services/image_annotation/image_annotation_service.cc
+++ b/services/image_annotation/image_annotation_service.cc
@@ -22,31 +22,24 @@
 constexpr base::FeatureParam<double> ImageAnnotationService::kMinOcrConfidence;
 
 ImageAnnotationService::ImageAnnotationService(
-    service_manager::mojom::ServiceRequest request,
+    mojo::PendingReceiver<mojom::ImageAnnotationService> receiver,
     std::string api_key,
-    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory)
-    : service_binding_(this, std::move(request)),
+    scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
+    std::unique_ptr<Annotator::Client> annotator_client)
+    : receiver_(this, std::move(receiver)),
       annotator_(GURL(kServerUrl.Get()),
                  kApiKey.Get().empty() ? std::move(api_key) : kApiKey.Get(),
                  base::TimeDelta::FromMilliseconds(kThrottleMs.Get()),
                  kBatchSize.Get(),
                  kMinOcrConfidence.Get(),
                  shared_url_loader_factory,
-                 service_binding_.GetConnector()) {}
+                 std::move(annotator_client)) {}
 
 ImageAnnotationService::~ImageAnnotationService() = default;
 
-void ImageAnnotationService::OnStart() {
-  registry_.AddInterface<mojom::Annotator>(base::BindRepeating(
-      &Annotator::BindReceiver, base::Unretained(&annotator_)));
-}
-
-// service_manager::Service:
-void ImageAnnotationService::OnBindInterface(
-    const service_manager::BindSourceInfo& source_info,
-    const std::string& interface_name,
-    mojo::ScopedMessagePipeHandle interface_pipe) {
-  registry_.BindInterface(interface_name, std::move(interface_pipe));
+void ImageAnnotationService::BindAnnotator(
+    mojo::PendingReceiver<mojom::Annotator> receiver) {
+  annotator_.BindReceiver(std::move(receiver));
 }
 
 }  // namespace image_annotation
diff --git a/services/image_annotation/image_annotation_service.h b/services/image_annotation/image_annotation_service.h
index 61348ee..e504bdd4 100644
--- a/services/image_annotation/image_annotation_service.h
+++ b/services/image_annotation/image_annotation_service.h
@@ -12,24 +12,25 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/field_trial_params.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "services/data_decoder/public/mojom/json_parser.mojom.h"
 #include "services/image_annotation/annotator.h"
+#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_binding.h"
-#include "services/service_manager/public/mojom/service.mojom.h"
 
 namespace image_annotation {
 
-class ImageAnnotationService : public service_manager::Service {
+class ImageAnnotationService : public mojom::ImageAnnotationService {
  public:
   // Whether or not to override service parameters for experimentation.
   static const base::Feature kExperiment;
 
   ImageAnnotationService(
-      service_manager::mojom::ServiceRequest request,
+      mojo::PendingReceiver<mojom::ImageAnnotationService> receiver,
       std::string api_key,
-      scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);
+      scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
+      std::unique_ptr<Annotator::Client> annotator_client);
   ~ImageAnnotationService() override;
 
  private:
@@ -49,15 +50,10 @@
   static constexpr base::FeatureParam<double> kMinOcrConfidence{
       &kExperiment, "min_ocr_confidence", 0.7};
 
-  // service_manager::Service:
-  void OnBindInterface(const service_manager::BindSourceInfo& source_info,
-                       const std::string& interface_name,
-                       mojo::ScopedMessagePipeHandle interface_pipe) override;
-  void OnStart() override;
+  // mojom::ImageAnnotationService implementation:
+  void BindAnnotator(mojo::PendingReceiver<mojom::Annotator> receiver) override;
 
-  service_manager::BinderRegistry registry_;
-  service_manager::ServiceBinding service_binding_;
-
+  mojo::Receiver<mojom::ImageAnnotationService> receiver_;
   Annotator annotator_;
 
   DISALLOW_COPY_AND_ASSIGN(ImageAnnotationService);
diff --git a/services/image_annotation/public/cpp/BUILD.gn b/services/image_annotation/public/cpp/BUILD.gn
index a60d2773..ca63856 100644
--- a/services/image_annotation/public/cpp/BUILD.gn
+++ b/services/image_annotation/public/cpp/BUILD.gn
@@ -17,20 +17,6 @@
   ]
 }
 
-source_set("manifest") {
-  sources = [
-    "manifest.cc",
-    "manifest.h",
-  ]
-
-  public_deps = [
-    "//base",
-    "//services/data_decoder/public/mojom:constants",
-    "//services/image_annotation/public/mojom",
-    "//services/service_manager/public/cpp",
-  ]
-}
-
 source_set("tests") {
   testonly = true
 
diff --git a/services/image_annotation/public/cpp/manifest.cc b/services/image_annotation/public/cpp/manifest.cc
deleted file mode 100644
index 101f67d..0000000
--- a/services/image_annotation/public/cpp/manifest.cc
+++ /dev/null
@@ -1,28 +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 "services/image_annotation/public/cpp/manifest.h"
-
-#include "base/no_destructor.h"
-#include "services/data_decoder/public/mojom/constants.mojom.h"
-#include "services/image_annotation/public/mojom/constants.mojom.h"
-#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
-#include "services/service_manager/public/cpp/manifest_builder.h"
-
-namespace image_annotation {
-
-const service_manager::Manifest& GetManifest() {
-  static base::NoDestructor<service_manager::Manifest> manifest{
-      service_manager::ManifestBuilder()
-          .WithServiceName(mojom::kServiceName)
-          .WithDisplayName("Image Annotation Service")
-          .ExposeCapability(
-              mojom::kAnnotationCapability,
-              service_manager::Manifest::InterfaceList<mojom::Annotator>())
-          .RequireCapability(data_decoder::mojom::kServiceName, "json_parser")
-          .Build()};
-  return *manifest;
-}
-
-}  // namespace image_annotation
diff --git a/services/image_annotation/public/cpp/manifest.h b/services/image_annotation/public/cpp/manifest.h
deleted file mode 100644
index 642379e..0000000
--- a/services/image_annotation/public/cpp/manifest.h
+++ /dev/null
@@ -1,16 +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 SERVICES_IMAGE_ANNOTATION_PUBLIC_CPP_MANIFEST_H_
-#define SERVICES_IMAGE_ANNOTATION_PUBLIC_CPP_MANIFEST_H_
-
-#include "services/service_manager/public/cpp/manifest.h"
-
-namespace image_annotation {
-
-const service_manager::Manifest& GetManifest();
-
-}  // namespace image_annotation
-
-#endif  // SERVICES_IMAGE_ANNOTATION_PUBLIC_CPP_MANIFEST_H_
diff --git a/services/image_annotation/public/mojom/BUILD.gn b/services/image_annotation/public/mojom/BUILD.gn
index 4dfe90d..4e6bd52 100644
--- a/services/image_annotation/public/mojom/BUILD.gn
+++ b/services/image_annotation/public/mojom/BUILD.gn
@@ -8,18 +8,4 @@
   sources = [
     "image_annotation.mojom",
   ]
-
-  public_deps = [
-    ":constants",
-  ]
-
-  deps = [
-    "//mojo/public/mojom/base",
-  ]
-}
-
-mojom("constants") {
-  sources = [
-    "constants.mojom",
-  ]
 }
diff --git a/services/image_annotation/public/mojom/constants.mojom b/services/image_annotation/public/mojom/constants.mojom
deleted file mode 100644
index 214c38e9..0000000
--- a/services/image_annotation/public/mojom/constants.mojom
+++ /dev/null
@@ -1,8 +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.
-
-module image_annotation.mojom;
-
-const string kServiceName = "image_annotation";
-const string kAnnotationCapability = "annotation";
diff --git a/services/image_annotation/public/mojom/image_annotation.mojom b/services/image_annotation/public/mojom/image_annotation.mojom
index 02e0fd7c..102ef42 100644
--- a/services/image_annotation/public/mojom/image_annotation.mojom
+++ b/services/image_annotation/public/mojom/image_annotation.mojom
@@ -64,3 +64,9 @@
                 pending_remote<ImageProcessor> image_processor)
                     => (AnnotateImageResult result);
 };
+
+// The main interface to the Image Annotation service.
+interface ImageAnnotationService {
+  // Binds an Annotator endpoint which can be used to annotate images.
+  BindAnnotator(pending_receiver<Annotator> receiver);
+};
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 903a0fc..532dd0a 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1053,28 +1053,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04",
-              "pool": "chrome.tests",
-              "ssd": "0"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1653,28 +1631,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04",
-              "pool": "chrome.tests",
-              "ssd": "0"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 9e036db6..00c723e 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -23214,11 +23214,6 @@
       }
     ]
   },
-  "android-jumbo-rel": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "android-kitkat-arm-rel": {
     "additional_compile_targets": [
       "cronet_test_instrumentation_apk",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index eadafe98..6292a7e 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1000,26 +1000,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1529,26 +1509,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2500,27 +2460,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -3063,27 +3002,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index c86b742..9685a08 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -216,26 +216,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -728,26 +708,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1594,27 +1554,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2245,27 +2184,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3797,27 +3715,6 @@
         "test": "installer_util_unittests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -10961,26 +10858,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -11486,26 +11363,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -12404,27 +12261,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -13007,27 +12843,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -14005,26 +13820,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -14530,26 +14325,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15382,26 +15157,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15907,26 +15662,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16761,26 +16496,6 @@
         "test": "boringssl_ssl_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -17271,26 +16986,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18140,26 +17835,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18665,26 +18340,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19535,26 +19190,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20060,26 +19695,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20851,21 +20466,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -21181,21 +20781,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -21909,22 +21494,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -22331,22 +21900,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -22924,21 +22477,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -23254,21 +22792,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -23960,21 +23483,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -24375,21 +23883,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25006,21 +24499,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25421,21 +24899,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -26052,21 +25515,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -26467,21 +25915,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -27188,27 +26621,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -27839,27 +27251,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -28828,27 +28219,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -29479,27 +28849,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -30468,27 +29817,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -31119,27 +30447,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -32018,21 +31325,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -32433,21 +31725,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -33064,21 +32341,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -33479,21 +32741,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -34110,21 +33357,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -34525,21 +33757,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -35156,21 +34373,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -35571,21 +34773,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -36202,21 +35389,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -36617,21 +35789,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -37308,26 +36465,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -37833,26 +36970,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -38699,26 +37816,6 @@
         "test": "browser_switcher_bho_unittests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -39295,26 +38392,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 89117a3..ec6a924d 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -1,21 +1,6 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "Jumbo Linux x64": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
-  "Jumbo Mac": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
-  "Jumbo Win x64": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "Linux Builder Goma Canary": {
     "additional_compile_targets": [
       "all"
@@ -1341,21 +1326,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1749,21 +1719,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2535,27 +2490,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-17134"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3186,27 +3120,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-17134"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -13480,27 +13393,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -14043,27 +13935,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -15015,26 +14886,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15540,26 +15391,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16789,27 +16620,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -17327,27 +17137,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -22572,28 +22361,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -23126,28 +22893,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15"
-            }
-          ],
-          "expiration": 21600
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -24407,22 +24152,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -24862,22 +24591,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index f9a3281..712f4ef 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -2453,27 +2453,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -3013,27 +2992,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "isolate_coverage_data": true,
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "isolate_coverage_data": true,
         "merge": {
           "args": [],
@@ -4323,26 +4281,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -4848,26 +4786,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5762,11 +5680,6 @@
       "empty_main"
     ]
   },
-  "linux-jumbo-rel": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "linux-ozone-rel": {
     "additional_compile_targets": [
       "chrome"
@@ -6092,26 +6005,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-14.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6617,26 +6510,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-14.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 28dd68c9..61fbda86 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -228,27 +228,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.10"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -750,27 +729,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.10"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1776,27 +1734,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.11"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2298,27 +2235,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.11"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3324,27 +3240,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3846,27 +3741,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -4917,27 +4791,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.13.6"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5439,27 +5292,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.13.6"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6500,27 +6332,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.13.6"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -7022,27 +6833,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "none",
-              "os": "Mac-10.13.6"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -7834,10 +7624,5 @@
         }
       }
     ]
-  },
-  "mac-jumbo-rel": {
-    "additional_compile_targets": [
-      "all"
-    ]
   }
 }
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 218c410f..9ecd945 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -3327,27 +3327,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -3952,27 +3931,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -4989,26 +4947,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5523,26 +5461,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6459,27 +6377,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -7065,27 +6962,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -8232,28 +8108,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -8871,28 +8725,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -10005,28 +9837,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -10657,28 +10467,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -11659,27 +11447,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -12314,27 +12081,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -13199,22 +12945,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -13621,22 +13351,6 @@
       },
       {
         "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-          "--test-launcher-print-test-stdio=always"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
-        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -17066,26 +16780,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -17677,26 +17371,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index a1bc1ed..b1b338f 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -172,21 +172,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -587,21 +572,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -1517,27 +1487,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -2171,27 +2120,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -4034,27 +3962,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-15063"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5243,21 +5150,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5658,21 +5550,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6497,21 +6374,6 @@
         "test": "browser_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_browser_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "browser_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6912,21 +6774,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webui_html_imports_polyfill_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -7554,10 +7401,5 @@
         }
       }
     ]
-  },
-  "win-jumbo-rel": {
-    "additional_compile_targets": [
-      "all"
-    ]
   }
 }
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 196c141..af859ee 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -48,7 +48,6 @@
     "//testing/buildbot/filters/code_coverage.browser_tests.filter",
     "//testing/buildbot/filters/pixel_browser_tests.filter",
     "//testing/buildbot/filters/webrtc_functional.browser_tests.filter",
-    "//testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter",
   ]
 }
 
@@ -126,14 +125,6 @@
   ]
 }
 
-source_set("interactive_ui_tests_filters") {
-  testonly = true
-
-  data = [
-    "//testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter",
-  ]
-}
-
 source_set("ozone_unittests_filters") {
   data = [
     "//testing/buildbot/filters/chromeos.ozone_unittests.filter",
diff --git a/testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter b/testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter
deleted file mode 100644
index be65b6e..0000000
--- a/testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter
+++ /dev/null
@@ -1,81 +0,0 @@
-# https://crbug.com/924031, https://crbug.com/925517: Need to test all Web UI
-# pages that have been migrated to use HTML imports polyfill with the Blink
-# HTML imports feature turned off, before the Blink team disables the feature by
-# default.
-
-CrExtensionsSidebarTest.*
-CrExtensionsToolbarTest.*
-CrExtensionsItemsTest.*
-CrExtensionsActivityLogTest.*
-CrExtensionsActivityLogHistoryTest.*
-CrExtensionsActivityLogHistoryItemTest.*
-CrExtensionsActivityLogStreamTest.*
-CrExtensionsActivityLogStreamItemTest.*
-CrExtensionsDetailViewTest.*
-CrExtensionsItemListTest.*
-CrExtensionsLoadErrorTest.*
-CrExtensionsManagerUnitTest.*
-CrExtensionsManagerUnitTestWithActivityLogFlag.*
-CrExtensionsManagerTestWithMultipleExtensionTypesInstalled.*
-CrExtensionsManagerTestWithIdQueryParam.*
-CrExtensionsManagerTestWithActivityLogFlag.*
-CrExtensionsShortcutTest.*
-CrExtensionsShortcutInputTest.*
-CrExtensionsPackDialogTest.*
-CrExtensionsOptionsDialogTest.*
-CrExtensionsErrorPageTest.*
-CrExtensionsCodeSectionTest.*
-CrExtensionsNavigationHelperTest.*
-CrExtensionsErrorConsoleTest.*
-CrExtensionsToggleRowTest.*
-CrExtensionsKioskModeTest.*
-CrExtensionsRuntimeHostPermissionsTest.*
-CrExtensionsHostPermissionsToggleListTest.*
-
-DownloadsItemTest.*
-DownloadsManagerTest.*
-DownloadsToolbarTest.*
-DownloadsUrlTest.*
-
-PrintPreviewAdvancedDialogTest.*
-PrintPreviewAdvancedItemTest.*
-PrintPreviewAppTest.*
-PrintPreviewBaseSettingsSectionTest.*
-PrintPreviewButtonStripTest.*
-PrintPreviewColorSettingsTest.*
-PrintPreviewCopiesSettingsTest.*
-PrintPreviewCustomMarginsTest.*
-PrintPreviewDestinationDialogTest.*
-PrintPreviewDestinationItemTest.*
-PrintPreviewDestinationListTest.*
-PrintPreviewDestinationSelectTest.*
-PrintPreviewDestinationSettingsTest.*
-PrintPreviewDpiSettingsTest.*
-PrintPreviewDuplexSettingsTest.*
-PrintPreviewHeaderTest.*
-PrintPreviewHeaderNewTest.*
-PrintPreviewInvalidSettingsBrowserTest.*
-PrintPreviewKeyEventTest.*
-PrintPreviewLayoutSettingsTest.*
-PrintPreviewLinkContainerTest.*
-PrintPreviewMarginsSettingsTest.*
-PrintPreviewMediaSizeSettingsTest.*
-PrintPreviewModelTest.*
-PrintPreviewModelSettingsAvailabilityTest.*
-PrintPreviewModelSettingsPolicyTest.*
-PrintPreviewNewDestinationSearchTest.*
-PrintPreviewNumberSettingsSectionTest.*
-PrintPreviewOtherOptionsSettingsTest.*
-PrintPreviewPagesSettingsTest.*
-PrintPreviewPagesPerSheetSettingsTest.*
-PrintPreviewPinSettingsTest.*
-PrintPreviewPolicyTest.*
-PrintPreviewPreviewAreaTest.*
-PrintPreviewPreviewGenerationTest.*
-PrintPreviewPrintButtonTest.*
-PrintPreviewRestoreStateTest.*
-PrintPreviewScalingSettingsTest.*
-PrintPreviewSelectBehaviorTest.*
-PrintPreviewSidebarTest.*
-PrintPreviewSettingsSelectTest.*
-PrintPreviewSystemDialogBrowserTest.*
diff --git a/testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter b/testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter
deleted file mode 100644
index b663fe2..0000000
--- a/testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter
+++ /dev/null
@@ -1,12 +0,0 @@
-# https://crbug.com/924031, https://crbug.com/925517: Need to test all Web UI
-# pages that have been migrated to use HTML imports polyfill with the Blink
-# HTML imports feature turned off, before the Blink team disables the feature by
-# default.
-
-CrExtensionsOptionsPageTest.*
-PrintPreviewButtonStripInteractiveTest.*
-PrintPreviewDestinationDialogInteractiveTest.*
-PrintPreviewNumberSettingsSectionInteractiveTest.BlurResetsEmptyInput
-PrintPreviewPagesSettingsTest.*
-PrintPreviewPrintHeaderInteractiveTest.FocusPrintOnReady
-PrintPreviewScalingSettingsInteractiveTest.InputAutoFocus
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 255bc00..39427d650 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2100,14 +2100,6 @@
       'Win 7 Tests x64 (1)',
     ],
   },
-  'webui_html_imports_polyfill_browser_tests': {
-    'remove_from': [
-      # chromium.win
-      'Win10 Tests x64 (dbg)',  # Matches browser_tests.
-      # chromium.clang
-      'CrWinAsan(dll)', # https://crbug.com/935598
-    ],
-  },
   'webview_instrumentation_test_apk': {
     'remove_from': [
       # This test frequently fails on Android, https://crbug.com/824959
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 6815685..21d7682 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4499,20 +4499,6 @@
       'snapshot_unittests': {},
       'sync_integration_tests': {},
       'views_unittests': {},
-      'webui_html_imports_polyfill_browser_tests': {
-        'args': [
-          '--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0',
-          '--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter',
-        ],
-        'test': 'browser_tests',
-      },
-      'webui_html_imports_polyfill_interactive_ui_tests': {
-        'args': [
-          '--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0',
-          '--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_interactive_ui_tests.filter',
-        ],
-        'test': 'interactive_ui_tests',
-      },
     },
 
     'oor_cors_gtests': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 7a0259fc..cc59a6a 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -591,11 +591,6 @@
         },
         'os_type': 'android',
       },
-      'android-jumbo-rel': {
-        'additional_compile_targets': [
-          'all'
-        ]
-      },
       'android-kitkat-arm-rel': {
         'mixins': [
           'kitkat',
@@ -1395,21 +1390,6 @@
   {
     'name': 'chromium.fyi',
     'machines': {
-      'Jumbo Linux x64': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
-      'Jumbo Mac': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
-      'Jumbo Win x64': {
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       'Linux Builder Goma Canary': {
         'mixins': [
           'linux-xenial',
@@ -3479,11 +3459,6 @@
           'empty_main',
         ],
       },
-      'linux-jumbo-rel': {
-        'additional_compile_targets': [
-          'all'
-        ]
-      },
       'linux-ozone-rel': {
         'mixins': [
           'linux-xenial',
@@ -3675,11 +3650,6 @@
           ],
         },
       },
-      'mac-jumbo-rel': {
-        'additional_compile_targets': [
-          'all'
-        ]
-      },
     },
   },
   {
@@ -4161,11 +4131,6 @@
           'isolated_scripts': 'chromium_dbg_isolated_scripts',
         },
       },
-      'win-jumbo-rel': {
-        'additional_compile_targets': [
-          'all'
-        ]
-      },
     },
   },
   {
diff --git a/testing/test.gni b/testing/test.gni
index 654cea2..9fdf9c71 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -48,9 +48,6 @@
 
     assert(_use_raw_android_executable || enable_java_templates)
 
-    _incremental_apk_only =
-        incremental_apk_by_default && !_use_raw_android_executable
-
     if (_use_raw_android_executable) {
       _exec_target = "${target_name}__exec"
       _dist_target = "${target_name}__dist"
@@ -161,61 +158,26 @@
           deps += [ ":${_unwind_table_asset_name}" ]
         }
       }
+    }
 
-      _test_runner_target = "${_output_name}__test_runner_script"
-      _incremental_test_name = "${_output_name}_incremental"
-      _incremental_test_runner_target =
-          "${_output_name}_incremental__test_runner_script"
-      if (_incremental_apk_only) {
-        _incremental_test_name = _output_name
-        _incremental_test_runner_target = _test_runner_target
-      }
+    test_runner_script(_test_runner_target) {
+      forward_variables_from(invoker, _wrapper_script_vars)
 
-      # Incremental test targets work only for .apks.
-      test_runner_script(_incremental_test_runner_target) {
-        forward_variables_from(invoker, _wrapper_script_vars)
+      if (_use_raw_android_executable) {
+        executable_dist_dir = "$root_out_dir/$_dist_target"
+      } else {
+        apk_target = ":$_apk_target"
+        incremental_apk = incremental_install
 
         # Dep needed for the test runner .runtime_deps file to pick up data
         # deps from the forward_variables_from(invoker, "*") on the library.
         data_deps = [
           ":$_library_target",
         ]
-        apk_target = ":$_apk_target"
-        test_name = _incremental_test_name
-        test_type = "gtest"
-        test_suite = _output_name
-        incremental_install = true
       }
-      group("${target_name}_incremental") {
-        testonly = true
-        data_deps = [
-          ":$_incremental_test_runner_target",
-        ]
-        deps = [
-          ":${_apk_target}_incremental",
-        ]
-      }
-    }
-
-    if (!_incremental_apk_only) {
-      test_runner_script(_test_runner_target) {
-        forward_variables_from(invoker, _wrapper_script_vars)
-
-        if (_use_raw_android_executable) {
-          executable_dist_dir = "$root_out_dir/$_dist_target"
-        } else {
-          apk_target = ":$_apk_target"
-
-          # Dep needed for the test runner .runtime_deps file to pick up data
-          # deps from the forward_variables_from(invoker, "*") on the library.
-          data_deps = [
-            ":$_library_target",
-          ]
-        }
-        test_name = _output_name
-        test_type = "gtest"
-        test_suite = _output_name
-      }
+      test_name = _output_name
+      test_suite = _output_name
+      test_type = "gtest"
     }
 
     # Create a wrapper script rather than using a group() in order to ensure
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 68435a9..8c85e4b 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -101,6 +101,16 @@
 // Enable Portals. https://crbug.com/865123.
 const base::Feature kPortals{"Portals", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// When kPortals is enabled, allow portals to load content that is third-party
+// (cross-origin) to the hosting page. Otherwise has no effect.
+//
+// This will be disabled by default by the time Portals is generally available,
+// either in origin trial or shipped.
+//
+// https://crbug.com/1013389
+const base::Feature kPortalsCrossOrigin{"PortalsCrossOrigin",
+                                        base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Enable limiting previews loading hints to specific resource types.
 const base::Feature kPreviewsResourceLoadingHintsSpecificResourceTypes{
     "PreviewsResourceLoadingHintsSpecificResourceTypes",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 2ece67b..9d9bf5bc 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -38,6 +38,7 @@
     kOffMainThreadServiceWorkerStartup;
 BLINK_COMMON_EXPORT extern const base::Feature kPlzDedicatedWorker;
 BLINK_COMMON_EXPORT extern const base::Feature kPortals;
+BLINK_COMMON_EXPORT extern const base::Feature kPortalsCrossOrigin;
 BLINK_COMMON_EXPORT extern const base::Feature
     kPreviewsResourceLoadingHintsSpecificResourceTypes;
 BLINK_COMMON_EXPORT extern const base::Feature
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 74ccfa6..3e58718 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -52,6 +52,11 @@
   // Indication that the associated frame contains a form that submits to an
   // insecure target url.
   DidContainInsecureFormAction();
+
+  // Indicates that a child frame requires its parent frame to send it information
+  // about whether it is occluded or has visual effects applied, in order to
+  // service IntersectionObserver's that track visibility.
+  SetNeedsOcclusionTracking(bool needs_tracking);
 };
 
 // Implemented in Blink, this interface defines frame-specific methods that will
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index d16b3e5..02cdb48 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -321,10 +321,6 @@
   // is being dragged.
   virtual void SetMouseCapture(bool capture) {}
 
-  // Announces that an embedded frame needs occlusion information from its
-  // parent frame.
-  virtual void SetNeedsOcclusionTracking(bool needs_tracking) {}
-
   // Lifecycle of the frame has changed.
   virtual void LifecycleStateChanged(mojom::FrameLifecycleState state) {}
 
diff --git a/third_party/blink/renderer/core/exported/web_element.cc b/third_party/blink/renderer/core/exported/web_element.cc
index 41aaa70..fa497af4 100644
--- a/third_party/blink/renderer/core/exported/web_element.cc
+++ b/third_party/blink/renderer/core/exported/web_element.cc
@@ -47,8 +47,6 @@
 
 namespace blink {
 
-using namespace html_names;
-
 bool WebElement::IsFormControlElement() const {
   return ConstUnwrap<Element>()->IsFormControlElement();
 }
@@ -67,7 +65,8 @@
       return true;
   }
 
-  return EqualIgnoringASCIICase(element->getAttribute(kRoleAttr), "textbox");
+  return EqualIgnoringASCIICase(element->getAttribute(html_names::kRoleAttr),
+                                "textbox");
 }
 
 WebString WebElement::TagName() const {
diff --git a/third_party/blink/renderer/core/exported/web_searchable_form_data.cc b/third_party/blink/renderer/core/exported/web_searchable_form_data.cc
index db51e31..45d8b42 100644
--- a/third_party/blink/renderer/core/exported/web_searchable_form_data.cc
+++ b/third_party/blink/renderer/core/exported/web_searchable_form_data.cc
@@ -47,8 +47,6 @@
 
 namespace blink {
 
-using namespace html_names;
-
 namespace {
 
 // Gets the encoding for the form.
@@ -92,7 +90,7 @@
   if (select.IsMultiple() || select.size() > 1) {
     for (auto* const option_element : select.GetOptionList()) {
       if (option_element->Selected() !=
-          option_element->FastHasAttribute(kSelectedAttr))
+          option_element->FastHasAttribute(html_names::kSelectedAttr))
         return false;
     }
     return true;
@@ -102,7 +100,7 @@
   // least one item is selected, determine which one.
   HTMLOptionElement* initial_selected = nullptr;
   for (auto* const option_element : select.GetOptionList()) {
-    if (option_element->FastHasAttribute(kSelectedAttr)) {
+    if (option_element->FastHasAttribute(html_names::kSelectedAttr)) {
       // The page specified the option to select.
       initial_selected = option_element;
       break;
@@ -121,8 +119,10 @@
 bool IsInDefaultState(const HTMLFormControlElement& form_element) {
   if (auto* input = ToHTMLInputElementOrNull(form_element)) {
     if (input->type() == input_type_names::kCheckbox ||
-        input->type() == input_type_names::kRadio)
-      return input->checked() == input->FastHasAttribute(kCheckedAttr);
+        input->type() == input_type_names::kRadio) {
+      return input->checked() ==
+             input->FastHasAttribute(html_names::kCheckedAttr);
+    }
   } else if (auto* select = DynamicTo<HTMLSelectElement>(form_element)) {
     return IsSelectInDefaultState(*select);
   }
@@ -223,7 +223,8 @@
       static_cast<HTMLInputElement*>(selected_input_element);
 
   // Only consider forms that GET data.
-  if (EqualIgnoringASCIICase(form_element->getAttribute(kMethodAttr), "post"))
+  if (EqualIgnoringASCIICase(
+          form_element->getAttribute(html_names::kMethodAttr), "post"))
     return;
 
   WTF::TextEncoding encoding;
diff --git a/third_party/blink/renderer/core/frame/performance_monitor.cc b/third_party/blink/renderer/core/frame/performance_monitor.cc
index e458264..a896990 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor.cc
+++ b/third_party/blink/renderer/core/frame/performance_monitor.cc
@@ -188,8 +188,6 @@
 
 void PerformanceMonitor::Will(const probe::ExecuteScript& probe) {
   WillExecuteScript(probe.context);
-
-  probe.CaptureStartTime();
 }
 
 void PerformanceMonitor::Did(const probe::ExecuteScript& probe) {
@@ -227,10 +225,6 @@
 
 void PerformanceMonitor::Will(const probe::V8Compile& probe) {
   UpdateTaskAttribution(probe.context);
-  if (!enabled_ || thresholds_[kLongTask].is_zero())
-    return;
-
-  v8_compile_start_time_ = probe.CaptureStartTime();
 }
 
 void PerformanceMonitor::Did(const probe::V8Compile& probe) {}
@@ -276,7 +270,6 @@
   layout_depth_ = 0;
   per_task_style_and_layout_time_ = base::TimeDelta();
   user_callback_ = nullptr;
-  v8_compile_start_time_ = base::TimeTicks();
 }
 
 void PerformanceMonitor::DidProcessTask(base::TimeTicks start_time,
diff --git a/third_party/blink/renderer/core/frame/performance_monitor.h b/third_party/blink/renderer/core/frame/performance_monitor.h
index c96a5bf..cec7542 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor.h
+++ b/third_party/blink/renderer/core/frame/performance_monitor.h
@@ -142,7 +142,6 @@
   unsigned layout_depth_ = 0;
   unsigned user_callback_depth_ = 0;
   const void* user_callback_;
-  base::TimeTicks v8_compile_start_time_;
 
   base::TimeDelta thresholds_[kAfterLast];
 
diff --git a/third_party/blink/renderer/core/frame/remote_frame_owner.cc b/third_party/blink/renderer/core/frame/remote_frame_owner.cc
index e3adcd17..9d6fdfa 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_owner.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame_owner.cc
@@ -95,9 +95,9 @@
   if (needs_tracking == needs_occlusion_tracking_)
     return;
   needs_occlusion_tracking_ = needs_tracking;
-  WebLocalFrameImpl* web_frame =
-      WebLocalFrameImpl::FromFrame(To<LocalFrame>(*frame_));
-  web_frame->Client()->SetNeedsOcclusionTracking(needs_tracking);
+  LocalFrame* local_frame = To<LocalFrame>(frame_.Get());
+  local_frame->GetLocalFrameHostRemote().SetNeedsOcclusionTracking(
+      needs_tracking);
 }
 
 bool RemoteFrameOwner::ShouldLazyLoadChildren() const {
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
index bc840df..90e1997 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer.cc
@@ -93,6 +93,76 @@
   return false;
 }
 
+void RecordVisibleLoadTimeForImage(
+    const LazyLoadImageObserver::VisibleLoadTimeMetrics&
+        visible_load_time_metrics) {
+  DCHECK(visible_load_time_metrics.has_initial_intersection_been_set);
+  DCHECK(!visible_load_time_metrics.time_when_first_visible.is_null());
+  DCHECK(!visible_load_time_metrics.time_when_first_load_finished.is_null());
+
+  base::TimeDelta visible_load_delay =
+      visible_load_time_metrics.time_when_first_load_finished -
+      visible_load_time_metrics.time_when_first_visible;
+  if (visible_load_delay < base::TimeDelta())
+    visible_load_delay = base::TimeDelta();
+
+  switch (GetNetworkStateNotifier().EffectiveType()) {
+    case WebEffectiveConnectionType::kTypeSlow2G:
+      if (visible_load_time_metrics.is_initially_intersecting) {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.Slow2G",
+            visible_load_delay);
+      } else {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.Slow2G",
+            visible_load_delay);
+      }
+      break;
+
+    case WebEffectiveConnectionType::kType2G:
+      if (visible_load_time_metrics.is_initially_intersecting) {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.2G",
+            visible_load_delay);
+      } else {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.2G",
+            visible_load_delay);
+      }
+      break;
+
+    case WebEffectiveConnectionType::kType3G:
+      if (visible_load_time_metrics.is_initially_intersecting) {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.3G",
+            visible_load_delay);
+      } else {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.3G",
+            visible_load_delay);
+      }
+      break;
+
+    case WebEffectiveConnectionType::kType4G:
+      if (visible_load_time_metrics.is_initially_intersecting) {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G",
+            visible_load_delay);
+      } else {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G",
+            visible_load_delay);
+      }
+      break;
+
+    case WebEffectiveConnectionType::kTypeUnknown:
+    case WebEffectiveConnectionType::kTypeOffline:
+      // No VisibleLoadTime histograms are recorded for these effective
+      // connection types.
+      break;
+  }
+}
+
 }  // namespace
 
 LazyLoadImageObserver::LazyLoadImageObserver(const Document& document)
@@ -183,8 +253,9 @@
 
   VisibleLoadTimeMetrics& visible_load_time_metrics =
       image_element->EnsureVisibleLoadTimeMetrics();
-  if (visible_load_time_metrics.has_initial_intersection_been_set) {
-    // The element has already been monitored.
+  if (!visible_load_time_metrics.time_when_first_visible.is_null()) {
+    // The time when the image first became visible has already been measured,
+    // so there's no need to monitor the visibility of the image any more.
     return;
   }
   if (!visibility_metrics_observer_) {
@@ -193,82 +264,23 @@
         WTF::BindRepeating(&LazyLoadImageObserver::OnVisibilityChanged,
                            WrapWeakPersistent(this)));
   }
-  visible_load_time_metrics.record_visibility_metrics = true;
   visibility_metrics_observer_->observe(image_element);
 }
 
 void LazyLoadImageObserver::OnLoadFinished(HTMLImageElement* image_element) {
   DCHECK(RuntimeEnabledFeatures::LazyImageVisibleLoadTimeMetricsEnabled());
-
   VisibleLoadTimeMetrics& visible_load_time_metrics =
       image_element->EnsureVisibleLoadTimeMetrics();
-  if (!visible_load_time_metrics.record_visibility_metrics)
+
+  if (!visible_load_time_metrics.time_when_first_load_finished.is_null())
+    return;
+  visible_load_time_metrics.time_when_first_load_finished =
+      base::TimeTicks::Now();
+
+  if (visible_load_time_metrics.time_when_first_visible.is_null())
     return;
 
-  visible_load_time_metrics.record_visibility_metrics = false;
-  visibility_metrics_observer_->unobserve(image_element);
-
-  base::TimeDelta visible_load_delay;
-  if (!visible_load_time_metrics.time_when_first_visible.is_null()) {
-    visible_load_delay = base::TimeTicks::Now() -
-                         visible_load_time_metrics.time_when_first_visible;
-  }
-
-  switch (GetNetworkStateNotifier().EffectiveType()) {
-    case WebEffectiveConnectionType::kTypeSlow2G:
-      if (visible_load_time_metrics.is_initially_intersecting) {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.Slow2G",
-            visible_load_delay);
-      } else {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.Slow2G",
-            visible_load_delay);
-      }
-      break;
-
-    case WebEffectiveConnectionType::kType2G:
-      if (visible_load_time_metrics.is_initially_intersecting) {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.2G",
-            visible_load_delay);
-      } else {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.2G",
-            visible_load_delay);
-      }
-      break;
-
-    case WebEffectiveConnectionType::kType3G:
-      if (visible_load_time_metrics.is_initially_intersecting) {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.3G",
-            visible_load_delay);
-      } else {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.3G",
-            visible_load_delay);
-      }
-      break;
-
-    case WebEffectiveConnectionType::kType4G:
-      if (visible_load_time_metrics.is_initially_intersecting) {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G",
-            visible_load_delay);
-      } else {
-        UMA_HISTOGRAM_MEDIUM_TIMES(
-            "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G",
-            visible_load_delay);
-      }
-      break;
-
-    case WebEffectiveConnectionType::kTypeUnknown:
-    case WebEffectiveConnectionType::kTypeOffline:
-      // No VisibleLoadTime histograms are recorded for these effective
-      // connection types.
-      break;
-  }
+  RecordVisibleLoadTimeForImage(visible_load_time_metrics);
 }
 
 void LazyLoadImageObserver::OnVisibilityChanged(
@@ -276,44 +288,45 @@
   DCHECK(!entries.IsEmpty());
 
   for (auto entry : entries) {
-    if (auto* image_element = ToHTMLImageElementOrNull(entry->target())) {
-      VisibleLoadTimeMetrics& visible_load_time_metrics =
-          image_element->EnsureVisibleLoadTimeMetrics();
-      if (!visible_load_time_metrics.has_initial_intersection_been_set) {
-        visible_load_time_metrics.is_initially_intersecting =
-            entry->isIntersecting();
-        visible_load_time_metrics.has_initial_intersection_been_set = true;
-      }
-      if (entry->isIntersecting()) {
-        DCHECK(visible_load_time_metrics.time_when_first_visible.is_null());
-        visible_load_time_metrics.time_when_first_visible =
-            base::TimeTicks::Now();
+    auto* image_element = ToHTMLImageElementOrNull(entry->target());
+    if (!image_element)
+      continue;
 
-        if (visible_load_time_metrics.record_visibility_metrics &&
-            image_element->GetDocument().GetFrame()) {
-          // Since the visibility metrics are recorded when the image finishes
-          // loading, this means that the image became visible before it
-          // finished loading.
+    VisibleLoadTimeMetrics& visible_load_time_metrics =
+        image_element->EnsureVisibleLoadTimeMetrics();
+    // The image's visiblity shouldn't still be monitored if the time when the
+    // image first became visible has already been measured.
+    DCHECK(visible_load_time_metrics.time_when_first_visible.is_null());
 
-          // Note: If the WebEffectiveConnectionType enum ever gets out of sync
-          // with net::EffectiveConnectionType, then both the AboveTheFold and
-          // BelowTheFold histograms here will have to be updated to record the
-          // sample in terms of net::EffectiveConnectionType instead of
-          // WebEffectiveConnectionType.
-          if (visible_load_time_metrics.is_initially_intersecting) {
-            UMA_HISTOGRAM_ENUMERATION(
-                "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
-                GetNetworkStateNotifier().EffectiveType());
-          } else {
-            UMA_HISTOGRAM_ENUMERATION(
-                "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
-                GetNetworkStateNotifier().EffectiveType());
-          }
-        }
-
-        visibility_metrics_observer_->unobserve(image_element);
-      }
+    if (!visible_load_time_metrics.has_initial_intersection_been_set) {
+      visible_load_time_metrics.has_initial_intersection_been_set = true;
+      visible_load_time_metrics.is_initially_intersecting =
+          entry->isIntersecting();
     }
+    if (!entry->isIntersecting())
+      continue;
+
+    visible_load_time_metrics.time_when_first_visible = base::TimeTicks::Now();
+    if (visible_load_time_metrics.time_when_first_load_finished.is_null()) {
+      // Note: If the WebEffectiveConnectionType enum ever gets out of sync
+      // with net::EffectiveConnectionType, then both the AboveTheFold and
+      // BelowTheFold histograms here will have to be updated to record the
+      // sample in terms of net::EffectiveConnectionType instead of
+      // WebEffectiveConnectionType.
+      if (visible_load_time_metrics.is_initially_intersecting) {
+        UMA_HISTOGRAM_ENUMERATION(
+            "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
+            GetNetworkStateNotifier().EffectiveType());
+      } else {
+        UMA_HISTOGRAM_ENUMERATION(
+            "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
+            GetNetworkStateNotifier().EffectiveType());
+      }
+    } else {
+      RecordVisibleLoadTimeForImage(visible_load_time_metrics);
+    }
+
+    visibility_metrics_observer_->unobserve(image_element);
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer.h b/third_party/blink/renderer/core/html/lazy_load_image_observer.h
index 2436ecb..3694171 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer.h
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer.h
@@ -32,11 +32,11 @@
     bool is_initially_intersecting = false;
     bool has_initial_intersection_been_set = false;
 
-    // True if metrics need to be recorded and has not been recorded yet.
-    bool record_visibility_metrics = false;
-
     // Set when the image first becomes visible (i.e. appears in the viewport).
     base::TimeTicks time_when_first_visible;
+
+    // Set when the first load event is dispatched for the image.
+    base::TimeTicks time_when_first_load_finished;
   };
 
   LazyLoadImageObserver(const Document&);
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
index 8ecf597..c3a4c2a 100644
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
+++ b/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
@@ -1451,6 +1451,202 @@
   }
 }
 
+TEST_F(LazyLoadAutomaticImagesTest, AboveTheFoldImageLoadedBeforeVisible) {
+  ScopedLazyImageLoadingMetadataFetchForTest
+      scoped_lazy_image_loading_metadata_fetch_for_test = false;
+  HistogramTester histogram_tester;
+
+  // Since the image is above the fold, ensure that the image starts loading
+  // before it becomes visible. When automatic lazyloading is enabled, the main
+  // way that an above-the-fold image that's relevant to automatic lazyload
+  // (i.e. would have visible load time metrics recorded for it) can be finshed
+  // loading by the time it becomes visible is for it to be fully loaded as one
+  // of the first K images seen by the parser.
+  SetLazyImageFirstKFullyLoad(1);
+
+  SimRequest main_resource("https://example.com/", "text/html");
+  SimSubresourceRequest image_resource("https://example.com/image.png",
+                                       "image/png");
+
+  LoadURL("https://example.com/");
+  main_resource.Complete(
+      "<body><img src='https://example.com/image.png' /></body>");
+  image_resource.Complete(ReadTestImage());
+
+  // VisibleLoadTime should not have been recorded yet, since the image is not
+  // visible yet.
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0, 1);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
+}
+
+TEST_F(LazyLoadAutomaticImagesTest, AboveTheFoldImageVisibleBeforeLoaded) {
+  ScopedLazyImageLoadingMetadataFetchForTest
+      scoped_lazy_image_loading_metadata_fetch_for_test = false;
+  HistogramTester histogram_tester;
+
+  SimRequest main_resource("https://example.com/", "text/html");
+  SimSubresourceRequest image_resource("https://example.com/image.png",
+                                       "image/png");
+
+  LoadURL("https://example.com/");
+  main_resource.Complete(
+      "<body><img src='https://example.com/image.png' /></body>");
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  // VisibleBeforeLoaded should have been recorded immediately when the image
+  // became visible.
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
+      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
+
+  // VisibleLoadTime should not have been recorded yet, since the image is not
+  // finished loading yet.
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
+
+  image_resource.Complete(ReadTestImage());
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
+      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 1);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
+}
+
+TEST_F(LazyLoadAutomaticImagesTest, BelowTheFoldImageLoadedBeforeVisible) {
+  ScopedLazyImageLoadingMetadataFetchForTest
+      scoped_lazy_image_loading_metadata_fetch_for_test = false;
+  HistogramTester histogram_tester;
+
+  SimRequest main_resource("https://example.com/", "text/html");
+  LoadURL("https://example.com/");
+  main_resource.Complete(String::Format(
+      R"HTML(
+        <body>
+        <div style='height: %dpx;'></div>
+        <img src='https://example.com/image.png' />
+        </body>)HTML",
+      kViewportHeight + kLoadingDistanceThreshold + 100));
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  SimSubresourceRequest image_resource("https://example.com/image.png",
+                                       "image/png");
+
+  // Scroll down such that the image is within kLoadingDistanceThreshold of the
+  // viewport, but isn't visible yet.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 200),
+                                                          kProgrammaticScroll);
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  image_resource.Complete(ReadTestImage());
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  // VisibleLoadTime should not have been recorded yet, since the image is not
+  // visible yet.
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
+
+  // Scroll down such that the image is visible.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
+      kProgrammaticScroll);
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0, 1);
+}
+
+TEST_F(LazyLoadAutomaticImagesTest, BelowTheFoldImageVisibleBeforeLoaded) {
+  ScopedLazyImageLoadingMetadataFetchForTest
+      scoped_lazy_image_loading_metadata_fetch_for_test = false;
+  HistogramTester histogram_tester;
+
+  SimRequest main_resource("https://example.com/", "text/html");
+  LoadURL("https://example.com/");
+  main_resource.Complete(String::Format(
+      R"HTML(
+        <body>
+        <div style='height: %dpx;'></div>
+        <img src='https://example.com/image.png' />
+        </body>)HTML",
+      kViewportHeight + kLoadingDistanceThreshold + 100));
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  SimSubresourceRequest image_resource("https://example.com/image.png",
+                                       "image/png");
+
+  // Scroll down such that the image is visible.
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
+      kProgrammaticScroll);
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  // VisibleBeforeLoaded should have been recorded immediately when the image
+  // became visible.
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
+      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
+
+  // VisibleLoadTime should not have been recorded yet, since the image is not
+  // finished loading yet.
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
+
+  image_resource.Complete(ReadTestImage());
+
+  Compositor().BeginFrame();
+  test::RunPendingTasks();
+
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
+  histogram_tester.ExpectUniqueSample(
+      "Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
+      static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
+  histogram_tester.ExpectTotalCount(
+      "Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 1);
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
index ca2b3fa5..30789a07 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
@@ -35,6 +35,8 @@
 
 void FakeLocalFrameHost::DidContainInsecureFormAction() {}
 
+void FakeLocalFrameHost::SetNeedsOcclusionTracking(bool needs_tracking) {}
+
 void FakeLocalFrameHost::BindFrameHostReceiver(
     mojo::ScopedInterfaceEndpointHandle handle) {
   receiver_.Bind(mojo::PendingAssociatedReceiver<mojom::blink::LocalFrameHost>(
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.h b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
index 75021ec6..755385d 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
@@ -33,6 +33,7 @@
                                  bool user_gesture) override;
   void DidDisplayInsecureContent() override;
   void DidContainInsecureFormAction() override;
+  void SetNeedsOcclusionTracking(bool needs_tracking) override;
 
  private:
   void BindFrameHostReceiver(mojo::ScopedInterfaceEndpointHandle handle);
diff --git a/third_party/blink/renderer/platform/fonts/font_cache.cc b/third_party/blink/renderer/platform/fonts/font_cache.cc
index ee82164..b4a7e95 100644
--- a/third_party/blink/renderer/platform/fonts/font_cache.cc
+++ b/third_party/blink/renderer/platform/fonts/font_cache.cc
@@ -115,7 +115,7 @@
 FontPlatformData* FontCache::SystemFontPlatformData(
     const FontDescription& font_description) {
   const AtomicString& family = FontCache::SystemFontFamily();
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_FUCHSIA)
   if (family.IsEmpty() || family == font_family_names::kSystemUi)
     return nullptr;
 #else
diff --git a/third_party/blink/web_tests/SmokeTests b/third_party/blink/web_tests/SmokeTests
index f0575713..fc5342a 100644
--- a/third_party/blink/web_tests/SmokeTests
+++ b/third_party/blink/web_tests/SmokeTests
@@ -754,6 +754,7 @@
 fast/text/international/bidi-LDB-2-HTML.html
 fast/text/international/bidi-neutral-directionality-paragraph-start.html
 fast/text/international/rtl-mark.html
+fast/text/small-caps-aat.html
 fast/text/stroking-decorations.html
 fast/text/vertical-spacing-crash.html
 fast/text/whitespace/whitespace-and-margin-wrap-after-list-marker-crash.html
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 47f87d0..4009d2d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5710,7 +5710,7 @@
 crbug.com/1005128 crypto/subtle/abandon-crypto-operation2.html [ Pass Crash ]
 
 # Sheriff 2019-09-27
-crbug.com/1008826 [ Mac10.13 ] http/tests/security/frameNavigation/xss-ALLOWED-parent-navigation-change.html [ Pass Failure Timeout ]
+crbug.com/1008826 [ Retina ] http/tests/security/frameNavigation/xss-ALLOWED-parent-navigation-change.html [ Pass Failure Timeout ]
 
 # Sheriff 2019-09-30
 crbug.com/1003715 [ Win10 ] http/tests/notifications/serviceworker-notification-properties.html [ Pass Failure Timeout ]
@@ -5775,3 +5775,6 @@
 
 # ecosystem-infra sheriff 2019-10-22
 crbug.com/1016762 external/wpt/html/cross-origin-opener-policy/popup-redirect-cache.https.html [ Pass Failure ]
+
+# cci-trooper 2019-10-22
+crbug.com/1016804 external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/tools/wpt/browser.py b/third_party/blink/web_tests/external/wpt/tools/wpt/browser.py
index 233778f..bed8c59 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wpt/browser.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wpt/browser.py
@@ -623,6 +623,7 @@
 
     def __init__(self, logger):
         super(ChromeAndroidBase, self).__init__(logger)
+        self.device_serial = None
 
     def install(self, dest=None, channel=None):
         raise NotImplementedError
@@ -646,7 +647,10 @@
             self.logger.warning("No package name provided.")
             return None
 
-        command = ['adb', 'shell', 'dumpsys', 'package', binary]
+        command = ['adb']
+        if self.device_serial:
+            command.extend(['-s', self.device_serial])
+        command.extend(['shell', 'dumpsys', 'package', binary])
         try:
             output = call(*command)
         except (subprocess.CalledProcessError, OSError):
@@ -687,7 +691,10 @@
         # For WebView, it is not trivial to change the WebView provider, so
         # we will just grab whatever is available.
         # https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/channels.md
-        command = ['adb', 'shell', 'dumpsys', 'webviewupdate']
+        command = ['adb']
+        if self.device_serial:
+            command.extend(['-s', self.device_serial])
+        command.extend(['shell', 'dumpsys', 'webviewupdate'])
         try:
             output = call(*command)
         except (subprocess.CalledProcessError, OSError):
diff --git a/third_party/blink/web_tests/external/wpt/tools/wpt/run.py b/third_party/blink/web_tests/external/wpt/tools/wpt/run.py
index de20e291..2525532 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wpt/run.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wpt/run.py
@@ -354,6 +354,8 @@
     browser_cls = browser.ChromeAndroid
 
     def setup_kwargs(self, kwargs):
+        if kwargs.get("device_serial"):
+            self.browser.device_serial = kwargs["device_serial"]
         browser_channel = kwargs["browser_channel"]
         if kwargs["package_name"] is None:
             kwargs["package_name"] = self.browser.find_binary(
@@ -398,6 +400,8 @@
     browser_cls = browser.AndroidWebview
 
     def setup_kwargs(self, kwargs):
+        if kwargs.get("device_serial"):
+            self.browser.device_serial = kwargs["device_serial"]
         if kwargs["webdriver_binary"] is None:
             webdriver_binary = self.browser.find_webdriver()
 
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/chrome_android.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/chrome_android.py
index 6722874..b4edc05 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/chrome_android.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/browsers/chrome_android.py
@@ -31,6 +31,7 @@
 
 def browser_kwargs(test_type, run_info_data, config, **kwargs):
     return {"package_name": kwargs["package_name"],
+            "device_serial": kwargs["device_serial"],
             "webdriver_binary": kwargs["webdriver_binary"],
             "webdriver_args": kwargs.get("webdriver_args")}
 
@@ -53,6 +54,9 @@
     assert kwargs["package_name"], "missing --package-name"
     executor_kwargs["capabilities"]["goog:chromeOptions"]["androidPackage"] = \
         kwargs["package_name"]
+    if kwargs.get("device_serial"):
+        executor_kwargs["capabilities"]["goog:chromeOptions"]["androidDeviceSerial"] = \
+            kwargs["device_serial"]
 
     return executor_kwargs
 
@@ -72,17 +76,22 @@
     """
 
     def __init__(self, logger, package_name, webdriver_binary="chromedriver",
-                 webdriver_args=None):
+                 device_serial=None, webdriver_args=None):
         Browser.__init__(self, logger)
         self.package_name = package_name
+        self.device_serial = device_serial
         self.server = ChromeDriverServer(self.logger,
                                          binary=webdriver_binary,
                                          args=webdriver_args)
         self.setup_adb_reverse()
 
     def _adb_run(self, args):
-        self.logger.info('adb ' + ' '.join(args))
-        subprocess.check_call(['adb'] + args)
+        cmd = ['adb']
+        if self.device_serial:
+            cmd.extend(['-s', self.device_serial])
+        cmd.extend(args)
+        self.logger.info(' '.join(cmd))
+        subprocess.check_call(cmd)
 
     def setup_adb_reverse(self):
         self._adb_run(['wait-for-device'])
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/text/small-caps-aat-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/text/small-caps-aat-expected.png
new file mode 100644
index 0000000..b551bfd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/text/small-caps-aat-expected.png
Binary files differ
diff --git a/third_party/closure_compiler/externs/html_imports.js b/third_party/closure_compiler/externs/html_imports.js
deleted file mode 100644
index 906713e..0000000
--- a/third_party/closure_compiler/externs/html_imports.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/** @fileoverview Externs for HTML imports polyfill. */
-
-/** @see https://github.com/webcomponents/html-imports */
-var HTMLImports = {};
-
-/** @param {!Function} callback */
-HTMLImports.whenReady = function(callback) {};
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index b30a6be5..907127b 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 661a07a41b8809b9df32914c11542a0c76e709d5
+Revision: fe52a01df1e9c8a5fe8b92872d4bf8689d0cd3b4
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_test.cc b/third_party/crashpad/crashpad/client/crash_report_database_test.cc
index ef217c1..ca66bce6 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_test.cc
+++ b/third_party/crashpad/crashpad/client/crash_report_database_test.cc
@@ -20,6 +20,7 @@
 #include "test/errors.h"
 #include "test/file.h"
 #include "test/filesystem.h"
+#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
@@ -670,7 +671,7 @@
 TEST_F(CrashReportDatabaseTest, Attachments) {
 #if defined(OS_MACOSX) || defined(OS_WIN)
   // Attachments aren't supported on Mac and Windows yet.
-  GTEST_SKIP();
+  DISABLED_TEST();
 #else
   std::unique_ptr<CrashReportDatabase::NewReport> new_report;
   ASSERT_EQ(db()->PrepareNewCrashReport(&new_report),
@@ -716,7 +717,7 @@
 TEST_F(CrashReportDatabaseTest, OrphanedAttachments) {
 #if defined(OS_MACOSX) || defined(OS_WIN)
   // Attachments aren't supported on Mac and Windows yet.
-  GTEST_SKIP();
+  DISABLED_TEST();
 #else
   // TODO: This is using paths that are specific to the generic implementation
   // and will need to be generalized for Mac and Windows.
diff --git a/third_party/crashpad/crashpad/compat/mac/AvailabilityMacros.h b/third_party/crashpad/crashpad/compat/mac/AvailabilityMacros.h
index a83d2a4..f105f94 100644
--- a/third_party/crashpad/crashpad/compat/mac/AvailabilityMacros.h
+++ b/third_party/crashpad/crashpad/compat/mac/AvailabilityMacros.h
@@ -59,16 +59,4 @@
 #define MAC_OS_X_VERSION_10_13 101300
 #endif
 
-// 10.14 SDK
-
-#ifndef MAC_OS_X_VERSION_10_14
-#define MAC_OS_X_VERSION_10_14 101400
-#endif
-
-// 10.15 SDK
-
-#ifndef MAC_OS_X_VERSION_10_15
-#define MAC_OS_X_VERSION_10_15 101500
-#endif
-
 #endif  // CRASHPAD_COMPAT_MAC_AVAILABILITYMACROS_H_
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
index 5b57236..d767700 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
@@ -38,6 +38,7 @@
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
+#include "test/gtest_disabled.h"
 #include "test/linux/fake_ptrace_connection.h"
 #include "test/linux/get_tls.h"
 #include "test/multiprocess.h"
@@ -427,7 +428,7 @@
   }
 
   void MultiprocessChild() override {
-    const LinuxVMSize stack_size = page_size_ * 4;
+    const LinuxVMSize stack_size = page_size_ * 3;
     GrowStack(stack_size, reinterpret_cast<LinuxVMAddress>(&stack_size));
   }
 
@@ -440,7 +441,7 @@
     } else {
       // Write-protect a page on our stack to split up the mapping
       LinuxVMAddress page_addr =
-          stack_address - (stack_address % page_size_) + 2 * page_size_;
+          stack_address - (stack_address % page_size_) + page_size_;
       ASSERT_EQ(
           mprotect(reinterpret_cast<void*>(page_addr), page_size_, PROT_READ),
           0)
@@ -808,7 +809,7 @@
   // presence of a libc symbol which was introduced in Q.
   if (!crashpad::internal::Dlsym(RTLD_DEFAULT,
                                  "android_fdsan_close_with_tag")) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   android_set_abort_message(kTestAbortMessage);
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_types/custom.cc b/third_party/crashpad/crashpad/snapshot/mac/process_types/custom.cc
index a76c3eb..3ca5646c 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_types/custom.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_types/custom.cc
@@ -19,7 +19,6 @@
 #include <sys/types.h>
 
 #include <algorithm>
-#include <limits>
 #include <type_traits>
 
 #include "base/logging.h"
@@ -27,7 +26,6 @@
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "snapshot/mac/process_types/internal.h"
-#include "util/mac/mac_util.h"
 #include "util/process/process_memory_mac.h"
 
 #if !DOXYGEN
@@ -142,8 +140,8 @@
       offsetof(dyld_all_image_infos<Traits>, sharedCacheSlide),  // 11
       offsetof(dyld_all_image_infos<Traits>, sharedCacheUUID),  // 12
       offsetof(dyld_all_image_infos<Traits>, infoArrayChangeTimestamp),  // 13
-      offsetof(dyld_all_image_infos<Traits>, end_14),  // 14
-      std::numeric_limits<size_t>::max(),  // 15, see below
+      offsetof(dyld_all_image_infos<Traits>, end_14_15),  // 14
+      offsetof(dyld_all_image_infos<Traits>, end_14_15),  // 15
       sizeof(dyld_all_image_infos<Traits>),  // 16
   };
 
@@ -153,27 +151,7 @@
 
   static_assert(std::is_unsigned<decltype(version)>::value,
                 "version must be unsigned");
-
-  if (version == 15) {
-    // Disambiguate between the two different layouts for version 15. The
-    // original one introduced in macOS 10.12 had the same size as version 14.
-    // The revised one in macOS 10.13 grew. It’s safe to assume that the
-    // dyld_all_image_infos structure came from the same system that’s now
-    // interpreting it, so use an OS version check.
-    int mac_os_x_minor_version = MacOSXMinorVersion();
-    if (mac_os_x_minor_version == 12) {
-      return offsetof(dyld_all_image_infos<Traits>, end_14);
-    }
-
-    DCHECK_GE(mac_os_x_minor_version, 13);
-    DCHECK_LE(mac_os_x_minor_version, 14);
-    return offsetof(dyld_all_image_infos<Traits>, platform);
-  }
-
-  size_t size = kSizeForVersion[version];
-  DCHECK_NE(size, std::numeric_limits<size_t>::max());
-
-  return size;
+  return kSizeForVersion[version];
 }
 
 // static
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_types/dyld_images.proctype b/third_party/crashpad/crashpad/snapshot/mac/process_types/dyld_images.proctype
index 3b040854..f92a800 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_types/dyld_images.proctype
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_types/dyld_images.proctype
@@ -121,29 +121,23 @@
   // the runtimes that use versions 14 and 15 were built with SDKs that did not
   // have this extra padding, it’s necessary to treat the element at index 4 on
   // 32-bit systems as outside of the version 14 and 15 structure. This is why
-  // |reserved| is only declared a 4-element array, with a special end_14 member
-  // (not present in the native definition) available to indicate the end of the
-  // native version 14 structure and the 10.12 version 15 structure, preceding
-  // the padding in the 32-bit structure that would natively be addressed at
-  // index 4 of |reserved|. Treat reserved_4_32 as only available in version 16
-  // of the structure.
+  // |reserved| is only declared a 4-element array, with a special end_14_15
+  // member (not present in the native definition) available to indicate the
+  // end of the native version 14 and 15 structure, preceding the padding in the
+  // 32-bit structure that would natively be addressed at index 4 of |reserved|.
+  // Treat reserved_4_32 as only available in version 16 of the structure.
   PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, reserved, [4])
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_4_64)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_5)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_6)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_7)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved64_64Only, reserved_8)
-  PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14)
+  PROCESS_TYPE_STRUCT_MEMBER(Nothing, end_14_15)
   PROCESS_TYPE_STRUCT_MEMBER(Reserved32_32Only, reserved_4_32)
 
-  // Version 15 (macOS 10.13). <mach-o/dyld_images.h> incorrectly claims that
-  // these were introduced at version 16. These fields are not present in macOS
-  // 10.12, which also identifies its structure as version 15.
+  // Version 16 (macOS 10.13)
   PROCESS_TYPE_STRUCT_MEMBER(UIntPtr, compact_dyld_image_info_addr)
   PROCESS_TYPE_STRUCT_MEMBER(ULong, compact_dyld_image_info_size)  // size_t
-
-  // Version 16 (macOS 10.15)
-  PROCESS_TYPE_STRUCT_MEMBER(uint32_t, platform)  // dyld_platform_t
 PROCESS_TYPE_STRUCT_END(dyld_all_image_infos)
 
 #endif  // ! PROCESS_TYPE_STRUCT_IMPLEMENT_INTERNAL_READ_INTO &&
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_types_test.cc b/third_party/crashpad/crashpad/snapshot/mac/process_types_test.cc
index 8523b9c..2ab3034c 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_types_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_types_test.cc
@@ -18,7 +18,6 @@
 #include <mach/mach.h>
 #include <string.h>
 
-#include <limits>
 #include <vector>
 
 #include "base/stl_util.h"
@@ -48,11 +47,13 @@
 TEST(ProcessTypes, DyldImagesSelf) {
   // Get the in-process view of dyld_all_image_infos, and check it for sanity.
   const dyld_all_image_infos* self_image_infos = DyldGetAllImageInfos();
-  const int mac_os_x_minor_version = MacOSXMinorVersion();
+  int mac_os_x_minor_version = MacOSXMinorVersion();
 
-  if (mac_os_x_minor_version >= 15) {
-    EXPECT_GE(self_image_infos->version, 16u);
-  } else if (mac_os_x_minor_version >= 12) {
+  // The 10.13 SDK defines dyld_all_image_infos version 16 and says that it’s
+  // used on 10.13, but 10.13db1 17A264c uses version 15.
+  //
+  // TODO(mark): Recheck later in the beta period, up to the 10.13 release.
+  if (mac_os_x_minor_version >= 12) {
     EXPECT_GE(self_image_infos->version, 15u);
   } else if (mac_os_x_minor_version >= 9) {
     EXPECT_GE(self_image_infos->version, 13u);
@@ -103,7 +104,7 @@
   ProcessReaderMac process_reader;
   ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
 
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
   constexpr uint32_t kDyldAllImageInfosVersionInSDK = 16;
 #elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
   constexpr uint32_t kDyldAllImageInfosVersionInSDK = 15;
@@ -119,33 +120,12 @@
 
   // Make sure that the size of the structure as declared in the SDK matches the
   // size expected for the version of the structure that the SDK describes.
-  //
-  // There are two possible layouts for version 15, and the
-  // ExpectedSizeForVersion() implementation infers the correct one based on the
-  // run-time OS version, so if the SDK defines the version 15 structure, this
-  // test can only be performed if the run-time OS natively uses the same format
-  // structure as the SDK.
-  bool test_expected_size_for_version_matches_sdk_sizeof;
-#if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_12
-  test_expected_size_for_version_matches_sdk_sizeof =
-      mac_os_x_minor_version == 12;
-#elif MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 && \
-    MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_14
-  test_expected_size_for_version_matches_sdk_sizeof =
-      mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14;
-#else
-  test_expected_size_for_version_matches_sdk_sizeof = true;
-#endif
-
-  if (test_expected_size_for_version_matches_sdk_sizeof) {
-    EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion(
-                  &process_reader, kDyldAllImageInfosVersionInSDK),
-              sizeof(dyld_all_image_infos));
-  }
+  EXPECT_EQ(process_types::dyld_all_image_infos::ExpectedSizeForVersion(
+                &process_reader, kDyldAllImageInfosVersionInSDK),
+            sizeof(dyld_all_image_infos));
 
   // Make sure that the computed sizes of various versions of this structure are
   // correct at different bitnessses.
-  constexpr size_t kSpecialCase = std::numeric_limits<size_t>::max();
   constexpr struct {
     uint32_t version;
     size_t size_32;
@@ -164,40 +144,13 @@
       {12, 84, 160},
       {13, 104, 184},
       {14, 164, 304},
-      {15, kSpecialCase, kSpecialCase},
-      {16, 184, 328},
+      {15, 164, 304},
+      {16, 176, 320},
   };
   for (size_t index = 0; index < base::size(kVersionsAndSizes); ++index) {
     uint32_t version = kVersionsAndSizes[index].version;
     SCOPED_TRACE(base::StringPrintf("index %zu, version %u", index, version));
 
-    if (version == 15) {
-      if (mac_os_x_minor_version == 12) {
-        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
-                      process_types::internal::Traits32>::
-                      ExpectedSizeForVersion(version),
-                  164u);
-        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
-                      process_types::internal::Traits64>::
-                      ExpectedSizeForVersion(version),
-                  304u);
-      } else if (mac_os_x_minor_version >= 13 && mac_os_x_minor_version <= 14) {
-        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
-                      process_types::internal::Traits32>::
-                      ExpectedSizeForVersion(version),
-                  176u);
-        EXPECT_EQ(process_types::internal::dyld_all_image_infos<
-                      process_types::internal::Traits64>::
-                      ExpectedSizeForVersion(version),
-                  320u);
-      }
-
-      continue;
-    }
-
-    ASSERT_NE(kVersionsAndSizes[index].size_32, kSpecialCase);
-    ASSERT_NE(kVersionsAndSizes[index].size_64, kSpecialCase);
-
     EXPECT_EQ(
         process_types::internal::dyld_all_image_infos<
             process_types::internal::Traits32>::ExpectedSizeForVersion(version),
@@ -353,7 +306,7 @@
 #endif
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
-  if (proctype_image_infos.version >= 15 && mac_os_x_minor_version >= 13) {
+  if (proctype_image_infos.version >= 16) {
     EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_addr,
               self_image_infos->compact_dyld_image_info_addr);
     EXPECT_EQ(proctype_image_infos.compact_dyld_image_info_size,
@@ -361,12 +314,6 @@
   }
 #endif
 
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
-  if (proctype_image_infos.version >= 16) {
-    EXPECT_EQ(proctype_image_infos.platform, self_image_infos->platform);
-  }
-#endif
-
   if (proctype_image_infos.version >= 1) {
     std::vector<process_types::dyld_image_info> proctype_image_info_vector(
         proctype_image_infos.infoArrayCount);
diff --git a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win_test.cc b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win_test.cc
index b4c44a83..486e6df 100644
--- a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win_test.cc
@@ -23,6 +23,7 @@
 #include "gtest/gtest.h"
 #include "snapshot/win/process_snapshot_win.h"
 #include "test/errors.h"
+#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -175,7 +176,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ExceptionSnapshotWinTest, ChildCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestCrashingChild(TestPaths::Architecture::k32Bit);
@@ -292,7 +293,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(SimulateCrash, ChildDumpWithoutCrashingWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestDumpWithoutCrashingChild(TestPaths::Architecture::k32Bit);
diff --git a/third_party/crashpad/crashpad/snapshot/win/extra_memory_ranges_test.cc b/third_party/crashpad/crashpad/snapshot/win/extra_memory_ranges_test.cc
index e0c17a2..dcef805 100644
--- a/third_party/crashpad/crashpad/snapshot/win/extra_memory_ranges_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/extra_memory_ranges_test.cc
@@ -25,6 +25,7 @@
 #include "client/simple_address_range_bag.h"
 #include "gtest/gtest.h"
 #include "snapshot/win/process_snapshot_win.h"
+#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -109,7 +110,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ExtraMemoryRanges, DontCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestExtraMemoryRanges(kDontCrash, TestPaths::Architecture::k32Bit);
@@ -117,7 +118,7 @@
 
 TEST(ExtraMemoryRanges, CrashDebugBreakWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestExtraMemoryRanges(kCrashDebugBreak, TestPaths::Architecture::k32Bit);
diff --git a/third_party/crashpad/crashpad/snapshot/win/module_snapshot_win_test.cc b/third_party/crashpad/crashpad/snapshot/win/module_snapshot_win_test.cc
index d9786cf..6a14cf5 100644
--- a/third_party/crashpad/crashpad/snapshot/win/module_snapshot_win_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/module_snapshot_win_test.cc
@@ -30,6 +30,7 @@
 #include "snapshot/annotation_snapshot.h"
 #include "snapshot/win/pe_image_reader.h"
 #include "snapshot/win/process_reader_win.h"
+#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -151,7 +152,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ModuleSnapshotWinTest, DontCrashWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestAnnotationsOnCrash(kDontCrash, TestPaths::Architecture::k32Bit);
@@ -159,7 +160,7 @@
 
 TEST(ModuleSnapshotWinTest, CrashDebugBreakWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestAnnotationsOnCrash(kCrashDebugBreak, TestPaths::Architecture::k32Bit);
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win_test.cc b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win_test.cc
index 7192aa85..8a3e6a4 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win_test.cc
@@ -20,6 +20,7 @@
 #include "snapshot/win/pe_image_reader.h"
 #include "snapshot/win/process_reader_win.h"
 #include "test/errors.h"
+#include "test/gtest_disabled.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
 #include "util/file/file_io.h"
@@ -119,7 +120,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ProcessSnapshotTest, CrashpadInfoChildWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestImageReaderChild(TestPaths::Architecture::k32Bit);
diff --git a/third_party/crashpad/crashpad/test/BUILD.gn b/third_party/crashpad/crashpad/test/BUILD.gn
index 7f44863..fb99bce6 100644
--- a/third_party/crashpad/crashpad/test/BUILD.gn
+++ b/third_party/crashpad/crashpad/test/BUILD.gn
@@ -25,6 +25,8 @@
     "filesystem.cc",
     "filesystem.h",
     "gtest_death.h",
+    "gtest_disabled.cc",
+    "gtest_disabled.h",
     "hex_string.cc",
     "hex_string.h",
     "main_arguments.cc",
diff --git a/third_party/crashpad/crashpad/test/gtest_disabled.cc b/third_party/crashpad/crashpad/test/gtest_disabled.cc
new file mode 100644
index 0000000..fab6802
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/gtest_disabled.cc
@@ -0,0 +1,83 @@
+// Copyright 2017 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/gtest_disabled.h"
+
+#include <stdio.h>
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace crashpad {
+namespace test {
+
+namespace {
+
+DisabledTestGtestEnvironment* g_instance;
+
+}  // namespace
+
+// static
+DisabledTestGtestEnvironment* DisabledTestGtestEnvironment::Get() {
+  if (!g_instance) {
+    g_instance = new DisabledTestGtestEnvironment();
+  }
+  return g_instance;
+}
+
+void DisabledTestGtestEnvironment::DisabledTest() {
+  const testing::TestInfo* test_info =
+      testing::UnitTest::GetInstance()->current_test_info();
+  std::string disabled_test = base::StringPrintf(
+      "%s.%s", test_info->test_case_name(), test_info->name());
+
+  // Show a DISABLED message using a format similar to gtest, along with a hint
+  // explaining that OK or FAILED will also appear.
+  printf(
+      "This test has been disabled dynamically.\n"
+      "It will appear as both DISABLED and OK or FAILED.\n"
+      "[ DISABLED ] %s\n",
+      disabled_test.c_str());
+
+  disabled_tests_.push_back(disabled_test);
+}
+
+DisabledTestGtestEnvironment::DisabledTestGtestEnvironment()
+    : testing::Environment(),
+      disabled_tests_() {
+  DCHECK(!g_instance);
+}
+
+DisabledTestGtestEnvironment::~DisabledTestGtestEnvironment() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+void DisabledTestGtestEnvironment::TearDown() {
+  if (!disabled_tests_.empty()) {
+    printf(
+        "[ DISABLED ] %" PRIuS " dynamically disabled test%s, listed below:\n"
+        "[ DISABLED ] %s also counted in PASSED or FAILED below.\n",
+        disabled_tests_.size(),
+        disabled_tests_.size() == 1 ? "" : "s",
+        disabled_tests_.size() == 1 ? "This test is" : "These tests are");
+    for (const std::string& disabled_test : disabled_tests_) {
+      printf("[ DISABLED ] %s\n", disabled_test.c_str());
+    }
+  }
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/test/gtest_disabled.h b/third_party/crashpad/crashpad/test/gtest_disabled.h
new file mode 100644
index 0000000..9415cba
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/gtest_disabled.h
@@ -0,0 +1,87 @@
+// Copyright 2017 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_TEST_GTEST_DISABLED_H_
+#define CRASHPAD_TEST_GTEST_DISABLED_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "gtest/gtest.h"
+
+//! \file
+
+namespace crashpad {
+namespace test {
+
+//! \brief Provides support for dynamically disabled gtest tests.
+//!
+//! A test runner must register this with gtest as follows prior to calling
+//! `RUN_ALL_TESTS()`:
+//! \code
+//!   testing::AddGlobalTestEnvironment(
+//!       crashpad::test::DisabledTestGtestEnvironment::Get());
+//! \endcode
+class DisabledTestGtestEnvironment final : public testing::Environment {
+ public:
+  //! \brief Returns the DisabledTestGtestEnvironment singleton instance,
+  //!     creating it if necessary.
+  static DisabledTestGtestEnvironment* Get();
+
+  //! \brief Displays a message about a test being disabled, and arranges for
+  //!     this information to be duplicated in TearDown().
+  //!
+  //! This method is for the internal use of the DISABLED_TEST() macro. Do not
+  //! call it directly, use the macro instead.
+  void DisabledTest();
+
+ private:
+  DisabledTestGtestEnvironment();
+  ~DisabledTestGtestEnvironment() override;
+
+  // testing::Environment:
+  void TearDown() override;
+
+  std::vector<std::string> disabled_tests_;
+
+  DISALLOW_COPY_AND_ASSIGN(DisabledTestGtestEnvironment);
+};
+
+}  // namespace test
+}  // namespace crashpad
+
+//! \brief Displays a message about a test being disabled, and returns early.
+//!
+//! gtest only provides a mechanism for tests to be disabled statically, by
+//! prefixing test case names or test names with `DISABLED_`. When it is
+//! necessary to disable tests dynamically, gtest provides no assistance. This
+//! macro displays a message about the disabled test and returns early. The
+//! dynamically disabled test will also be displayed during gtest global test
+//! environment tear-down before the test executable exits.
+//!
+//! This macro may only be invoked from the context of a gtest test.
+//!
+//! There’s a long-standing <a
+//! href="https://groups.google.com/d/topic/googletestframework/Nwh3u7YFuN4">gtest
+//! feature request</a> to provide this functionality directly in gtest, but
+//! since it hasn’t been implemented, this macro provides a local mechanism to
+//! achieve it.
+#define DISABLED_TEST()                                                    \
+  do {                                                                     \
+    ::crashpad::test::DisabledTestGtestEnvironment::Get()->DisabledTest(); \
+    return;                                                                \
+  } while (false)
+
+#endif  // CRASHPAD_TEST_GTEST_DISABLED_H_
diff --git a/third_party/crashpad/crashpad/test/gtest_main.cc b/third_party/crashpad/crashpad/test/gtest_main.cc
index 5a54691..ebdbeb9d5 100644
--- a/third_party/crashpad/crashpad/test/gtest_main.cc
+++ b/third_party/crashpad/crashpad/test/gtest_main.cc
@@ -14,6 +14,7 @@
 
 #include "build/build_config.h"
 #include "gtest/gtest.h"
+#include "test/gtest_disabled.h"
 #include "test/main_arguments.h"
 #include "test/multiprocess_exec.h"
 
@@ -50,6 +51,8 @@
 
 int main(int argc, char* argv[]) {
   crashpad::test::InitializeMainArguments(argc, argv);
+  testing::AddGlobalTestEnvironment(
+      crashpad::test::DisabledTestGtestEnvironment::Get());
 
   std::string child_func_name;
   if (GetChildTestFunctionName(&child_func_name)) {
diff --git a/third_party/crashpad/crashpad/test/test.gyp b/third_party/crashpad/crashpad/test/test.gyp
index d00256a7..8101f8f 100644
--- a/third_party/crashpad/crashpad/test/test.gyp
+++ b/third_party/crashpad/crashpad/test/test.gyp
@@ -37,6 +37,8 @@
         'filesystem.cc',
         'filesystem.h',
         'gtest_death.h',
+        'gtest_disabled.cc',
+        'gtest_disabled.h',
         'hex_string.cc',
         'hex_string.h',
         'linux/fake_ptrace_connection.cc',
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn
index c233f96..dec7d5c4 100644
--- a/third_party/crashpad/crashpad/util/BUILD.gn
+++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -281,7 +281,7 @@
     if (crashpad_use_boringssl_for_http_transport_socket) {
       defines += [ "CRASHPAD_USE_BORINGSSL" ]
 
-      if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
+      if (crashpad_is_in_fuchsia) {
         deps += [ "//third_party/boringssl" ]
       } else {
         libs = [
@@ -545,7 +545,7 @@
       ]
       defines = [ "CRASHPAD_USE_BORINGSSL" ]
 
-      if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
+      if (crashpad_is_in_fuchsia) {
         deps += [ "//third_party/boringssl" ]
       } else {
         libs = [
diff --git a/third_party/crashpad/crashpad/util/file/directory_reader_test.cc b/third_party/crashpad/crashpad/util/file/directory_reader_test.cc
index 812deaf..f03669e2 100644
--- a/third_party/crashpad/crashpad/util/file/directory_reader_test.cc
+++ b/third_party/crashpad/crashpad/util/file/directory_reader_test.cc
@@ -22,6 +22,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
 #include "test/filesystem.h"
+#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
@@ -47,7 +48,7 @@
 
 TEST(DirectoryReader, BadPaths_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -143,7 +144,7 @@
 
 TEST(DirectoryReader, FilesAndDirectories_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestFilesAndDirectories(true);
diff --git a/third_party/crashpad/crashpad/util/file/filesystem_test.cc b/third_party/crashpad/crashpad/util/file/filesystem_test.cc
index 37a9290b..3430c3f 100644
--- a/third_party/crashpad/crashpad/util/file/filesystem_test.cc
+++ b/third_party/crashpad/crashpad/util/file/filesystem_test.cc
@@ -21,6 +21,7 @@
 #include "gtest/gtest.h"
 #include "test/errors.h"
 #include "test/filesystem.h"
+#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "util/misc/time.h"
 
@@ -92,7 +93,7 @@
 
 TEST(Filesystem, FileModificationTime_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -223,7 +224,7 @@
 
 TEST(Filesystem, MoveFileOrDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -301,7 +302,7 @@
 
 TEST(Filesystem, IsRegularFile_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -343,7 +344,7 @@
 
 TEST(Filesystem, IsDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -392,7 +393,7 @@
 
 TEST(Filesystem, RemoveFile_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
@@ -449,7 +450,7 @@
 
 TEST(Filesystem, RemoveDirectory_SymbolicLinks) {
   if (!CanCreateSymbolicLinks()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   ScopedTempDir temp_dir;
diff --git a/third_party/crashpad/crashpad/util/linux/auxiliary_vector_test.cc b/third_party/crashpad/crashpad/util/linux/auxiliary_vector_test.cc
index 00af5ab..d4cfcb7 100644
--- a/third_party/crashpad/crashpad/util/linux/auxiliary_vector_test.cc
+++ b/third_party/crashpad/crashpad/util/linux/auxiliary_vector_test.cc
@@ -120,9 +120,8 @@
 
 #if defined(AT_SYSINFO_EHDR)
   LinuxVMAddress vdso_addr;
-  if (aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr)) {
-    EXPECT_TRUE(mappings.FindMapping(vdso_addr));
-  }
+  ASSERT_TRUE(aux.GetValue(AT_SYSINFO_EHDR, &vdso_addr));
+  EXPECT_TRUE(mappings.FindMapping(vdso_addr));
 #endif  // AT_SYSINFO_EHDR
 
 #if defined(AT_EXECFN)
diff --git a/third_party/crashpad/crashpad/util/net/tls.gni b/third_party/crashpad/crashpad/util/net/tls.gni
index eb968450..f979b62 100644
--- a/third_party/crashpad/crashpad/util/net/tls.gni
+++ b/third_party/crashpad/crashpad/util/net/tls.gni
@@ -18,6 +18,5 @@
   # TODO(scottmg): https://crbug.com/crashpad/266 fuchsia:DX-690: BoringSSL
   # was removed from the Fuchsia SDK. Re-enable it when we have a way to acquire
   # a BoringSSL lib again.
-  crashpad_use_boringssl_for_http_transport_socket =
-      crashpad_is_in_fuchsia || (crashpad_is_linux && crashpad_is_in_chromium)
+  crashpad_use_boringssl_for_http_transport_socket = crashpad_is_in_fuchsia
 }
diff --git a/third_party/crashpad/crashpad/util/win/process_info_test.cc b/third_party/crashpad/crashpad/util/win/process_info_test.cc
index a43358d..59aaa7a9 100644
--- a/third_party/crashpad/crashpad/util/win/process_info_test.cc
+++ b/third_party/crashpad/crashpad/util/win/process_info_test.cc
@@ -26,6 +26,7 @@
 #include "build/build_config.h"
 #include "gtest/gtest.h"
 #include "test/errors.h"
+#include "test/gtest_disabled.h"
 #include "test/scoped_temp_dir.h"
 #include "test/test_paths.h"
 #include "test/win/child_launcher.h"
@@ -202,7 +203,7 @@
 #if defined(ARCH_CPU_64_BITS)
 TEST(ProcessInfo, OtherProcessWOW64) {
   if (!TestPaths::Has32BitBuildArtifacts()) {
-    GTEST_SKIP();
+    DISABLED_TEST();
   }
 
   TestOtherProcess(TestPaths::Architecture::k32Bit);
diff --git a/third_party/polymer/v1_0/components-chromium/polymer2/polymer.html b/third_party/polymer/v1_0/components-chromium/polymer2/polymer.html
index 28250a9e..82d9b033 100644
--- a/third_party/polymer/v1_0/components-chromium/polymer2/polymer.html
+++ b/third_party/polymer/v1_0/components-chromium/polymer2/polymer.html
@@ -6,5 +6,4 @@
 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
 Code distributed by Google as part of the polymer project is also
 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
---><html><head></head><body><div hidden="" by-polymer-bundler=""></div>
-<script src="polymer-extracted.js"></script></body></html>
\ No newline at end of file
+--><html><head></head><body><div hidden="" by-polymer-bundler=""></div><script src="polymer-extracted.js"></script></body></html>
\ No newline at end of file
diff --git a/third_party/polymer/v1_0/minify_polymer.py b/third_party/polymer/v1_0/minify_polymer.py
index 87d004f..cb4ebb97 100644
--- a/third_party/polymer/v1_0/minify_polymer.py
+++ b/third_party/polymer/v1_0/minify_polymer.py
@@ -43,8 +43,8 @@
         '--strip-comments',
         '--inline-scripts',
         '--inline-css',
-        '--in-html', os.path.join(tmp_dir, 'polymer.html'),
-        '--out-html', os.path.join(tmp_out_dir, 'polymer.html')
+        '--out-file', os.path.join(tmp_out_dir, 'polymer.html'),
+        os.path.join(tmp_dir, 'polymer.html'),
     ])
 
     # Extract the JS to a separate file named polymer-extracted.js.
diff --git a/third_party/webrtc_overrides/BUILD.gn b/third_party/webrtc_overrides/BUILD.gn
index 1189cea..87f318c 100644
--- a/third_party/webrtc_overrides/BUILD.gn
+++ b/third_party/webrtc_overrides/BUILD.gn
@@ -10,6 +10,12 @@
   ]
 }
 
+component("webrtc_component") {
+  public_deps = [
+    "//third_party/webrtc/api:libjingle_peerconnection_api",
+  ]
+}
+
 source_set("webrtc") {
   public_deps = [
     # TODO(kjellander): Start cleaning up this target as soon as
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index f3e047a..b458b9f 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -52,6 +52,9 @@
                   ' tools/clang/scripts/process_crashreports.py'
                   ' (only works inside Google) which will upload a report')
 
+FIRST_LLVM_COMMIT = '97724f18c79c7cc81ced24239eb5e883bf1398ef'
+
+
 
 def RunCommand(command, msvc_arch=None, env=None, fail_hard=True):
   """Run command and return success (True) or failure; or if fail_hard is
@@ -104,9 +107,11 @@
   if os.path.isdir(dir):
     os.chdir(dir)
     # git diff-index --quiet returns success when there is no diff.
+    # Also check that the first commit is reachable.
     if (RunCommand(['git', 'diff-index', '--quiet', 'HEAD'], fail_hard=False)
         and RunCommand(['git', 'fetch'], fail_hard=False)
-        and RunCommand(['git', 'checkout', commit], fail_hard=False)):
+        and RunCommand(['git', 'checkout', commit], fail_hard=False)
+        and RunCommand(['git', 'show', FIRST_LLVM_COMMIT], fail_hard=False)):
       return
 
     # If we can't use the current repo, delete it.
@@ -114,12 +119,7 @@
     print('Removing %s.' % dir)
     RmTree(dir)
 
-  # Do a somewhat shallow clone to save on bandwidth for GitHub and for us.
-  # The depth was chosen to be deep enough to contain the version we're
-  # building, and shallow enough to save significantly on bandwidth compared to
-  # a full clone.
-  clone_cmd = ['git', 'clone', '--depth', '10000',
-               'https://github.com/llvm/llvm-project/', dir]
+  clone_cmd = ['git', 'clone', 'https://github.com/llvm/llvm-project/', dir]
 
   if RunCommand(clone_cmd, fail_hard=False):
     os.chdir(dir)
@@ -147,12 +147,10 @@
   return ref['object']['sha']
 
 
-def GetSvnRevision(commit):
-  """Get the svn revision corresponding to a git commit in the LLVM repo."""
-  commit = json.loads(UrlOpen(('https://api.github.com/repos/llvm/'
-                               'llvm-project/git/commits/' + commit)))
-  revision = re.search("llvm-svn: ([0-9]+)$", commit['message']).group(1)
-  return revision
+def GetCommitCount(commit):
+  """Get the number of commits from FIRST_LLVM_COMMIT to commit."""
+  return subprocess.check_output(['git', 'rev-list', '--count',
+                                  FIRST_LLVM_COMMIT + '..' + commit]).rstrip()
 
 
 def DeleteChromeToolsShim():
@@ -371,6 +369,9 @@
     return 1
 
 
+  # Don't buffer stdout, so that print statements are immediately flushed.
+  sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+
   # The gnuwin package also includes curl, which is needed to interact with the
   # github API below.
   # TODO(crbug.com/965937): Use urllib once our Python is recent enough, and
@@ -384,11 +385,13 @@
   global CLANG_REVISION, PACKAGE_VERSION
   if args.llvm_force_head_revision:
     CLANG_REVISION = GetLatestLLVMCommit()
-    PACKAGE_VERSION = '%s-%s-0' % (GetSvnRevision(CLANG_REVISION),
-                                   CLANG_REVISION[:8])
 
-  # Don't buffer stdout, so that print statements are immediately flushed.
-  sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+  if not args.skip_checkout:
+    CheckoutLLVM(CLANG_REVISION, LLVM_DIR);
+
+  if args.llvm_force_head_revision:
+    PACKAGE_VERSION = 'n%s-%s-0' % (GetCommitCount(CLANG_REVISION),
+                                   CLANG_REVISION[:8])
 
   print('Locally building clang %s...' % PACKAGE_VERSION)
   WriteStampFile('', STAMP_FILE)
@@ -397,8 +400,6 @@
   AddCMakeToPath(args)
   DeleteChromeToolsShim()
 
-  if not args.skip_checkout:
-    CheckoutLLVM(CLANG_REVISION, LLVM_DIR);
 
   if args.skip_build:
     return 0
diff --git a/tools/clang/scripts/upload_revision.py b/tools/clang/scripts/upload_revision.py
index 5f0a53f..7d762e2 100755
--- a/tools/clang/scripts/upload_revision.py
+++ b/tools/clang/scripts/upload_revision.py
@@ -18,7 +18,7 @@
 import subprocess
 import sys
 
-from build import GetSvnRevision
+from build import GetCommitCount
 
 # Path constants.
 THIS_DIR = os.path.dirname(__file__)
@@ -66,14 +66,14 @@
   args = parser.parse_args()
 
   clang_git_revision = args.clang_git_revision[0]
-  clang_svn_revision = GetSvnRevision(clang_git_revision)
+  clang_svn_revision = GetCommitCount(clang_git_revision)
   clang_sub_revision = args.clang_sub_revision
 
   # Needs shell=True on Windows due to git.bat in depot_tools.
   git_revision = subprocess.check_output(
       ["git", "rev-parse", "origin/master"], shell=is_win).strip()
 
-  print("Making a patch for Clang {}-{}-{}".format(
+  print("Making a patch for Clang n{}-{}-{}".format(
       clang_svn_revision, clang_git_revision[:8], clang_sub_revision))
   print("Chrome revision: {}".format(git_revision))
 
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 2bc6f874..2da1853 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -68,7 +68,6 @@
       'android-cronet-x86-dbg': 'android_cronet_debug_static_bot_x86',
       'android-cronet-x86-rel': 'android_cronet_release_bot_minimal_symbols_x86',
       'android-incremental-dbg': 'android_incremental_debug_bot',
-      'android-jumbo-rel': 'android_jumbo_release_bot_minimal_symbols',
       'android-kitkat-arm-rel': 'android_release_bot_minimal_symbols',
 
       # This bot must use the gpu_tests mixin to match 'Android FYI Release (Nexus 5X)'
@@ -288,9 +287,6 @@
 
       'ios-simulator-code-coverage': 'clang_code_coverage_ios',
       'ios-simulator': 'ios_error',
-      'Jumbo Linux x64': 'jumbo_large_chunks_release_bot_compile_only',
-      'Jumbo Mac': 'jumbo_release_bot_compile_only',
-      'Jumbo Win x64': 'jumbo_release_bot_compile_only',
       'Libfuzzer Upload Chrome OS ASan': 'libfuzzer_chromeos_asan_release_bot',
       'Libfuzzer Upload Linux ASan': 'libfuzzer_asan_release_bot',
       'Libfuzzer Upload Linux ASan Debug': 'libfuzzer_asan_debug_bot',
@@ -449,9 +445,6 @@
       'fuchsia-x64-cast': 'release_bot_fuchsia_cast',
       'fuchsia-x64-dbg': 'debug_bot_fuchsia_compile_only',
       'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
-      # linux-jumbo-rel is identical to linux-rel for perf comparisons, except
-      # for the jumbo part.
-      'linux-jumbo-rel': 'gpu_tests_release_bot_jumbo_no_symbols_use_dummy_lastchange',
       'linux-ozone-rel': 'ozone_linux_release_bot',
       'linux-trusty-rel': 'gpu_tests_release_bot',
     },
@@ -479,7 +472,6 @@
     'chromium.mac': {
       'Mac Builder': 'gpu_tests_release_bot_minimal_symbols',
       'Mac Builder (dbg)': 'gpu_tests_debug_bot',
-      'mac-jumbo-rel': 'jumbo_large_chunks_release_bot_compile_only',
       'ios-device': 'ios_error',
       'ios-device-xcode-clang': 'ios_error',
       'ios-simulator': 'ios_error',
@@ -613,7 +605,6 @@
       # Windows bots take too long to link w/ full symbols and time out.
       'Win Builder': 'gpu_tests_release_bot_x86_minimal_symbols',
       'Win Builder (dbg)': 'gpu_tests_debug_bot_x86',
-      'win-jumbo-rel': 'jumbo_large_chunks_release_bot_compile_only',
       'Win x64 Builder': 'gpu_tests_release_bot_minimal_symbols',
       'Win x64 Builder (dbg)': 'gpu_tests_debug_bot',
       'Windows deterministic': 'release_bot_x86_minimal_symbols',
@@ -800,7 +791,6 @@
       'linux-clang-tidy-rel': 'release_trybot',
       'linux-dcheck-off-rel': 'release_trybot_dcheck_off',
       'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
-      'linux-jumbo-rel': 'jumbo_large_chunks_release_bot_compile_only',
       'linux-libfuzzer-asan-rel': 'libfuzzer_asan_release_trybot',
       'linux-ozone-rel': 'ozone_linux_release_trybot',
       'linux-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
@@ -863,7 +853,6 @@
       'ios-simulator-full-configs': 'ios_error',
       'ios-simulator-cronet': 'ios_error',
       'ios-simulator-xcode-clang': 'ios_error',
-      'mac-jumbo-rel': 'jumbo_large_chunks_release_bot_compile_only',
       'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
       'mac_chromium_10.10': 'gpu_tests_release_trybot_deterministic_mac',
       'mac_chromium_10.12_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
@@ -910,7 +899,6 @@
       'win-annotator-rel': 'release_trybot',
       'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release',
       'win-celab-try-rel': 'celab_release_bot',
-      'win-jumbo-rel': 'jumbo_large_chunks_release_bot_compile_only',
       'win-libfuzzer-asan-rel': 'libfuzzer_windows_asan_release_trybot',
       'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
       'win_x64_archive': 'release_trybot',
@@ -1108,10 +1096,6 @@
       'android', 'incremental', 'debug_bot',
     ],
 
-    'android_jumbo_release_bot_minimal_symbols': [
-      'android', 'jumbo', 'release_bot', 'minimal_symbols',
-    ],
-
     'android_release_bot_minimal_symbols': [
       'android', 'release_bot', 'minimal_symbols',
       'strip_debug_info',
@@ -1686,11 +1670,6 @@
       'use_clang_coverage', 'partial_code_coverage_instrumentation',
     ],
 
-    'gpu_tests_release_bot_jumbo_no_symbols_use_dummy_lastchange': [
-      'gpu_tests', 'release_bot', 'jumbo_non_goma_chunks', 'no_symbols',
-      'use_dummy_lastchange',
-    ],
-
     'gpu_tests_release_trybot': [
       'gpu_tests', 'release_trybot',
     ],
@@ -1718,14 +1697,6 @@
     # build files.
     'ios_error': [ 'error'],
 
-    'jumbo_release_bot_compile_only': [
-      'jumbo', 'release_bot', 'compile_only',
-    ],
-
-    'jumbo_large_chunks_release_bot_compile_only': [
-      'jumbo_non_goma_chunks', 'release_bot', 'compile_only',
-    ],
-
     'libfuzzer_asan_debug_bot': [
       'libfuzzer', 'asan', 'debug_bot', 'shared', 'chromeos_codecs', 'pdf_xfa', 'disable_nacl', 'optimize_for_fuzzing', 'disable_seed_corpus',
     ],
@@ -2293,7 +2264,7 @@
     },
 
     'incremental': {
-      'gn_args': 'disable_incremental_isolated_processes=true incremental_apk_by_default=true',
+      'gn_args': 'incremental_install=true',
     },
 
     'internal_gles_conform_tests': {
@@ -2308,14 +2279,6 @@
       'gn_args': 'use_jacoco_coverage=true',
     },
 
-    'jumbo': {
-      'gn_args': 'use_jumbo_build=true',
-    },
-
-    'jumbo_non_goma_chunks': {
-      'gn_args': 'use_jumbo_build=true jumbo_file_merge_limit=50',
-    },
-
     'libcxx': {
       'gn_args': 'use_custom_libcxx=true',
     },
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index c02f457..b03839f1 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -211,7 +211,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosHighContrast" enum="BooleanEnabled"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dmazzoni@chromium.org</owner>
   <owner>kenjibaheux@google.com</owner>
   <summary>
@@ -317,7 +317,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSpokenFeedback" enum="BooleanEnabled"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dmazzoni@chromium.org</owner>
   <owner>kenjibaheux@google.com</owner>
   <owner>aleventhal@google.com</owner>
@@ -391,7 +391,7 @@
 </histogram>
 
 <histogram name="Accessibility.ImageLabels.FromSettings.ToggleSetting"
-    enum="BooleanEnabled" expires_after="2020-02-15">
+    enum="BooleanEnabled" expires_after="2020-04-19">
   <owner>katie@chromium.org</owner>
   <owner>dmazzoni@chromium.org</owner>
   <summary>
@@ -404,7 +404,7 @@
 </histogram>
 
 <histogram name="Accessibility.ImageLabels.ModalDialogAccepted"
-    enum="BooleanAccepted" expires_after="2020-02-15">
+    enum="BooleanAccepted" expires_after="2020-04-19">
   <owner>katie@chromium.org</owner>
   <owner>dmazzoni@chromium.org</owner>
   <summary>
@@ -514,7 +514,7 @@
 </histogram>
 
 <histogram name="Accessibility.ModeFlag" enum="AccessibilityModeFlagEnum"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dmazzoni@chromium.org</owner>
   <owner>aboxhall@chromium.org</owner>
   <summary>
@@ -4392,7 +4392,7 @@
 </histogram>
 
 <histogram name="AndroidSms.FcmMessageDispatchRetry"
-    enum="AndroidSmsFcmMessageType" expires_after="2020-02-16">
+    enum="AndroidSmsFcmMessageType" expires_after="2020-04-19">
   <owner>azeemarshad@chromium.org</owner>
   <summary>
     Records message types for which a retry was attempted when dispatching to
@@ -4485,7 +4485,7 @@
 </histogram>
 
 <histogram name="AnimatedImage.NumOfFramesSkipped" units="count"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>khushalsagar@chromium.org</owner>
   <summary>
     If the frame rate for the image animation can not be reached, frames in the
@@ -4601,7 +4601,7 @@
 </histogram>
 
 <histogram name="AppBanners.InstallEvent" enum="AppBannersInstallEvent"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>pjmclachlan@google.com</owner>
   <owner>pcovell@google.com</owner>
   <summary>
@@ -5608,7 +5608,7 @@
 </histogram>
 
 <histogram name="Apps.AppListRecommendedResponse.Count" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>napper@chromium.org</owner>
   <owner>robsc@chromium.org</owner>
   <summary>
@@ -6521,7 +6521,7 @@
 </histogram>
 
 <histogram name="Arc.CupsPrinting.PageCount" units="units"
-    expires_after="2020-01-20">
+    expires_after="2020-04-19">
   <owner>skau@chromium.org</owner>
   <owner>vkuzkokov@google.com</owner>
   <summary>
@@ -6847,7 +6847,7 @@
 </histogram>
 
 <histogram name="Arc.OOMKills.Score" units="badness score"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>elijahtaylor@google.com</owner>
   <owner>shihuis@google.com</owner>
   <summary>
@@ -6987,7 +6987,7 @@
 </histogram>
 
 <histogram name="Arc.Provisioning.TimeDelta.Failure" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>alexchau@google.com</owner>
@@ -6998,7 +6998,7 @@
 </histogram>
 
 <histogram name="Arc.Provisioning.TimeDelta.Success" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>alexchau@google.com</owner>
@@ -7102,7 +7102,7 @@
 </histogram>
 
 <histogram name="Arc.StateByUserType" enum="ArcEnableState"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>elijahtaylor@google.com</owner>
@@ -7641,7 +7641,7 @@
 </histogram>
 
 <histogram name="Ash.Login.Lock.NumPasswordAttempts.UntilFailure" units="count"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>kerrnel@google.com</owner>
   <summary>
     The number of incorrect password entered in ChromeOS login/lock screen until
@@ -7699,7 +7699,7 @@
 </histogram>
 
 <histogram name="Ash.NightLight.Temperature"
-    enum="AshNightLightTemperatureRanges" expires_after="2020-02-16">
+    enum="AshNightLightTemperatureRanges" expires_after="2020-04-19">
   <owner>afakhry@chromium.org</owner>
   <summary>
     The ranges in which the selected values of the Night Light color temperature
@@ -8043,7 +8043,7 @@
 </histogram>
 
 <histogram name="Ash.Shelf.Palette.Usage" enum="PaletteTrayOptions"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>jdufault@chromium.org</owner>
   <summary>
     Recorded every time that the palette option has been selected from the
@@ -8108,7 +8108,7 @@
 </histogram>
 
 <histogram name="Ash.ShelfAlignmentUsage" enum="ShelfAlignmentValue"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>kuscher@google.com</owner>
   <summary>
     The current state of the shelf (alignment) when the shelf launcher is used
@@ -8880,7 +8880,7 @@
 </histogram>
 
 <histogram name="Assistant.OptInFlowStatus" enum="AssistantOptInFlowStatus"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>updowndota@chromium.org</owner>
   <summary>Record the status of the Assistant opt-in flow.</summary>
 </histogram>
@@ -9043,7 +9043,7 @@
 </histogram>
 
 <histogram name="Assistant.ServiceEnabledUserCount" enum="BooleanEnabled"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>xiaohuic@chromium.org</owner>
   <owner>meilinw@chromium.org</owner>
   <summary>
@@ -9993,7 +9993,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.ErrorTypeOfAutoMachinePasswordChange"
-    enum="AuthPolicyErrorType" expires_after="2020-02-16">
+    enum="AuthPolicyErrorType" expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10043,7 +10043,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.ErrorTypeOfRefreshDevicePolicy"
-    enum="AuthPolicyErrorType" expires_after="2020-02-16">
+    enum="AuthPolicyErrorType" expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10054,7 +10054,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.ErrorTypeOfRefreshUserPolicy"
-    enum="AuthPolicyErrorType" expires_after="2020-02-16">
+    enum="AuthPolicyErrorType" expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10065,7 +10065,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.FailedTriesOfKinit" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10078,7 +10078,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.FailedTriesOfSmbClient" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10090,7 +10090,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.NumGposToDownload" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10125,7 +10125,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToGetUserStatus" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10172,7 +10172,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunKinit" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10185,7 +10185,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunKlist" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10211,7 +10211,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunNetAdsGpo" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10223,7 +10223,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunNetAdsInfo" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10246,7 +10246,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunNetAdsSearch" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10258,7 +10258,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunNetAdsWorkgroup" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -10270,7 +10270,7 @@
 </histogram>
 
 <histogram name="AuthPolicy.TimeToRunSmbclient" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>fsandrade@chromium.org</owner>
   <owner>ljusten@chromium.org</owner>
   <owner>tomdobro@chromium.org</owner>
@@ -13954,7 +13954,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Registration.OneShot.CouldFire"
-    enum="BooleanCouldFireImmediately" expires_after="2020-02-16">
+    enum="BooleanCouldFireImmediately" expires_after="2020-04-19">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
@@ -15517,7 +15517,7 @@
 </histogram>
 
 <histogram name="Blink.ImageDecoders.Jpeg.Area" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>andrescj@chromium.org</owner>
   <summary>
     Number of pixels in a decoded JPEG image. Recorded after decoding is done by
@@ -15531,7 +15531,7 @@
 </histogram>
 
 <histogram name="Blink.ImageDecoders.Jpeg.ColorSpace" enum="JpegColorSpace"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>andrescj@chromium.org</owner>
   <owner>mcasas@chromium.org</owner>
   <summary>
@@ -15746,7 +15746,7 @@
 </histogram>
 
 <histogram base="true" name="Blink.MainFrame.StyleAndLayoutRatio" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>schenney@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="BlinkMainFrameUpdateTimeSuffixes" -->
@@ -16475,7 +16475,7 @@
 </histogram>
 
 <histogram name="Blink.StyleAndLayout.UpdateTime" units="microseconds"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>layout-dev@chromium.org</owner>
   <summary>
     Time spent updating style and layout in the Blink document lifecycle.
@@ -16613,7 +16613,7 @@
 </histogram>
 
 <histogram name="Blink.UseCounter.FeaturePolicy.AttributeAllowlistType"
-    enum="FeaturePolicyAllowlistType" expires_after="M79">
+    enum="FeaturePolicyAllowlistType" expires_after="2020-04-19">
   <owner>iclelland@chromium.org</owner>
   <owner>feature-control@chromium.org</owner>
   <summary>
@@ -18807,7 +18807,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.ChildLaunchFailures"
-    enum="RendererType" expires_after="2020-02-16">
+    enum="RendererType" expires_after="2020-04-19">
   <owner>wfh@chromium.org</owner>
   <summary>Count of renderer process launch failures grouped by type.</summary>
 </histogram>
@@ -18906,7 +18906,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.SpareProcessMaybeTakeAction"
-    enum="SpareProcessMaybeTakeAction" expires_after="2020-02-16">
+    enum="SpareProcessMaybeTakeAction" expires_after="2020-04-19">
   <owner>alexmos@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <summary>
@@ -21050,7 +21050,7 @@
 </histogram>
 
 <histogram name="ChromeOS.PlatformVerification.Result"
-    enum="ChromeOSPlatformVerificationResult" expires_after="2020-02-16">
+    enum="ChromeOSPlatformVerificationResult" expires_after="2020-04-19">
   <owner>apronin@chromium.org</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
@@ -21890,7 +21890,7 @@
 </histogram>
 
 <histogram name="ComponentUpdater.Calls" enum="ComponentUpdaterCalls"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>sorin@chromium.org</owner>
   <summary>
     The number of times the component updater called UpdateClient::Install or
@@ -22522,7 +22522,7 @@
 </histogram>
 
 <histogram name="Compositing.Renderer.CALayerResult" enum="CALayerResult"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ccameron@chromium.org</owner>
   <summary>
     The outcome of attempting to replace all renderer tiles with CALayers.
@@ -22703,7 +22703,7 @@
 </histogram>
 
 <histogram name="Compositing.Renderer.PictureMemoryUsageKb" units="KB"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>paint-dev@chromium.org</owner>
   <summary>
     Total estimated memory used by SkPictures in the layer tree, in kilobytes.
@@ -22806,7 +22806,7 @@
 
 <histogram
     name="Compositing.RenderPass.AppendQuadData.CheckerboardedNeedRasterContentArea"
-    units="pixels/frame" expires_after="2020-02-16">
+    units="pixels/frame" expires_after="2020-04-19">
   <owner>weiliangc@chromium.org</owner>
   <summary>
     Checkerboarded area, in number of pixels, that has recording but does not
@@ -22904,7 +22904,7 @@
 </histogram>
 
 <histogram name="Compositing.SurfaceAggregator.AggregateUs"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>weiliangc@chromium.org</owner>
   <summary>
     Time spent aggregating compositor frames from different surfaces in
@@ -25599,7 +25599,7 @@
 </histogram>
 
 <histogram name="Cookie.TimeBlockedOnLoad" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>nharper@chromium.org</owner>
   <summary>
     The amount of time (ms) between the cookie store load starting and
@@ -25745,7 +25745,7 @@
   </summary>
 </histogram>
 
-<histogram name="Cookie.Type" enum="CookieType" expires_after="2020-02-16">
+<histogram name="Cookie.Type" enum="CookieType" expires_after="2020-04-19">
   <owner>mkwst@chromium.org</owner>
   <summary>For each cookie added to the store, record it's type(s).</summary>
 </histogram>
@@ -25881,7 +25881,7 @@
 </histogram>
 
 <histogram name="Cras.HighestInputHardwareLevel" units="frames"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>yuhsuan@chromium.org</owner>
   <owner>chromeos-audio@google.com</owner>
   <summary>
@@ -26658,7 +26658,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.FormatCompletedError"
-    enum="CrosDisksClientFormatError" expires_after="2020-02-16">
+    enum="CrosDisksClientFormatError" expires_after="2020-04-19">
   <owner>austinct@chromium.org</owner>
   <summary>
     The error code of disk format signals received from the Chrome OS cros-disks
@@ -26667,7 +26667,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.FormatTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>austinct@chromium.org</owner>
   <summary>
     Time taken for the Chrome OS cros-disks daemon to perform a format
@@ -26702,7 +26702,7 @@
 </histogram>
 
 <histogram name="CrosDisksClient.UnmountError" enum="CrosDisksClientMountError"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>amistry@chromium.org</owner>
   <summary>
     The error code of disk unmount operations returned from the Chrome OS
@@ -27207,7 +27207,7 @@
 </histogram>
 
 <histogram name="CryptAuth.Enrollment.Result" enum="BooleanSuccess"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>jhawkins@chromium.org</owner>
   <summary>
     The top-level result of the CryptAuth device enrollment process.
@@ -33949,7 +33949,7 @@
 </histogram>
 
 <histogram name="Download.BandwidthOverallBytesPerSecond" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dtrainor@chromium.org</owner>
   <summary>
     Overall bandwidth seen for the download. Note that this is measured at the
@@ -34098,7 +34098,7 @@
 </histogram>
 
 <histogram name="Download.CountsChrome" enum="ChromeDownloadCountType"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dtrainor@chromium.org</owner>
   <summary>
     Various individual counts in the download system, for example the number of
@@ -34471,7 +34471,7 @@
 </histogram>
 
 <histogram name="Download.HistorySize2" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>dtrainor@chromium.org</owner>
   <summary>
     The number of items in the History database, at the time a new download is
@@ -37634,7 +37634,7 @@
 </histogram>
 
 <histogram name="Enterprise.PolicyInvalidationsRegistrationResult"
-    enum="BooleanSuccess" expires_after="2020-02-14">
+    enum="BooleanSuccess" expires_after="2020-04-19">
   <owner>poromov@chromium.org</owner>
   <summary>
     Tracks the result of registration for policy invalidations. It will fail
@@ -38781,7 +38781,7 @@
 </histogram>
 
 <histogram name="Event.InputEventPrediction.Accuracy.Scroll" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <obsolete>
     Deprecated 10/2019.
   </obsolete>
@@ -38929,7 +38929,7 @@
 </histogram>
 
 <histogram name="Event.Latency.BlockingTime.KeyPressDefaultAllowed" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
@@ -39891,7 +39891,7 @@
 </histogram>
 
 <histogram name="Event.Latency.Scroll.Wheel.TimeToScrollUpdateSwapBegin2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between initial creation of a wheel event and start of the frame swap
@@ -40164,7 +40164,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollBegin.Touch.TimeToHandled2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between initial creation of a touch event and the first generated
@@ -40265,7 +40265,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollBegin.Wheel.GpuSwap2" units="microseconds"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between gpu starts to swap the first ScrollUpdate gesture event in a
@@ -40283,7 +40283,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollBegin.Wheel.HandledToRendererSwap2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between the first ScrollUpdate gesture event in a given scroll gesture
@@ -40843,7 +40843,7 @@
 
 <histogram
     name="Event.Latency.ScrollUpdate.Touch.RendererSwapToBrowserNotified2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between the renderer starts to swap a frame induced by ScrollUpdate
@@ -41009,7 +41009,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollUpdate.Wheel.HandledToRendererSwap2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between the ScrollUpdate gesture event with wheel source, is handled on
@@ -41059,7 +41059,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollUpdate.Wheel.TimeToHandled2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>tdresser@chromium.org</owner>
   <summary>
     Time between initial creation of a wheel event and the generated
@@ -48221,7 +48221,7 @@
 </histogram>
 
 <histogram name="FileBrowser.FormatFileSystemType"
-    enum="FileManagerFormatFileSystemType" expires_after="2020-02-16">
+    enum="FileManagerFormatFileSystemType" expires_after="2020-04-19">
   <owner>austinct@chromium.org</owner>
   <summary>
     Chrome OS File Browser: this records the filesystem selected when formatting
@@ -51282,7 +51282,7 @@
   </summary>
 </histogram>
 
-<histogram name="GPU.ContextMemory" units="MB" expires_after="2020-02-16">
+<histogram name="GPU.ContextMemory" units="MB" expires_after="2020-04-19">
   <owner>ericrk@chromium.org</owner>
   <summary>The amount of memory used by a GL Context.</summary>
 </histogram>
@@ -52058,7 +52058,7 @@
 </histogram>
 
 <histogram name="GPU.IOSurface.CATransactionTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ccameron@chromium.org</owner>
   <summary>
     The time that it took to update the CALayer tree and commit the transaction.
@@ -52067,7 +52067,7 @@
 </histogram>
 
 <histogram name="GPU.IOSurface.CreateTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ccameron@chromium.org</owner>
   <summary>
     The time that it took for a call to IOSurfaceCreate to complete.
@@ -52542,7 +52542,7 @@
 </histogram>
 
 <histogram name="GPU.TextureR16Ext_LuminanceF16" enum="GpuTextureResultR16_L16"
-    expires_after="M79">
+    expires_after="2020-04-19">
   <owner>hubbe@chromium.org</owner>
   <owner>rijubrata.bhaumik@intel.com</owner>
   <owner>media-dev@chromium.org</owner>
@@ -54330,7 +54330,7 @@
   </summary>
 </histogram>
 
-<histogram name="HttpCache.AccessToDone" units="ms" expires_after="2020-02-16">
+<histogram name="HttpCache.AccessToDone" units="ms" expires_after="2020-04-19">
   <owner>morlovich@chromium.org</owner>
   <summary>
     For every http cache transaction with a pattern (see HttpCache.Pattern), the
@@ -57981,7 +57981,7 @@
 </histogram>
 
 <histogram name="IOS.WKWebViewStartProvisionalNavigationWithEmptyURL"
-    enum="Boolean" expires_after="2020-02-16">
+    enum="Boolean" expires_after="2020-04-19">
   <owner>mrsuyi@chromium.org</owner>
   <owner>eugenebut@chromium.org</owner>
   <summary>
@@ -59712,7 +59712,7 @@
 </histogram>
 
 <histogram name="Login.OfflineFailure.IsKnownUser" enum="LoginIsKnownUser"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>glevin@chromium.org</owner>
   <summary>
     On offline login failure, records whether it is for an existing user.
@@ -59720,7 +59720,7 @@
 </histogram>
 
 <histogram name="Login.OfflineSuccess.Attempts" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>glevin@chromium.org</owner>
   <summary>
     On offline login success, records number of attempts, including success.
@@ -59728,7 +59728,7 @@
 </histogram>
 
 <histogram name="Login.PasswordChangeFlow" enum="LoginPasswordChangeFlow"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>xiyuan@chromium.org</owner>
   <owner>omrilio@chromium.org</owner>
   <summary>
@@ -59761,7 +59761,7 @@
 </histogram>
 
 <histogram name="Login.ReauthReason" enum="LoginReauthReasons"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>achuith@chromium.org</owner>
   <summary>
     Tracks the reason why a user was sent through the GAIA re-auth flow.
@@ -71633,7 +71633,7 @@
 </histogram>
 
 <histogram name="MultiDevice.BetterTogetherSuite.MultiDeviceFeatureState"
-    enum="MultiDevice_FeatureState" expires_after="2020-02-16">
+    enum="MultiDevice_FeatureState" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>khorimoto@chromium.org</owner>
   <summary>
@@ -71679,7 +71679,7 @@
 
 <histogram
     name="MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Disable.FailedFeature"
-    enum="MultiDevice_DeviceSyncService_Features" expires_after="2020-02-16">
+    enum="MultiDevice_DeviceSyncService_Features" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>khorimoto@chromium.org</owner>
   <summary>
@@ -71689,7 +71689,7 @@
 
 <histogram
     name="MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Enable.FailedFeature"
-    enum="MultiDevice_DeviceSyncService_Features" expires_after="2020-02-16">
+    enum="MultiDevice_DeviceSyncService_Features" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>khorimoto@chromium.org</owner>
   <summary>Breaks down which features failed when attempted to enable.</summary>
@@ -71705,7 +71705,7 @@
 <histogram
     name="MultiDevice.DeviceSyncService.SetSoftwareFeatureState.Result.FailureReason"
     enum="MultiDevice_DeviceSyncService_DeviceSyncRequestFailureReason"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>khorimoto@chromium.org</owner>
   <summary>
@@ -92269,7 +92269,7 @@
 </histogram>
 
 <histogram name="NQE.Kbps.OnECTComputation" units="Kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>bengr@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -96417,7 +96417,7 @@
 </histogram>
 
 <histogram name="OOBE.ErrorScreensTime.Update" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>achuith@chromium.org</owner>
   <summary>Time spent on error screens during update.</summary>
 </histogram>
@@ -105413,7 +105413,7 @@
   </summary>
 </histogram>
 
-<histogram name="PDF.PageCount" units="pages" expires_after="2020-02-16">
+<histogram name="PDF.PageCount" units="pages" expires_after="2020-04-19">
   <owner>hnakashima@chromium.org</owner>
   <summary>
     Tracks the number of pages in PDF documents opened in the PDF viewer.
@@ -110396,7 +110396,7 @@
 </histogram>
 
 <histogram name="Power.BacklightLevelOnBattery" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>tbroch@chromium.org</owner>
   <summary>
     The level of the backlight as a percentage when the user is on battery.
@@ -110776,7 +110776,7 @@
 </histogram>
 
 <histogram name="Power.ExternalDisplayOpenResult"
-    enum="ExternalDisplayOpenResult" expires_after="2020-02-16">
+    enum="ExternalDisplayOpenResult" expires_after="2020-04-19">
   <owner>tbroch@chromium.org</owner>
   <summary>
     The result of attempting to open an I2C device to control an external
@@ -110941,7 +110941,7 @@
 </histogram>
 
 <histogram name="Power.KeyboardBacklightLevel" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>tbroch@chromium.org</owner>
   <summary>
     The level of the keyboard backlight as a percentage. Sampled every 30
@@ -114225,7 +114225,7 @@
 </histogram>
 
 <histogram name="PrintPreview.InitializationTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>thestig@chromium.org</owner>
   <summary>
     Time from when print preview is intiated until the preview PDF generation is
@@ -114478,7 +114478,7 @@
 </histogram>
 
 <histogram name="PrintPreview.RenderAndGeneratePDFTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>thestig@chromium.org</owner>
   <summary>
     Time taken to render and generate PDF for print preview. (Includes time to
@@ -114511,7 +114511,7 @@
 </histogram>
 
 <histogram name="PrintPreview.RenderToPDFTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>thestig@chromium.org</owner>
   <summary>Time taken to render to PDF for print preview.</summary>
 </histogram>
@@ -114686,7 +114686,7 @@
 </histogram>
 
 <histogram name="Profile.AddNewUser" enum="ProfileAddNewUser"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>rogerta@chromium.org</owner>
   <summary>The frequency of ways that new user profiles are added.</summary>
 </histogram>
@@ -114858,7 +114858,7 @@
 </histogram>
 
 <histogram name="Profile.DeleteProfileAction" enum="ProfileDeleteAction"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <owner>anthonyvd@chromium.org</owner>
@@ -114989,7 +114989,7 @@
 </histogram>
 
 <histogram name="Profile.NetUserCount" enum="ProfileNetUserCount"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>rogerta@chromium.org</owner>
   <summary>
     Counts of users added and deleted. Percentages are not meaningful. Please
@@ -126933,7 +126933,7 @@
 </histogram>
 
 <histogram name="Scheduling.Renderer.DrawIntervalWithCompositedAnimations2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>paint-dev@chromium.org</owner>
   <summary>
     The time delta between the draw times of back-to-back BeginImplFrames,
@@ -126969,7 +126969,7 @@
 </histogram>
 
 <histogram name="Scheduling.Renderer.DrawIntervalWithMainThreadAnimations2"
-    units="microseconds" expires_after="2020-02-16">
+    units="microseconds" expires_after="2020-04-19">
   <owner>paint-dev@chromium.org</owner>
   <summary>
     The time delta between the draw times of back-to-back BeginImplFrames of new
@@ -127099,7 +127099,7 @@
 </histogram>
 
 <histogram name="ScreenLocker.AuthenticationFailure" enum="UnlockType"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>sammiequon@chromium.org</owner>
   <summary>
     What type of authentication was attempted when the user failed to unlock the
@@ -129493,6 +129493,29 @@
   </summary>
 </histogram>
 
+<histogram name="Security.LegacyTLS.DownloadStarted" enum="Boolean"
+    expires_after="M85">
+  <owner>cthomp@chromium.org</owner>
+  <summary>
+    Records whether the page would have a legacy TLS warning (if the user were
+    in the appropriate field trial) when a download is initiated from the page
+    (*not* the legacy TLS status of the download URL itself). This is recorded
+    regardless of whether the warning is actually displayed. This histogram is
+    not recorded for downloads that are initiated in a new tab or window, as the
+    legacy TLS status of the initiating page cannot be tracked.
+  </summary>
+</histogram>
+
+<histogram name="Security.LegacyTLS.OnCommit" enum="Boolean"
+    expires_after="M85">
+  <owner>cthomp@chromium.org</owner>
+  <owner>estark@chromium.org</owner>
+  <summary>
+    Records whether the page would have a legacy TLS warning (if the user were
+    in the appropriate field trial) when the navigation to the page commits.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Security.PageEndReason" enum="PageEndReason"
     expires_after="M81">
   <owner>cthomp@chromium.org</owner>
@@ -130290,7 +130313,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.CanMakePaymentEvent.Time" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>jinho.bang@samsung.com</owner>
   <summary>
     The time taken between dispatching an CanMakePaymentEvent to a Service
@@ -130516,7 +130539,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.FetchEvent.HasResponse.Time" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>falken@chromium.org</owner>
   <summary>
     The time taken between dispatching a FetchEvent to a Service Worker and
@@ -130526,7 +130549,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.FetchEvent.MainResource.Status"
-    enum="ServiceWorkerStatusCode" expires_after="2020-02-16">
+    enum="ServiceWorkerStatusCode" expires_after="2020-04-19">
   <owner>falken@chromium.org</owner>
   <summary>
     The result of dispatching a fetch event to a Service Worker for a main
@@ -130535,7 +130558,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.FetchEvent.Subresource.Status"
-    enum="ServiceWorkerStatusCode" expires_after="2020-02-16">
+    enum="ServiceWorkerStatusCode" expires_after="2020-04-19">
   <owner>falken@chromium.org</owner>
   <summary>
     The result of dispatching a fetch event to a Service Worker for a
@@ -131660,7 +131683,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.StartWorker.Status"
-    enum="ServiceWorkerStatusCode" expires_after="2020-02-16">
+    enum="ServiceWorkerStatusCode" expires_after="2020-04-19">
   <owner>falken@chromium.org</owner>
   <summary>
     The result of trying to start a Service Worker that is already installed.
@@ -131680,7 +131703,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.StartWorker.Time" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>falken@chromium.org</owner>
   <summary>
     The time taken to start a Service Worker that is already installed, from
@@ -131981,7 +132004,7 @@
 </histogram>
 
 <histogram name="ServiceWorkerCache.DiskCacheCreateEntryResult"
-    enum="NetErrorCodes" expires_after="2019-12-31">
+    enum="NetErrorCodes" expires_after="2020-04-19">
   <owner>wanderview@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
@@ -132000,7 +132023,7 @@
 </histogram>
 
 <histogram name="ServiceWorkerCache.IndexSizeDifference" units="bytes"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>cmumford@chromium.org</owner>
   <summary>
     The absolute difference between the ServiceWorker Cache size saved to the
@@ -132219,7 +132242,7 @@
 </histogram>
 
 <histogram name="Session.OpenedTabCounts" units="operations"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>rohitrao@chromium.org</owner>
   <summary>
     The number of times the user changed the active tab (this can happen when
@@ -132393,7 +132416,7 @@
 </histogram>
 
 <histogram name="Session.TotalDuration.WithAccount" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>feuunk@chromium.org</owner>
   <summary>
     The total session duration (see Session.TotalDuration) that was spent with
@@ -132422,7 +132445,7 @@
 </histogram>
 
 <histogram name="Session.TotalDuration.WithoutAccount" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>feuunk@chromium.org</owner>
   <summary>
     The total session duration (see Session.TotalDuration) that was spent
@@ -132569,7 +132592,7 @@
 </histogram>
 
 <histogram name="SessionRestore.ForegroundTabFirstLoaded" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>chrisha@chromium.org</owner>
   <owner>georgesak@chromium.org</owner>
   <summary>
@@ -132917,7 +132940,7 @@
 </histogram>
 
 <histogram name="SessionRestore.TabCount" units="tabs"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>chrisha@chromium.org</owner>
   <summary>
     The number of tabs involved in a single session restore event.
@@ -134276,7 +134299,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallAppsToShow" units="apps"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134299,7 +134322,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDevicesToShow" units="devices"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134333,7 +134356,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDialogShown" enum="SharingDialogType"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -134374,7 +134397,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallSelectedAppIndex" units="index"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134386,7 +134409,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallSelectedDeviceIndex" units="index"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134398,7 +134421,7 @@
 </histogram>
 
 <histogram name="Sharing.DeviceRegistrationResult"
-    enum="SharingDeviceRegistrationResult" expires_after="2020-02-02">
+    enum="SharingDeviceRegistrationResult" expires_after="2020-04-19">
   <owner>alexchau@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -134408,7 +134431,7 @@
 </histogram>
 
 <histogram name="Sharing.DeviceUnregistrationResult"
-    enum="SharingDeviceRegistrationResult" expires_after="2020-02-02">
+    enum="SharingDeviceRegistrationResult" expires_after="2020-04-19">
   <owner>alexchau@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -134417,7 +134440,7 @@
   </summary>
 </histogram>
 
-<histogram name="Sharing.MessageAckTime" units="ms" expires_after="2020-02-02">
+<histogram name="Sharing.MessageAckTime" units="ms" expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingMessage" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134429,7 +134452,7 @@
 </histogram>
 
 <histogram name="Sharing.MessageReceivedType" enum="SharingMessageType"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
 <!-- Name completed by histogram_suffixes name="SharingMessage" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -134495,7 +134518,7 @@
 </histogram>
 
 <histogram name="Sharing.SharedClipboardDevicesToShow" units="devices"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>yasmo@chromium.org</owner>
   <summary>
@@ -134508,7 +134531,7 @@
 </histogram>
 
 <histogram name="Sharing.SharedClipboardDialogShown" enum="SharingDialogType"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>yasmo@chromium.org</owner>
   <summary>
@@ -134517,7 +134540,7 @@
 </histogram>
 
 <histogram name="Sharing.SharedClipboardSelectedDeviceIndex" units="index"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>yasmo@chromium.org</owner>
   <summary>
@@ -134528,7 +134551,7 @@
 </histogram>
 
 <histogram name="Sharing.SharedClipboardSelectedTextSize" units="characters"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>yasmo@chromium.org</owner>
   <summary>
@@ -134538,7 +134561,7 @@
 </histogram>
 
 <histogram name="Sharing.VapidKeyCreationResult"
-    enum="SharingVapidKeyCreationResult" expires_after="2020-02-02">
+    enum="SharingVapidKeyCreationResult" expires_after="2020-04-19">
   <owner>alexchau@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -134639,7 +134662,7 @@
 </histogram>
 
 <histogram name="Shutdown.ShutdownType" enum="ShutdownType"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hashimoto@chromium.org</owner>
   <summary>The type of the last shutdown.</summary>
 </histogram>
@@ -134765,7 +134788,7 @@
 </histogram>
 
 <histogram name="SignedExchange.Prefetch.LoadResult2"
-    enum="SignedExchangeLoadResult" expires_after="2020-02-16">
+    enum="SignedExchangeLoadResult" expires_after="2020-04-19">
   <owner>kinuko@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
   <summary>
@@ -135102,7 +135125,7 @@
 </histogram>
 
 <histogram name="Signin.CookieJar.SignedInCount" units="accounts"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>skym@chromium.org</owner>
   <summary>The number of signed in accounts in the cookie jar.</summary>
 </histogram>
@@ -135138,7 +135161,7 @@
 </histogram>
 
 <histogram base="true" name="Signin.DiceMigrationNotReady.Reason"
-    enum="AccountReconcilorInconsistencyReason" expires_after="2020-01-12">
+    enum="AccountReconcilorInconsistencyReason" expires_after="2020-04-19">
   <owner>msalama@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -135148,7 +135171,7 @@
 </histogram>
 
 <histogram name="Signin.DiceMigrationStatus" enum="SigninDiceMigrationStatus"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>droger@chromium.org</owner>
   <summary>
     The Dice migration status, recorded at startup for each profile.
@@ -135239,7 +135262,7 @@
 </histogram>
 
 <histogram name="Signin.GetAccessTokenFinished" enum="GoogleServiceAuthError"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>droger@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -135250,7 +135273,7 @@
 </histogram>
 
 <histogram name="Signin.GetAccessTokenRetry" enum="GoogleServiceAuthError"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>droger@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -135288,7 +135311,7 @@
 </histogram>
 
 <histogram name="Signin.ListAccountsFailure" enum="GoogleServiceAuthError"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -135362,7 +135385,7 @@
 </histogram>
 
 <histogram name="Signin.Multilogin.NumberOfAccounts" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>droger@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>Number of accounts in tokens sent to Gaia Multilogin.</summary>
@@ -135395,7 +135418,7 @@
 </histogram>
 
 <histogram name="Signin.OAuth2TokenGetFailure" enum="GoogleServiceAuthError"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -135416,7 +135439,7 @@
 </histogram>
 
 <histogram name="Signin.OAuthMultiloginResponseStatus"
-    enum="OAuthMultiloginResponseStatus" expires_after="2020-02-16">
+    enum="OAuthMultiloginResponseStatus" expires_after="2020-04-19">
   <owner>droger@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>Response status received from gaia Multilogin.</summary>
@@ -135686,13 +135709,13 @@
 </histogram>
 
 <histogram name="Signin.SigninCompletedAccessPoint" enum="SigninAccessPoint"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>gogerald@chromium.org</owner>
   <summary>Logs the original access point of each completed sign in.</summary>
 </histogram>
 
 <histogram name="Signin.SigninReason" enum="SigninReason"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>gogerald@chromium.org</owner>
   <summary>Logs the reason of each completed sign in.</summary>
 </histogram>
@@ -135712,7 +135735,7 @@
 </histogram>
 
 <histogram name="Signin.SigninStartedAccessPoint" enum="SigninAccessPoint"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>gogerald@chromium.org</owner>
   <summary>
     Logs the original access point that displayed the signin or reauth Gaia
@@ -135730,14 +135753,14 @@
 </histogram>
 
 <histogram name="Signin.SignoutProfile" enum="SigninSignoutProfile"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>Track how a profile gets signed out.</summary>
 </histogram>
 
 <histogram name="Signin.SSOAuth.GetIdentities.ErrorCode"
-    enum="SigninSSOAuthGetIdentitiesErrorCode" expires_after="2020-02-16">
+    enum="SigninSSOAuthGetIdentitiesErrorCode" expires_after="2020-04-19">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -135802,7 +135825,7 @@
 </histogram>
 
 <histogram name="Signin.TokenServiceDiceCompatible" enum="Boolean"
-    expires_after="2020-02-01">
+    expires_after="2020-04-19">
   <owner>msalama@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -137019,7 +137042,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.EngagementScore" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
@@ -137074,7 +137097,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.EngagementScoreBucket" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <summary>
     The percentage of sites on a user's profile that have engagement scores that
@@ -137084,7 +137107,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.EngagementType"
-    enum="SiteEngagementServiceEngagementType" expires_after="2020-02-16">
+    enum="SiteEngagementServiceEngagementType" expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
@@ -137105,7 +137128,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.MedianEngagement" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
@@ -137116,7 +137139,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.OriginsEngaged" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
@@ -137185,7 +137208,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.TotalEngagement" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
@@ -137221,7 +137244,7 @@
 </histogram>
 
 <histogram name="SiteIsolatedCodeCache.WASM.Behaviour"
-    enum="SiteIsolatedCodeCacheWASMBehaviour" expires_after="2020-02-16">
+    enum="SiteIsolatedCodeCacheWASMBehaviour" expires_after="2020-04-19">
   <owner>bbudge@chromium.org</owner>
   <summary>
     The behaviour of site isolated web assembly code cache recorded for each
@@ -137243,7 +137266,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.BrowsingInstanceCount" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>creis@chromium.org</owner>
   <summary>
     The count of all current BrowsingInstances. Recorded once per UMA ping.
@@ -137340,7 +137363,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.CurrentRendererProcessCount" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>creis@chromium.org</owner>
   <summary>
     The count of all renderer processes, including WebUI and extensions.
@@ -137635,7 +137658,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.OutOfProcessIframes" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>nasko@chromium.org</owner>
   <summary>
     The count of all out-of-process iframes. Recorded once per UMA ping.
@@ -137657,7 +137680,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.ProxyCount" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>creis@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <owner>site-isolation-dev@chromium.org</owner>
@@ -137667,7 +137690,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.ProxyCountPerBrowsingInstance" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>creis@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <owner>site-isolation-dev@chromium.org</owner>
@@ -137701,7 +137724,7 @@
 </histogram>
 
 <histogram name="SiteIsolation.SiteInstancesPerBrowsingInstance" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>nasko@chromium.org</owner>
   <summary>
     The count of SiteInstances in a single BrowsingInstance. Recorded each UMA
@@ -138541,7 +138564,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthMethodChoice.SignIn"
-    enum="SmartLockAuthMethodChoice" expires_after="2020-02-16">
+    enum="SmartLockAuthMethodChoice" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <summary>Records the user's sign in method choice.</summary>
 </histogram>
@@ -138557,7 +138580,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthMethodChoice.Unlock"
-    enum="SmartLockAuthMethodChoice" expires_after="2020-02-16">
+    enum="SmartLockAuthMethodChoice" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <summary>Records the user's unlock method choice.</summary>
 </histogram>
@@ -138573,7 +138596,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthResult.SignIn" enum="BooleanSuccess"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>hsuregan@chromium.org</owner>
   <summary>
@@ -138583,7 +138606,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthResult.SignIn.Failure"
-    enum="SmartLockAuthResultFailureReason" expires_after="2020-02-16">
+    enum="SmartLockAuthResultFailureReason" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <owner>hsuregan@chromium.org</owner>
   <summary>
@@ -138602,7 +138625,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthResult.Unlock" enum="BooleanSuccess"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <summary>
     Count of successful and failed attempts to unlock the Chromebook, after the
@@ -138611,7 +138634,7 @@
 </histogram>
 
 <histogram name="SmartLock.AuthResult.Unlock.Failure"
-    enum="SmartLockAuthResultFailureReason" expires_after="2020-02-16">
+    enum="SmartLockAuthResultFailureReason" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <summary>
     Shows why an unlock attempt failed during the authentication phase. Breaks
@@ -138630,7 +138653,7 @@
 </histogram>
 
 <histogram name="SmartLock.EnabledDevicesCount" units="devices"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>jhawkins@chromium.org</owner>
   <summary>
     A count of the number of SmartLock devices which the user has enabled to
@@ -138640,7 +138663,7 @@
 </histogram>
 
 <histogram name="SmartLock.EnabledState" enum="SmartLockEnabledState"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>jhawkins@chromium.org</owner>
   <summary>
     The state of the SmartLock feature for the current user, set at login. The
@@ -138714,7 +138737,7 @@
 </histogram>
 
 <histogram name="SmartLock.MultiDeviceFeatureState"
-    enum="MultiDevice_FeatureState" expires_after="2020-02-16">
+    enum="MultiDevice_FeatureState" expires_after="2020-04-19">
   <owner>hansberry@chromium.org</owner>
   <summary>
     Indicates the feature state of the SmartLock feature. This metric is emitted
@@ -139737,7 +139760,7 @@
 </histogram>
 
 <histogram name="SSL.CertificateErrorHelpCenterVisited"
-    enum="SSLErrorLearnMoreNavigationResult" expires_after="2020-02-16">
+    enum="SSLErrorLearnMoreNavigationResult" expires_after="2020-04-19">
   <owner>carlosil@chromium.org</owner>
   <summary>
     When the &quot;Learn more&quot; link on an SSL interstitial is clicked, this
@@ -140149,7 +140172,7 @@
 </histogram>
 
 <histogram name="Stability.CrashedProcessAge.Renderer" units="ms"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>lukasza@chromium.org</owner>
   <summary>
     The age of a crashed extension process. Not logged on iOS. Logged together
@@ -140577,7 +140600,7 @@
 </histogram>
 
 <histogram name="Startup.AfterStartupTaskCount" units="units"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>michaeln@chromium.org</owner>
   <summary>
     The number of after-startup tasks that were queued prior to startup
@@ -144020,7 +144043,7 @@
 </histogram>
 
 <histogram name="Sync.CryptographerReady" enum="SyncCryptographerReadyState"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>zea@chromium.org</owner>
   <summary>
     Breakdown of sync users whose cryptographer is fully ready for encryption
@@ -144155,7 +144178,7 @@
   </summary>
 </histogram>
 
-<histogram name="Sync.DeviceCount2" units="units" expires_after="2020-02-16">
+<histogram name="Sync.DeviceCount2" units="units" expires_after="2020-04-19">
   <owner>mastiz@chromium.org</owner>
   <owner>jkrcal@chromium.org</owner>
   <summary>
@@ -145192,7 +145215,7 @@
 </histogram>
 
 <histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed2" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>mastiz@chromium.org</owner>
   <owner>melandory@chromium.org</owner>
   <summary>
@@ -150593,7 +150616,7 @@
 </histogram>
 
 <histogram name="Translate.BubbleUiEvent" enum="TranslateBubbleUiEvent"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>groby@google.com</owner>
   <summary>Tracks UI events related to the translate bubble.</summary>
 </histogram>
@@ -152668,7 +152691,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2020-04-12">
+    expires_after="2020-04-19">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
@@ -154167,7 +154190,7 @@
 </histogram>
 
 <histogram name="V8.CompileMicroSeconds" units="microseconds"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>yangguo@chromium.org</owner>
   <summary>
     Time spent in V8 compiler (full codegen) excluding parser.
@@ -154701,7 +154724,7 @@
 </histogram>
 
 <histogram name="V8.GCIncrementalMarkingReason" enum="GarbageCollectionReason"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ulan@chromium.org</owner>
   <summary>Reason an incremental marking was started in V8.</summary>
 </histogram>
@@ -154796,7 +154819,7 @@
 </histogram>
 
 <histogram name="V8.GCScavengerForeground" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hpayer@chromium.org</owner>
   <summary>
     Time spent in scavenging phase of GC in a foreground isolate.
@@ -155129,6 +155152,15 @@
   <summary>Time spent preparsing source code.</summary>
 </histogram>
 
+<histogram name="V8.RegExpBacktracks" units="backtracks"
+    expires_after="2020-04-23">
+  <owner>jgruber@chromium.org</owner>
+  <owner>mvstanton@chromium.org</owner>
+  <summary>
+    The number of backtracks performed in a single regexp execution.
+  </summary>
+</histogram>
+
 <histogram name="V8.Rewriting" units="units" expires_after="2014-09-16">
   <obsolete>
     This histogram is no longer present in V8.
@@ -158050,7 +158082,7 @@
 </histogram>
 
 <histogram name="WebAudio.AudioContext.HardwareSampleRate" units="Hz"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>rtoy@chromium.org</owner>
   <owner>hongchan@chromium.org</owner>
   <summary>
@@ -158797,7 +158829,7 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Context.ForceCloseReason"
-    enum="IDBContextForcedCloseReason" expires_after="2020-02-16">
+    enum="IDBContextForcedCloseReason" expires_after="2020-04-19">
   <owner>cmumford@chromium.org</owner>
   <summary>The reason that a forced-close of a backing store occurred.</summary>
 </histogram>
@@ -158889,7 +158921,7 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.OpenTime" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>cmumford@chromium.org</owner>
   <summary>
     The time that it takes to open IndexedDB's LevelDB backing store.
@@ -158934,7 +158966,7 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.BFE"
-    enum="PlatformFileError" expires_after="2020-02-16">
+    enum="PlatformFileError" expires_after="2020-04-19">
   <owner>cmumford@chromium.org</owner>
   <summary>
     Errors (base::File::Error) encountered by a single LevelDBEnv method when
@@ -160168,7 +160200,7 @@
 </histogram>
 
 <histogram name="WebFont.HadBlankText" enum="BooleanHadBlankText"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>kenjibaheux@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
   <summary>
@@ -160832,7 +160864,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.DelayedPacketOutageEventMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hlundin@chromium.org</owner>
   <summary>
     Measures the duration of each packet loss concealment (a.k.a. expand) event
@@ -160844,7 +160876,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.DelayedPacketOutageEventsPerMinute"
-    units="events/minute" expires_after="2020-02-16">
+    units="events/minute" expires_after="2020-04-19">
   <owner>hlundin@chromium.org</owner>
   <summary>
     Counts the number of delayed packet outage events per minute. The range is
@@ -161152,7 +161184,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.JitterBufferFullPerMinute" units="events/minute"
-    expires_after="M78">
+    expires_after="2020-04-19">
   <owner>minyue@chromium.org</owner>
   <summary>
     Frequency that audio packets hits the capacity of WebRTC jitter buffer. A
@@ -161210,7 +161242,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.ReceiverDeviceDelayMs" units="ms"
-    expires_after="2020-02-02">
+    expires_after="2020-04-19">
   <owner>hlundin@chromium.org</owner>
   <summary>
     The sound card's buffering delay for the receiving side. Sampled once every
@@ -161253,7 +161285,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.TargetBitrateInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>hlundin@chromium.org</owner>
   <summary>
     The target bitrate in kbps that the audio codec should try to produce on
@@ -161382,7 +161414,7 @@
 </histogram>
 
 <histogram name="WebRTC.BWE.InitialBandwidthEstimate" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>The bandwidth estimate 2 seconds into a WebRTC call.</summary>
 </histogram>
@@ -161403,7 +161435,7 @@
 </histogram>
 
 <histogram name="WebRTC.BWE.InitialVsConvergedDiff" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     The difference between the bandwidth estimate at 2 seconds and 20 seconds
@@ -161499,7 +161531,7 @@
 </histogram>
 
 <histogram name="WebRTC.BWE.RampUpTimeTo2000kbpsInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     The time it takes the estimated bandwidth to reach 2000 kbps from the first
@@ -161508,7 +161540,7 @@
 </histogram>
 
 <histogram name="WebRTC.BWE.RampUpTimeTo500kbpsInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     The time it takes the estimated bandwidth to reach 500 kbps from the first
@@ -161527,7 +161559,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.AudioBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average audio bitrate received during a call, counted from first packet
@@ -161565,7 +161597,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.PacerBitrateInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average pacer bitrate during a call, counted from first packet sent until
@@ -161575,7 +161607,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.RtcpBitrateReceivedInBps" units="bits/s"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>holmer@chromium.org</owner>
   <summary>
     Average RTCP bitrate received during a call, counted from first packet
@@ -161597,7 +161629,7 @@
 </histogram>
 
 <histogram name="WebRTC.Call.TimeReceivingVideoRtpPacketsInSeconds" units="s"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>saza@chromium.org</owner>
   <summary>
     The amount of time between the arrival of the first and last video RTP
@@ -161634,7 +161666,7 @@
 </histogram>
 
 <histogram name="WebRTC.DataChannelCounters" enum="DataChannelCounters"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>perkj@chromium.org</owner>
   <summary>
     Counters on creation, opening, and a few main attributes of data channels.
@@ -161794,7 +161826,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.ConnectionState"
-    enum="IceConnectionStates" expires_after="2020-02-16">
+    enum="IceConnectionStates" expires_after="2020-04-19">
   <owner>qingsi@google.com</owner>
   <owner>jeroendb@google.com</owner>
   <summary>
@@ -161929,7 +161961,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.SdpComplexUsage.CreateAnswer"
-    enum="PeerConnectionSdpUsageCategory" expires_after="2020-02-16">
+    enum="PeerConnectionSdpUsageCategory" expires_after="2020-04-19">
   <owner>hbos@chromium.org</owner>
   <summary>
     The SDP usage category (&quot;safe&quot;, &quot;unsafe&quot; or
@@ -162025,7 +162057,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.SdpSemanticRequested"
-    enum="PeerConnectionSdpSemanticRequested" expires_after="2020-02-16">
+    enum="PeerConnectionSdpSemanticRequested" expires_after="2020-04-19">
   <owner>hta@chromium.org</owner>
   <summary>
     What SDP semantic (Unified Plan, Plan B or &quot;use default&quot;) has been
@@ -162049,7 +162081,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.Simulcast.ApplyLocalDescription"
-    enum="SimulcastApiVersion" expires_after="2020-02-16">
+    enum="SimulcastApiVersion" expires_after="2020-04-19">
   <owner>amithi@chromium.org</owner>
   <summary>
     Was simulcast applied to the local description and with which API surface.
@@ -162197,7 +162229,7 @@
 </histogram>
 
 <histogram name="WebRTC.SentVideoTrackDuration" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>perkj@chromium.org</owner>
   <summary>
     Durations of video tracks sent over a PeerConnection. The stopwatch starts
@@ -162398,7 +162430,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.BitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received bits per second for a received video stream. Recorded
@@ -162474,7 +162506,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.DelayedFramesToRenderer" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of delayed frames to renderer for a received video stream.
@@ -162483,7 +162515,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average delay of delayed frames to renderer for a received video stream.
@@ -162509,7 +162541,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.DroppedFrames.Encoder" units="frames"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ilnik@chromium.org</owner>
   <summary>
     Total number of frames dropped by an encoder's internal rate limiter for a
@@ -162536,7 +162568,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.DroppedFrames.Receiver" units="frames"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>ilnik@chromium.org</owner>
   <summary>
     Total number of frames dropped by a WebRTC on the receive side because they
@@ -162581,7 +162613,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.EndToEndDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average end-to-end delay per frame for a received video stream. Recorded
@@ -162599,7 +162631,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.FecBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received FEC bits per second for a received video stream.
@@ -162609,7 +162641,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.FecBitrateSentInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent FEC bits per second for a sent video stream. Recorded
@@ -162619,7 +162651,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.FirPacketsReceivedPerMinute"
-    units="packets/minute" expires_after="2020-02-16">
+    units="packets/minute" expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received RTCP FIR packets per minute for a sent video stream.
@@ -162629,7 +162661,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.FirPacketsSentPerMinute" units="packets/minute"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent RTCP FIR packets per minute for a received video stream.
@@ -162639,7 +162671,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.H264DecoderImpl.Event"
-    enum="WebRtcH264DecoderImplEvent" expires_after="2020-02-16">
+    enum="WebRtcH264DecoderImplEvent" expires_after="2020-04-19">
   <owner>hbos@chromium.org</owner>
   <summary>
     The number of |H264DecoderImpl| events, such as an initialization or
@@ -162652,7 +162684,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.H264EncoderImpl.Event"
-    enum="WebRtcH264EncoderImplEvent" expires_after="2020-02-16">
+    enum="WebRtcH264EncoderImplEvent" expires_after="2020-04-19">
   <owner>hbos@chromium.org</owner>
   <summary>
     The number of |H264EncoderImpl| events, such as an initialization or
@@ -162675,7 +162707,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.InputHeightInPixels" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average input height per frame (for incoming frames to video engine) for
@@ -162684,7 +162716,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.InputWidthInPixels" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average input width per frame (for incoming frames to video engine) for
@@ -162720,7 +162752,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.JitterBufferDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Average jitter buffer delay for a received video stream. Recorded when a
@@ -162729,7 +162761,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.KeyFramesReceivedInPermille" units="permille"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Permille of frames that are key frames for a received video stream. Recorded
@@ -162738,7 +162770,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.KeyFramesSentInPermille" units="permille"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Permille of frames that are key frames for a sent video stream. Recorded
@@ -162764,7 +162796,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.MediaBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received media payload bits per second for a received video
@@ -162783,7 +162815,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.NackPacketsReceivedPerMinute"
-    units="packets/minute" expires_after="2020-02-16">
+    units="packets/minute" expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received RTCP NACK packets per minute for a sent video stream.
@@ -162793,7 +162825,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.NackPacketsSentPerMinute" units="packets/minute"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent RTCP NACK packets per minute for a received video stream.
@@ -162839,7 +162871,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.PaddingBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received padding bits per second for a received video stream.
@@ -162849,7 +162881,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.PaddingBitrateSentInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent padding bits per second for a sent video stream. Recorded
@@ -162859,7 +162891,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.PausedTimeInPercent" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of time that the video has been paused for a sent video stream.
@@ -162868,7 +162900,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.PliPacketsReceivedPerMinute"
-    units="packets/minute" expires_after="2020-02-16">
+    units="packets/minute" expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received RTCP PLI packets per minute for a sent video stream.
@@ -162878,7 +162910,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.PliPacketsSentPerMinute" units="packets/minute"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent RTCP PLI packets per minute for a received video stream.
@@ -162888,7 +162920,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.QualityLimitedResolutionDownscales"
-    units="downscales" expires_after="2020-02-16">
+    units="downscales" expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     For frames that are downscaled in resolution due to quality, the average
@@ -162898,7 +162930,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.QualityLimitedResolutionInPercent" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of sent frames that are downscaled in resolution due to quality
@@ -162907,7 +162939,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.ReceivedFecPacketsInPercent" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of received FEC packets for a received video stream. Recorded
@@ -162916,7 +162948,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.ReceivedHeightInPixels" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average received height per frame for a received video stream. Recorded
@@ -162925,7 +162957,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.ReceivedPacketsLostInPercent" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of received packets lost for a received video stream. Recorded
@@ -162934,7 +162966,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.ReceivedWidthInPixels" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average received width per frame for a received video stream. Recorded
@@ -162943,7 +162975,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.ReceiveStreamLifetimeInSeconds" units="seconds"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The lifetime of a video receive stream. Recorded when a VideoReceiveStream
@@ -162952,7 +162984,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RecoveredMediaPacketsInPercentOfFec" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of recovered media packets from FEC packets for a received video
@@ -162971,7 +163003,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RenderSqrtPixelsPerSecond" units="pps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of pixels (sqrt(width*height)) of sent frames to the renderer per
@@ -162981,7 +163013,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RetransmittedBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of retransmitted bits per second for a received video stream.
@@ -162991,7 +163023,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RetransmittedBitrateSentInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of retransmitted bits per second for a sent video stream.
@@ -163001,7 +163033,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RtpToNtpFreqOffsetInKhz" units="kHz"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The absolute value of the difference between the estimated frequency during
@@ -163013,7 +163045,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RtxBitrateReceivedInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of received bits over RTX per second for a received video stream.
@@ -163023,7 +163055,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.RtxBitrateSentInKbps" units="kbps"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The number of sent bits over RTX per second for a sent video stream.
@@ -163516,7 +163548,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.SendDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average send delay of sent packets for a sent video stream. Recorded
@@ -163526,7 +163558,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.SendSideDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average delay (of average delays) of sent packets for a sent video
@@ -163538,7 +163570,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.SendSideDelayMaxInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average delay (of max delays) of sent packets for a sent video stream.
@@ -163550,7 +163582,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.SendStreamLifetimeInSeconds" units="seconds"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The lifetime of a video send stream. Recorded when a VideoSendStream
@@ -163569,7 +163601,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.SentHeightInPixels" units="pixels"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     The average sent height per frame for a sent video stream. Recorded when a
@@ -163606,7 +163638,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.TargetDelayInMs" units="ms"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Average target delay (jitter delay + decode time + render delay) for a
@@ -163632,7 +163664,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.UniqueNackRequestsReceivedInPercent" units="%"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>asapersson@chromium.org</owner>
   <summary>
     Percentage of unique RTCP NACK requests that are received in response to a
@@ -164065,7 +164097,7 @@
 </histogram>
 
 <histogram name="WebUsb.ChooserClosed" enum="WebUsbChooserClosed"
-    expires_after="2020-02-16">
+    expires_after="2020-04-19">
   <owner>reillyg@chromium.org</owner>
   <owner>juncai@chromium.org</owner>
   <summary>
@@ -171368,6 +171400,14 @@
   <affected-histogram name="Cryptohome.LECredential.Sync"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="LegacyTLS" separator=".">
+  <suffix name="LegacyTLS_NotTriggered"
+      label="Page didn't trigger legacy TLS warning"/>
+  <suffix name="LegacyTLS_Triggered" label="Page triggered legacy TLS warning"/>
+  <affected-histogram name="Security.PageEndReason"/>
+  <affected-histogram name="Security.TimeOnPage2"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="LevelDBBFEMethods" separator=".">
   <owner>cmumford@chromium.org</owner>
   <suffix name="CreateDir" label="ChromiumEnv::CreateDir"/>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index ea26b87c..64dcb822 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -4934,6 +4934,19 @@
   </metric>
 </event>
 
+<event name="MobileMenu.FindInPage">
+  <owner>frechette@chromium.org</owner>
+  <summary>
+    User pressed 'Find in page' in the app menu.
+  </summary>
+  <metric name="HasOccurred" enum="Boolean">
+    <summary>
+      A boolean signaling that the event has occurred (typically only records
+      true values).
+    </summary>
+  </metric>
+</event>
+
 <event name="NavigationPredictorAnchorElementMetrics">
   <owner>ryansturm@chromium.org</owner>
   <summary>
@@ -5943,6 +5956,8 @@
     <aggregation>
       <history>
         <index fields="profile.country"/>
+        <index fields="profile.is_dominant_version"/>
+        <index fields="profile.is_latest_version"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -8296,6 +8311,12 @@
   <summary>
     Collected when a page is opened in a Trusted Web Activity.
   </summary>
+  <metric name="HasOccurred" enum="Boolean">
+    <summary>
+      A boolean signaling that the event has occurred (typically only records
+      true values).
+    </summary>
+  </metric>
 </event>
 
 <event name="UserActivity">
diff --git a/tools/perf/benchmarks/system_health_smoke_test.py b/tools/perf/benchmarks/system_health_smoke_test.py
index ddf7229..53970a6 100644
--- a/tools/perf/benchmarks/system_health_smoke_test.py
+++ b/tools/perf/benchmarks/system_health_smoke_test.py
@@ -36,6 +36,9 @@
 
 
 _DISABLED_TESTS = frozenset({
+  # crbug.com/983326 - flaky.
+  'system_health.memory_desktop/browse_accessibility:media:youtube',
+
   # crbug.com/878390 - These stories are already covered by their 2018 versions
   # and will later be removed.
   'system_health.memory_mobile/browse:tech:discourse_infinite_scroll',
diff --git a/tools/perf/core/results_processor/compute_metrics.py b/tools/perf/core/results_processor/compute_metrics.py
index 6cb31ca..d415d02 100644
--- a/tools/perf/core/results_processor/compute_metrics.py
+++ b/tools/perf/core/results_processor/compute_metrics.py
@@ -7,8 +7,6 @@
 import os
 import time
 
-from core.results_processor import util
-
 from tracing.metrics import metric_runner
 
 
@@ -24,7 +22,7 @@
 HISTOGRAM_DICTS_FILE = 'histogram_dicts.json'
 
 
-def _PoolWorker(test_result):
+def _RunMetric(test_result):
   metrics = [tag['value'] for tag in test_result['tags']
              if tag['key'] == 'tbmv2']
   html_trace = test_result['outputArtifacts'][HTML_TRACE_NAME]
@@ -56,7 +54,7 @@
   return mre_result.pairs.get('histograms', [])
 
 
-def ComputeTBMv2Metrics(intermediate_results):
+def ComputeTBMv2Metrics(test_result):
   """Compute metrics on aggregated traces in parallel.
 
   For each test run that has an aggregate trace and some TBMv2 metrics listed
@@ -64,40 +62,32 @@
   histograms. Note: the order of histograms in the results may be different
   from the order of tests in intermediate_results.
   """
-  histogram_dicts = []
-  work_list = []
-  for test_result in intermediate_results['testResults']:
-    artifacts = test_result.get('outputArtifacts', {})
-    # TODO(crbug.com/981349): If metrics have already been computed in
-    # Telemetry, we read it from the file. Remove this branch after Telemetry
-    # does not compute metrics anymore.
-    if HISTOGRAM_DICTS_FILE in artifacts:
-      with open(artifacts[HISTOGRAM_DICTS_FILE]['filePath']) as f:
-        histogram_dicts += json.load(f)
-      del artifacts[HISTOGRAM_DICTS_FILE]
-      continue
+  artifacts = test_result.get('outputArtifacts', {})
+  # TODO(crbug.com/981349): If metrics have already been computed in
+  # Telemetry, we read it from the file. Remove this branch after Telemetry
+  # does not compute metrics anymore.
+  if HISTOGRAM_DICTS_FILE in artifacts:
+    with open(artifacts[HISTOGRAM_DICTS_FILE]['filePath']) as f:
+      test_result['_histograms'].ImportDicts(json.load(f))
+    del artifacts[HISTOGRAM_DICTS_FILE]
+    return
 
-    if test_result['status'] == 'SKIP':
-      continue
+  if test_result['status'] == 'SKIP':
+    return
 
-    if (HTML_TRACE_NAME not in artifacts or
-        not any(tag['key'] == 'tbmv2' for tag in test_result.get('tags', []))):
-      continue
+  if (HTML_TRACE_NAME not in artifacts or
+      not any(tag['key'] == 'tbmv2' for tag in test_result.get('tags', []))):
+    return
 
-    trace_size_in_mib = (os.path.getsize(artifacts[HTML_TRACE_NAME]['filePath'])
-                         / (2 ** 20))
-    # Bails out on traces that are too big. See crbug.com/812631 for more
-    # details.
-    # TODO(crbug.com/1010041): Return a non-zero exit code in this case.
-    if trace_size_in_mib > 400:
-      test_result['status'] = 'FAIL'
-      logging.error('%s: Trace size is too big: %s MiB',
-                    test_result['testPath'], trace_size_in_mib)
-      continue
+  trace_size_in_mib = (os.path.getsize(artifacts[HTML_TRACE_NAME]['filePath'])
+                       / (2 ** 20))
+  # Bails out on traces that are too big. See crbug.com/812631 for more
+  # details.
+  # TODO(crbug.com/1010041): Return a non-zero exit code in this case.
+  if trace_size_in_mib > 400:
+    test_result['status'] = 'FAIL'
+    logging.error('%s: Trace size is too big: %s MiB',
+                  test_result['testPath'], trace_size_in_mib)
+    return
 
-    work_list.append(test_result)
-
-  for dicts in util.ApplyInParallel(_PoolWorker, work_list):
-    histogram_dicts += dicts
-
-  return histogram_dicts
+  test_result['_histograms'].ImportDicts(_RunMetric(test_result))
diff --git a/tools/perf/core/results_processor/compute_metrics_unittest.py b/tools/perf/core/results_processor/compute_metrics_unittest.py
index 6afc3df0..02dc6f0 100644
--- a/tools/perf/core/results_processor/compute_metrics_unittest.py
+++ b/tools/perf/core/results_processor/compute_metrics_unittest.py
@@ -11,6 +11,7 @@
 from tracing.mre import job
 from tracing.mre import mre_result
 from tracing.value import histogram
+from tracing.value import histogram_set
 
 import mock
 
@@ -21,22 +22,14 @@
 
 class ComputeMetricsTest(unittest.TestCase):
   def testComputeTBMv2Metrics(self):
-    in_results = testing.IntermediateResults([
-        testing.TestResult(
-            'benchmark/story1',
-            output_artifacts={
-                compute_metrics.HTML_TRACE_NAME:
-                    testing.Artifact('/trace1.html', 'gs://trace1.html')},
-            tags=['tbmv2:metric1'],
-        ),
-        testing.TestResult(
-            'benchmark/story2',
-            output_artifacts={
-                compute_metrics.HTML_TRACE_NAME:
-                    testing.Artifact('/trace2.html', 'gs://trace2.html')},
-            tags=['tbmv2:metric2'],
-        ),
-    ])
+    test_result = testing.TestResult(
+        'benchmark/story1',
+        output_artifacts={
+            compute_metrics.HTML_TRACE_NAME:
+                testing.Artifact('/trace1.html', 'gs://trace1.html')},
+        tags=['tbmv2:metric1'],
+    )
+    test_result['_histograms'] = histogram_set.HistogramSet()
 
     test_dict = histogram.Histogram('a', 'unitless').AsDict()
     metrics_result = mre_result.MreResult()
@@ -46,42 +39,41 @@
       with mock.patch(RUN_METRICS_METHOD) as run_metrics_mock:
         getsize_mock.return_value = 1000
         run_metrics_mock.return_value = metrics_result
-        histogram_dicts = compute_metrics.ComputeTBMv2Metrics(in_results)
+        compute_metrics.ComputeTBMv2Metrics(test_result)
 
-    self.assertEqual(histogram_dicts, [test_dict, test_dict])
-    self.assertEqual(in_results['testResults'][0]['status'], 'PASS')
-    self.assertEqual(in_results['testResults'][1]['status'], 'PASS')
+    histogram_dicts = test_result['_histograms'].AsDicts()
+    self.assertEqual(histogram_dicts, [test_dict])
+    self.assertEqual(test_result['status'], 'PASS')
 
   def testComputeTBMv2MetricsTraceTooBig(self):
-    in_results = testing.IntermediateResults([
-        testing.TestResult(
-            'benchmark/story1',
-            output_artifacts={
-                compute_metrics.HTML_TRACE_NAME:
-                    testing.Artifact('/trace1.html', 'gs://trace1.html')},
-            tags=['tbmv2:metric1'],
-        ),
-    ])
+    test_result = testing.TestResult(
+        'benchmark/story1',
+        output_artifacts={
+            compute_metrics.HTML_TRACE_NAME:
+                testing.Artifact('/trace1.html', 'gs://trace1.html')},
+        tags=['tbmv2:metric1'],
+    )
+    test_result['_histograms'] = histogram_set.HistogramSet()
 
     with mock.patch(GETSIZE_METHOD) as getsize_mock:
       with mock.patch(RUN_METRICS_METHOD) as run_metrics_mock:
         getsize_mock.return_value = 1e9
-        histogram_dicts = compute_metrics.ComputeTBMv2Metrics(in_results)
+        compute_metrics.ComputeTBMv2Metrics(test_result)
         self.assertEqual(run_metrics_mock.call_count, 0)
 
+    histogram_dicts = test_result['_histograms'].AsDicts()
     self.assertEqual(histogram_dicts, [])
-    self.assertEqual(in_results['testResults'][0]['status'], 'FAIL')
+    self.assertEqual(test_result['status'], 'FAIL')
 
   def testComputeTBMv2MetricsFailure(self):
-    in_results = testing.IntermediateResults([
-        testing.TestResult(
-            'benchmark/story1',
-            output_artifacts={
-                compute_metrics.HTML_TRACE_NAME:
-                    testing.Artifact('/trace1.html', 'gs://trace1.html')},
-            tags=['tbmv2:metric1'],
-        ),
-    ])
+    test_result = testing.TestResult(
+        'benchmark/story1',
+        output_artifacts={
+            compute_metrics.HTML_TRACE_NAME:
+                testing.Artifact('/trace1.html', 'gs://trace1.html')},
+        tags=['tbmv2:metric1'],
+    )
+    test_result['_histograms'] = histogram_set.HistogramSet()
 
     metrics_result = mre_result.MreResult()
     metrics_result.AddFailure(failure.Failure(job.Job(0), 0, 0, 0, 0, 0))
@@ -90,26 +82,27 @@
       with mock.patch(RUN_METRICS_METHOD) as run_metrics_mock:
         getsize_mock.return_value = 100
         run_metrics_mock.return_value = metrics_result
-        histogram_dicts = compute_metrics.ComputeTBMv2Metrics(in_results)
+        compute_metrics.ComputeTBMv2Metrics(test_result)
 
+    histogram_dicts = test_result['_histograms'].AsDicts()
     self.assertEqual(histogram_dicts, [])
-    self.assertEqual(in_results['testResults'][0]['status'], 'FAIL')
+    self.assertEqual(test_result['status'], 'FAIL')
 
   def testComputeTBMv2MetricsSkipped(self):
-    in_results = testing.IntermediateResults([
-        testing.TestResult(
-            'benchmark/story1',
-            output_artifacts={
-                compute_metrics.HTML_TRACE_NAME:
-                    testing.Artifact('/trace1.html', 'gs://trace1.html')},
-            tags=['tbmv2:metric1'],
-            status='SKIP',
-        ),
-    ])
+    test_result = testing.TestResult(
+        'benchmark/story1',
+        output_artifacts={
+            compute_metrics.HTML_TRACE_NAME:
+                testing.Artifact('/trace1.html', 'gs://trace1.html')},
+        tags=['tbmv2:metric1'],
+        status='SKIP',
+    )
+    test_result['_histograms'] = histogram_set.HistogramSet()
 
     with mock.patch(RUN_METRICS_METHOD) as run_metrics_mock:
-      histogram_dicts = compute_metrics.ComputeTBMv2Metrics(in_results)
+      compute_metrics.ComputeTBMv2Metrics(test_result)
       self.assertEqual(run_metrics_mock.call_count, 0)
 
+    histogram_dicts = test_result['_histograms'].AsDicts()
     self.assertEqual(histogram_dicts, [])
-    self.assertEqual(in_results['testResults'][0]['status'], 'SKIP')
+    self.assertEqual(test_result['status'], 'SKIP')
diff --git a/tools/perf/core/results_processor/formatters/json3_output.py b/tools/perf/core/results_processor/formatters/json3_output.py
index 96b144c..378156c1 100644
--- a/tools/perf/core/results_processor/formatters/json3_output.py
+++ b/tools/perf/core/results_processor/formatters/json3_output.py
@@ -9,6 +9,7 @@
 """
 
 import collections
+import datetime
 import json
 import os
 import urllib
@@ -19,18 +20,18 @@
 OUTPUT_FILENAME = 'test-results.json'
 
 
-def ProcessIntermediateResults(intermediate_results, options):
+def ProcessIntermediateResults(test_results, options):
   """Process intermediate results and write output in output_dir."""
-  results = Convert(intermediate_results, options.output_dir)
+  results = Convert(test_results, options.output_dir)
   with open(os.path.join(options.output_dir, OUTPUT_FILENAME), 'w') as f:
     json.dump(results, f, sort_keys=True, indent=4, separators=(',', ': '))
 
 
-def Convert(in_results, base_dir):
+def Convert(test_results, base_dir):
   """Convert intermediate results to the JSON Test Results Format.
 
   Args:
-    in_results: The parsed intermediate results.
+    test_results: The parsed intermediate results.
     base_dir: A string with the path to a base directory; artifact file paths
       will be written relative to this.
 
@@ -40,7 +41,7 @@
   results = {'tests': {}}
   status_counter = collections.Counter()
 
-  for result in in_results['testResults']:
+  for result in test_results:
     benchmark_name, story_name = result['testPath'].split('/')
     story_name = urllib.unquote(story_name)
     actual_status = result['status']
@@ -77,10 +78,17 @@
       if test['shard'] is None:
         del test['shard']
 
-  benchmark_run = in_results['benchmarkRun']
+  # Test results are written in order of execution, so the first test start
+  # time is approximately the start time of the whole suite.
+  test_suite_start_time = (test_results[0]['startTime'] if test_results
+                           else datetime.datetime.utcnow().isoformat() + 'Z')
+  # If Telemetry stops with a unhandleable error, then remaining stories
+  # are marked as unexpectedly skipped.
+  interrupted = any(t['status'] == 'SKIP' and not t['isExpected']
+                    for t in test_results)
   results.update(
-      seconds_since_epoch=util.IsoTimestampToEpoch(benchmark_run['startTime']),
-      interrupted=benchmark_run['interrupted'],
+      seconds_since_epoch=util.IsoTimestampToEpoch(test_suite_start_time),
+      interrupted=interrupted,
       num_failures_by_type=dict(status_counter),
       path_delimiter='/',
       version=3,
diff --git a/tools/perf/core/results_processor/formatters/json3_output_unittest.py b/tools/perf/core/results_processor/formatters/json3_output_unittest.py
index 8cb396f..b601d8d 100644
--- a/tools/perf/core/results_processor/formatters/json3_output_unittest.py
+++ b/tools/perf/core/results_processor/formatters/json3_output_unittest.py
@@ -13,13 +13,11 @@
   def setUp(self):
     self.base_dir = 'base_dir'
 
-  def Convert(self, test_results, **kwargs):
-    base_dir = kwargs.pop('base_dir', self.base_dir)
-    original_results = testing.IntermediateResults(test_results, **kwargs)
-    intermediate_results = copy.deepcopy(original_results)
-    results = json3_output.Convert(intermediate_results, base_dir)
+  def Convert(self, test_results):
+    test_results_copy = copy.deepcopy(test_results)
+    results = json3_output.Convert(test_results_copy, self.base_dir)
     # Convert should not modify the original intermediate results.
-    self.assertEqual(intermediate_results, original_results)
+    self.assertEqual(test_results_copy, test_results)
     return results
 
   def FindTestResult(self, results, benchmark, story):
@@ -29,15 +27,15 @@
       node = node[key]
     return node
 
-  def testEmptyResults(self):
-    results = self.Convert(
-        [], start_time='2009-02-13T23:31:30.987000Z', interrupted=False)
+  def testStartTime(self):
+    results = self.Convert([
+        testing.TestResult('benchmark/story',
+                           start_time='2009-02-13T23:31:30.987000Z')
+    ])
 
     self.assertFalse(results['interrupted'])
-    self.assertEqual(results['num_failures_by_type'], {})
     self.assertEqual(results['path_delimiter'], '/')
     self.assertEqual(results['seconds_since_epoch'], 1234567890.987)
-    self.assertEqual(results['tests'], {})
     self.assertEqual(results['version'], 3)
 
   def testSingleTestCase(self):
diff --git a/tools/perf/core/results_processor/processor.py b/tools/perf/core/results_processor/processor.py
index 52873dd..210f57b 100644
--- a/tools/perf/core/results_processor/processor.py
+++ b/tools/perf/core/results_processor/processor.py
@@ -8,6 +8,7 @@
 the standalone version of Results Processor.
 """
 
+import datetime
 import json
 import logging
 import os
@@ -28,7 +29,12 @@
 from tracing.value import histogram_set
 from tracing.value import legacy_unit_info
 
+# Telemetry results file is deprecated.
+# TODO(crbug.com/981349): Remove this constant after Telemetry swithes to
+# the new file.
 TELEMETRY_RESULTS = '_telemetry_results.jsonl'
+TEST_RESULTS = '_test_results.jsonl'
+DIAGNOSTICS_NAME = 'diagnostics.json'
 MEASUREMENTS_NAME = 'measurements.json'
 
 FORMATS_WITH_METRICS = ['csv', 'histograms', 'html']
@@ -48,17 +54,31 @@
   if not getattr(options, 'output_formats', None):
     return 0
 
-  intermediate_results = _LoadIntermediateResults(
-      os.path.join(options.intermediate_dir, TELEMETRY_RESULTS))
+  test_results = _LoadTestResults(options.intermediate_dir)
+  if not test_results:
+    # TODO(crbug.com/981349): Make sure that no one is expecting Results
+    # Processor to output results in the case of empty input
+    # and make this an error.
+    logging.warning('No test results to process.')
 
-  AggregateTraces(intermediate_results)
+  upload_bucket = options.upload_bucket
+  results_label = options.results_label
+  test_suite_start = (test_results[0]['startTime'] if test_results
+                      else datetime.datetime.utcnow().isoformat() + 'Z')
+  run_identifier = RunIdentifier(results_label, test_suite_start)
+  should_compute_metrics = any(
+      fmt in FORMATS_WITH_METRICS for fmt in options.output_formats)
 
-  UploadArtifacts(
-      intermediate_results, options.upload_bucket, options.results_label)
+  util.ApplyInParallel(
+      lambda result: ProcessTestResult(
+          result, upload_bucket, results_label, run_identifier,
+          test_suite_start, should_compute_metrics),
+      test_results,
+      on_failure=lambda result: result.update(status='FAIL'),
+  )
 
-  if any(fmt in FORMATS_WITH_METRICS for fmt in options.output_formats):
-    histogram_dicts = _ComputeMetrics(intermediate_results,
-                                      options.results_label)
+  if should_compute_metrics:
+    histogram_dicts = ExtractHistograms(test_results)
 
   for output_format in options.output_formats:
     logging.info('Processing format: %s', output_format)
@@ -66,12 +86,33 @@
     if output_format in FORMATS_WITH_METRICS:
       formatter.ProcessHistogramDicts(histogram_dicts, options)
     else:
-      formatter.ProcessIntermediateResults(intermediate_results, options)
+      formatter.ProcessIntermediateResults(test_results, options)
 
-  return GenerateExitCode(intermediate_results)
+  return GenerateExitCode(test_results)
 
 
-def GenerateExitCode(intermediate_results):
+def ProcessTestResult(test_result, upload_bucket, results_label,
+                      run_identifier, test_suite_start, should_compute_metrics):
+  AggregateTraces(test_result)
+  if upload_bucket is not None:
+    UploadArtifacts(test_result, upload_bucket, run_identifier)
+
+  if should_compute_metrics:
+    test_result['_histograms'] = histogram_set.HistogramSet()
+    compute_metrics.ComputeTBMv2Metrics(test_result)
+    ExtractMeasurements(test_result)
+    AddDiagnosticsToHistograms(test_result, test_suite_start, results_label)
+
+
+def ExtractHistograms(test_results):
+  histograms = histogram_set.HistogramSet()
+  for result in test_results:
+    histograms.Merge(result['_histograms'])
+  histograms.DeduplicateDiagnostics()
+  return histograms.AsDicts()
+
+
+def GenerateExitCode(test_results):
   """Generate an exit code as expected by callers.
 
   Returns:
@@ -79,69 +120,63 @@
     -1 if all tests were skipped.
     0 otherwise.
   """
-  if any(r['status'] == 'FAIL' for r in intermediate_results['testResults']):
+  if any(r['status'] == 'FAIL' for r in test_results):
     return 1
-  if all(r['status'] == 'SKIP' for r in intermediate_results['testResults']):
+  if all(r['status'] == 'SKIP' for r in test_results):
     return -1
   return 0
 
 
-def _LoadIntermediateResults(intermediate_file):
-  """Load intermediate results from a file into a single dict."""
-  results = {'benchmarkRun': {}, 'testResults': []}
+def _LoadTestResults(intermediate_dir):
+  """Load intermediate results from a file into a list of test results."""
+  # Try to load the results from the new file first, then from the old one.
+  # TODO(crbug.com/981349): Remove fallback when Telemetry switches to the
+  # new format.
+  intermediate_file = os.path.join(intermediate_dir, TEST_RESULTS)
+  if not os.path.exists(intermediate_file):
+    intermediate_file = os.path.join(intermediate_dir, TELEMETRY_RESULTS)
+
+  benchmark_run = {}
+  test_results = []
   with open(intermediate_file) as f:
     for line in f:
       record = json.loads(line)
+      # TODO(crbug.com/981349): Stop reading benchmarkRun messages when
+      # Telemetry switches to the new format.
       if 'benchmarkRun' in record:
-        results['benchmarkRun'].update(record['benchmarkRun'])
+        benchmark_run.update(record['benchmarkRun'])
       if 'testResult' in record:
-        test_result = record['testResult']
-        results['testResults'].append(test_result)
-  return results
+        test_results.append(record['testResult'])
+  for test_result in test_results:
+    test_result['_benchmarkRun'] = benchmark_run
+  return test_results
 
 
-def _AggregateTraceWorker(artifacts):
-  traces = [name for name in artifacts if name.startswith('trace/')]
-  trace_files = [artifacts.pop(name)['filePath'] for name in traces]
-  html_path = os.path.join(
-      os.path.dirname(os.path.commonprefix(trace_files)),
-      compute_metrics.HTML_TRACE_NAME)
-  trace_data.SerializeAsHtml(trace_files, html_path)
-  artifacts[compute_metrics.HTML_TRACE_NAME] = {
-    'filePath': html_path,
-    'contentType': 'text/html',
-  }
-
-
-def AggregateTraces(intermediate_results):
+def AggregateTraces(test_result):
   """Replace individual traces with an aggregate one for each test result.
 
-  For each test run with traces, generates an aggregate HTML trace. Removes
+  For a test run with traces, generates an aggregate HTML trace. Removes
   all entries for individual traces and adds one entry for aggregate one.
   """
-  work_list = []
-  for result in intermediate_results['testResults']:
-    artifacts = result.get('outputArtifacts', {})
-    # TODO(crbug.com/981349): Stop checking for HTML_TRACE_NAME after
-    # Telemetry does not aggregate traces anymore.
-    if (any(name.startswith('trace/') for name in artifacts) and
-        compute_metrics.HTML_TRACE_NAME not in artifacts):
-      work_list.append(artifacts)
-
-  if work_list:
-    for _ in util.ApplyInParallel(_AggregateTraceWorker, work_list):
-      pass
-
-  # TODO(crbug.com/981349): This is to clean up traces that have been
-  # aggregated by Telemetry. Remove this after Telemetry no longer does this.
-  for result in intermediate_results['testResults']:
-    artifacts = result.get('outputArtifacts', {})
-    for name in artifacts.keys():
-      if name.startswith('trace/'):
-        del artifacts[name]
+  artifacts = test_result.get('outputArtifacts', {})
+  traces = [name for name in artifacts if name.startswith('trace/')]
+  # TODO(crbug.com/981349): Stop checking for HTML_TRACE_NAME after
+  # Telemetry does not aggregate traces anymore.
+  if traces and compute_metrics.HTML_TRACE_NAME not in artifacts:
+    trace_files = [artifacts[name]['filePath'] for name in traces]
+    html_path = os.path.join(
+        os.path.dirname(os.path.commonprefix(trace_files)),
+        compute_metrics.HTML_TRACE_NAME)
+    trace_data.SerializeAsHtml(trace_files, html_path)
+    artifacts[compute_metrics.HTML_TRACE_NAME] = {
+      'filePath': html_path,
+      'contentType': 'text/html',
+    }
+  for name in traces:
+    del artifacts[name]
 
 
-def _RunIdentifier(results_label, start_time):
+def RunIdentifier(results_label, test_suite_start):
   """Construct an identifier for the current script run"""
   if results_label:
     identifier_parts = [re.sub(r'\W+', '_', results_label)]
@@ -149,80 +184,67 @@
     identifier_parts = []
   # Time is rounded to seconds and delimiters are removed.
   # The first 19 chars of the string match 'YYYY-MM-DDTHH:MM:SS'.
-  identifier_parts.append(re.sub(r'\W+', '', start_time[:19]))
+  identifier_parts.append(re.sub(r'\W+', '', test_suite_start[:19]))
   identifier_parts.append(str(random.randint(1, 1e5)))
   return '_'.join(identifier_parts)
 
 
-def UploadArtifacts(intermediate_results, upload_bucket, results_label):
+def UploadArtifacts(test_result, upload_bucket, run_identifier):
   """Upload all artifacts to cloud.
 
-  For each test run, uploads all its artifacts to cloud and sets remoteUrl
+  For a test run, uploads all its artifacts to cloud and sets remoteUrl
   fields in intermediate_results.
   """
-  if upload_bucket is None:
-    return
-
-  run_identifier = _RunIdentifier(
-      results_label, intermediate_results['benchmarkRun']['startTime'])
-  work_list = []
-
-  for result in intermediate_results['testResults']:
-    artifacts = result.get('outputArtifacts', {})
-    for name, artifact in artifacts.iteritems():
-      if 'remoteUrl' in artifact:
-        continue
-      # TODO(crbug.com/981349): Remove this check after Telemetry does not
-      # save histograms as an artifact anymore.
-      if name == compute_metrics.HISTOGRAM_DICTS_FILE:
-        continue
-      remote_name = '/'.join([run_identifier, result['testPath'], name])
-      work_list.append((artifact, remote_name))
-
-  def PoolUploader(work_item):
-    artifact, remote_name = work_item
+  artifacts = test_result.get('outputArtifacts', {})
+  for name, artifact in artifacts.iteritems():
+    if 'remoteUrl' in artifact:
+      continue
+    # TODO(crbug.com/981349): Remove check for HISTOGRAM_DICTS_FILE
+    # after Telemetry does not save histograms as an artifact anymore.
+    # Another TODO(crbug.com/981349): Think of a more general way to
+    # specify which artifacts deserve uploading.
+    if name in [compute_metrics.HISTOGRAM_DICTS_FILE, MEASUREMENTS_NAME]:
+      continue
+    remote_name = '/'.join([run_identifier, test_result['testPath'], name])
     artifact['remoteUrl'] = cloud_storage.Insert(
         upload_bucket, remote_name, artifact['filePath'])
-
-  for _ in util.ApplyInParallel(PoolUploader, work_list):
-    pass
-
-  for result in intermediate_results['testResults']:
-    artifacts = result.get('outputArtifacts', {})
-    for name, artifact in artifacts.iteritems():
-      logging.info('Uploaded %s of %s to %s', name, result['testPath'],
-                   artifact['remoteUrl'])
+    logging.info('Uploaded %s of %s to %s', name, test_result['testPath'],
+                 artifact['remoteUrl'])
 
 
-def _ComputeMetrics(intermediate_results, results_label):
-  histogram_dicts = compute_metrics.ComputeTBMv2Metrics(intermediate_results)
-  histogram_dicts += ExtractMeasurements(intermediate_results)
-  histogram_dicts = AddDiagnosticsToHistograms(
-      histogram_dicts, intermediate_results, results_label)
-  return histogram_dicts
+def AddDiagnosticsToHistograms(test_result, test_suite_start, results_label):
+  """Add diagnostics to all histograms of a test run.
 
+  Reads diagnostics from the test artifact and adds them to all histograms.
+  This overwrites the corresponding diagnostics previously set by e.g.
+  run_metrics.
+  """
+  artifacts = test_result.get('outputArtifacts', {})
+  if DIAGNOSTICS_NAME in artifacts:
+    with open(artifacts[DIAGNOSTICS_NAME]['filePath']) as f:
+      diagnostics = json.load(f)['diagnostics']
+  # TODO(crbug.com/981349): Remove this branch when Telemetry switches to the
+  # new format.
+  else:
+    diagnostics = test_result.get('_benchmarkRun', {}).get('diagnostics', {})
 
-def AddDiagnosticsToHistograms(histogram_dicts, intermediate_results,
-                                results_label):
-  """Add diagnostics to histogram dicts"""
-  histograms = histogram_set.HistogramSet()
-  histograms.ImportDicts(histogram_dicts)
-  diagnostics = intermediate_results['benchmarkRun'].get('diagnostics', {})
   for name, diag in diagnostics.items():
     # For now, we only support GenericSet diagnostics that are serialized
     # as lists of values.
     assert isinstance(diag, list)
-    histograms.AddSharedDiagnosticToAllHistograms(
+    test_result['_histograms'].AddSharedDiagnosticToAllHistograms(
         name, generic_set.GenericSet(diag))
 
+  timestamp_ms = util.IsoTimestampToEpoch(test_suite_start) * 1e3
+  test_result['_histograms'].AddSharedDiagnosticToAllHistograms(
+      reserved_infos.BENCHMARK_START.name, date_range.DateRange(timestamp_ms))
+
+
   if results_label is not None:
-    histograms.AddSharedDiagnosticToAllHistograms(
+    test_result['_histograms'].AddSharedDiagnosticToAllHistograms(
         reserved_infos.LABELS.name,
         generic_set.GenericSet([results_label]))
 
-  histograms.DeduplicateDiagnostics()
-  return histograms.AsDicts()
-
 
 def MeasurementToHistogram(name, measurement):
   unit = measurement['unit']
@@ -238,22 +260,10 @@
                                     description=description)
 
 
-def _GlobalDiagnostics(benchmark_run):
-  """Extract diagnostics information about the whole benchmark run.
-
-  These diagnostics will be added to ad-hoc measurements recorded by
-  benchmarks.
-  """
-  timestamp_ms = util.IsoTimestampToEpoch(benchmark_run['startTime']) * 1e3
-  return {
-    reserved_infos.BENCHMARK_START.name: date_range.DateRange(timestamp_ms),
-  }
-
-
 def _StoryDiagnostics(test_result):
   """Extract diagnostics information about the specific story.
 
-  These diagnostics will be added to ad-hoc measurements recorded by
+  These diagnostics will be added only to ad-hoc measurements recorded by
   benchmarks.
   """
   benchmark_name, story_name = test_result['testPath'].split('/', 1)
@@ -266,23 +276,16 @@
   }
 
 
-def ExtractMeasurements(intermediate_results):
+def ExtractMeasurements(test_result):
   """Add ad-hoc measurements to histogram dicts"""
-  histograms = histogram_set.HistogramSet()
-  global_diagnostics = _GlobalDiagnostics(intermediate_results['benchmarkRun'])
-
-  for result in intermediate_results['testResults']:
-    artifacts = result.get('outputArtifacts', {})
-    if MEASUREMENTS_NAME in artifacts:
-      with open(artifacts[MEASUREMENTS_NAME]['filePath']) as f:
-        measurements = json.load(f)['measurements']
-      diagnostics = global_diagnostics.copy()
-      diagnostics.update(_StoryDiagnostics(result))
-      for name, measurement in measurements.iteritems():
-        histograms.AddHistogram(MeasurementToHistogram(name, measurement),
-                                diagnostics=diagnostics)
-
-  return histograms.AsDicts()
+  artifacts = test_result.get('outputArtifacts', {})
+  if MEASUREMENTS_NAME in artifacts:
+    with open(artifacts[MEASUREMENTS_NAME]['filePath']) as f:
+      measurements = json.load(f)['measurements']
+    diagnostics = _StoryDiagnostics(test_result)
+    for name, measurement in measurements.iteritems():
+      test_result['_histograms'].AddHistogram(
+          MeasurementToHistogram(name, measurement), diagnostics=diagnostics)
 
 
 def main(args=None):
diff --git a/tools/perf/core/results_processor/processor_test.py b/tools/perf/core/results_processor/processor_test.py
index 900de29..4180d1b54 100644
--- a/tools/perf/core/results_processor/processor_test.py
+++ b/tools/perf/core/results_processor/processor_test.py
@@ -42,18 +42,35 @@
   def tearDown(self):
     shutil.rmtree(self.output_dir)
 
-  def SerializeIntermediateResults(self, *args, **kwargs):
-    in_results = testing.IntermediateResults(*args, **kwargs)
-    testing.SerializeIntermediateResults(in_results, os.path.join(
+  def SerializeIntermediateResults(self, *test_results):
+    testing.SerializeIntermediateResults(test_results, os.path.join(
         self.intermediate_dir, processor.TELEMETRY_RESULTS))
 
+  def CreateHistogramsArtifact(self, hist):
+    """Create an artifact with histograms."""
+    histogram_dicts = [hist.AsDict()]
+    hist_file = os.path.join(self.output_dir,
+                             compute_metrics.HISTOGRAM_DICTS_FILE)
+    with open(hist_file, 'w') as f:
+      json.dump(histogram_dicts, f)
+    return testing.Artifact(hist_file)
+
+  def CreateDiagnosticsArtifact(self, **diagnostics):
+    """Create an artifact with diagnostics."""
+    diag_file = os.path.join(self.output_dir,
+                             processor.DIAGNOSTICS_NAME)
+    with open(diag_file, 'w') as f:
+      json.dump({'diagnostics': diagnostics}, f)
+    return testing.Artifact(diag_file)
+
   def testJson3Output(self):
-    self.SerializeIntermediateResults([
+    self.SerializeIntermediateResults(
         testing.TestResult(
-            'benchmark/story', run_duration='1.1s', tags=['shard:7']),
+            'benchmark/story', run_duration='1.1s', tags=['shard:7'],
+            start_time='2009-02-13T23:31:30.987000Z'),
         testing.TestResult(
             'benchmark/story', run_duration='1.2s', tags=['shard:7']),
-    ], start_time='2009-02-13T23:31:30.987000Z')
+    )
 
     processor.main([
         '--output-format', 'json-test-results',
@@ -80,7 +97,7 @@
     self.assertEqual(test_result['shard'], 7)
 
   def testJson3OutputWithArtifacts(self):
-    self.SerializeIntermediateResults([
+    self.SerializeIntermediateResults(
         testing.TestResult(
             'benchmark/story',
             output_artifacts={
@@ -88,8 +105,9 @@
                 'trace/telemetry': testing.Artifact('/telemetry.json'),
                 'trace.html':
                     testing.Artifact('/trace.html', 'gs://trace.html'),
-            },
-    )])
+            }
+        ),
+    )
 
     processor.main([
         '--output-format', 'json-test-results',
@@ -110,26 +128,21 @@
     self.assertEqual(artifacts['trace.html'], ['gs://trace.html'])
 
   def testHistogramsOutput(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
-        diagnostics={
-            'benchmarks': ['benchmark'],
-            'osNames': ['linux'],
-            'documentationUrls': [['documentation', 'url']],
-        },
-        start_time='2009-02-13T23:31:30.987000Z',
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+                processor.DIAGNOSTICS_NAME:
+                    self.CreateDiagnosticsArtifact(
+                        benchmarks=['benchmark'],
+                        osNames=['linux'],
+                        documentationUrls=[['documentation', 'url']]),
+            },
+            start_time='2009-02-13T23:31:30.987000Z',
+        ),
     )
 
     processor.main([
@@ -146,31 +159,32 @@
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
     self.assertEqual(len(out_histograms), 1)
-    self.assertEqual(out_histograms.GetFirstHistogram().name, 'a')
-    self.assertEqual(out_histograms.GetFirstHistogram().unit, 'unitless')
 
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertEqual(len(diag_values), 4)
-    self.assertIn(['benchmark'], diag_values)
-    self.assertIn(['linux'], diag_values)
-    self.assertIn([['documentation', 'url']], diag_values)
-    self.assertIn(['label'], diag_values)
+    hist = out_histograms.GetFirstHistogram()
+    self.assertEqual(hist.name, 'a')
+    self.assertEqual(hist.unit, 'unitless')
+
+    self.assertEqual(hist.diagnostics['benchmarks'],
+                     generic_set.GenericSet(['benchmark']))
+    self.assertEqual(hist.diagnostics['osNames'],
+                     generic_set.GenericSet(['linux']))
+    self.assertEqual(hist.diagnostics['documentationUrls'],
+                     generic_set.GenericSet([['documentation', 'url']]))
+    self.assertEqual(hist.diagnostics['labels'],
+                     generic_set.GenericSet(['label']))
+    self.assertEqual(hist.diagnostics['benchmarkStart'],
+                     date_range.DateRange(1234567890987))
 
   def testHistogramsOutputResetResults(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
     )
 
     processor.main([
@@ -195,25 +209,21 @@
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
     self.assertEqual(len(out_histograms), 1)
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertNotIn(['label1'], diag_values)
-    self.assertIn(['label2'], diag_values)
+
+    hist = out_histograms.GetFirstHistogram()
+    self.assertEqual(hist.diagnostics['labels'],
+                     generic_set.GenericSet(['label2']))
 
   def testHistogramsOutputAppendResults(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
     )
 
     processor.main([
@@ -237,9 +247,11 @@
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
     self.assertEqual(len(out_histograms), 2)
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertIn(['label1'], diag_values)
-    self.assertIn(['label2'], diag_values)
+
+    expected_labels = set(['label1', 'label2'])
+    observed_labels = set(label for hist in out_histograms
+                           for label in hist.diagnostics['labels'])
+    self.assertEqual(observed_labels, expected_labels)
 
   def testHistogramsOutputNoMetricsFromTelemetry(self):
     trace_file = os.path.join(self.output_dir, compute_metrics.HTML_TRACE_NAME)
@@ -247,16 +259,14 @@
       pass
 
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    compute_metrics.HTML_TRACE_NAME:
-                        testing.Artifact(trace_file, 'gs://trace.html')
-                },
-                tags=['tbmv2:sampleMetric'],
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HTML_TRACE_NAME:
+                    testing.Artifact(trace_file, 'gs://trace.html')
+            },
+            tags=['tbmv2:sampleMetric'],
+        ),
     )
 
     processor.main([
@@ -284,13 +294,11 @@
       json.dump({'traceEvents': []}, f)
 
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={'trace/json': testing.Artifact(json_trace)},
-                tags=['tbmv2:sampleMetric'],
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={'trace/json': testing.Artifact(json_trace)},
+            tags=['tbmv2:sampleMetric'],
+        ),
     )
 
     processor.main([
@@ -324,16 +332,14 @@
     start_iso = datetime.datetime.utcfromtimestamp(start_ts).isoformat() + 'Z'
 
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    processor.MEASUREMENTS_NAME: testing.Artifact(measure_file)
-                },
-                tags=['story_tag:test']
-            ),
-        ],
-        start_time=start_iso,
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                processor.MEASUREMENTS_NAME: testing.Artifact(measure_file)
+            },
+            tags=['story_tag:test'],
+            start_time=start_iso,
+        ),
     )
 
     processor.main([
@@ -379,26 +385,21 @@
                      date_range.DateRange(start_ts * 1e3))
 
   def testHtmlOutput(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
-        diagnostics={
-            'benchmarks': ['benchmark'],
-            'osNames': ['linux'],
-            'documentationUrls': [['documentation', 'url']],
-        },
-        start_time='2009-02-13T23:31:30.987000Z',
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+                processor.DIAGNOSTICS_NAME:
+                    self.CreateDiagnosticsArtifact(
+                        benchmarks=['benchmark'],
+                        osNames=['linux'],
+                        documentationUrls=[['documentation', 'url']]),
+            },
+            start_time='2009-02-13T23:31:30.987000Z',
+        ),
     )
 
     processor.main([
@@ -415,18 +416,33 @@
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
     self.assertEqual(len(out_histograms), 1)
-    self.assertEqual(out_histograms.GetFirstHistogram().name, 'a')
-    self.assertEqual(out_histograms.GetFirstHistogram().unit, 'unitless')
 
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertEqual(len(diag_values), 4)
-    self.assertIn(['benchmark'], diag_values)
-    self.assertIn(['linux'], diag_values)
-    self.assertIn([['documentation', 'url']], diag_values)
-    self.assertIn(['label'], diag_values)
+    hist = out_histograms.GetFirstHistogram()
+    self.assertEqual(hist.name, 'a')
+    self.assertEqual(hist.unit, 'unitless')
+
+    self.assertEqual(hist.diagnostics['benchmarks'],
+                     generic_set.GenericSet(['benchmark']))
+    self.assertEqual(hist.diagnostics['osNames'],
+                     generic_set.GenericSet(['linux']))
+    self.assertEqual(hist.diagnostics['documentationUrls'],
+                     generic_set.GenericSet([['documentation', 'url']]))
+    self.assertEqual(hist.diagnostics['labels'],
+                     generic_set.GenericSet(['label']))
+    self.assertEqual(hist.diagnostics['benchmarkStart'],
+                     date_range.DateRange(1234567890987))
 
   def testHtmlOutputResetResults(self):
-    self.SerializeIntermediateResults([])
+    self.SerializeIntermediateResults(
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
+    )
 
     processor.main([
         '--output-format', 'html',
@@ -449,12 +465,23 @@
 
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertNotIn(['label1'], diag_values)
-    self.assertIn(['label2'], diag_values)
+    self.assertEqual(len(out_histograms), 1)
+
+    hist = out_histograms.GetFirstHistogram()
+    self.assertEqual(hist.diagnostics['labels'],
+                     generic_set.GenericSet(['label2']))
 
   def testHtmlOutputAppendResults(self):
-    self.SerializeIntermediateResults([])
+    self.SerializeIntermediateResults(
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
+    )
 
     processor.main([
         '--output-format', 'html',
@@ -476,32 +503,30 @@
 
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertIn(['label1'], diag_values)
-    self.assertIn(['label2'], diag_values)
+    self.assertEqual(len(out_histograms), 2)
+
+    expected_labels = set(['label1', 'label2'])
+    observed_labels = set(label for hist in out_histograms
+                           for label in hist.diagnostics['labels'])
+    self.assertEqual(observed_labels, expected_labels)
 
   def testCsvOutput(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
     test_hist = histogram.Histogram('a', 'ms')
     test_hist.AddSample(3000)
-    with open(hist_file, 'w') as f:
-      json.dump([test_hist.AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
-        diagnostics={
-            'benchmarks': ['benchmark'],
-            'osNames': ['linux'],
-            'documentationUrls': [['documentation', 'url']],
-        },
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(test_hist),
+                processor.DIAGNOSTICS_NAME:
+                    self.CreateDiagnosticsArtifact(
+                        benchmarks=['benchmark'],
+                        osNames=['linux'],
+                        documentationUrls=[['documentation', 'url']]),
+            },
+            start_time='2009-02-13T23:31:30.987000Z',
+        ),
     )
 
     processor.main([
@@ -519,7 +544,7 @@
         ('name', 'a'), ('unit', 'ms'), ('avg', '3000'), ('count', '1'),
         ('max', '3000'), ('min', '3000'), ('std', '0'), ('sum', '3000'),
         ('architectures', ''), ('benchmarks', 'benchmark'),
-        ('benchmarkStart', ''), ('bots', ''),
+        ('benchmarkStart', '2009-02-13 23:31:30'), ('bots', ''),
         ('builds', ''), ('deviceIds', ''), ('displayLabel', 'label'),
         ('masters', ''), ('memoryAmounts', ''), ('osNames', 'linux'),
         ('osVersions', ''), ('productVersions', ''),
@@ -529,20 +554,15 @@
     self.assertEqual(actual, expected)
 
   def testCsvOutputResetResults(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
     )
 
     processor.main([
@@ -567,20 +587,15 @@
     self.assertIn('label2', lines[1])
 
   def testCsvOutputAppendResults(self):
-    hist_file = os.path.join(self.output_dir,
-                             compute_metrics.HISTOGRAM_DICTS_FILE)
-    with open(hist_file, 'w') as f:
-      json.dump([histogram.Histogram('a', 'unitless').AsDict()], f)
-
     self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'histogram_dicts.json': testing.Artifact(hist_file)
-                },
-            ),
-        ],
+        testing.TestResult(
+            'benchmark/story',
+            output_artifacts={
+                compute_metrics.HISTOGRAM_DICTS_FILE:
+                    self.CreateHistogramsArtifact(
+                        histogram.Histogram('a', 'unitless')),
+            },
+        ),
     )
 
     processor.main([
@@ -605,10 +620,10 @@
     self.assertIn('label1', lines[2])
 
   def testExitCodeHasFailures(self):
-    self.SerializeIntermediateResults([
+    self.SerializeIntermediateResults(
         testing.TestResult('benchmark/story', status='PASS'),
         testing.TestResult('benchmark/story', status='FAIL'),
-    ])
+    )
 
     exit_code = processor.main([
         '--output-format', 'json-test-results',
@@ -618,10 +633,10 @@
     self.assertEqual(exit_code, 1)
 
   def testExitCodeAllSkipped(self):
-    self.SerializeIntermediateResults([
+    self.SerializeIntermediateResults(
         testing.TestResult('benchmark/story', status='SKIP'),
         testing.TestResult('benchmark/story', status='SKIP'),
-    ])
+    )
 
     exit_code = processor.main([
         '--output-format', 'json-test-results',
@@ -631,10 +646,10 @@
     self.assertEqual(exit_code, -1)
 
   def testExitCodeSomeSkipped(self):
-    self.SerializeIntermediateResults([
+    self.SerializeIntermediateResults(
         testing.TestResult('benchmark/story', status='SKIP'),
         testing.TestResult('benchmark/story', status='PASS'),
-    ])
+    )
 
     exit_code = processor.main([
         '--output-format', 'json-test-results',
diff --git a/tools/perf/core/results_processor/processor_unittest.py b/tools/perf/core/results_processor/processor_unittest.py
index c68a1ed..919e4b2 100644
--- a/tools/perf/core/results_processor/processor_unittest.py
+++ b/tools/perf/core/results_processor/processor_unittest.py
@@ -4,6 +4,7 @@
 
 """Unit tests for results_processor methods."""
 
+import datetime
 import os
 import unittest
 
@@ -12,141 +13,95 @@
 from core.results_processor import processor
 from core.results_processor import testing
 
-from tracing.value import histogram
+from tracing.value.diagnostics import generic_set
+from tracing.value.diagnostics import date_range
 from tracing.value import histogram_set
 
 
 class ResultsProcessorUnitTests(unittest.TestCase):
   def testAddDiagnosticsToHistograms(self):
-    histogram_dicts = [histogram.Histogram('a', 'unitless').AsDict()]
+    test_result = testing.TestResult('benchmark/story')
+    test_result['_histograms'] = histogram_set.HistogramSet()
+    test_result['_histograms'].CreateHistogram('a', 'unitless', [0])
 
-    in_results = testing.IntermediateResults(
-        test_results=[],
-        diagnostics={
-            'benchmarks': ['benchmark'],
-            'osNames': ['linux'],
-            'documentationUrls': [['documentation', 'url']],
-        },
-    )
+    start_ts = 1500000000
+    start_iso = datetime.datetime.utcfromtimestamp(start_ts).isoformat() + 'Z'
 
-    histograms_with_diagnostics = processor.AddDiagnosticsToHistograms(
-        histogram_dicts, in_results, results_label='label')
 
-    out_histograms = histogram_set.HistogramSet()
-    out_histograms.ImportDicts(histograms_with_diagnostics)
-    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
-    self.assertEqual(len(diag_values), 4)
-    self.assertIn(['benchmark'], diag_values)
-    self.assertIn(['linux'], diag_values)
-    self.assertIn([['documentation', 'url']], diag_values)
-    self.assertIn(['label'], diag_values)
+    processor.AddDiagnosticsToHistograms(
+        test_result, test_suite_start=start_iso, results_label='label')
+
+    hist = test_result['_histograms'].GetFirstHistogram()
+    self.assertEqual(hist.diagnostics['labels'],
+                     generic_set.GenericSet(['label']))
+    self.assertEqual(hist.diagnostics['benchmarkStart'],
+                     date_range.DateRange(start_ts * 1e3))
 
   def testUploadArtifacts(self):
-    in_results = testing.IntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={'log': testing.Artifact('/log.log')},
-            ),
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                  'trace.html': testing.Artifact('/trace.html'),
-                  'screenshot': testing.Artifact('/screenshot.png'),
-                },
-            ),
-        ],
+    test_result = testing.TestResult(
+        'benchmark/story',
+        output_artifacts={
+          'logs': testing.Artifact('/log.log'),
+          'trace.html': testing.Artifact('/trace.html'),
+          'screenshot': testing.Artifact('/screenshot.png'),
+        },
     )
 
     with mock.patch('py_utils.cloud_storage.Insert') as cloud_patch:
       cloud_patch.return_value = 'gs://url'
-      processor.UploadArtifacts(in_results, 'bucket', None)
+      processor.UploadArtifacts(test_result, 'bucket', 'run1')
       cloud_patch.assert_has_calls([
-          mock.call('bucket', mock.ANY, '/log.log'),
-          mock.call('bucket', mock.ANY, '/trace.html'),
-          mock.call('bucket', mock.ANY, '/screenshot.png'),
+          mock.call('bucket', 'run1/benchmark/story/logs', '/log.log'),
+          mock.call('bucket', 'run1/benchmark/story/trace.html', '/trace.html'),
+          mock.call('bucket', 'run1/benchmark/story/screenshot',
+                    '/screenshot.png'),
         ],
         any_order=True,
       )
 
-    for result in in_results['testResults']:
-      for artifact in result['outputArtifacts'].itervalues():
-        self.assertEqual(artifact['remoteUrl'], 'gs://url')
+    for artifact in test_result['outputArtifacts'].itervalues():
+      self.assertEqual(artifact['remoteUrl'], 'gs://url')
 
-  def testUploadArtifacts_CheckRemoteUrl(self):
-    in_results = testing.IntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={
-                    'trace.html': testing.Artifact('/trace.html')
-                },
-            ),
-        ],
-        start_time='2019-10-01T12:00:00.123456Z',
-    )
-
-    with mock.patch('py_utils.cloud_storage.Insert') as cloud_patch:
-      with mock.patch('random.randint') as randint_patch:
-        randint_patch.return_value = 54321
-        processor.UploadArtifacts(in_results, 'bucket', 'src@abc + 123')
-        cloud_patch.assert_called_once_with(
-            'bucket',
-            'src_abc_123_20191001T120000_54321/benchmark/story/trace.html',
-            '/trace.html'
-        )
+  def testRunIdentifier(self):
+    with mock.patch('random.randint') as randint_patch:
+      randint_patch.return_value = 54321
+      run_identifier = processor.RunIdentifier(
+          results_label='src@abc + 123',
+          test_suite_start='2019-10-01T12:00:00.123456Z')
+    self.assertEqual(run_identifier, 'src_abc_123_20191001T120000_54321')
 
   def testAggregateTraces(self):
-    in_results = testing.IntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story1',
-                output_artifacts={
-                    'trace/1.json': testing.Artifact(
-                        os.path.join('test_run', 'story1', 'trace', '1.json')),
-                },
-            ),
-            testing.TestResult(
-                'benchmark/story2',
-                output_artifacts={
-                    'trace/1.json': testing.Artifact(
-                        os.path.join('test_run', 'story2', 'trace', '1.json')),
-                    'trace/2.json': testing.Artifact(
-                        os.path.join('test_run', 'story2', 'trace', '2.json')),
-                },
-            ),
-        ],
+    test_result = testing.TestResult(
+        'benchmark/story2',
+        output_artifacts={
+            'trace/1.json': testing.Artifact(
+                os.path.join('test_run', 'story2', 'trace', '1.json')),
+            'trace/2.json': testing.Artifact(
+                os.path.join('test_run', 'story2', 'trace', '2.json')),
+        },
     )
 
-    with mock.patch('tracing.trace_data.trace_data.SerializeAsHtml') as patch:
-      processor.AggregateTraces(in_results)
+    serialize_method = 'tracing.trace_data.trace_data.SerializeAsHtml'
+    with mock.patch(serialize_method) as mock_serialize:
+      processor.AggregateTraces(test_result)
 
-    call_list = [list(call[0]) for call in patch.call_args_list]
-    self.assertEqual(len(call_list), 2)
-    for call in call_list:
-      call[0] = set(call[0])
-    self.assertIn(
-        [
-            set([os.path.join('test_run', 'story1', 'trace', '1.json')]),
-            os.path.join('test_run', 'story1', 'trace', 'trace.html'),
-        ],
-        call_list
+    self.assertEqual(mock_serialize.call_count, 1)
+    trace_files, file_path = mock_serialize.call_args[0][:2]
+    self.assertEqual(
+        set(trace_files),
+        set([
+            os.path.join('test_run', 'story2', 'trace', '1.json'),
+            os.path.join('test_run', 'story2', 'trace', '2.json'),
+        ]),
     )
-    self.assertIn(
-        [
-            set([
-                os.path.join('test_run', 'story2', 'trace', '1.json'),
-                os.path.join('test_run', 'story2', 'trace', '2.json'),
-            ]),
-            os.path.join('test_run', 'story2', 'trace', 'trace.html'),
-        ],
-        call_list
+    self.assertEqual(
+        file_path,
+        os.path.join('test_run', 'story2', 'trace', 'trace.html'),
     )
 
-    for result in in_results['testResults']:
-      artifacts = result['outputArtifacts']
-      self.assertEqual(len(artifacts), 1)
-      self.assertEqual(artifacts.keys()[0], 'trace.html')
+    artifacts = test_result['outputArtifacts']
+    self.assertEqual(len(artifacts), 1)
+    self.assertEqual(artifacts.keys()[0], 'trace.html')
 
   def testMeasurementToHistogram(self):
     hist = processor.MeasurementToHistogram('a', {
diff --git a/tools/perf/core/results_processor/testing.py b/tools/perf/core/results_processor/testing.py
index 2f3b8b9..a73304b 100644
--- a/tools/perf/core/results_processor/testing.py
+++ b/tools/perf/core/results_processor/testing.py
@@ -7,33 +7,6 @@
 import json
 
 
-_BENCHMARK_START_KEYS = set(['startTime'])
-
-
-def IntermediateResults(test_results, start_time='2015-10-21T07:28:00.000Z',
-                        finalized=True, interrupted=False, diagnostics=None):
-  """Build a dict of 'parsed' intermediate results.
-
-  Args:
-    test_results: A sequence of testResult dicts.
-    start_time: An optional UTC timestamp recording when a benchmark started
-      running.
-    finalized: An optional bool indicating whether the benchmark run finalized.
-      Defaults to True.
-    interrupted: An optional bool indicating whether the benchmark run was
-      interrupted. Defaults to False.
-  """
-  return {
-      'benchmarkRun': {
-          'startTime': start_time,
-          'finalized': finalized,
-          'interrupted': interrupted,
-          'diagnostics': diagnostics or {},
-      },
-      'testResults': list(test_results)
-  }
-
-
 def TestResult(test_path, status='PASS', is_expected=None,
                start_time='2015-10-21T07:28:00.000Z', run_duration='1.00s',
                output_artifacts=None, tags=None):
@@ -97,29 +70,16 @@
   """Serialize intermediate results to a filepath.
 
   Args:
-    in_results: A dict with intermediate results, e.g. as produced by
-      IntermediateResults or parsed from an intermediate results file.
-    filpath: A file path where to serialize the intermediate results.
+    in_results: A list of test results.
+    filepath: A file path where to serialize the intermediate results.
   """
-  # Split benchmarkRun into fields recorded at startup and when finishing.
-  benchmark_start = {}
-  benchmark_finish = {}
-  for key, value in in_results['benchmarkRun'].items():
-    d = benchmark_start if key in _BENCHMARK_START_KEYS else benchmark_finish
-    d[key] = value
-
-  # Serialize individual records as a sequence of json lines.
   with open(filepath, 'w') as fp:
-    _SerializeRecord({'benchmarkRun': benchmark_start}, fp)
-    for test_result in in_results['testResults']:
-      _SerializeRecord({'testResult': test_result}, fp)
-    _SerializeRecord({'benchmarkRun': benchmark_finish}, fp)
+    for test_result in in_results:
+      json.dump({'testResult': test_result}, fp,
+                sort_keys=True, separators=(',', ':'))
+      fp.write('\n')
 
 
 def _SplitTag(tag):
   key, value = tag.split(':', 1)
   return {'key': key, 'value': value}
-
-
-def _SerializeRecord(record, fp):
-  fp.write(json.dumps(record, sort_keys=True, separators=(',', ':')) + '\n')
diff --git a/tools/perf/core/results_processor/util.py b/tools/perf/core/results_processor/util.py
index 3425c46..8cb21b8 100644
--- a/tools/perf/core/results_processor/util.py
+++ b/tools/perf/core/results_processor/util.py
@@ -9,16 +9,13 @@
 from multiprocessing.dummy import Pool as ThreadPool
 
 
-def ApplyInParallel(function, work_list):
+def ApplyInParallel(function, work_list, on_failure=None):
   """Apply a function to all values in work_list in parallel.
 
   Args:
     function: A function with one argument.
     work_list: Any iterable with arguments for the function.
-
-  Returns:
-    A generator over results. The order of results might not match the
-    order of the arguments in the work_list.
+    on_failure: A function to run in case of a failure.
   """
   if not work_list:
     return
@@ -35,17 +32,17 @@
 
   def function_with_try(arg):
     try:
-      return function(arg)
+      function(arg)
     except Exception:  # pylint: disable=broad-except
       # logging exception here is the only way to get a stack trace since
       # multiprocessing's pool implementation does not save that data. See
       # crbug.com/953365.
       logging.exception('Exception while running %s' % function.__name__)
-      raise
+      if on_failure:
+        on_failure(arg)
 
   try:
-    for result in pool.imap_unordered(function_with_try, work_list):
-      yield result
+    pool.imap_unordered(function_with_try, work_list)
     pool.close()
     pool.join()
   finally:
diff --git a/tools/perf/core/results_processor/util_unittest.py b/tools/perf/core/results_processor/util_unittest.py
index a7af11b38..ad932256 100644
--- a/tools/perf/core/results_processor/util_unittest.py
+++ b/tools/perf/core/results_processor/util_unittest.py
@@ -9,15 +9,16 @@
 
 class UtilTests(unittest.TestCase):
   def testApplyInParallel(self):
-    work_list = [1, 2, 3]
-    fun = lambda x: x * x
-    result = set(util.ApplyInParallel(fun, work_list))
-    self.assertEqual(result, set([1, 4, 9]))
-
-  def testApplyInParallelExceptionRaised(self):
-    work_list = [1, 2, 3]
+    work_list = [[1], [2], [3]]
     def fun(x):
-      if x == 3:
+      x.extend(x)
+    util.ApplyInParallel(fun, work_list)
+    self.assertEqual(work_list, [[1, 1], [2, 2], [3, 3]])
+
+  def testApplyInParallelOnFailure(self):
+    work_list = [[1], [2], [3]]
+    def fun(x):
+      if x == [3]:
         raise RuntimeError()
-    with self.assertRaises(RuntimeError):
-      list(util.ApplyInParallel(fun, work_list))
+    util.ApplyInParallel(fun, work_list, on_failure=lambda x: x.pop())
+    self.assertEqual(work_list, [[1], [2], []])
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 1fd66cdb..58aa6fc 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -337,3 +337,21 @@
 
   seed_corpus = "fuzz_corpus"
 }
+
+test("accessibility_perftests") {
+  testonly = true
+  sources = [
+    "ax_node_position_perftest.cc",
+  ]
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//mojo/core/test:run_all_unittests",
+    "//skia",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//testing/perf",
+    "//ui/accessibility/mojom",
+  ]
+}
diff --git a/ui/accessibility/ax_node_position_perftest.cc b/ui/accessibility/ax_node_position_perftest.cc
new file mode 100644
index 0000000..fc8378a
--- /dev/null
+++ b/ui/accessibility/ax_node_position_perftest.cc
@@ -0,0 +1,171 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/stringprintf.h"
+#include "base/timer/lap_timer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_serializable_tree.h"
+#include "ui/accessibility/ax_tree_serializer.h"
+#include "ui/accessibility/ax_tree_update.h"
+
+namespace ui {
+
+using TestPositionType = std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
+
+namespace {
+
+constexpr int kLaps = 5000;
+constexpr int kWarmupLaps = 5;
+constexpr char kMetricCallsPerSecondRunsPerS[] = "calls_per_second";
+
+class AXPositionPerfTest : public testing::Test {
+ public:
+  AXPositionPerfTest() = default;
+  ~AXPositionPerfTest() override = default;
+
+ protected:
+  void SetUp() override;
+  void TearDown() override;
+
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story) {
+    perf_test::PerfResultReporter reporter("AXPositionPerfTest.", story);
+    reporter.RegisterImportantMetric(kMetricCallsPerSecondRunsPerS, "runs/s");
+    return reporter;
+  }
+
+  AXTree tree_;
+
+  DISALLOW_COPY_AND_ASSIGN(AXPositionPerfTest);
+};
+
+void AXPositionPerfTest::SetUp() {
+  // Setup a root with 5 child kGenericContainer with 5 kStaticText each.
+  // Each kStaticText contains 5 characters of text.
+  //
+  // +------------------------+--------------+
+  // | Tree Hierarchy + Role  | anchor_id(s) |
+  // +------------------------+--------------+
+  // | ++kRootWebArea         | 1            |
+  // | ++++kGenericContainer  | 2            |
+  // | ++++++kStaticText      | 7 - 11       |
+  // | ++++kGenericContainer  | 3            |
+  // | ++++++kStaticText      | 12 - 16      |
+  // | ++++kGenericContainer  | 4            |
+  // | ++++++kStaticText      | 17 - 21      |
+  // | ++++kGenericContainer  | 5            |
+  // | ++++++kStaticText      | 22 - 26      |
+  // | ++++kGenericContainer  | 6            |
+  // | ++++++kStaticText      | 27 - 31      |
+  // +------------------------+--------------+
+
+  constexpr int kNumberOfGroups = 5;
+  constexpr int kStaticTextNodesPerGroup = 5;
+  constexpr int kNumberOfStaticTextNodes =
+      kNumberOfGroups * kStaticTextNodesPerGroup;
+
+  constexpr int kGroupNodesStartIndex = 1;
+  constexpr int kStaticTextNodesStartIndex =
+      kGroupNodesStartIndex + kNumberOfGroups;
+
+  AXNode::AXID current_id = 0;
+  std::vector<AXNodeData> nodes;
+  nodes.resize(1 + kNumberOfGroups + kNumberOfStaticTextNodes);
+
+  AXNodeData& root_data = nodes[0];
+  root_data.id = ++current_id;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+
+  for (int group_index = 0; group_index < kNumberOfGroups; ++group_index) {
+    AXNodeData& group = nodes[kGroupNodesStartIndex + group_index];
+    group.id = ++current_id;
+    group.role = ax::mojom::Role::kGenericContainer;
+    root_data.child_ids.push_back(group.id);
+  }
+
+  for (int text_index = 0; text_index < kNumberOfStaticTextNodes;
+       ++text_index) {
+    const int group_index = text_index / kStaticTextNodesPerGroup;
+    AXNodeData& group = nodes[kGroupNodesStartIndex + group_index];
+    AXNodeData& static_text = nodes[kStaticTextNodesStartIndex + text_index];
+    static_text.id = ++current_id;
+    static_text.role = ax::mojom::Role::kStaticText;
+    static_text.SetName(base::StringPrintf("id_%02X", static_text.id));
+    group.child_ids.push_back(static_text.id);
+  }
+
+  AXTreeUpdate initial_state;
+  initial_state.root_id = nodes[0].id;
+  initial_state.nodes = nodes;
+  initial_state.has_tree_data = true;
+  initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
+  initial_state.tree_data.title = "Perftest title";
+  AXSerializableTree src_tree(initial_state);
+
+  std::unique_ptr<AXTreeSource<const AXNode*, AXNodeData, AXTreeData>>
+      tree_source(src_tree.CreateTreeSource());
+  AXTreeSerializer<const AXNode*, AXNodeData, AXTreeData> serializer(
+      tree_source.get());
+  AXTreeUpdate update;
+  serializer.SerializeChanges(src_tree.root(), &update);
+  ASSERT_TRUE(tree_.Unserialize(update));
+  AXNodePosition::SetTree(&tree_);
+}
+
+void AXPositionPerfTest::TearDown() {
+  AXNodePosition::SetTree(nullptr);
+}
+
+}  // namespace
+
+TEST_F(AXPositionPerfTest, AsTreePositionFromTextPosition) {
+  TestPositionType text_position = AXNodePosition::CreateTextPosition(
+      tree_.data().tree_id, /*anchor_id=*/1, /*text_offset=*/103,
+      ax::mojom::TextAffinity::kDownstream);
+
+  // The time limit is unused. Use kLaps for the check interval so the time is
+  // only measured once.
+  base::LapTimer timer(kWarmupLaps, base::TimeDelta(), kLaps);
+  for (int i = 0; i < kLaps + kWarmupLaps; ++i) {
+    TestPositionType as_tree_position = text_position->AsTreePosition();
+    timer.NextLap();
+  }
+
+  auto reporter = SetUpReporter("AsTreePositionFromTextPosition");
+  reporter.AddResult(kMetricCallsPerSecondRunsPerS, timer.LapsPerSecond());
+}
+
+TEST_F(AXPositionPerfTest, AsLeafTextPositionFromTextPosition) {
+  TestPositionType text_position = AXNodePosition::CreateTextPosition(
+      tree_.data().tree_id, /*anchor_id=*/1, /*text_offset=*/103,
+      ax::mojom::TextAffinity::kDownstream);
+
+  // The time limit is unused. Use kLaps for the check interval so the time is
+  // only measured once.
+  base::LapTimer timer(kWarmupLaps, base::TimeDelta(), kLaps);
+  for (int i = 0; i < kLaps + kWarmupLaps; ++i) {
+    TestPositionType as_tree_position = text_position->AsLeafTextPosition();
+    timer.NextLap();
+  }
+
+  auto reporter = SetUpReporter("AsLeafTextPositionFromTextPosition");
+  reporter.AddResult(kMetricCallsPerSecondRunsPerS, timer.LapsPerSecond());
+}
+
+TEST_F(AXPositionPerfTest, AsLeafTextPositionFromTreePosition) {
+  TestPositionType tree_position = AXNodePosition::CreateTreePosition(
+      tree_.data().tree_id, /*anchor_id=*/1, /*child_index=*/4);
+
+  base::LapTimer timer(kWarmupLaps, base::TimeDelta(), kLaps);
+  for (int i = 0; i < kLaps + kWarmupLaps; ++i) {
+    TestPositionType as_tree_position = tree_position->AsLeafTextPosition();
+    timer.NextLap();
+  }
+
+  auto reporter = SetUpReporter("AsLeafTextPositionFromTreePosition");
+  reporter.AddResult(kMetricCallsPerSecondRunsPerS, timer.LapsPerSecond());
+}
+
+}  // namespace ui
diff --git a/ui/gl/gl_image_egl.cc b/ui/gl/gl_image_egl.cc
index 3386eae..1dad989 100644
--- a/ui/gl/gl_image_egl.cc
+++ b/ui/gl/gl_image_egl.cc
@@ -5,6 +5,7 @@
 #include "ui/gl/gl_image_egl.h"
 
 #include "ui/gl/egl_util.h"
+#include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_surface_egl.h"
 
 namespace gl {
@@ -50,7 +51,11 @@
   DCHECK_EQ(BIND, ShouldBindOrCopy());
 
   glEGLImageTargetTexture2DOES(target, egl_image_);
-  return glGetError() == static_cast<GLenum>(GL_NO_ERROR);
+  const GLenum error = glGetError();
+
+  DLOG_IF(ERROR, error != GL_NO_ERROR)
+      << "Error binding EGLImage: " << GLEnums::GetStringError(error);
+  return error == GL_NO_ERROR;
 }
 
 }  // namespace gl
diff --git a/ui/ozone/common/linux/gbm_wrapper.cc b/ui/ozone/common/linux/gbm_wrapper.cc
index 6d11f2c..00970e3 100644
--- a/ui/ozone/common/linux/gbm_wrapper.cc
+++ b/ui/ozone/common/linux/gbm_wrapper.cc
@@ -235,8 +235,19 @@
                                               uint32_t flags) override {
     struct gbm_bo* bo =
         gbm_bo_create(device_, size.width(), size.height(), format, flags);
-    if (!bo)
+    if (!bo) {
+#if DCHECK_IS_ON()
+      const char fourcc_as_string[5] = {format & 0xFF, format >> 8 & 0xFF,
+                                        format >> 16 & 0xFF,
+                                        format >> 24 & 0xFF, 0};
+
+      LOG(WARNING) << "Failed to create GBM BO, " << fourcc_as_string << ", "
+                   << size.ToString() << ", flags: 0x" << std::hex << flags
+                   << "; gbm_device_is_format_supported() = "
+                   << gbm_device_is_format_supported(device_, format, flags);
+#endif
       return nullptr;
+    }
 
     return CreateBufferForBO(bo, format, size, flags);
   }
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.cc b/ui/ozone/platform/drm/gpu/drm_thread.cc
index 0dfcf9f..ff1e7967 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread.cc
@@ -42,11 +42,9 @@
       return GBM_BO_USE_TEXTURING;
     case gfx::BufferUsage::SCANOUT:
       return GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING;
-      break;
     case gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE:
       return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE | GBM_BO_USE_SCANOUT |
              GBM_BO_USE_TEXTURING;
-      break;
     case gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE:
       return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE;
     case gfx::BufferUsage::SCANOUT_CPU_READ_WRITE:
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index dc8c8fa..1cade3a 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -981,7 +981,7 @@
         {DAYS, plural, =0 {Active today} =1 {Active 1 day ago} other {Active # days ago}}
       </message>
       <message name="IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN" desc="The label for the initiating origin shown in Click to Call dialogs.">
-        Number initiated from <ph name="ORIGIN">$1<ex>https://google.com</ex></ph>
+        Number from <ph name="ORIGIN">$1<ex>https://google.com</ex></ph>
       </message>
 
       <!-- Sharing content types -->
diff --git a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN.png.sha1 b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN.png.sha1
index f47def1..11454493 100644
--- a/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN.png.sha1
+++ b/ui/strings/ui_strings_grd/IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_INITIATING_ORIGIN.png.sha1
@@ -1 +1 @@
-13c78c8e166977023ad414654032cb20c3b394ed
\ No newline at end of file
+ce36faebd30dc6e3ddaa229087073ed60905e1f9
\ No newline at end of file
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index b044d183..ae86da1 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -460,12 +460,6 @@
   GetWidget()->SetBounds(bubble_bounds);
 }
 
-BubbleFrameView* BubbleDialogDelegateView::GetBubbleFrameView() const {
-  const NonClientView* view =
-      GetWidget() ? GetWidget()->non_client_view() : nullptr;
-  return view ? static_cast<BubbleFrameView*>(view->frame_view()) : nullptr;
-}
-
 void BubbleDialogDelegateView::UpdateColorsFromTheme() {
   if (!color_explicitly_set_)
     color_ = GetNativeTheme()->GetSystemColor(
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index 9a2c6b9..41d60cb 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -36,7 +36,6 @@
 
 namespace views {
 
-class BubbleFrameView;
 class Button;
 
 // BubbleDialogDelegateView is a special DialogDelegateView for bubbles.
@@ -187,8 +186,6 @@
   // Resize and potentially move the bubble to fit the content's preferred size.
   virtual void SizeToContents();
 
-  BubbleFrameView* GetBubbleFrameView() const;
-
   // Allows the up and down arrow keys to tab between items.
   void EnableUpDownKeyboardAccelerators();
 
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index 1e66f0d..0977091 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -245,21 +245,19 @@
 }
 
 void LabelButton::Layout() {
-  ink_drop_container_->SetBoundsRect(GetLocalBounds());
+  gfx::Rect child_area = GetLocalBounds();
 
-  // By default, GetChildAreaBounds() ignores the top and bottom border, but we
-  // want the image to respect it.
-  gfx::Rect child_area(GetChildAreaBounds());
+  ink_drop_container_->SetBoundsRect(child_area);
   // The space that the label can use. Its actual bounds may be smaller if the
   // label is short.
-  gfx::Rect label_area(child_area);
+  gfx::Rect label_area = child_area;
 
   gfx::Insets insets = GetInsets();
   child_area.Inset(insets);
   // Labels can paint over the vertical component of the border insets.
   label_area.Inset(insets.left(), 0, insets.right(), 0);
 
-  gfx::Size image_size(image_->GetPreferredSize());
+  gfx::Size image_size = image_->GetPreferredSize();
   image_size.SetToMin(child_area.size());
 
   const auto horizontal_alignment = GetHorizontalAlignment();
@@ -275,7 +273,7 @@
       std::min(label_area.width(), label_->GetPreferredSize().width()),
       label_area.height());
 
-  gfx::Point image_origin(child_area.origin());
+  gfx::Point image_origin = child_area.origin();
   if (label_->GetMultiLine()) {
     // Right now this code currently only works for CheckBox and RadioButton
     // descendants that have multi-line enabled for their label.
@@ -391,10 +389,6 @@
   image()->DestroyLayer();
 }
 
-gfx::Rect LabelButton::GetChildAreaBounds() {
-  return GetLocalBounds();
-}
-
 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
   params->button.checked = false;
   params->button.indeterminate = false;
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index 1358e05..3a3a19b 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -142,10 +142,6 @@
   // set with SetBorder.
   void UpdateThemedBorder();
 
-  // Returns the available area for the label and image. Subclasses can change
-  // these bounds if they need room to do manual painting.
-  gfx::Rect GetChildAreaBounds();
-
   // Fills |params| with information about the button.
   virtual void GetExtraParams(ui::NativeTheme::ExtraParams* params) const;
 
diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc
index 8104daf..32ab932 100644
--- a/ui/views/focus/focus_manager.cc
+++ b/ui/views/focus/focus_manager.cc
@@ -377,6 +377,14 @@
     delegate_->OnDidChangeFocus(old_focused_view, focused_view_);
 }
 
+void FocusManager::SetFocusedView(View* view) {
+  FocusChangeReason reason = FocusChangeReason::kDirectFocusChange;
+  if (in_restoring_focused_view_)
+    reason = FocusChangeReason::kFocusRestore;
+
+  SetFocusedViewWithReason(view, reason);
+}
+
 void FocusManager::ClearFocus() {
   // SetFocusedView(nullptr) is going to clear out the stored view to. We need
   // to persist it in this case.
@@ -438,12 +446,8 @@
       } else {
         // This usually just sets the focus if this view is focusable, but
         // let the view override RequestFocus if necessary.
+        base::AutoReset<bool> in_restore_bit(&in_restoring_focused_view_, true);
         view->RequestFocus();
-
-        // If it succeeded, the reason would be incorrect; set it to
-        // focus restore.
-        if (focused_view_ == view)
-          focus_change_reason_ = FocusChangeReason::kFocusRestore;
       }
     }
     // The |keyboard_accessible_| mode may have changed while the widget was
diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h
index f18186d..38f4b7c 100644
--- a/ui/views/focus/focus_manager.h
+++ b/ui/views/focus/focus_manager.h
@@ -171,9 +171,7 @@
   // a reason). If the focus change should only happen if the view is
   // currenty focusable, enabled, and visible, call view->RequestFocus().
   void SetFocusedViewWithReason(View* view, FocusChangeReason reason);
-  void SetFocusedView(View* view) {
-    SetFocusedViewWithReason(view, FocusChangeReason::kDirectFocusChange);
-  }
+  void SetFocusedView(View* view);
 
   // Get the reason why the focus most recently changed.
   FocusChangeReason focus_change_reason() const { return focus_change_reason_; }
@@ -378,6 +376,9 @@
   // access is enabled.
   bool keyboard_accessible_ = false;
 
+  // Whether FocusManager is currently trying to restore a focused view.
+  bool in_restoring_focused_view_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(FocusManager);
 };
 
diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc
index 192a529..c81905a 100644
--- a/ui/views/focus/focus_manager_unittest.cc
+++ b/ui/views/focus/focus_manager_unittest.cc
@@ -39,11 +39,9 @@
 enum FocusTestEventType { ON_FOCUS = 0, ON_BLUR };
 
 struct FocusTestEvent {
-  FocusTestEvent(FocusTestEventType type, int view_id)
-      : type(type), view_id(view_id) {}
-
   FocusTestEventType type;
   int view_id;
+  FocusManager::FocusChangeReason focus_change_reason;
 };
 
 class SimpleTestView : public View {
@@ -55,11 +53,19 @@
   }
 
   void OnFocus() override {
-    event_list_->push_back(FocusTestEvent(ON_FOCUS, GetID()));
+    event_list_->push_back({
+        ON_FOCUS,
+        GetID(),
+        GetFocusManager()->focus_change_reason(),
+    });
   }
 
   void OnBlur() override {
-    event_list_->push_back(FocusTestEvent(ON_BLUR, GetID()));
+    event_list_->push_back({
+        ON_BLUR,
+        GetID(),
+        GetFocusManager()->focus_change_reason(),
+    });
   }
 
  private:
@@ -84,6 +90,8 @@
   ASSERT_EQ(1, static_cast<int>(event_list.size()));
   EXPECT_EQ(ON_FOCUS, event_list[0].type);
   EXPECT_EQ(kView1ID, event_list[0].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[0].focus_change_reason);
 
   event_list.clear();
   view2->RequestFocus();
@@ -92,12 +100,18 @@
   EXPECT_EQ(kView1ID, event_list[0].view_id);
   EXPECT_EQ(ON_FOCUS, event_list[1].type);
   EXPECT_EQ(kView2ID, event_list[1].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[0].focus_change_reason);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[1].focus_change_reason);
 
   event_list.clear();
   GetFocusManager()->ClearFocus();
   ASSERT_EQ(1, static_cast<int>(event_list.size()));
   EXPECT_EQ(ON_BLUR, event_list[0].type);
   EXPECT_EQ(kView2ID, event_list[0].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[0].focus_change_reason);
 }
 
 TEST_F(FocusManagerTest, FocusChangeListener) {
@@ -706,7 +720,10 @@
 }
 
 TEST_F(FocusManagerTest, StoreFocusedView) {
-  View* view = new View;
+  std::vector<FocusTestEvent> event_list;
+  const int kView1ID = 1;
+  SimpleTestView* view = new SimpleTestView(&event_list, kView1ID);
+
   // Add view to the view hierarchy and make it focusable.
   GetWidget()->GetRootView()->AddChildView(view);
   view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
@@ -716,13 +733,39 @@
   EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
   EXPECT_TRUE(GetFocusManager()->RestoreFocusedView());
   EXPECT_EQ(view, GetFocusManager()->GetStoredFocusView());
+  ASSERT_EQ(3, static_cast<int>(event_list.size()));
+  EXPECT_EQ(ON_FOCUS, event_list[0].type);
+  EXPECT_EQ(kView1ID, event_list[0].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[0].focus_change_reason);
+  EXPECT_EQ(ON_BLUR, event_list[1].type);
+  EXPECT_EQ(kView1ID, event_list[1].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[1].focus_change_reason);
+  EXPECT_EQ(ON_FOCUS, event_list[2].type);
+  EXPECT_EQ(kView1ID, event_list[2].view_id);
+  EXPECT_EQ(FocusChangeReason::kFocusRestore,
+            event_list[2].focus_change_reason);
 
   // Repeat with |true|.
+  event_list.clear();
   GetFocusManager()->SetFocusedView(view);
   GetFocusManager()->StoreFocusedView(true);
   EXPECT_EQ(nullptr, GetFocusManager()->GetFocusedView());
   EXPECT_TRUE(GetFocusManager()->RestoreFocusedView());
   EXPECT_EQ(view, GetFocusManager()->GetStoredFocusView());
+  ASSERT_EQ(2, static_cast<int>(event_list.size()));
+  EXPECT_EQ(ON_BLUR, event_list[0].type);
+  EXPECT_EQ(kView1ID, event_list[0].view_id);
+  EXPECT_EQ(FocusChangeReason::kDirectFocusChange,
+            event_list[0].focus_change_reason);
+  EXPECT_EQ(ON_FOCUS, event_list[1].type);
+  EXPECT_EQ(kView1ID, event_list[1].view_id);
+  EXPECT_EQ(FocusChangeReason::kFocusRestore,
+            event_list[1].focus_change_reason);
+
+  // Necessary for clean teardown.
+  GetFocusManager()->ClearFocus();
 }
 
 #if defined(OS_MACOSX)
diff --git a/ui/views/test/focus_manager_test.h b/ui/views/test/focus_manager_test.h
index 0efde67..f01ed7d 100644
--- a/ui/views/test/focus_manager_test.h
+++ b/ui/views/test/focus_manager_test.h
@@ -17,6 +17,8 @@
 
 class FocusManagerTest : public ViewsTestBase, public WidgetDelegate {
  public:
+  using FocusChangeReason = FocusManager::FocusChangeReason;
+
   FocusManagerTest();
   ~FocusManagerTest() override;
 
diff --git a/ui/views/window/dialog_delegate.cc b/ui/views/window/dialog_delegate.cc
index 6268da293..6c706d4d 100644
--- a/ui/views/window/dialog_delegate.cc
+++ b/ui/views/window/dialog_delegate.cc
@@ -197,6 +197,7 @@
 NonClientFrameView* DialogDelegate::CreateNonClientFrameView(Widget* widget) {
   if (use_custom_frame())
     return CreateDialogFrameView(widget);
+
   return WidgetDelegate::CreateNonClientFrameView(widget);
 }
 
@@ -237,6 +238,15 @@
   return GetWidget()->client_view()->AsDialogClientView();
 }
 
+BubbleFrameView* DialogDelegate::GetBubbleFrameView() const {
+  if (!use_custom_frame())
+    return nullptr;
+
+  const NonClientView* view =
+      GetWidget() ? GetWidget()->non_client_view() : nullptr;
+  return view ? static_cast<BubbleFrameView*>(view->frame_view()) : nullptr;
+}
+
 views::LabelButton* DialogDelegate::GetOkButton() {
   DCHECK(GetWidget()) << "Don't call this before OnDialogInitialized";
   auto* client = GetDialogClientView();
diff --git a/ui/views/window/dialog_delegate.h b/ui/views/window/dialog_delegate.h
index d809864..783d536 100644
--- a/ui/views/window/dialog_delegate.h
+++ b/ui/views/window/dialog_delegate.h
@@ -19,6 +19,7 @@
 
 namespace views {
 
+class BubbleFrameView;
 class DialogClientView;
 class DialogObserver;
 class LabelButton;
@@ -155,6 +156,10 @@
   const DialogClientView* GetDialogClientView() const;
   DialogClientView* GetDialogClientView();
 
+  // Returns the BubbleFrameView of this dialog delegate. A bubble frame view
+  // will only be created when use_custom_frame() is true.
+  BubbleFrameView* GetBubbleFrameView() const;
+
   // Helpers for accessing parts of the DialogClientView without needing to know
   // about DialogClientView. Do not call these before OnDialogInitialized.
   views::LabelButton* GetOkButton();
diff --git a/ui/webui/resources/cr_polymer_resources.grdp b/ui/webui/resources/cr_polymer_resources.grdp
index 386af40..269ee7d 100644
--- a/ui/webui/resources/cr_polymer_resources.grdp
+++ b/ui/webui/resources/cr_polymer_resources.grdp
@@ -26,8 +26,6 @@
   <structure name="IDR_WEBUI_HTML_SEARCH_HIGHLIGHT_UTILS"
              file="html/search_highlight_utils.html" type="chrome_html"
              compress="gzip" />
-  <structure name="IDR_WEBUI_HTML_SUBPAGE_LOADER"
-             file="html/subpage_loader.html" type="chrome_html" />
 
   <!-- CSS resources -->
   <structure name="IDR_WEBUI_CSS_MD_COLORS"
@@ -43,8 +41,6 @@
   <structure name="IDR_WEBUI_JS_CR_UI_FOCUS_WITHOUT_INK"
              file="js/cr/ui/focus_without_ink.js" type="chrome_html"
              compress="gzip" />
-  <structure name="IDR_WEBUI_JS_CRISPER_LOADER"
-             file="js/crisper_loader.js" type="chrome_html" compress="gzip" />
   <structure name="IDR_WEBUI_JS_FIND_SHORTCUT_BEHAVIOR"
              file="js/find_shortcut_behavior.js" type="chrome_html"
              compress="gzip" />
@@ -60,6 +56,4 @@
   <structure name="IDR_WEBUI_JS_SEARCH_HIGHLIGHT_UTILS"
              file="js/search_highlight_utils.js" type="chrome_html"
              compress="gzip" />
-  <structure name="IDR_WEBUI_JS_SUBPAGE_LOADER"
-             file="js/subpage_loader.js" type="chrome_html" />
 </grit-part>
diff --git a/ui/webui/resources/html/subpage_loader.html b/ui/webui/resources/html/subpage_loader.html
deleted file mode 100644
index a8bd4ba..0000000
--- a/ui/webui/resources/html/subpage_loader.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<meta charset="utf-8">
-<script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js">
-</script>
-<script src="subpage_loader.js"></script>
diff --git a/ui/webui/resources/js/subpage_loader.js b/ui/webui/resources/js/subpage_loader.js
deleted file mode 100644
index 8dd3dd9..0000000
--- a/ui/webui/resources/js/subpage_loader.js
+++ /dev/null
@@ -1,9 +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.
-
-const subpageImport = document.createElement('link');
-subpageImport.rel = 'import';
-const params = new URLSearchParams(window.location.search.substring(1));
-subpageImport.href = params.get('file');
-document.head.appendChild(subpageImport);
diff --git a/ui/webui/resources/polymer_resources.grdp b/ui/webui/resources/polymer_resources.grdp
index 482592c..1411832 100644
--- a/ui/webui/resources/polymer_resources.grdp
+++ b/ui/webui/resources/polymer_resources.grdp
@@ -28,11 +28,11 @@
              file="../../../third_party/polymer/v1_0/components-chromium/font-roboto/roboto.html"
              type="chrome_html"
              compress="gzip" />
-  <structure name="IDR_POLYMER_1_0_HTML_IMPORTS_HTML_IMPORTS_MIN_JS"
-             file="../../../third_party/polymer/v1_0/components-chromium/html-imports/html-imports.min.js"
-             type="chrome_html"
-             compress="gzip" />
   <if expr="chromeos">
+    <structure name="IDR_POLYMER_1_0_HTML_IMPORTS_HTML_IMPORTS_MIN_JS"
+               file="../../../third_party/polymer/v1_0/components-chromium/html-imports/html-imports.min.js"
+               type="chrome_html"
+               compress="gzip" />
     <structure name="IDR_POLYMER_1_0_HTML_IMPORTS_V0_HTML_IMPORTS_MIN_JS"
                file="../../../third_party/polymer/v1_0/components-chromium/html-imports-v0/html-imports.min.js"
                type="chrome_html"
diff --git a/weblayer/browser/browser_controller_impl.cc b/weblayer/browser/browser_controller_impl.cc
index 4c442c4..48a8652 100644
--- a/weblayer/browser/browser_controller_impl.cc
+++ b/weblayer/browser/browser_controller_impl.cc
@@ -259,7 +259,7 @@
   // If |processing_enter_fullscreen_| is true, it means the callback is being
   // called while processing EnterFullscreenModeForTab(). WebContents doesn't
   // deal well with this. FATAL as Android generally doesn't run with DCHECKs.
-  LOG_IF(FATAL, !processing_enter_fullscreen_)
+  LOG_IF(FATAL, processing_enter_fullscreen_)
       << "exiting fullscreen while entering fullscreen is not supported";
   web_contents_->ExitFullscreen(/* will_cause_resize */ false);
 }
diff --git a/weblayer/public/java/org/chromium/weblayer/BrowserController.java b/weblayer/public/java/org/chromium/weblayer/BrowserController.java
index 33fe878..e229ead 100644
--- a/weblayer/public/java/org/chromium/weblayer/BrowserController.java
+++ b/weblayer/public/java/org/chromium/weblayer/BrowserController.java
@@ -62,6 +62,7 @@
                 mImpl.setFullscreenDelegateClient(mFullscreenDelegateClient);
             } else {
                 mImpl.setFullscreenDelegateClient(null);
+                mFullscreenDelegateClient = null;
             }
         } catch (RemoteException e) {
             throw new APICallException(e);
diff --git a/weblayer/shell/android/BUILD.gn b/weblayer/shell/android/BUILD.gn
index 500f704..f774911 100644
--- a/weblayer/shell/android/BUILD.gn
+++ b/weblayer/shell/android/BUILD.gn
@@ -257,8 +257,9 @@
   ]
   java_files = [
     "javatests/src/org/chromium/weblayer/test/BrowserObserverTest.java",
-    "javatests/src/org/chromium/weblayer/test/EventUtils.java",
     "javatests/src/org/chromium/weblayer/test/DownloadDelegateTest.java",
+    "javatests/src/org/chromium/weblayer/test/EventUtils.java",
+    "javatests/src/org/chromium/weblayer/test/FullscreenDelegateTest.java",
     "javatests/src/org/chromium/weblayer/test/NavigationTest.java",
     "javatests/src/org/chromium/weblayer/test/SmokeTest.java",
     "javatests/src/org/chromium/weblayer/test/ExecuteScriptTest.java",
diff --git a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/FullscreenDelegateTest.java b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/FullscreenDelegateTest.java
new file mode 100644
index 0000000..0850bf76
--- /dev/null
+++ b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/FullscreenDelegateTest.java
@@ -0,0 +1,125 @@
+// 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.weblayer.test;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.Criteria;
+import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.weblayer.FullscreenDelegate;
+import org.chromium.weblayer.shell.WebLayerShellActivity;
+
+/**
+ * Tests that FullscreenDelegate methods are invoked as expected.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class FullscreenDelegateTest {
+    @Rule
+    public WebLayerShellActivityTestRule mActivityTestRule = new WebLayerShellActivityTestRule();
+
+    private EmbeddedTestServer mTestServer;
+    private WebLayerShellActivity mActivity;
+    private Delegate mDelegate;
+
+    private static class Delegate extends FullscreenDelegate {
+        public int mEnterFullscreenCount;
+        public int mExitFullscreenCount;
+        public Runnable mExitFullscreenRunnable;
+
+        @Override
+        public void enterFullscreen(Runnable exitFullscreenRunner) {
+            mEnterFullscreenCount++;
+            mExitFullscreenRunnable = exitFullscreenRunner;
+        }
+
+        @Override
+        public void exitFullscreen() {
+            mExitFullscreenCount++;
+        }
+
+        public void waitForFullscreen() {
+            CriteriaHelper.pollInstrumentationThread(new Criteria() {
+                @Override
+                public boolean isSatisfied() {
+                    return mEnterFullscreenCount == 1;
+                }
+            }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
+
+        public void waitForExitFullscreen() {
+            CriteriaHelper.pollInstrumentationThread(new Criteria() {
+                @Override
+                public boolean isSatisfied() {
+                    return mExitFullscreenCount == 1;
+                }
+            }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mTestServer = new EmbeddedTestServer();
+        mTestServer.initializeNative(InstrumentationRegistry.getInstrumentation().getContext(),
+                EmbeddedTestServer.ServerHTTPSSetting.USE_HTTP);
+        mTestServer.addDefaultHandlers("weblayer/test/data");
+        Assert.assertTrue(mTestServer.start(0));
+
+        String url = mTestServer.getURL("/fullscreen.html");
+        mActivity = mActivityTestRule.launchShellWithUrl(url);
+        Assert.assertNotNull(mActivity);
+        mDelegate = new Delegate();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowserController().setFullscreenDelegate(mDelegate); });
+
+        // First touch enters fullscreen.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForFullscreen();
+        Assert.assertEquals(1, mDelegate.mEnterFullscreenCount);
+    }
+
+    @After
+    public void tearDown() {
+        mTestServer.stopAndDestroyServer();
+    }
+
+    @Test
+    @SmallTest
+    public void testFullscreen() {
+        // Second touch exits.
+        EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testExitFullscreenWhenDelegateCleared() {
+        // Clearing the FullscreenDelegate should exit fullscreen.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getBrowserController().setFullscreenDelegate(null); });
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testExitFullscreenUsingRunnable() {
+        // Running the runnable supplied to the delegate should exit fullscreen.
+        TestThreadUtils.runOnUiThreadBlocking(mDelegate.mExitFullscreenRunnable);
+        mDelegate.waitForExitFullscreen();
+        Assert.assertEquals(1, mDelegate.mExitFullscreenCount);
+    }
+}
diff --git a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java
index 1484a9a..7238e86 100644
--- a/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java
+++ b/weblayer/shell/android/javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java
@@ -107,7 +107,8 @@
     }
 
     /**
-     * Starts the WebLayer activity and loads the given URL.
+     * Starts the WebLayer activity and completely loads the given URL (this calls
+     * navigateAndWait()).
      */
     public WebLayerShellActivity launchShellWithUrl(String url) {
         Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/weblayer/test/data/fullscreen.html b/weblayer/test/data/fullscreen.html
new file mode 100644
index 0000000..1674236
--- /dev/null
+++ b/weblayer/test/data/fullscreen.html
@@ -0,0 +1,15 @@
+<html>
+  <body>
+    <p id='x'></p>
+  </body>
+  <script>
+    function toggleFullscreen() {
+      if (!document.fullscreenElement) {
+        document.getElementById('x').requestFullscreen();
+      } else {
+        document.exitFullscreen();
+      }
+    }
+    document.addEventListener('touchend', function(e) { toggleFullscreen(); }, false);
+  </script>
+</html>
diff --git a/weblayer/test/test_launcher_delegate_impl.cc b/weblayer/test/test_launcher_delegate_impl.cc
index 6f267fd4..a1a5218 100644
--- a/weblayer/test/test_launcher_delegate_impl.cc
+++ b/weblayer/test/test_launcher_delegate_impl.cc
@@ -17,7 +17,6 @@
   // Browser tests are expected not to tear-down various globals and may
   // complete with the thread priority being above NORMAL.
   test_suite.DisableCheckForLeakedGlobals();
-  test_suite.DisableCheckForThreadPriorityAtTestEnd();
   return test_suite.Run();
 }