diff --git a/AUTHORS b/AUTHORS
index ee29afc..7523e48 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -288,6 +288,7 @@
 Finbar Crago <finbar.crago@gmail.com>
 François Beaufort <beaufort.francois@gmail.com>
 Francois Kritzinger <francoisk777@gmail.com>
+Francois Marier <francois@brave.com>
 Francois Rauch <leopardb@gmail.com>
 Frankie Dintino <fdintino@theatlantic.com>
 Franklin Ta <fta2012@gmail.com>
diff --git a/DEPS b/DEPS
index 0c19c770..d4dc159 100644
--- a/DEPS
+++ b/DEPS
@@ -185,7 +185,7 @@
   # 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': '85f423a4d84295de3950317c7dcc490cd754632d',
+  'v8_revision': 'df9027436b4ac15ff08529327a0f950888b796f7',
   # 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.
@@ -201,7 +201,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '52e9146a65fddd9687894c03b2051f9829cb57e7',
+  'pdfium_revision': 'c58babd220a9dc4d13757be8799bffbd59cac73b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -244,7 +244,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'c8083d168105c495ebde433cf786d5ded217ade8',
+  'catapult_revision': '7431e17d79d017e6724a161cca7bc49b232ec9d0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -252,7 +252,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'd85dc3874aaafc3b4abd61cce2d497418c2ed413',
+  'devtools_frontend_revision': 'a0840e9a2f6a2c7c2f5e423350ca7657df07b14b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -308,11 +308,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': '4bb7b1ddf3b12aa3ad80fa63ccf4afe87e0e6987',
+  'shaderc_revision': '99ca03e1ac3a78c7d0e1ca7b3a1f6d973e0c1fc7',
   # 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': 'e5cb8f2eff7e2b0f672c0d20fb97ca17fe28e94a',
+  'dawn_revision': 'cd170a5c7216cfe26572bf380bdfd2e843c1a207',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -904,7 +904,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '59a3b2fd5d0ef813c51821a9012a9d91da86843b',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ce09ca54f86e989ce1e57ceeb896a9d13ff10f08',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1051,7 +1051,7 @@
   },
 
   'src/third_party/hunspell_dictionaries':
-    Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + 'b22df2b32d70abd5ccd0e184540548af46f5b15b',
+    Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + 'e96f230bf759bfe46125ca92e0aa3fe72d9f0227',
 
   'src/third_party/icu':
     Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'dbd3825b31041d782c5b504c59dcfb5ac7dda08c',
@@ -1297,7 +1297,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '28f5c8101b20c52e303de0193a12950b77806c1b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd3e59119bcf5d8b7bbe7dfa7bed1a98b0c0ccf41',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1495,7 +1495,7 @@
     Var('chromium_git') + '/external/github.com/SeleniumHQ/selenium/py.git' + '@' + 'd0045ec570c1a77612db35d1e92f05e1d27b4d53',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '88d715c9115a5ce65c0bf660209dfeee9131ccd0',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '4f3976e9b368ccfe7b9dd02014351936296dc72c',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + 'b42aeaa3fb21d78e59c47d2a9916acb380494496',
@@ -1568,7 +1568,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6e62267bbb4210bb52e1a7c0699e7353bc0f6ae0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@69949ed05dcf3ca151fd1bc12817ef2f92bec7ec',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index c42b941..713bb64 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1142,6 +1142,9 @@
     'ios_web': {
       'filepath': 'ios/web(_view)?/',
     },
+    'isolated_prefetch': {
+      'filepath': 'chrome/browser/prerender/isolated',
+    },
     'language': {
       'filepath': 'chrome/browser/language|'\
                   'components/language|'\
@@ -1614,17 +1617,12 @@
                   '|chrome/browser/ui/webui/settings/'\
                   '|chrome/test/data/webui/settings/',
     },
-    'settings_forked_os_settings': {
-      'filepath': 'chrome/browser/resources/settings/basic_page/'\
-                  '|chrome/browser/resources/settings/chromeos/'\
-                  '|chrome/browser/resources/settings/settings_menu/'\
-                  '|chrome/browser/resources/settings/settings_page/'\
-                  '|chrome/browser/resources/settings/settings_ui/'\
-                  '|chrome/browser/resources/settings/os_settings_resouces.grd'\
-                  '|chrome/browser/resources/settings/os_settings_resouces_vulcanized.grd',
-    },
     'settings_os_settings': {
-      'filepath': 'chrome/browser/resources/settings/chromeos/',
+      'filepath': 'chrome/browser/resources/settings/chromeos/'\
+                  '|chrome/browser/resources/settings/os_settings_resouces.grd'\
+                  '|chrome/browser/resources/settings/os_settings_resouces_vulcanized.grd'\
+                  '|chrome/browser/ui/webui/settings/chromeos/'\
+                  '|chrome/test/data/webui/settings/chromeos/',
     },
     'sharing': {
       'filepath': 'chrome/browser/sharing/|'\
@@ -2469,6 +2467,8 @@
                      'marq+watch@chromium.org'],
     'ios_web': ['ios-reviews+web@chromium.org',
                 'eugenebut@chromium.org'],
+    'isolated_prefetch': ['robertogden+watch@chromium.org',
+                          'marcinjb+p4watch@google.com'],
     'language': ['language-reviews@chromium.org'],
     'libaom': ['fgalligan@chromium.org',
                'johannkoenig@chromium.org',
@@ -2636,15 +2636,13 @@
                        'nhiroki@chromium.org',
                        'serviceworker-reviews@chromium.org',
                        'shimazu+serviceworker@chromium.org'],
-    'settings': ['michaelpg+watch-md-settings@chromium.org',
-                 'stevenjb+watch-md-settings@chromium.org',
-                 'hsuregan+watch@chromium.org',
-                 'jordynass+watch@chromium.org',
-                 'maybelle+watch@chromium.org'],
-    'settings_forked_os_settings': [
-                 'maybelle@chromium.org'],
-    'settings_os_settings': [
-                 'jhawkins+watch@chromium.org'],
+    'settings': ['dpapad+watch-settings@chromium.org',
+                 'michaelpg+watch-settings@chromium.org'],
+    'settings_os_settings': ['hsuregan+watch-os-settings@chromium.org',
+                             'jamescook+watch-os-settings@chromium.org',
+                             'jhawkins+watch-os-settings@chromium.org',
+                             'khorimoto+watch-os-settings@chromium.org',
+                             'zentaro+watch-os-settings@chromium.org'],
     'sharing': ['peter@chromium.org',
                 'unido-reviews@chromium.org'],
     'site_engagement': ['dominickn+watch-engagement@chromium.org'],
@@ -2753,10 +2751,10 @@
     'vulkan': ['cblume+vulkan@chromium.org'],
     'wake_lock': ['mattreynolds+watch@chromium.org',
                   'raphael.kubo.da.costa@intel.com'],
-    'wallpapers': ['hsuregan+watch@chromium.org',
-                   'jhawkins+watch@chromium.org',
-                   'jordynass+watch@chromium.org',
-                   'maybelle+watch@chromium.org'],
+    'wallpapers': ['hsuregan+watch-wallpapers@chromium.org',
+                   'jhawkins+watch-wallpapers@chromium.org',
+                   'khorimoto+watch-wallpapers@chromium.org',
+                   'zentaro+watch-wallpapers@chromium.org'],
     'web_applications': ['alancutter+chrome-cls@chromium.org',
                          'dominickn+watch-web_applications@chromium.org',
                          'ericwilligers+watch-bmo@chromium.org',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index d760d4b0..2849ac45 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2239,6 +2239,10 @@
     "assistant/test/assistant_ash_test_base.h",
     "assistant/test/test_assistant_service.cc",
     "assistant/test/test_assistant_service.h",
+    "assistant/test/test_assistant_web_view.cc",
+    "assistant/test/test_assistant_web_view.h",
+    "assistant/test/test_assistant_web_view_factory.cc",
+    "assistant/test/test_assistant_web_view_factory.h",
     "display/display_configuration_controller_test_api.cc",
     "display/display_configuration_controller_test_api.h",
     "display/mirror_window_test_api.cc",
diff --git a/ash/app_list/views/assistant/assistant_dialog_plate.cc b/ash/app_list/views/assistant/assistant_dialog_plate.cc
index 7788dc5..5f1a33f 100644
--- a/ash/app_list/views/assistant/assistant_dialog_plate.cc
+++ b/ash/app_list/views/assistant/assistant_dialog_plate.cc
@@ -288,12 +288,11 @@
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
   // Molecule icon.
-  molecule_icon_ = LogoView::Create();
+  molecule_icon_ = AddChildView(LogoView::Create());
   molecule_icon_->SetID(AssistantViewID::kModuleIcon);
   molecule_icon_->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
   molecule_icon_->SetState(LogoView::State::kMoleculeWavy,
                            /*animate=*/false);
-  AddChildView(molecule_icon_);
 
   // Input modality layout container.
   input_modality_layout_container_ = new views::View();
diff --git a/ash/assistant/test/assistant_ash_test_base.cc b/ash/assistant/test/assistant_ash_test_base.cc
index d2eb2db..1368d48 100644
--- a/ash/assistant/test/assistant_ash_test_base.cc
+++ b/ash/assistant/test/assistant_ash_test_base.cc
@@ -11,6 +11,7 @@
 #include "ash/app_list/views/assistant/assistant_main_view.h"
 #include "ash/app_list/views/assistant/assistant_page_view.h"
 #include "ash/assistant/assistant_controller.h"
+#include "ash/assistant/test/test_assistant_web_view_factory.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/keyboard/ui/test/keyboard_test_util.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
@@ -62,7 +63,8 @@
 }  // namespace
 
 AssistantAshTestBase::AssistantAshTestBase()
-    : test_api_(AssistantTestApi::Create()) {}
+    : test_api_(AssistantTestApi::Create()),
+      test_web_view_factory_(std::make_unique<TestAssistantWebViewFactory>()) {}
 
 AssistantAshTestBase::~AssistantAshTestBase() = default;
 
diff --git a/ash/assistant/test/assistant_ash_test_base.h b/ash/assistant/test/assistant_ash_test_base.h
index 73c77b0..70d03b6 100644
--- a/ash/assistant/test/assistant_ash_test_base.h
+++ b/ash/assistant/test/assistant_ash_test_base.h
@@ -23,8 +23,9 @@
 
 class AssistantController;
 class AssistantInteractionController;
-class TestAssistantService;
 class AssistantTestApi;
+class TestAssistantService;
+class TestAssistantWebViewFactory;
 
 // Helper class to make testing the Assistant Ash UI easier.
 class AssistantAshTestBase : public AshTestBase {
@@ -145,6 +146,7 @@
   TestAssistantService* assistant_service();
 
   std::unique_ptr<AssistantTestApi> test_api_;
+  std::unique_ptr<TestAssistantWebViewFactory> test_web_view_factory_;
   base::test::ScopedFeatureList scoped_feature_list_;
   AssistantController* controller_ = nullptr;
 
diff --git a/ash/assistant/test/test_assistant_web_view.cc b/ash/assistant/test/test_assistant_web_view.cc
new file mode 100644
index 0000000..351edfc
--- /dev/null
+++ b/ash/assistant/test/test_assistant_web_view.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/test/test_assistant_web_view.h"
+
+#include "base/threading/sequenced_task_runner_handle.h"
+
+namespace ash {
+
+TestAssistantWebView::TestAssistantWebView() = default;
+
+TestAssistantWebView::~TestAssistantWebView() = default;
+
+void TestAssistantWebView::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void TestAssistantWebView::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+gfx::NativeView TestAssistantWebView::GetNativeView() {
+  // Not yet implemented for unittests.
+  return nullptr;
+}
+
+bool TestAssistantWebView::GoBack() {
+  // Not yet implemented for unittests.
+  return false;
+}
+
+void TestAssistantWebView::Navigate(const GURL& url) {
+  // Simulate navigation by notifying |observers_| of the expected event that
+  // would normally signal navigation completion. We do this asynchronously to
+  // more accurately simulate real-world conditions.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](const base::WeakPtr<TestAssistantWebView>& self) {
+                       if (self) {
+                         for (auto& observer : self->observers_)
+                           observer.DidStopLoading();
+                       }
+                     },
+                     weak_factory_.GetWeakPtr()));
+}
+
+}  // namespace ash
diff --git a/ash/assistant/test/test_assistant_web_view.h b/ash/assistant/test/test_assistant_web_view.h
new file mode 100644
index 0000000..14c1a8e
--- /dev/null
+++ b/ash/assistant/test/test_assistant_web_view.h
@@ -0,0 +1,36 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_H_
+#define ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_H_
+
+#include "ash/public/cpp/assistant/assistant_web_view_2.h"
+#include "base/observer_list.h"
+
+namespace ash {
+
+// An implementation of AssistantWebView2 for use in unittests.
+class TestAssistantWebView : public AssistantWebView2 {
+ public:
+  TestAssistantWebView();
+  TestAssistantWebView(const TestAssistantWebView& copy) = delete;
+  TestAssistantWebView& operator=(const TestAssistantWebView& assign) = delete;
+  ~TestAssistantWebView() override;
+
+  // AssistantWebView2:
+  void AddObserver(Observer* observer) override;
+  void RemoveObserver(Observer* observer) override;
+  gfx::NativeView GetNativeView() override;
+  bool GoBack() override;
+  void Navigate(const GURL& url) override;
+
+ private:
+  base::ObserverList<Observer> observers_;
+
+  base::WeakPtrFactory<TestAssistantWebView> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_H_
diff --git a/ash/assistant/test/test_assistant_web_view_factory.cc b/ash/assistant/test/test_assistant_web_view_factory.cc
new file mode 100644
index 0000000..b27857d
--- /dev/null
+++ b/ash/assistant/test/test_assistant_web_view_factory.cc
@@ -0,0 +1,20 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/test/test_assistant_web_view_factory.h"
+
+#include "ash/assistant/test/test_assistant_web_view.h"
+
+namespace ash {
+
+TestAssistantWebViewFactory::TestAssistantWebViewFactory() = default;
+
+TestAssistantWebViewFactory::~TestAssistantWebViewFactory() = default;
+
+std::unique_ptr<AssistantWebView2> TestAssistantWebViewFactory::Create(
+    const AssistantWebView2::InitParams& params) {
+  return std::make_unique<TestAssistantWebView>();
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/assistant/test/test_assistant_web_view_factory.h b/ash/assistant/test/test_assistant_web_view_factory.h
new file mode 100644
index 0000000..546ea76
--- /dev/null
+++ b/ash/assistant/test/test_assistant_web_view_factory.h
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_FACTORY_H_
+#define ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_FACTORY_H_
+
+#include <memory>
+
+#include "ash/public/cpp/assistant/assistant_web_view_factory.h"
+
+namespace ash {
+
+// An implementation of AssistantWebViewFactory for use in unittests.
+class TestAssistantWebViewFactory : public AssistantWebViewFactory {
+ public:
+  TestAssistantWebViewFactory();
+  TestAssistantWebViewFactory(const AssistantWebViewFactory& copy) = delete;
+  TestAssistantWebViewFactory& operator=(
+      const AssistantWebViewFactory& assign) = delete;
+  ~TestAssistantWebViewFactory() override;
+
+  // AssistantWebViewFactory:
+  std::unique_ptr<AssistantWebView2> Create(
+      const AssistantWebView2::InitParams& params) override;
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_TEST_TEST_ASSISTANT_WEB_VIEW_FACTORY_H_
diff --git a/ash/assistant/ui/assistant_mini_view.cc b/ash/assistant/ui/assistant_mini_view.cc
index 5e83edc..5d3a6de3 100644
--- a/ash/assistant/ui/assistant_mini_view.cc
+++ b/ash/assistant/ui/assistant_mini_view.cc
@@ -76,11 +76,11 @@
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
   // Molecule icon.
-  LogoView* molecule_icon = LogoView::Create();
+  std::unique_ptr<LogoView> molecule_icon = LogoView::Create();
   molecule_icon->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
   molecule_icon->SetState(LogoView::State::kMoleculeWavy,
                           /*animate=*/false);
-  AddChildView(molecule_icon);
+  AddChildView(std::move(molecule_icon));
 
   // Label.
   label_->SetAutoColorReadabilityEnabled(false);
diff --git a/ash/assistant/ui/assistant_web_container_view.cc b/ash/assistant/ui/assistant_web_container_view.cc
index 567c3cd4..693cab8 100644
--- a/ash/assistant/ui/assistant_web_container_view.cc
+++ b/ash/assistant/ui/assistant_web_container_view.cc
@@ -85,12 +85,6 @@
   views::Widget* widget = new views::Widget;
   widget->Init(std::move(params));
 
-  // TODO(b/146351046): Temporary workaround for an a11y bug b/144765770.
-  // Should be removed once we have moved off of the Content Service
-  // (tracked in b/146351046).
-  widget->client_view()->SetFocusBehavior(FocusBehavior::ALWAYS);
-  widget->client_view()->RequestFocus();
-
   SetLayoutManager(std::make_unique<views::FillLayout>());
   SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
 
diff --git a/ash/assistant/ui/assistant_web_view.cc b/ash/assistant/ui/assistant_web_view.cc
index b88f652..0acc310d9 100644
--- a/ash/assistant/ui/assistant_web_view.cc
+++ b/ash/assistant/ui/assistant_web_view.cc
@@ -13,10 +13,10 @@
 #include "ash/assistant/ui/assistant_web_view_delegate.h"
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/assistant/assistant_web_view_factory.h"
 #include "base/bind.h"
 #include "base/callback.h"
 #include "chromeos/services/assistant/public/features.h"
-#include "services/content/public/cpp/navigable_contents_view.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/display.h"
@@ -80,27 +80,6 @@
   SchedulePaint();
 }
 
-void AssistantWebView::OnFocus() {
-  if (contents_)
-    contents_->Focus();
-}
-
-void AssistantWebView::AboutToRequestFocusFromTabTraversal(bool reverse) {
-  if (contents_) {
-    // TODO(b/146351046): Temporary workaround for b/145213680. Should be
-    // removed once we have moved off of the Content Service (tracked in
-    // b/146351046).
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(
-                       [](base::WeakPtr<AssistantWebView> view) {
-                         if (view)
-                           view->GetFocusManager()->ClearFocus();
-                       },
-                       weak_factory_.GetWeakPtr()));
-    contents_->FocusThroughTabTraversal(reverse);
-  }
-}
-
 void AssistantWebView::InitLayout() {
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical));
@@ -120,20 +99,10 @@
 
 bool AssistantWebView::OnCaptionButtonPressed(AssistantButtonId id) {
   // We need special handling of the back button. When possible, the back button
-  // should navigate backwards in the web contents' history stack.
-  if (id == AssistantButtonId::kBack && contents_) {
-    contents_->GoBack(base::BindOnce(
-        [](const base::WeakPtr<AssistantWebView>& assistant_web_view,
-           bool success) {
-          // If we can't navigate back in the web contents' history stack we
-          // defer back to our primary caption button delegate.
-          if (!success && assistant_web_view) {
-            assistant_web_view->assistant_view_delegate_
-                ->GetCaptionBarDelegate()
-                ->OnCaptionButtonPressed(AssistantButtonId::kBack);
-          }
-        },
-        weak_factory_.GetWeakPtr()));
+  // should navigate backwards in the WebContents' history stack. If we can't go
+  // back, control is returned to the primary caption button delegate.
+  if (id == AssistantButtonId::kBack && contents_view_ &&
+      contents_view_->GoBack()) {
     return true;
   }
 
@@ -144,20 +113,20 @@
 
 void AssistantWebView::DidStopLoading() {
   // We should only respond to the |DidStopLoading| event the first time, to add
-  // the view for the navigable contents to our view hierarchy and perform other
-  // one-time view initializations.
+  // the view for contents to our view hierarchy and perform other one-time view
+  // initializations.
   if (contents_view_initialized_)
     return;
 
   contents_view_initialized_ = true;
 
   UpdateContentSize();
-  AddChildView(contents_->GetView()->view());
+  AddChildView(contents_view_.get());
   SetFocusBehavior(FocusBehavior::ALWAYS);
 
-  // We need to clip the corners of our web contents to match our container.
+  // We need to clip the corners of our WebContents to match our container.
   if (!chromeos::assistant::features::IsAssistantWebContainerEnabled()) {
-    contents_->GetView()->native_view()->layer()->SetRoundedCornerRadius(
+    contents_view_->GetNativeView()->layer()->SetRoundedCornerRadius(
         {/*top_left=*/0, /*top_right=*/0, /*bottom_right=*/kCornerRadiusDip,
          /*bottom_left=*/kCornerRadiusDip});
   }
@@ -179,11 +148,11 @@
     return;
   }
 
-  // Otherwise we'll allow our web contents to navigate freely.
-  contents_->Navigate(url);
+  // Otherwise we'll allow our WebContents to navigate freely.
+  contents_view_->Navigate(url);
 }
 
-void AssistantWebView::UpdateCanGoBack(bool can_go_back) {
+void AssistantWebView::DidChangeCanGoBack(bool can_go_back) {
   if (!chromeos::assistant::features::IsAssistantWebContainerEnabled())
     return;
 
@@ -210,23 +179,22 @@
 void AssistantWebView::OpenUrl(const GURL& url) {
   RemoveContents();
 
-  if (!contents_factory_.is_bound()) {
-    assistant_view_delegate_->GetNavigableContentsFactoryForView(
-        contents_factory_.BindNewPipeAndPassReceiver());
-  }
+  AssistantWebView2::InitParams contents_params;
+  contents_params.suppress_navigation = true;
 
-  auto contents_params = content::mojom::NavigableContentsParams::New();
-  contents_params->suppress_navigations = true;
+  contents_view_ = AssistantWebViewFactory::Get()->Create(contents_params);
 
-  contents_ = std::make_unique<content::NavigableContents>(
-      contents_factory_.get(), std::move(contents_params));
+  // We retain ownership of |contents_view_| as it is only added to the view
+  // hierarchy once loading stops and we want to ensure that it is cleaned up in
+  // the rare chance that that never occurs.
+  contents_view_->set_owned_by_client();
 
-  // We observe |contents_| so that we can handle events from the underlying
-  // web contents.
-  contents_->AddObserver(this);
+  // We observe |contents_view_| so that we can handle events from the
+  // underlying WebContents.
+  contents_view_->AddObserver(this);
 
   // Navigate to the specified |url|.
-  contents_->Navigate(url);
+  contents_view_->Navigate(url);
 }
 
 void AssistantWebView::OnUsableWorkAreaChanged(
@@ -237,32 +205,32 @@
 }
 
 void AssistantWebView::RemoveContents() {
-  if (!contents_)
+  if (!contents_view_)
     return;
 
-  views::View* view = contents_->GetView()->view();
-  if (view)
-    RemoveChildView(view);
+  RemoveChildView(contents_view_.get());
 
   SetFocusBehavior(FocusBehavior::NEVER);
-  contents_->RemoveObserver(this);
-  contents_.reset();
+
+  contents_view_->RemoveObserver(this);
+  contents_view_.reset();
+
   contents_view_initialized_ = false;
 }
 
 void AssistantWebView::UpdateContentSize() {
-  if (!contents_ || !contents_view_initialized_)
+  if (!contents_view_ || !contents_view_initialized_)
     return;
 
   if (chromeos::assistant::features::IsAssistantWebContainerEnabled()) {
-    contents_->GetView()->view()->SetPreferredSize(GetPreferredSize());
+    contents_view_->SetPreferredSize(GetPreferredSize());
     return;
   }
 
   const gfx::Size preferred_size = gfx::Size(
       kPreferredWidthDip, GetHeightForWidth(kPreferredWidthDip) -
                               caption_bar_->GetPreferredSize().height());
-  contents_->GetView()->view()->SetPreferredSize(preferred_size);
+  contents_view_->SetPreferredSize(preferred_size);
 }
 
 }  // namespace ash
diff --git a/ash/assistant/ui/assistant_web_view.h b/ash/assistant/ui/assistant_web_view.h
index 2403373..fc00ba29 100644
--- a/ash/assistant/ui/assistant_web_view.h
+++ b/ash/assistant/ui/assistant_web_view.h
@@ -12,11 +12,10 @@
 #include "ash/assistant/model/assistant_ui_model_observer.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/ui/caption_bar.h"
+#include "ash/public/cpp/assistant/assistant_web_view_2.h"
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "services/content/public/cpp/navigable_contents.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -28,12 +27,11 @@
 // standalone Assistant UI.
 // AssistantWebView is a child of AssistantContainerView which allows Assistant
 // UI to render remotely hosted content within its bubble. It provides a
-// CaptionBar for window level controls and embeds web contents with help from
-// the Content Service.
+// CaptionBar for window level controls and embeds WebContents.
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantWebView
     : public views::View,
       public CaptionBarDelegate,
-      public content::NavigableContentsObserver,
+      public AssistantWebView2::Observer,
       public AssistantUiModelObserver {
  public:
   AssistantWebView(AssistantViewDelegate* assistant_view_delegate,
@@ -45,18 +43,16 @@
   gfx::Size CalculatePreferredSize() const override;
   int GetHeightForWidth(int width) const override;
   void ChildPreferredSizeChanged(views::View* child) override;
-  void OnFocus() override;
-  void AboutToRequestFocusFromTabTraversal(bool reverse) override;
 
   // CaptionBarDelegate:
   bool OnCaptionButtonPressed(AssistantButtonId id) override;
 
-  // content::NavigableContentsObserver:
+  // AssistantWebView2::Observer:
   void DidStopLoading() override;
   void DidSuppressNavigation(const GURL& url,
                              WindowOpenDisposition disposition,
                              bool from_user_gesture) override;
-  void UpdateCanGoBack(bool can_go_back) override;
+  void DidChangeCanGoBack(bool can_go_back) override;
 
   // AssistantUiModelObserver:
   void OnUiVisibilityChanged(
@@ -86,14 +82,10 @@
   AssistantWebViewDelegate* const web_container_view_delegate_;
 
   CaptionBar* caption_bar_ = nullptr;  // Owned by view hierarchy.
-
-  mojo::Remote<content::mojom::NavigableContentsFactory> contents_factory_;
-  std::unique_ptr<content::NavigableContents> contents_;
+  std::unique_ptr<AssistantWebView2> contents_view_;
 
   bool contents_view_initialized_ = false;
 
-  base::WeakPtrFactory<AssistantWebView> weak_factory_{this};
-
   DISALLOW_COPY_AND_ASSIGN(AssistantWebView);
 };
 
diff --git a/ash/assistant/ui/dialog_plate/mic_view.cc b/ash/assistant/ui/dialog_plate/mic_view.cc
index fce904f..afc8e1e 100644
--- a/ash/assistant/ui/dialog_plate/mic_view.cc
+++ b/ash/assistant/ui/dialog_plate/mic_view.cc
@@ -72,9 +72,8 @@
       views::BoxLayout::MainAxisAlignment::kCenter);
 
   // Logo view.
-  logo_view_ = LogoView::Create();
+  logo_view_ = logo_view_container->AddChildView(LogoView::Create());
   logo_view_->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
-  logo_view_container->AddChildView(logo_view_);
 
   // Initialize state.
   UpdateState(/*animate=*/false);
diff --git a/ash/assistant/ui/logo_view/logo_view.cc b/ash/assistant/ui/logo_view/logo_view.cc
index b6603c20..1ca8255 100644
--- a/ash/assistant/ui/logo_view/logo_view.cc
+++ b/ash/assistant/ui/logo_view/logo_view.cc
@@ -18,11 +18,11 @@
 LogoView::~LogoView() = default;
 
 // static
-LogoView* LogoView::Create() {
+std::unique_ptr<LogoView> LogoView::Create() {
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
-  return new LogoViewImpl();
+  return std::make_unique<LogoViewImpl>();
 #else
-  return new LogoView();
+  return std::make_unique<LogoView>();
 #endif
 }
 
diff --git a/ash/assistant/ui/logo_view/logo_view.h b/ash/assistant/ui/logo_view/logo_view.h
index 18634e0..74d5a95 100644
--- a/ash/assistant/ui/logo_view/logo_view.h
+++ b/ash/assistant/ui/logo_view/logo_view.h
@@ -5,6 +5,8 @@
 #ifndef ASH_ASSISTANT_UI_LOGO_VIEW_LOGO_VIEW_H_
 #define ASH_ASSISTANT_UI_LOGO_VIEW_LOGO_VIEW_H_
 
+#include <memory>
+
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "ui/views/view.h"
@@ -32,7 +34,7 @@
   virtual void SetSpeechLevel(float speech_level) {}
 
   // Creates LogoView based on the build flag ENABLE_CROS_LIBASSISTANT.
-  static LogoView* Create();
+  static std::unique_ptr<LogoView> Create();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LogoView);
diff --git a/ash/assistant/ui/main_stage/assistant_header_view.cc b/ash/assistant/ui/main_stage/assistant_header_view.cc
index 9456d94f..b0957071 100644
--- a/ash/assistant/ui/main_stage/assistant_header_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_header_view.cc
@@ -80,7 +80,7 @@
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
   // Molecule icon.
-  molecule_icon_ = LogoView::Create();
+  molecule_icon_ = AddChildView(LogoView::Create());
   molecule_icon_->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
   molecule_icon_->SetState(LogoView::State::kMoleculeWavy,
                            /*animate=*/false);
@@ -88,8 +88,6 @@
   // The molecule icon will be animated on its own layer.
   molecule_icon_->SetPaintToLayer();
   molecule_icon_->layer()->SetFillsBoundsOpaquely(false);
-
-  AddChildView(molecule_icon_);
 }
 
 void AssistantHeaderView::OnResponseChanged(
diff --git a/ash/public/cpp/assistant/assistant_web_view_2.h b/ash/public/cpp/assistant/assistant_web_view_2.h
index 46644b76..8235524 100644
--- a/ash/public/cpp/assistant/assistant_web_view_2.h
+++ b/ash/public/cpp/assistant/assistant_web_view_2.h
@@ -17,8 +17,8 @@
 
 // TODO(b/146520500): Rename to AssistantWebView after freeing up name which is
 // currently in use.
-// A view which wraps a views::WebView (and associated content::WebContents) to
-// work around dependency restrictions in Ash.
+// A view which wraps a views::WebView (and associated WebContents) to work
+// around dependency restrictions in Ash.
 class ASH_PUBLIC_EXPORT AssistantWebView2 : public views::View {
  public:
   // Initialization parameters which dictate how an instance of
@@ -29,14 +29,14 @@
     ~InitParams();
 
     // If enabled, AssistantWebView2 will automatically resize to the size
-    // desired by its embedded content::WebContents. Note that, if specified,
-    // the content::WebContents will be bounded by |min_size| and |max_size|.
+    // desired by its embedded WebContents. Note that, if specified, the
+    // WebContents will be bounded by |min_size| and |max_size|.
     bool enable_auto_resize = false;
     base::Optional<gfx::Size> min_size = base::nullopt;
     base::Optional<gfx::Size> max_size = base::nullopt;
 
     // If enabled, AssistantWebView2 will suppress navigation attempts of its
-    // embedded content::WebContents. When navigation suppression occurs,
+    // embedded WebContents. When navigation suppression occurs,
     // Observer::DidSuppressNavigation() will be invoked.
     bool suppress_navigation = false;
   };
@@ -44,16 +44,19 @@
   // An observer which receives AssistantWebView2 events.
   class Observer : public base::CheckedObserver {
    public:
-    // Invoked when the embedded content::WebContents has stopped loading.
+    // Invoked when the embedded WebContents has stopped loading.
     virtual void DidStopLoading() {}
 
-    // Invoked when the embedded content::WebContents has suppressed navigation.
+    // Invoked when the embedded WebContents has suppressed navigation.
     virtual void DidSuppressNavigation(const GURL& url,
                                        WindowOpenDisposition disposition,
                                        bool from_user_gesture) {}
 
-    // Invoked when the focused node within the embedded content::WebContents
-    // has changed.
+    // Invoked when the embedded WebContents' ability to go back has changed.
+    virtual void DidChangeCanGoBack(bool can_go_back) {}
+
+    // Invoked when the focused node within the embedded WebContents has
+    // changed.
     virtual void DidChangeFocusedNode(const gfx::Rect& node_bounds_in_screen) {}
   };
 
@@ -63,12 +66,15 @@
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
-  // Invoke to navigate back in the embedded content::WebContents' navigation
-  // stack. If backwards navigation is not possible, return |false|. Otherwise
-  // returns |true| to indicate success.
+  // Returns the native view associated w/ the underlying WebContents.
+  virtual gfx::NativeView GetNativeView() = 0;
+
+  // Invoke to navigate back in the embedded WebContents' navigation stack. If
+  // backwards navigation is not possible, return |false|. Otherwise returns
+  // |true| to indicate success.
   virtual bool GoBack() = 0;
 
-  // Invoke to navigate the embedded content::WebContents' to |url|.
+  // Invoke to navigate the embedded WebContents' to |url|.
   virtual void Navigate(const GURL& url) = 0;
 
  protected:
diff --git a/ash/system/machine_learning/BUILD.gn b/ash/system/machine_learning/BUILD.gn
index ec5f145..aa6c695 100644
--- a/ash/system/machine_learning/BUILD.gn
+++ b/ash/system/machine_learning/BUILD.gn
@@ -5,7 +5,5 @@
 import("//third_party/protobuf/proto_library.gni")
 
 proto_library("user_settings_event_proto") {
-  sources = [
-    "user_settings_event.proto",
-  ]
+  sources = [ "user_settings_event.proto" ]
 }
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 83945037..f5c8ace 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3040,6 +3040,7 @@
       "fuchsia/service_directory_test_base.cc",
       "fuchsia/service_directory_test_base.h",
       "fuchsia/service_provider_impl_unittest.cc",
+      "fuchsia/time_zone_data_unittest.cc",
       "message_loop/fd_watch_controller_posix_unittest.cc",
       "posix/file_descriptor_shuffle_unittest.cc",
       "task/thread_pool/task_tracker_posix_unittest.cc",
diff --git a/base/allocator/partition_allocator/page_allocator.cc b/base/allocator/partition_allocator/page_allocator.cc
index b2e8cf3..28d90fe 100644
--- a/base/allocator/partition_allocator/page_allocator.cc
+++ b/base/allocator/partition_allocator/page_allocator.cc
@@ -239,14 +239,21 @@
   return false;
 }
 
-void ReleaseReservation() {
+bool ReleaseReservation() {
   // To avoid deadlock, call only FreePages.
   subtle::SpinLock::Guard guard(GetReserveLock());
-  if (s_reservation_address != nullptr) {
-    FreePages(s_reservation_address, s_reservation_size);
-    s_reservation_address = nullptr;
-    s_reservation_size = 0;
-  }
+  if (!s_reservation_address)
+    return false;
+
+  FreePages(s_reservation_address, s_reservation_size);
+  s_reservation_address = nullptr;
+  s_reservation_size = 0;
+  return true;
+}
+
+bool HasReservationForTesting() {
+  subtle::SpinLock::Guard guard(GetReserveLock());
+  return s_reservation_address != nullptr;
 }
 
 uint32_t GetAllocPageErrorCode() {
diff --git a/base/allocator/partition_allocator/page_allocator.h b/base/allocator/partition_allocator/page_allocator.h
index a93694b..da070bc 100644
--- a/base/allocator/partition_allocator/page_allocator.h
+++ b/base/allocator/partition_allocator/page_allocator.h
@@ -182,7 +182,12 @@
 
 // Releases any reserved address space. |AllocPages| calls this automatically on
 // an allocation failure. External allocators may also call this on failure.
-BASE_EXPORT void ReleaseReservation();
+//
+// Returns true when an existing reservation was released.
+BASE_EXPORT bool ReleaseReservation();
+
+// Returns true if there is currently an address space reservation.
+BASE_EXPORT bool HasReservationForTesting();
 
 // Returns |errno| (POSIX) or the result of |GetLastError| (Windows) when |mmap|
 // (POSIX) or |VirtualAlloc| (Windows) fails.
diff --git a/base/fuchsia/intl_profile_watcher_unittest.cc b/base/fuchsia/intl_profile_watcher_unittest.cc
index 605f73a5..23d8543 100644
--- a/base/fuchsia/intl_profile_watcher_unittest.cc
+++ b/base/fuchsia/intl_profile_watcher_unittest.cc
@@ -167,14 +167,6 @@
   base::RunLoop run_loop_;
 };
 
-// Unit tests are run in an environment where intl is not provided, so the FIDL
-// calls always fail.
-TEST(IntlServiceNotAvailableTest, GetPrimaryTimeZoneIdForIcuInitialization) {
-  EXPECT_STREQ(
-      "",
-      IntlProfileWatcher::GetPrimaryTimeZoneIdForIcuInitialization().c_str());
-}
-
 // Unit tests are run in an environment where intl is not provided.
 // However, this is not exposed by the API.
 TEST(IntlServiceNotAvailableTest, IntlProfileWatcher) {
diff --git a/base/fuchsia/time_zone_data_unittest.cc b/base/fuchsia/time_zone_data_unittest.cc
new file mode 100644
index 0000000..9d597106
--- /dev/null
+++ b/base/fuchsia/time_zone_data_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/i18n/icu_util.h"
+
+#include "base/environment.h"
+#include "base/files/file_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/common/unicode/uclean.h"
+#include "third_party/icu/source/i18n/unicode/timezone.h"
+
+namespace base {
+namespace i18n {
+
+class TimeZoneDataTest : public testing::Test {
+ protected:
+  TimeZoneDataTest() : env_(base::Environment::Create()) {}
+
+  void TearDown() override { u_cleanup(); }
+
+  void GetActualRevision(std::string* icu_version) {
+    UErrorCode err = U_ZERO_ERROR;
+    *icu_version = icu::TimeZone::getTZDataVersion(err);
+    ASSERT_EQ(U_ZERO_ERROR, err) << u_errorName(err);
+  }
+
+  std::unique_ptr<base::Environment> env_;
+};
+
+TEST_F(TimeZoneDataTest, RevisionFromConfig) {
+  // Config data is not available on Chromium test runners, so don't actually
+  // run this test there.  A marker for this is the presence of revision.txt.
+  if (!base::PathExists(base::FilePath("/config/data/tzdata/revision.txt")))
+    return;
+
+  // Ensure that timezone data is loaded from the default location.
+  ASSERT_TRUE(env_->UnSetVar("ICU_TIMEZONE_FILES_DIR"));
+
+  InitializeICU();
+  std::string expected;
+  ASSERT_TRUE(base::ReadFileToString(
+      base::FilePath("/config/data/tzdata/revision.txt"), &expected));
+  std::string actual;
+  GetActualRevision(&actual);
+  EXPECT_EQ(expected, actual);
+}
+
+TEST_F(TimeZoneDataTest, RevisionFromTestData) {
+  // Ensure that timezone data is loaded from test data.
+  ASSERT_TRUE(env_->SetVar("ICU_TIMEZONE_FILES_DIR",
+                           "/pkg/base/test/data/tzdata/2019a/44/le"));
+
+  InitializeICU();
+  std::string actual;
+  GetActualRevision(&actual);
+  EXPECT_EQ("2019a", actual);
+}
+
+}  // namespace i18n
+}  // namespace base
diff --git a/base/i18n/icu_util.cc b/base/i18n/icu_util.cc
index 1b525a5..7f7398d6 100644
--- a/base/i18n/icu_util.cc
+++ b/base/i18n/icu_util.cc
@@ -11,7 +11,9 @@
 #include <string>
 
 #include "base/debug/alias.h"
+#include "base/environment.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/files/memory_mapped_file.h"
 #include "base/logging.h"
 #include "base/path_service.h"
@@ -79,6 +81,28 @@
 const char kIcuDataFileName[] = "icudtl.dat";
 const char kIcuExtraDataFileName[] = "icudtl_extra.dat";
 
+// Time zone data loading.
+// For now, only Fuchsia has a meaningful use case for this feature, so it is
+// only implemented for OS_FUCHSIA.
+#if defined(OS_FUCHSIA)
+// The environment variable used to point the ICU data loader to the directory
+// containing timezone data. This is available from ICU version 54. The env
+// variable approach is antiquated by today's standards (2019), but is the
+// recommended way to configure ICU.
+//
+// See for details: http://userguide.icu-project.org/datetime/timezone
+const char kIcuTimeZoneEnvVariable[] = "ICU_TIMEZONE_FILES_DIR";
+
+// We assume that Fuchsia will provide time zone data at this path for Chromium
+// to load, and that the path will be timely updated when Fuchsia needs to
+// uprev the ICU version it is using. There are unit tests that will fail at
+// Fuchsia roll time in case either Chromium or Fuchsia get upgraded to
+// mutually incompatible ICU versions. That should be enough to alert the
+// developers of the need to keep ICU library versions in ICU and Fuchsia in
+// reasonable sync.
+const char kIcuTimeZoneDataDir[] = "/config/data/tzdata/icu/44/le";
+#endif  // defined(OS_FUCHSIA)
+
 #if defined(OS_ANDROID)
 const char kAssetsPathPrefix[] = "assets/";
 #endif  // defined(OS_ANDROID)
@@ -181,10 +205,38 @@
   g_icudtl_region = pf_region->region;
 }
 
+// Loads timezone data, for configurations where it makes sense.  If the
+// timezone data directory is not set up properly, we continue, but log
+// appropriate information for debugging.
+void InitializeTimeZoneData() {
+#if defined(OS_FUCHSIA)
+  std::unique_ptr<base::Environment> env = base::Environment::Create();
+
+  // We only set the variable if it was not already set by a test.  Fuchsia
+  // normally does not otherwise set env variables in production code.
+  if (!env->HasVar(kIcuTimeZoneEnvVariable)) {
+    env->SetVar(kIcuTimeZoneEnvVariable, kIcuTimeZoneDataDir);
+  }
+  std::string tzdata_dir;
+  env->GetVar(kIcuTimeZoneEnvVariable, &tzdata_dir);
+
+  // Try opening the path to check if present. No need to verify that it is a
+  // directory since ICU loading will return an error if the TimeZone data is
+  // wrong.
+  if (!base::DirectoryExists(base::FilePath(tzdata_dir))) {
+    PLOG(ERROR) << "Could not open: '" << tzdata_dir
+                << "', proceeding without loading the timezone database";
+    return;
+  }
+#endif  // defined(OS_FUCHSIA)
+}
+
 int LoadIcuData(PlatformFile data_fd,
                 const MemoryMappedFile::Region& data_region,
                 std::unique_ptr<MemoryMappedFile>* out_mapped_data_file,
                 UErrorCode* out_error_code) {
+  InitializeTimeZoneData();
+
   if (data_fd == kInvalidPlatformFile) {
     LOG(ERROR) << "Invalid file descriptor to ICU data received.";
     return 1;  // To debug http://crbug.com/445616.
diff --git a/base/process/memory.cc b/base/process/memory.cc
index 5b987339b..3034d00 100644
--- a/base/process/memory.cc
+++ b/base/process/memory.cc
@@ -2,9 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/process/memory.h"
 #include "base/debug/alias.h"
 #include "base/logging.h"
-#include "base/process/memory.h"
+#include "base/partition_alloc_buildflags.h"
+#if BUILDFLAG(USE_PARTITION_ALLOC)
+#include "base/allocator/partition_allocator/page_allocator.h"
+#endif
 #include "build/build_config.h"
 
 namespace base {
@@ -28,7 +32,7 @@
   OnNoMemory(size);
 }
 
-#endif
+#endif  // !defined(OS_WIN)
 
 // Defined in memory_mac.mm for Mac.
 #if !defined(OS_MACOSX)
@@ -49,6 +53,16 @@
   return true;
 }
 
+#endif  // defined(OS_MACOSX)
+
+namespace internal {
+bool ReleaseAddressSpaceReservation() {
+#if BUILDFLAG(USE_PARTITION_ALLOC)
+  return ReleaseReservation();
+#else
+  return false;
 #endif
+}
+}  // namespace internal
 
 }  // namespace base
diff --git a/base/process/memory.h b/base/process/memory.h
index 1dd2f412..3fb2caa1 100644
--- a/base/process/memory.h
+++ b/base/process/memory.h
@@ -40,6 +40,12 @@
 BASE_EXPORT bool AdjustOOMScore(ProcessId process, int score);
 #endif
 
+namespace internal {
+// Returns true if address-space was released. Some configurations reserve part
+// of the process address-space for special allocations (e.g. WASM).
+bool ReleaseAddressSpaceReservation();
+}  // namespace internal
+
 #if defined(OS_WIN)
 namespace win {
 
diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc
index 483168b..6a6091a6 100644
--- a/base/process/memory_linux.cc
+++ b/base/process/memory_linux.cc
@@ -37,10 +37,18 @@
   LOG(FATAL) << "Out of memory.";
 }
 
-void OnNoMemory() {
+// NOINLINE as base::`anonymous namespace`::OnNoMemory() is recognized by the
+// crash server.
+NOINLINE void OnNoMemory() {
   OnNoMemorySize(0);
 }
 
+void ReleaseReservationOrTerminate() {
+  if (internal::ReleaseAddressSpaceReservation())
+    return;
+  OnNoMemory();
+}
+
 }  // namespace
 
 void EnableTerminationOnHeapCorruption() {
@@ -49,7 +57,7 @@
 
 void EnableTerminationOnOutOfMemory() {
   // Set the new-out of memory handler.
-  std::set_new_handler(&OnNoMemory);
+  std::set_new_handler(&ReleaseReservationOrTerminate);
   // If we're using glibc's allocator, the above functions will override
   // malloc and friends and make them die on out of memory.
 
diff --git a/base/process/memory_unittest.cc b/base/process/memory_unittest.cc
index f45e98a..abce67e77 100644
--- a/base/process/memory_unittest.cc
+++ b/base/process/memory_unittest.cc
@@ -9,9 +9,11 @@
 #include <stddef.h>
 
 #include <limits>
+#include <vector>
 
 #include "base/allocator/allocator_check.h"
 #include "base/allocator/buildflags.h"
+#include "base/allocator/partition_allocator/page_allocator.h"
 #include "base/compiler_specific.h"
 #include "base/debug/alias.h"
 #include "base/memory/aligned_memory.h"
@@ -497,6 +499,66 @@
 }
 #endif  // OS_WIN
 
+#if defined(ARCH_CPU_32_BITS) && (defined(OS_WIN) || defined(OS_LINUX))
+
+void TestAllocationsReleaseReservation(void* (*alloc_fn)(size_t),
+                                       void (*free_fn)(void*)) {
+  base::ReleaseReservation();
+  base::EnableTerminationOnOutOfMemory();
+
+  constexpr size_t kMiB = 1 << 20;
+  constexpr size_t kReservationSize = 512 * kMiB;  // MiB.
+
+  size_t reservation_size = kReservationSize;
+  while (!base::ReserveAddressSpace(reservation_size)) {
+    reservation_size -= 16 * kMiB;
+  }
+  ASSERT_TRUE(base::HasReservationForTesting());
+  ASSERT_GT(reservation_size, 0u);
+
+  // Allocate a large area at a time to bump into address space exhaustion
+  // before other limits. It is important not to do a larger allocation, to
+  // verify that we can allocate without removing the reservation. On the other
+  // hand, must be large enough to make the underlying implementation call
+  // mmap()/VirtualAlloc().
+  size_t allocation_size = reservation_size / 2;
+
+  std::vector<void*> areas;
+  // Pre-reserve the vector to make sure that we don't hit the address space
+  // limit while resizing the array.
+  areas.reserve(((2 * 4096 * kMiB) / allocation_size) + 1);
+
+  while (true) {
+    void* area = alloc_fn(allocation_size / 2);
+    ASSERT_TRUE(area);
+    areas.push_back(area);
+
+    // Working as intended, the allocation was successful, and the reservation
+    // was dropped instead of crashing.
+    //
+    // Meaning that the test is either successful, or crashes.
+    if (!base::HasReservationForTesting())
+      break;
+  }
+
+  EXPECT_GE(areas.size(), 2u)
+      << "Should be able to allocate without releasing the reservation";
+
+  for (void* ptr : areas)
+    free_fn(ptr);
+}
+
+TEST_F(OutOfMemoryHandledTest, MallocReleasesReservation) {
+  TestAllocationsReleaseReservation(malloc, free);
+}
+
+TEST_F(OutOfMemoryHandledTest, NewReleasesReservation) {
+  TestAllocationsReleaseReservation(
+      [](size_t size) { return static_cast<void*>(new char[size]); },
+      [](void* ptr) { delete[] static_cast<char*>(ptr); });
+}
+#endif  // defined(ARCH_CPU_32_BITS) && (defined(OS_WIN) || defined(OS_LINUX))
+
 // TODO(b.kelemen): make UncheckedMalloc and UncheckedCalloc work
 // on Windows as well.
 TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) {
diff --git a/base/process/memory_win.cc b/base/process/memory_win.cc
index 4fff911..f66089c 100644
--- a/base/process/memory_win.cc
+++ b/base/process/memory_win.cc
@@ -43,7 +43,7 @@
 #pragma warning(push)
 #pragma warning(disable: 4702)  // Unreachable code after the _exit.
 
-NOINLINE int OnNoMemory(size_t size) {
+[[noreturn]] NOINLINE int OnNoMemory(size_t size) {
   // Kill the process. This is important for security since most of code
   // does not check the result of memory allocation.
   // https://msdn.microsoft.com/en-us/library/het71c37.aspx
@@ -54,11 +54,18 @@
 
   // Safety check, make sure process exits here.
   _exit(win::kOomExceptionCode);
-  return 0;
 }
 
 #pragma warning(pop)
 
+// Return a non-0 value to retry the allocation.
+int ReleaseReservationOrTerminate(size_t size) {
+  constexpr int kRetryAllocation = 1;
+  if (internal::ReleaseAddressSpaceReservation())
+    return kRetryAllocation;
+  OnNoMemory(size);
+}
+
 }  // namespace
 
 void TerminateBecauseOutOfMemory(size_t size) {
@@ -71,8 +78,9 @@
 }
 
 void EnableTerminationOnOutOfMemory() {
-  _set_new_handler(&OnNoMemory);
-  _set_new_mode(1);
+  constexpr int kCallNewHandlerOnAllocationFailure = 1;
+  _set_new_handler(&ReleaseReservationOrTerminate);
+  _set_new_mode(kCallNewHandlerOnAllocationFailure);
 }
 
 // Implemented using a weak symbol.
diff --git a/base/test/data/tzdata/2019a/44/le/metaZones.res b/base/test/data/tzdata/2019a/44/le/metaZones.res
new file mode 100644
index 0000000..3b3ee013
--- /dev/null
+++ b/base/test/data/tzdata/2019a/44/le/metaZones.res
Binary files differ
diff --git a/base/test/data/tzdata/2019a/44/le/timezoneTypes.res b/base/test/data/tzdata/2019a/44/le/timezoneTypes.res
new file mode 100644
index 0000000..895ed217
--- /dev/null
+++ b/base/test/data/tzdata/2019a/44/le/timezoneTypes.res
Binary files differ
diff --git a/base/test/data/tzdata/2019a/44/le/zoneinfo64.res b/base/test/data/tzdata/2019a/44/le/zoneinfo64.res
new file mode 100644
index 0000000..7e0afd40
--- /dev/null
+++ b/base/test/data/tzdata/2019a/44/le/zoneinfo64.res
Binary files differ
diff --git a/base/test/data/tzdata/README.md b/base/test/data/tzdata/README.md
new file mode 100644
index 0000000..bc384ec
--- /dev/null
+++ b/base/test/data/tzdata/README.md
@@ -0,0 +1,6 @@
+# Timezone data for testing
+
+This directory contains the fixed timezone data version 2019a for testing.  It
+is used in the runner tests to show that loading these files from a specified
+location results in the TZ data version "2019a" becoming available to the
+binaries.
diff --git a/base/win/registry.cc b/base/win/registry.cc
index c66a8dd7..e5ad261 100644
--- a/base/win/registry.cc
+++ b/base/win/registry.cc
@@ -106,6 +106,22 @@
   }
 }
 
+RegKey::RegKey(RegKey&& other)
+    : key_(other.key_),
+      wow64access_(other.wow64access_),
+      key_watcher_(std::move(other.key_watcher_)) {
+  other.key_ = nullptr;
+  other.wow64access_ = 0;
+}
+
+RegKey& RegKey::operator=(RegKey&& other) {
+  Close();
+  std::swap(key_, other.key_);
+  std::swap(wow64access_, other.wow64access_);
+  key_watcher_ = std::move(other.key_watcher_);
+  return *this;
+}
+
 RegKey::~RegKey() {
   Close();
 }
diff --git a/base/win/registry.h b/base/win/registry.h
index 2589e60..784b58b 100644
--- a/base/win/registry.h
+++ b/base/win/registry.h
@@ -38,6 +38,8 @@
   RegKey();
   explicit RegKey(HKEY key);
   RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access);
+  RegKey(RegKey&& other) noexcept;
+  RegKey& operator=(RegKey&& other);
   ~RegKey();
 
   LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access);
diff --git a/base/win/registry_unittest.cc b/base/win/registry_unittest.cc
index 2f678cbb..e992421 100644
--- a/base/win/registry_unittest.cc
+++ b/base/win/registry_unittest.cc
@@ -402,6 +402,47 @@
   EXPECT_FALSE(delegate.WasCalled());
 }
 
+TEST_F(RegistryTest, TestMoveConstruct) {
+  RegKey key;
+  std::wstring foo_key(kRootKey);
+
+  ASSERT_EQ(key.Create(HKEY_CURRENT_USER, foo_key.c_str(), KEY_SET_VALUE),
+            ERROR_SUCCESS);
+  RegKey key2(std::move(key));
+
+  // The old key should be meaningless now.
+  EXPECT_EQ(key.Handle(), nullptr);
+
+  // And the new one should work just fine.
+  EXPECT_NE(key2.Handle(), nullptr);
+  EXPECT_EQ(key2.WriteValue(L"foo", 1U), ERROR_SUCCESS);
+}
+
+TEST_F(RegistryTest, TestMoveAssign) {
+  RegKey key;
+  RegKey key2;
+  std::wstring foo_key(kRootKey);
+  const wchar_t kFooValueName[] = L"foo";
+
+  ASSERT_EQ(key.Create(HKEY_CURRENT_USER, foo_key.c_str(),
+                       KEY_SET_VALUE | KEY_QUERY_VALUE),
+            ERROR_SUCCESS);
+  ASSERT_EQ(key.WriteValue(kFooValueName, 1U), ERROR_SUCCESS);
+  ASSERT_EQ(key2.Create(HKEY_CURRENT_USER, (foo_key + L"\\child").c_str(),
+                        KEY_SET_VALUE),
+            ERROR_SUCCESS);
+  key2 = std::move(key);
+
+  // The old key should be meaningless now.
+  EXPECT_EQ(key.Handle(), nullptr);
+
+  // And the new one should hold what was the old one.
+  EXPECT_NE(key2.Handle(), nullptr);
+  DWORD foo = 0;
+  ASSERT_EQ(key2.ReadValueDW(kFooValueName, &foo), ERROR_SUCCESS);
+  EXPECT_EQ(foo, 1U);
+}
+
 }  // namespace
 
 }  // namespace win
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index 18731b7..68552ef 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -390,10 +390,11 @@
     <ignore regexp="android_webview/test/shell/res/raw/resource_file.html"/>
     <ignore regexp="android_webview/test/shell/res/raw/resource_icon.png"/>
     <ignore regexp="android_webview/tools/automated_ui_tests/java/res/layout/"/>
-    <!-- TODO(https://crbug.com/1017190): Remove suppressions once we lint entire app rather than
-         each individual target -->
+    <!-- TODO(crbug.com/1017190): Remove the following 3 suppressions once we lint entire app rather
+         than each individual target -->
     <ignore regexp="components/browser_ui/strings/android/browser_ui_strings_grd"/>
     <ignore regexp="chrome/browser/ui/android/strings/ui_strings_grd"/>
+    <ignore regexp="The resource `R.drawable.*_expand_.*` appears to be unused"/>
     <!-- 1 resource used by android tv to generate resources.zip file -->
     <ignore regexp="chromecast/internal/shell/browser/android/java/res/drawable-hdpi/ic_settings_cast.png"/>
     <!-- 1 string used by Android's policies system, pulled from app directly -->
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index d8c4fd6e..d9253fb 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -143,11 +143,10 @@
   # rest with 0xAA. This makes behavior of uninitialized memory bugs consistent,
   # recognizeble in debugger, and crashes memory accesses by uninitialized
   # pointers.
-  # TODO(vitalybuka): investigate why some debug windows bots fail tests.
+  # TODO(vitalybuka):
   # 'is_android' breaks content_shell_test_apk on android-kitkat-arm-rel.
   # 'use_xcode_clang' may call old clang.
-  init_stack_vars = !is_android && !(is_win && is_debug) && !use_xcode_clang &&
-                    !is_official_build
+  init_stack_vars = !is_android && !use_xcode_clang && !is_official_build
 }
 
 declare_args() {
diff --git a/build/config/fuchsia/tests.cmx b/build/config/fuchsia/tests.cmx
index 3df91c1..cd29609 100644
--- a/build/config/fuchsia/tests.cmx
+++ b/build/config/fuchsia/tests.cmx
@@ -1,6 +1,7 @@
 {
   "sandbox": {
     "features": [
+      "config-data",
       "isolated-persistent-storage",
       "isolated-temp",
       "root-ssl-certificates",
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 2d35d90..c000f33 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -2833,13 +2833,6 @@
   return !arg->isLazyGenerated();
 }
 
-MATCHER_P(MatchesInvScale, expected, "") {
-  SkSize scale;
-  arg.decomposeScale(&scale, nullptr);
-  SkSize inv = SkSize::Make(1.0f / scale.width(), 1.0f / scale.height());
-  return inv == expected;
-}
-
 MATCHER_P2(MatchesRect, rect, scale, "") {
   EXPECT_EQ(arg->x(), rect.x() * scale.width());
   EXPECT_EQ(arg->y(), rect.y() * scale.height());
@@ -2899,7 +2892,7 @@
   EXPECT_CALL(canvas, willSave()).InSequence(s);
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, willSave()).InSequence(s);
-  EXPECT_CALL(canvas, didConcat(SkMatrix::MakeTrans(8.0f, 8.0f)));
+  EXPECT_CALL(canvas, didTranslate(8.0f, 8.0f));
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, OnDrawRectWithColor(0u));
   EXPECT_CALL(canvas, willRestore()).InSequence(s);
@@ -2943,7 +2936,8 @@
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, didConcat(SkMatrix::MakeTrans(5.0f, 7.0f)));
   EXPECT_CALL(canvas, willSave()).InSequence(s);
-  EXPECT_CALL(canvas, didConcat(MatchesInvScale(scale_adjustment[0])));
+  EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(),
+                               1.0f / scale_adjustment[0].height()));
   EXPECT_CALL(canvas, onDrawImage(NonLazyImage(), 0.0f, 0.0f,
                                   MatchesQuality(quality[0])));
   EXPECT_CALL(canvas, willRestore()).InSequence(s);
@@ -2987,7 +2981,8 @@
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, didConcat(SkMatrix::MakeScale(2.f, 1.5f)));
   EXPECT_CALL(canvas, willSave()).InSequence(s);
-  EXPECT_CALL(canvas, didConcat(MatchesInvScale(scale_adjustment[0])));
+  EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(),
+                               1.0f / scale_adjustment[0].height()));
   EXPECT_CALL(canvas, onDrawImage(NonLazyImage(), 0.0f, 0.0f,
                                   MatchesQuality(quality[0])));
   EXPECT_CALL(canvas, willRestore()).InSequence(s);
@@ -3033,7 +3028,8 @@
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, OnSaveLayer()).InSequence(s);
   EXPECT_CALL(canvas, willSave()).InSequence(s);
-  EXPECT_CALL(canvas, didConcat(MatchesInvScale(scale_adjustment[0])));
+  EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(),
+                               1.0f / scale_adjustment[0].height()));
   EXPECT_CALL(canvas, onDrawImage(NonLazyImage(), 0.0f, 0.0f,
                                   MatchesQuality(quality[0])));
   EXPECT_CALL(canvas, willRestore()).InSequence(s);
@@ -3073,7 +3069,8 @@
 
   // Save/scale/image/restore from DrawImageop.
   EXPECT_CALL(canvas, willSave()).InSequence(s);
-  EXPECT_CALL(canvas, didConcat(MatchesInvScale(scale_adjustment[0])));
+  EXPECT_CALL(canvas, didScale(1.0f / scale_adjustment[0].width(),
+                               1.0f / scale_adjustment[0].height()));
   EXPECT_CALL(canvas, onDrawImage(NonLazyImage(), 0.0f, 0.0f,
                                   MatchesQuality(quality[0])));
   EXPECT_CALL(canvas, willRestore()).InSequence(s);
@@ -3140,7 +3137,8 @@
     if (op->GetType() == PaintOpType::DrawImage) {
       // Save/scale/image/restore from DrawImageop.
       EXPECT_CALL(canvas, willSave()).InSequence(s);
-      EXPECT_CALL(canvas, didConcat(MatchesInvScale(expected_scale)));
+      EXPECT_CALL(canvas, didScale(1.0f / expected_scale.width(),
+                                   1.0f / expected_scale.height()));
       EXPECT_CALL(canvas, onDrawImage(NonLazyImage(), 0.0f, 0.0f, _));
       EXPECT_CALL(canvas, willRestore()).InSequence(s);
       op->Raster(&canvas, params);
diff --git a/cc/raster/raster_source_unittest.cc b/cc/raster/raster_source_unittest.cc
index 7c73489..d21339b 100644
--- a/cc/raster/raster_source_unittest.cc
+++ b/cc/raster/raster_source_unittest.cc
@@ -559,12 +559,11 @@
   StrictMock<MockCanvas> mock_canvas;
   Sequence s;
 
-  SkMatrix m;
-  m.setScale(1.f / recording_scale, 1.f / recording_scale);
+  SkScalar scale = 1.f / recording_scale;
 
   // The recording source has no ops, so will only do the setup.
   EXPECT_CALL(mock_canvas, willSave()).InSequence(s);
-  EXPECT_CALL(mock_canvas, didConcat(m)).InSequence(s);
+  EXPECT_CALL(mock_canvas, didScale(scale, scale)).InSequence(s);
   EXPECT_CALL(mock_canvas, willRestore()).InSequence(s);
 
   gfx::Size small_size(50, 50);
diff --git a/cc/test/test_skcanvas.h b/cc/test/test_skcanvas.h
index f66b2b4..6c293a2 100644
--- a/cc/test/test_skcanvas.h
+++ b/cc/test/test_skcanvas.h
@@ -64,7 +64,10 @@
                     SrcRectConstraint));
   MOCK_METHOD5(onDrawArc,
                void(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&));
+  MOCK_METHOD1(didConcat44, void(const SkScalar[16]));
   MOCK_METHOD1(didConcat, void(const SkMatrix&));
+  MOCK_METHOD2(didScale, void(SkScalar, SkScalar));
+  MOCK_METHOD2(didTranslate, void(SkScalar, SkScalar));
   MOCK_METHOD2(onDrawOval, void(const SkRect&, const SkPaint&));
   MOCK_METHOD2(onCustomCallback, void(SkCanvas*, uint32_t));
   MOCK_METHOD0(onFlush, void());
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 6772b94..db1899c 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -171,37 +171,27 @@
   # the VR resurces to chrome_app_java_resources.
   android_resources("chrome_vr_java_resources") {
     resource_dirs = [ "//chrome/android/java/res_vr" ]
-    deps = [
-      ":chrome_app_java_resources",
-    ]
+    deps = [ ":chrome_app_java_resources" ]
   }
 }
 
 if (enable_feed_in_chrome) {
   android_resources("chrome_feed_java_resources") {
     resource_dirs = [ "//chrome/android/feed/core/java/res" ]
-    deps = [
-      ":chrome_app_java_resources",
-    ]
+    deps = [ ":chrome_app_java_resources" ]
     custom_package = "org.chromium.chrome.feed"
   }
 }
 
 android_resources("chrome_download_java_resources") {
   resource_dirs = [ "//chrome/android/java/res_download" ]
-  deps = [
-    ":chrome_app_java_resources",
-  ]
+  deps = [ ":chrome_app_java_resources" ]
   custom_package = "org.chromium.chrome.download"
 }
 
 android_library("app_hooks_java") {
-  sources = [
-    app_hooks_impl,
-  ]
-  deps = [
-    ":chrome_java",
-  ]
+  sources = [ app_hooks_impl ]
+  deps = [ ":chrome_java" ]
   jacoco_never_instrument = true
 }
 
@@ -215,9 +205,7 @@
 
 java_cpp_template("vr_build_config") {
   package_path = "org/chromium/chrome/browser/vr"
-  sources = [
-    "//chrome/android/java/src/org/chromium/chrome/browser/vr/VrBuildConfig.template",
-  ]
+  sources = [ "//chrome/android/java/src/org/chromium/chrome/browser/vr/VrBuildConfig.template" ]
   if (enable_vr) {
     defines = [ "ENABLE_VR" ]
   }
@@ -226,12 +214,8 @@
 # This prevents a cyclic dependency for features depending on the compositor.
 # TODO(crbug.com/846440): Move this to features/compositor when ready.
 android_library("chrome_public_java") {
-  sources = [
-    "//chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewResizer.java",
-  ]
-  deps = [
-    "//third_party/android_deps:androidx_annotation_annotation_java",
-  ]
+  sources = [ "//chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewResizer.java" ]
+  deps = [ "//third_party/android_deps:androidx_annotation_annotation_java" ]
 }
 
 android_library("chrome_java") {
@@ -550,9 +534,7 @@
 action("chrome_android_java_google_api_keys_srcjar") {
   script = "//build/android/gyp/java_google_api_keys.py"
   _output_path = "$target_gen_dir/$target_name.srcjar"
-  outputs = [
-    _output_path,
-  ]
+  outputs = [ _output_path ]
   args = [
     "--srcjar",
     rebase_path(_output_path, root_build_dir),
@@ -600,54 +582,38 @@
 }
 
 java_cpp_enum("sync_user_settings_enums_java") {
-  sources = [
-    "//components/sync/driver/sync_user_settings.h",
-  ]
+  sources = [ "//components/sync/driver/sync_user_settings.h" ]
 }
 
 java_cpp_strings("chrome_android_java_switches_srcjar") {
-  sources = [
-    "//chrome/common/chrome_switches.cc",
-  ]
+  sources = [ "//chrome/common/chrome_switches.cc" ]
   template = "//chrome/android/java_templates/ChromeSwitches.java.tmpl"
 }
 
 proto_java_library("partner_location_descriptor_proto_java") {
   proto_path = "java/src/org/chromium/chrome/browser/omnibox/geo"
-  sources = [
-    "$proto_path/partner_location_descriptor.proto",
-  ]
+  sources = [ "$proto_path/partner_location_descriptor.proto" ]
 }
 
 proto_java_library("thumbnail_cache_entry_proto_java") {
   proto_path = "java/src/org/chromium/chrome/browser/widget"
-  sources = [
-    "$proto_path/thumbnail_cache_entry.proto",
-  ]
+  sources = [ "$proto_path/thumbnail_cache_entry.proto" ]
 }
 
 proto_java_library("update_proto_java") {
   proto_path = "java/src/org/chromium/chrome/browser/omaha/metrics"
-  sources = [
-    "$proto_path/update_success_tracking.proto",
-  ]
+  sources = [ "$proto_path/update_success_tracking.proto" ]
 }
 
 proto_java_library("usage_stats_proto_java") {
   proto_path = "../browser/android/usage_stats"
-  sources = [
-    "$proto_path/website_event.proto",
-  ]
+  sources = [ "$proto_path/website_event.proto" ]
 }
 
 java_cpp_template("resource_id_javagen") {
-  sources = [
-    "java/ResourceId.template",
-  ]
+  sources = [ "java/ResourceId.template" ]
   package_path = "org/chromium/chrome/browser"
-  inputs = [
-    "../browser/android/resource_id.h",
-  ]
+  inputs = [ "../browser/android/resource_id.h" ]
 }
 
 junit_binary("chrome_junit_tests") {
@@ -720,9 +686,7 @@
     "//url/mojom:url_mojom_gurl_java",
   ]
 
-  data_deps = [
-    "//testing/buildbot/filters:chrome_junit_tests_filters",
-  ]
+  data_deps = [ "//testing/buildbot/filters:chrome_junit_tests_filters" ]
 
   package_name = chrome_public_manifest_package
 }
@@ -730,9 +694,7 @@
 process_version("chrome_version_constants") {
   process_only = true
   template_file = "java/ChromeVersionConstants.java.version"
-  sources = [
-    branding_file_path,
-  ]
+  sources = [ branding_file_path ]
   output = _chrome_version_java_file
 }
 
@@ -1119,9 +1081,7 @@
   resource_dirs = [ "java/res_chromium" ]
 
   # Dep needed to ensure override works properly.
-  deps = [
-    ":chrome_app_java_resources",
-  ]
+  deps = [ ":chrome_app_java_resources" ]
 }
 
 version_resource_dir = "$target_gen_dir/templates/chrome_version_xml/res"
@@ -1129,37 +1089,26 @@
 process_version("version_xml") {
   process_only = true
   template_file = "java/version_strings.xml.template"
-  sources = [
-    "//chrome/VERSION",
-  ]
+  sources = [ "//chrome/VERSION" ]
   output = version_resource_file
 }
 
 android_resources("product_version_resources") {
-  sources = [
-    version_resource_file,
-  ]
+  sources = [ version_resource_file ]
   custom_package = "org.chromium.base"
-  deps = [
-    ":version_xml",
-  ]
+  deps = [ ":version_xml" ]
 }
 
 java_group("chrome_public_non_pak_assets") {
-  deps = [
-    "//chrome/android/webapk/libs/runtime_library:runtime_library_assets",
-  ]
+  deps =
+      [ "//chrome/android/webapk/libs/runtime_library:runtime_library_assets" ]
 }
 
 java_group("chrome_public_v8_assets") {
   if (use_v8_context_snapshot) {
-    deps = [
-      "//tools/v8_context_snapshot:v8_context_snapshot_assets",
-    ]
+    deps = [ "//tools/v8_context_snapshot:v8_context_snapshot_assets" ]
   } else {
-    deps = [
-      "//v8:v8_external_startup_data_assets",
-    ]
+    deps = [ "//v8:v8_external_startup_data_assets" ]
   }
 }
 
@@ -1184,9 +1133,7 @@
 
   # Add dep to ensure these override the ones in
   # chrome_public_apk_template_resources.
-  deps = [
-    ":chrome_public_apk_template_resources",
-  ]
+  deps = [ ":chrome_public_apk_template_resources" ]
   variables = [ "manifest_package=$chrome_public_test_manifest_package" ]
 }
 
@@ -1196,9 +1143,7 @@
     "../browser/android/chrome_entry_point.cc",
     chrome_jni_registration_header,
   ]
-  deps = [
-    ":chrome_jni_registration($default_toolchain)",
-  ]
+  deps = [ ":chrome_jni_registration($default_toolchain)" ]
 
   if (enable_vr) {
     deps += [ "//chrome/browser/android/vr:module_factory" ]
@@ -1309,13 +1254,9 @@
             _fat_lib_toolchain = android_secondary_abi_toolchain
           }
         }
-        deps = [
-          ":${_lib}($_fat_lib_toolchain)",
-        ]
+        deps = [ ":${_lib}($_fat_lib_toolchain)" ]
 
-        inputs = [
-          get_label_info(deps[0], "root_out_dir") + _lib_path,
-        ]
+        inputs = [ get_label_info(deps[0], "root_out_dir") + _lib_path ]
         output = _resource_whitelist_file
       }
 
@@ -1339,17 +1280,11 @@
           _system_webview_en_US_locale_pak =
               "$root_out_dir/android_webview/locales/en-US.pak"
 
-          inputs = [
-            _system_webview_en_US_locale_pak,
-          ]
+          inputs = [ _system_webview_en_US_locale_pak ]
 
-          outputs = [
-            _system_webview_locale_whitelist_file,
-          ]
+          outputs = [ _system_webview_locale_whitelist_file ]
 
-          deps = [
-            "//android_webview:repack_locales",
-          ]
+          deps = [ "//android_webview:repack_locales" ]
 
           args = [
             "list-id",
@@ -1367,9 +1302,7 @@
             _system_webview_locale_whitelist_file,
           ]
 
-          outputs = [
-            _locale_whitelist_file,
-          ]
+          outputs = [ _locale_whitelist_file ]
 
           deps = [
             ":$_resource_whitelist_target",
@@ -1431,9 +1364,7 @@
       }
       treat_as_locale_paks = true
 
-      deps = [
-        ":${_variant}_paks",
-      ]
+      deps = [ ":${_variant}_paks" ]
     }
 
     # This target explicitly includes locale paks via deps.
@@ -1485,9 +1416,7 @@
 template("libmonochrome_apk_or_bundle_tmpl") {
   chrome_common_shared_library(target_name) {
     forward_variables_from(invoker, "*")
-    sources = [
-      "../browser/android/monochrome_entry_point.cc",
-    ]
+    sources = [ "../browser/android/monochrome_entry_point.cc" ]
     deps = [
       "//android_webview/lib",
       "//android_webview/nonembedded",
@@ -1516,9 +1445,7 @@
 
   if (android_64bit_target_cpu) {
     group("monochrome_64_secondary_abi_lib") {
-      public_deps = [
-        ":libmonochrome_64($android_secondary_abi_toolchain)",
-      ]
+      public_deps = [ ":libmonochrome_64($android_secondary_abi_toolchain)" ]
     }
   }
 } else {
@@ -1528,9 +1455,7 @@
 
   # 32-bit browser library alias targets, pulled in by 64-bit WebView builds.
   group("monochrome_secondary_abi_lib") {
-    public_deps = [
-      ":libmonochrome($android_secondary_abi_toolchain)",
-    ]
+    public_deps = [ ":libmonochrome($android_secondary_abi_toolchain)" ]
   }
 }
 
@@ -1692,9 +1617,7 @@
 
 android_resource_sizes_test("resource_sizes_chrome_public_apk") {
   apk_name = "ChromePublic"
-  data_deps = [
-    ":chrome_public_apk",
-  ]
+  data_deps = [ ":chrome_public_apk" ]
 }
 
 chrome_public_apk_or_module_tmpl("chrome_modern_public_apk") {
@@ -1717,9 +1640,8 @@
     "//chrome/android:chrome_java",
     "//content/public/android:content_java",
   ]
-  sources = [
-    "java/src/org/chromium/chrome/browser/MonochromeApplication.java",
-  ]
+  sources =
+      [ "java/src/org/chromium/chrome/browser/MonochromeApplication.java" ]
   min_sdk_version = 24
 }
 
@@ -1828,9 +1750,7 @@
       shared_resources_whitelist_locales = locales
     }
 
-    deps = [
-      "//chrome/android:app_hooks_java",
-    ]
+    deps = [ "//chrome/android:app_hooks_java" ]
     if (!_is_trichrome) {
       deps += [
         ":monochrome_java",
@@ -2079,9 +1999,7 @@
     "//chrome/browser/util:javatests",
   ]
 
-  data_deps = [
-    "//testing/buildbot/filters:chrome_public_test_apk_filters",
-  ]
+  data_deps = [ "//testing/buildbot/filters:chrome_public_test_apk_filters" ]
 
   if (enable_chrome_android_internal) {
     data_deps += [ "//clank/build/bot/filters:chrome_public_test_apk_filters" ]
@@ -2121,9 +2039,7 @@
     min_sdk_version = 24
     target_sdk_version = 24
 
-    deps = [
-      ":chrome_test_vr_java",
-    ]
+    deps = [ ":chrome_test_vr_java" ]
     proguard_enabled = false
   }
 }
@@ -2190,9 +2106,8 @@
       "javatests/src/org/chromium/chrome/test/smoke/AndroidManifest.xml"
   target_sdk_version = android_sdk_version
   testonly = true
-  sources = [
-    "javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java",
-  ]
+  sources =
+      [ "javatests/src/org/chromium/chrome/test/smoke/ChromeSmokeTest.java" ]
   deps = [
     "//base:base_java_test_support",
     "//chrome/test/android:chrome_java_test_pagecontroller",
@@ -2313,15 +2228,9 @@
   if (defined(expected_static_initializer_count)) {
     action_with_pydeps("monochrome_static_initializers") {
       script = "//build/android/gyp/assert_static_initializers.py"
-      inputs = [
-        "$root_build_dir/apks/MonochromePublic.apk",
-      ]
-      outputs = [
-        "$target_gen_dir/$target_name.stamp",
-      ]
-      deps = [
-        ":monochrome_public_apk",
-      ]
+      inputs = [ "$root_build_dir/apks/MonochromePublic.apk" ]
+      outputs = [ "$target_gen_dir/$target_name.stamp" ]
+      deps = [ ":monochrome_public_apk" ]
       args = [
         "--expected-count=$expected_static_initializer_count",
         "--tool-prefix",
@@ -2386,18 +2295,14 @@
 if (is_official_build) {
   # Used for binary size monitoring.
   create_app_bundle_minimal_apks("chrome_modern_public_minimal_apks") {
-    deps = [
-      ":chrome_modern_public_bundle",
-    ]
+    deps = [ ":chrome_modern_public_bundle" ]
     bundle_path = "$root_build_dir/apks/ChromeModernPublic.aab"
   }
 
   android_resource_sizes_test(
       "resource_sizes_chrome_modern_public_minimal_apks") {
     file_path = "$root_build_dir/apks/ChromeModernPublic.minimal.apks"
-    data_deps = [
-      ":chrome_modern_public_minimal_apks",
-    ]
+    data_deps = [ ":chrome_modern_public_minimal_apks" ]
   }
 }
 
@@ -2536,17 +2441,13 @@
   if (is_official_build) {
     # Used for binary size monitoring.
     create_app_bundle_minimal_apks("monochrome_public_minimal_apks") {
-      deps = [
-        ":monochrome_public_bundle",
-      ]
+      deps = [ ":monochrome_public_bundle" ]
       bundle_path = "$root_build_dir/apks/MonochromePublic.aab"
     }
     android_resource_sizes_test(
         "resource_sizes_monochrome_public_minimal_apks") {
       file_path = "$root_build_dir/apks/MonochromePublic.minimal.apks"
-      data_deps = [
-        ":monochrome_public_minimal_apks",
-      ]
+      data_deps = [ ":monochrome_public_minimal_apks" ]
     }
   }
 
diff --git a/chrome/android/chrome_common_shared_library.gni b/chrome/android/chrome_common_shared_library.gni
index 0927f56..a491d1b 100644
--- a/chrome/android/chrome_common_shared_library.gni
+++ b/chrome/android/chrome_common_shared_library.gni
@@ -78,9 +78,7 @@
     # Use a dynamically-generated linker script.
     configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
     deps += [ ":$_linker_script_target" ]
-    inputs = [
-      "$_linker_script",
-    ]
+    inputs = [ "$_linker_script" ]
     if (!defined(ldflags)) {
       ldflags = []
     }
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index f0b8d0e7..3d81efa 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -663,10 +663,8 @@
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategory.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTile.java",
-  "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTileView.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesIPH.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java",
-  "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesTileView.java",
   "java/src/org/chromium/chrome/browser/explore_sites/StableScrollLayoutManager.java",
@@ -1369,7 +1367,6 @@
   "java/src/org/chromium/chrome/browser/services/gcm/GCMBackgroundTask.java",
   "java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java",
   "java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java",
-  "java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java",
   "java/src/org/chromium/chrome/browser/settings/HyperlinkPreference.java",
   "java/src/org/chromium/chrome/browser/settings/LearnMorePreference.java",
   "java/src/org/chromium/chrome/browser/settings/LocationSettings.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 5fc2e2b..7b306df8 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -72,7 +72,6 @@
   "junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTaskUnitTest.java",
   "junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java",
   "junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPageStateUnitTest.java",
-  "junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSectionUnitTest.java",
   "junit/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtilsTest.java",
   "junit/src/org/chromium/chrome/browser/feedback/FeedbackCollectorTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencerTest.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 12c17b0..0d3abf1 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -113,13 +113,9 @@
       }
 
       if (android_64bit_target_cpu) {
-        deps = [
-          "//chrome/android:lib${library_target}($android_secondary_abi_toolchain)",
-        ]
+        deps = [ "//chrome/android:lib${library_target}($android_secondary_abi_toolchain)" ]
       } else {
-        deps = [
-          "//chrome/android:lib${library_target}",
-        ]
+        deps = [ "//chrome/android:lib${library_target}" ]
       }
     }
   } else if (defined(invoker.shared_library_for_unwind_asset)) {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/BUILD.gn b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/BUILD.gn
index 518d578..980412c 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/BUILD.gn
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/BUILD.gn
@@ -400,9 +400,7 @@
 
 android_resources("sharedstream_ui_resources") {
   resource_dirs = [ "sharedstream/ui/res" ]
-  deps = [
-    "//chrome/android:chrome_app_java_resources",
-  ]
+  deps = [ "//chrome/android:chrome_app_java_resources" ]
   custom_package = "org.chromium.chrome.browser.feed.library.sharedstream.ui"
 }
 
@@ -413,18 +411,14 @@
 
 android_resources("basicstream_internal_drivers_resources") {
   resource_dirs = [ "sharedstream/ui/res" ]
-  deps = [
-    "//chrome/android:chrome_app_java_resources",
-  ]
+  deps = [ "//chrome/android:chrome_app_java_resources" ]
   custom_package =
       "org.chromium.chrome.browser.feed.library.basicstream.internal.drivers"
 }
 
 android_resources("basicstream_internal_viewholders_resources") {
   resource_dirs = [ "basicstream/internal/viewholders/res" ]
-  deps = [
-    ":sharedstream_ui_resources",
-  ]
+  deps = [ ":sharedstream_ui_resources" ]
   custom_package = "org.chromium.chrome.browser.feed.library.basicstream.internal.viewholders"
 }
 
@@ -433,9 +427,7 @@
     "basicstream/res/",
     "sharedstream/ui/res",
   ]
-  deps = [
-    "//chrome/android:chrome_app_java_resources",
-  ]
+  deps = [ "//chrome/android:chrome_app_java_resources" ]
   custom_package = "org.chromium.chrome.browser.feed.library.basicstream"
 }
 
@@ -446,9 +438,7 @@
 
 android_resources("sharedstream_contextmenumanager_resources") {
   resource_dirs = [ "sharedstream/ui/res" ]
-  deps = [
-    "//chrome/android:chrome_app_java_resources",
-  ]
+  deps = [ "//chrome/android:chrome_app_java_resources" ]
   custom_package =
       "org.chromium.chrome.browser.feed.library.sharedstream.contextmenumanager"
 }
diff --git a/chrome/android/java/res/drawable-hdpi/vr_headset.png b/chrome/android/java/res/drawable-hdpi/vr_headset.png
new file mode 100644
index 0000000..bb72cf4f
--- /dev/null
+++ b/chrome/android/java/res/drawable-hdpi/vr_headset.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/vr_headset.png b/chrome/android/java/res/drawable-mdpi/vr_headset.png
new file mode 100644
index 0000000..42622a47
--- /dev/null
+++ b/chrome/android/java/res/drawable-mdpi/vr_headset.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/vr_headset.png b/chrome/android/java/res/drawable-xhdpi/vr_headset.png
new file mode 100644
index 0000000..5ee2bb1
--- /dev/null
+++ b/chrome/android/java/res/drawable-xhdpi/vr_headset.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/vr_headset.png b/chrome/android/java/res/drawable-xxhdpi/vr_headset.png
new file mode 100644
index 0000000..57278c11
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxhdpi/vr_headset.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/vr_headset.png b/chrome/android/java/res/drawable-xxxhdpi/vr_headset.png
new file mode 100644
index 0000000..261bda3
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxxhdpi/vr_headset.png
Binary files differ
diff --git a/chrome/android/java/res/drawable/ic_article_blue_24dp.xml b/chrome/android/java/res/drawable/ic_article_blue_24dp.xml
deleted file mode 100644
index 223976ff..0000000
--- a/chrome/android/java/res/drawable/ic_article_blue_24dp.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<vector xmlns:tools="http://schemas.android.com/tools" tools:targetApi="21"
-    android:autoMirrored="true" android:height="24dp"
-    android:viewportHeight="24" android:viewportWidth="24"
-    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="@color/default_icon_color_blue" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
-</vector>
diff --git a/chrome/android/java/res/drawable/ic_directions_run_blue_24dp.xml b/chrome/android/java/res/drawable/ic_directions_run_blue_24dp.xml
deleted file mode 100644
index 473c8d9..0000000
--- a/chrome/android/java/res/drawable/ic_directions_run_blue_24dp.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<vector
-    xmlns:tools="http://schemas.android.com/tools" tools:targetApi="21"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp" android:width="24dp"
-    android:viewportHeight="24.0" android:viewportWidth="24.0">
-    <path android:fillColor="@color/default_icon_color_blue" android:pathData="M13.49,5.48c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.89,19.38l1,-4.4 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3c1.3,1.5 3.3,2.5 5.5,2.5v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1l-5.2,2.2v4.7h2v-3.4l1.8,-0.7 -1.6,8.1 -4.9,-1 -0.4,2 7,1.4z"/>
-</vector>
diff --git a/chrome/android/java/res/drawable/ic_shopping_basket_blue_24dp.xml b/chrome/android/java/res/drawable/ic_shopping_basket_blue_24dp.xml
deleted file mode 100644
index aabda18..0000000
--- a/chrome/android/java/res/drawable/ic_shopping_basket_blue_24dp.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<vector android:height="24dp" android:width="24dp"
-    xmlns:tools="http://schemas.android.com/tools" tools:targetApi="21"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:viewportHeight="24.0" android:viewportWidth="24.0">
-    <path android:fillColor="@color/default_icon_color_blue" android:pathData="M17.21,9l-4.38,-6.56c-0.19,-0.28 -0.51,-0.42 -0.83,-0.42 -0.32,0 -0.64,0.14 -0.83,0.43L6.79,9L2,9c-0.55,0 -1,0.45 -1,1 0,0.09 0.01,0.18 0.04,0.27l2.54,9.27c0.23,0.84 1,1.46 1.92,1.46h13c0.92,0 1.69,-0.62 1.93,-1.46l2.54,-9.27L23,10c0,-0.55 -0.45,-1 -1,-1h-4.79zM9,9l3,-4.4L15,9L9,9zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
-</vector>
diff --git a/chrome/android/java/res/layout/explore_sites_category_tile_view.xml b/chrome/android/java/res/layout/explore_sites_category_tile_view.xml
deleted file mode 100644
index 258f178b..0000000
--- a/chrome/android/java/res/layout/explore_sites_category_tile_view.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryTileView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="@dimen/tile_view_width"
-    android:layout_height="wrap_content"
-    android:paddingStart="@dimen/tile_view_padding"
-    android:paddingEnd="@dimen/tile_view_padding" >
-      <include
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        layout="@layout/tile_view_modern" />
-</org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryTileView>
diff --git a/chrome/android/java/res/layout/explore_sites_category_tile_view_condensed.xml b/chrome/android/java/res/layout/explore_sites_category_tile_view_condensed.xml
deleted file mode 100644
index 86ad1c5..0000000
--- a/chrome/android/java/res/layout/explore_sites_category_tile_view_condensed.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-
-<org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryTileView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="@dimen/tile_view_width_condensed"
-    android:layout_height="wrap_content"
-    android:paddingStart="@dimen/tile_view_padding"
-    android:paddingEnd="@dimen/tile_view_padding" >
-      <include
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        layout="@layout/tile_view_modern_condensed" />
-</org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryTileView>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 34aed1b..cafafb9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -265,7 +265,7 @@
     public static final String INTENT_BLOCK_EXTERNAL_FORM_REDIRECT_NO_GESTURE =
             "IntentBlockExternalFormRedirectsNoGesture";
     public static final String INTEREST_FEED_CONTENT_SUGGESTIONS = "InterestFeedContentSuggestions";
-    public static final String JELLY_BEAN_SUPPORTED = "JellyBeanSupported";
+    public static final String KITKAT_SUPPORTED = "KitKatSupported";
     public static final String LOOKALIKE_NAVIGATION_URL_SUGGESTIONS_UI =
             "LookalikeUrlNavigationSuggestionsUI";
     public static final String SEARCH_ENGINE_PROMO_EXISTING_DEVICE =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
index b0266ff..46b54e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
@@ -243,7 +243,8 @@
      */
     private void shareImageWithLastShareComponent() {
         retrieveImage((Uri imageUri) -> {
-            ShareHelper.shareImage(mWindow, ShareHelper.getLastShareComponentName(null), imageUri);
+            ShareHelper.shareImage(
+                    mWindow, ShareHelper.getLastShareByChromeComponentName(), imageUri);
         });
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
index 2d2404b..f15b561 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java
@@ -32,25 +32,6 @@
     }
 
     /**
-     * @Deprecated Please use getCatalog instead.
-     *
-     * Fetches the catalog data from disk for Explore surfaces.
-     *
-     * Callback will be called with |null| if an error occurred.
-     */
-    @Deprecated
-    public static void getEspCatalog(
-            Profile profile, Callback<List<ExploreSitesCategory>> callback) {
-        if (sCatalogForTesting != null) {
-            callback.onResult(sCatalogForTesting);
-            return;
-        }
-
-        List<ExploreSitesCategory> result = new ArrayList<>();
-        ExploreSitesBridgeJni.get().getEspCatalog(profile, result, callback);
-    }
-
-    /**
      * Retrieves the catalog data for Explore surfaces by attempting to retrieve the data from disk.
      *
      * If the catalog is not available on the disk, then loads the catalog from the network onto the
@@ -101,19 +82,6 @@
     }
 
     /**
-     * Returns a Bitmap representing a summary of the sites available in the catalog for a specific
-     * category.
-     */
-    public static void getCategoryImage(
-            Profile profile, int categoryID, int pixelSize, Callback<Bitmap> callback) {
-        if (sCatalogForTesting != null) {
-            callback.onResult(null);
-            return;
-        }
-        ExploreSitesBridgeJni.get().getCategoryImage(profile, categoryID, pixelSize, callback);
-    }
-
-    /**
      * Returns a Bitmap representing a summary of the sites available in the catalog.
      */
     public static void getSummaryImage(Profile profile, int pixelSize, Callback<Bitmap> callback) {
@@ -157,15 +125,6 @@
     }
 
     /**
-     * Gets the current Finch variation for last MostLikely icon that is configured by flag or
-     * experiment.
-     */
-    @MostLikelyVariation
-    public static int getIconVariation() {
-        return ExploreSitesBridgeJni.get().getIconVariation();
-    }
-
-    /**
      * Gets the current Finch variation for dense that is configured by flag or experiment.
      * */
     @DenseVariation
@@ -174,9 +133,7 @@
     }
 
     public static boolean isEnabled(@ExploreSitesVariation int variation) {
-        return variation == ExploreSitesVariation.ENABLED
-                || variation == ExploreSitesVariation.PERSONALIZED
-                || variation == ExploreSitesVariation.MOST_LIKELY;
+        return variation == ExploreSitesVariation.ENABLED;
     }
 
     public static boolean isExperimental(@ExploreSitesVariation int variation) {
@@ -187,18 +144,6 @@
         return variation != DenseVariation.ORIGINAL;
     }
 
-    public static boolean isIntegratedWithMostLikely(@ExploreSitesVariation int variation) {
-        return variation == ExploreSitesVariation.MOST_LIKELY;
-    }
-
-    /**
-     * Increments the ntp_shown_count for a particular category.
-     * @param categoryId the row id of the category to increment show count for.
-     */
-    public static void incrementNtpShownCount(Profile profile, int categoryId) {
-        ExploreSitesBridgeJni.get().incrementNtpShownCount(profile, categoryId);
-    }
-
     @CalledByNative
     static void scheduleDailyTask() {
         ExploreSitesBackgroundTask.schedule(false /* updateCurrent */);
@@ -222,19 +167,13 @@
     @NativeMethods
     interface Natives {
         int getVariation();
-        int getIconVariation();
         int getDenseVariation();
-        void getEspCatalog(Profile profile, List<ExploreSitesCategory> result,
-                Callback<List<ExploreSitesCategory>> callback);
         void getIcon(Profile profile, int siteID, Callback<Bitmap> callback);
         void updateCatalogFromNetwork(
                 Profile profile, boolean isImmediateFetch, Callback<Boolean> callback);
-        void getCategoryImage(
-                Profile profile, int categoryID, int pixelSize, Callback<Bitmap> callback);
         void getSummaryImage(Profile profile, int pixelSize, Callback<Bitmap> callback);
         void blacklistSite(Profile profile, String url);
         void recordClick(Profile profile, String url, int type);
-        void incrementNtpShownCount(Profile profile, int categoryId);
         void getCatalog(Profile profile, int source, List<ExploreSitesCategory> result,
                 Callback<List<ExploreSitesCategory>> callback);
         void initializeCatalog(Profile profile, int source);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTileView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTileView.java
deleted file mode 100644
index 3889a8a72..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTileView.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.explore_sites;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.compositor.animation.CompositorAnimationHandler;
-import org.chromium.chrome.browser.ntp.TitleUtil;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.suggestions.tile.TileWithTextView;
-
-/**
- * A category tile for ExploreSites, containing an icon that is a composition of sites' favicons
- * within the category.  Alternatively, a MORE button.
- */
-public class ExploreSitesCategoryTileView extends TileWithTextView {
-    private static final int FADE_ANIMATION_TIME_MS = 300;
-    private static final int TITLE_LINES = 1;
-    private static final boolean SUPPORTED_OFFLINE = false;
-
-    /** The data currently associated to this tile. */
-    private ExploreSitesCategory mCategory;
-
-    /**
-     * Constructor for inflating from XML.
-     */
-    public ExploreSitesCategoryTileView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    /**
-     * Initializes the view using the data held by {@code tile}. This should be called immediately
-     * after inflation.
-     * @param category The object that holds the data to populate this view.
-     */
-    public void initialize(ExploreSitesCategory category, Profile profile) {
-        super.initialize(TitleUtil.getTitleForDisplay(category.getTitle(), category.getUrl()),
-                SUPPORTED_OFFLINE, category.getDrawable(), TITLE_LINES);
-        mCategory = category;
-
-        // Correct the properties of the icon for categories, it should be the entire size of the
-        // icon background now.
-        mIconView.setScaleType(ImageView.ScaleType.CENTER);
-        MarginLayoutParams layoutParams = (MarginLayoutParams) mIconView.getLayoutParams();
-        int tileViewIconSize =
-                getContext().getResources().getDimensionPixelSize(R.dimen.tile_view_icon_size);
-        layoutParams.width = tileViewIconSize;
-        layoutParams.height = tileViewIconSize;
-        layoutParams.topMargin = getContext().getResources().getDimensionPixelSize(
-                R.dimen.tile_view_icon_background_margin_top_modern);
-        mIconView.setLayoutParams(layoutParams);
-        Context context = getContext();
-
-        if (mCategory.getType() == ExploreSitesCategory.CategoryType.MORE_BUTTON) {
-            ExploreSitesIPH.configureIPH(this, profile);
-        }
-    }
-
-    /** Retrieves url associated with this view. */
-    public String getUrl() {
-        return mCategory.getUrl();
-    }
-
-    public ExploreSitesCategory getCategory() {
-        return mCategory;
-    }
-
-    /** Renders icon based on tile data.  */
-    public void renderIcon(ExploreSitesCategory category) {
-        mCategory = category;
-        // If the category is a placeholder, just instantly render it, as we can assume there has
-        // been no appreciable delay since the NTP was initialized.
-        if (mCategory.isPlaceholder()) {
-            setIconDrawable(category.getDrawable());
-            return;
-        }
-        fadeThumbnailIn(category.getDrawable());
-    }
-
-    private void fadeThumbnailIn(Drawable thumbnail) {
-        int duration = FADE_ANIMATION_TIME_MS;
-
-        // If animations are disabled, just show the thumbnail.
-        if (CompositorAnimationHandler.isInTestingMode()) {
-            setIconDrawable(thumbnail);
-            return;
-        }
-
-        // We have some transition time, but no existing icon.  TransitionDrawable requires two or
-        // more drawables to crossfade, so manually fade in here.
-        if (mIconView.getDrawable() == null) {
-            mIconView.setImageDrawable(thumbnail);
-            mIconView.setAlpha(0.0f);
-            mIconView.animate().alpha(1.0f).setDuration(duration).start();
-            return;
-        }
-
-        mIconView.animate()
-                .alpha(0.0f)
-                .setDuration(duration / 2)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIconView.setImageDrawable(thumbnail);
-                        mIconView.animate().alpha(1.0f).setDuration(duration / 2).start();
-                    }
-                })
-                .start();
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
index 5d063c7..65cf49a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
@@ -52,8 +52,7 @@
 public class ExploreSitesPage extends BasicNativePage {
     private static final long BACK_NAVIGATION_TIMEOUT_FOR_UMA = DateUtils.SECOND_IN_MILLIS * 30;
     private static final String CONTEXT_MENU_USER_ACTION_PREFIX = "ExploreSites";
-    private static final int INITIAL_SCROLL_POSITION = 3;
-    private static final int INITIAL_SCROLL_POSITION_PERSONALIZED = 0;
+    private static final int INITIAL_SCROLL_POSITION = 0;
     private static final String NAVIGATION_ENTRY_PAGE_STATE_KEY = "ExploreSitesPageState";
     // Constants that dictate sizes of rows and columns
     private static final int MAX_COLUMNS_DENSE_TITLE_BOTTOM = 5;
@@ -270,13 +269,7 @@
             }
         });
 
-        // We don't want to scroll to the 4th category if personalized
-        // or integrated with Most Likely.
-        int variation = ExploreSitesBridge.getVariation();
-        mInitialScrollPosition = variation == ExploreSitesVariation.PERSONALIZED
-                        || ExploreSitesBridge.isIntegratedWithMostLikely(variation)
-                ? INITIAL_SCROLL_POSITION_PERSONALIZED
-                : INITIAL_SCROLL_POSITION;
+        mInitialScrollPosition = INITIAL_SCROLL_POSITION;
 
         ExploreSitesBridge.getCatalog(mProfile,
                 ExploreSitesCatalogUpdateRequestSource.EXPLORE_SITES_PAGE, this::translateToModel);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java
deleted file mode 100644
index f637cc8a..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSection.java
+++ /dev/null
@@ -1,263 +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.explore_sites;
-
-import static org.chromium.components.feature_engagement.EventConstants.EXPLORE_SITES_TILE_TAPPED;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.support.graphics.drawable.VectorDrawableCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.explore_sites.ExploreSitesCategory.CategoryType;
-import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
-import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
-import org.chromium.chrome.browser.ntp.NewTabPageUma;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.suggestions.SuggestionsConfig.TileStyle;
-import org.chromium.chrome.browser.suggestions.tile.TileGridLayout;
-import org.chromium.chrome.browser.util.ViewUtils;
-import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.ui.base.PageTransition;
-import org.chromium.ui.mojom.WindowOpenDisposition;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Describes a portion of UI responsible for rendering a group of categories.
- * It abstracts general tasks related to initializing and fetching data for the UI.
- */
-public class ExploreSitesSection {
-    private static final String TAG = "ExploreSitesSection";
-    private static final int MAX_CATEGORIES = 3;
-    // This is number of times in a row categories are shown in a row before being rotated out.
-    private static final int TIMES_PER_ROUND = 3;
-    // Max times a category is shown due to rotation.
-    private static final int MAX_TIMES_ROTATED = 6;
-
-    @TileStyle
-    private int mStyle;
-    private Profile mProfile;
-    private NativePageNavigationDelegate mNavigationDelegate;
-    private TileGridLayout mExploreSection;
-
-    public ExploreSitesSection(View view, Profile profile,
-            NativePageNavigationDelegate navigationDelegate, @TileStyle int style) {
-        mProfile = profile;
-        mStyle = style;
-        mExploreSection = (TileGridLayout) view;
-        mExploreSection.setMaxRows(1);
-        mExploreSection.setMaxColumns(MAX_CATEGORIES + 1);
-        mNavigationDelegate = navigationDelegate;
-        initialize();
-    }
-
-    private void initialize() {
-        RecordUserAction.record("Android.ExploreSitesNTP.Opened");
-        ExploreSitesBridge.getEspCatalog(mProfile, this::gotEspCatalog);
-    }
-
-    private Drawable getVectorDrawable(int resource) {
-        return VectorDrawableCompat.create(
-                getContext().getResources(), resource, getContext().getTheme());
-    }
-
-    private Context getContext() {
-        return mExploreSection.getContext();
-    }
-
-    /**
-     * Creates the predetermined categories for when we don't yet have a catalog from the
-     * ExploreSites API server.
-     */
-    private List<ExploreSitesCategory> createDefaultCategoryTiles() {
-        List<ExploreSitesCategory> categoryList = new ArrayList<>();
-
-        // News category.
-        ExploreSitesCategory category = ExploreSitesCategory.createPlaceholder(CategoryType.NEWS,
-                getContext().getString(R.string.explore_sites_default_category_news));
-        category.setDrawable(getVectorDrawable(R.drawable.ic_article_blue_24dp));
-        categoryList.add(category);
-
-        // Shopping category.
-        category = ExploreSitesCategory.createPlaceholder(CategoryType.SHOPPING,
-                getContext().getString(R.string.explore_sites_default_category_shopping));
-        category.setDrawable(getVectorDrawable(R.drawable.ic_shopping_basket_blue_24dp));
-        categoryList.add(category);
-
-        // Sport category.
-        category = ExploreSitesCategory.createPlaceholder(CategoryType.SPORT,
-                getContext().getString(R.string.explore_sites_default_category_sports));
-        category.setDrawable(getVectorDrawable(R.drawable.ic_directions_run_blue_24dp));
-        categoryList.add(category);
-
-        return categoryList;
-    }
-
-    private ExploreSitesCategory createMoreTileCategory() {
-        ExploreSitesCategory category =
-                ExploreSitesCategory.createPlaceholder(CategoryType.MORE_BUTTON,
-                        getContext().getString(R.string.explore_sites_top_sites_tile));
-        category.setDrawable(getVectorDrawable(R.drawable.ic_arrow_forward_blue_24dp));
-        return category;
-    }
-
-    private void createTileView(int tileIndex, ExploreSitesCategory category) {
-        ExploreSitesCategoryTileView tileView;
-        if (mStyle == TileStyle.MODERN_CONDENSED) {
-            tileView = (ExploreSitesCategoryTileView) LayoutInflater.from(getContext())
-                               .inflate(R.layout.explore_sites_category_tile_view_condensed,
-                                       mExploreSection, false);
-        } else {
-            tileView = (ExploreSitesCategoryTileView) LayoutInflater.from(getContext())
-                               .inflate(R.layout.explore_sites_category_tile_view, mExploreSection,
-                                       false);
-        }
-        tileView.initialize(category, mProfile);
-        mExploreSection.addView(tileView);
-        tileView.setOnClickListener((View v) -> onClicked(tileIndex, category, v));
-    }
-
-    /**
-     * Checks the result, if it indicates that we don't have a valid catalog, request one from the
-     * network.  If the network request fails, just continue but otherwise retry getting the catalog
-     * from the ExploreSitesBridge.
-     */
-    private void gotEspCatalog(List<ExploreSitesCategory> categoryList) {
-        boolean loadingCatalogFromNetwork = false;
-        if (categoryList == null || categoryList.size() == 0) {
-            loadingCatalogFromNetwork = true;
-            ExploreSitesBridge.updateCatalogFromNetwork(mProfile, true /*isImmediateFetch*/,
-                    (Boolean success) -> { updateCategoryIcons(); });
-            RecordHistogram.recordEnumeratedHistogram("ExploreSites.CatalogUpdateRequestSource",
-                    ExploreSitesCatalogUpdateRequestSource.NEW_TAB_PAGE,
-                    ExploreSitesCatalogUpdateRequestSource.NUM_ENTRIES);
-        }
-        RecordHistogram.recordBooleanHistogram(
-                "ExploreSites.NTPLoadingCatalogFromNetwork", loadingCatalogFromNetwork);
-        // Initialize with defaults right away.
-        initializeCategoryTiles(categoryList);
-    }
-
-    private void updateCategoryIcons() {
-        Map<Integer, ExploreSitesCategoryTileView> viewTypes = new HashMap<>();
-        for (int i = 0; i < mExploreSection.getChildCount(); i++) {
-            ExploreSitesCategoryTileView v =
-                    (ExploreSitesCategoryTileView) mExploreSection.getChildAt(i);
-            ExploreSitesCategory category = v.getCategory();
-            if (category == null || category.isMoreButton()) continue;
-            viewTypes.put(category.getType(), v);
-        }
-
-        ExploreSitesBridge.getEspCatalog(mProfile, (List<ExploreSitesCategory> categoryList) -> {
-            if (categoryList == null) return;
-            for (ExploreSitesCategory category : categoryList) {
-                ExploreSitesCategoryTileView v = viewTypes.get(category.getType());
-                if (v == null) {
-                    continue;
-                }
-                int iconSizePx = v.getContext().getResources().getDimensionPixelSize(
-                        R.dimen.tile_view_icon_size);
-                ExploreSitesBridge.getCategoryImage(
-                        mProfile, category.getId(), iconSizePx, (Bitmap image) -> {
-                            if (image != null) {
-                                category.setDrawable(ViewUtils.createRoundedBitmapDrawable(
-                                        v.getContext().getResources(), image, iconSizePx / 2));
-                                v.renderIcon(category);
-                            }
-                        });
-            }
-        });
-    }
-
-    private void initializeCategoryTiles(List<ExploreSitesCategory> categoryList) {
-        boolean needIcons = true;
-        if (categoryList == null || categoryList.size() == 0) {
-            categoryList = createDefaultCategoryTiles();
-            needIcons = false; // Icons are already prepared in the default tiles.
-        }
-
-        boolean isPersonalized =
-                ExploreSitesBridge.getVariation() == ExploreSitesVariation.PERSONALIZED;
-
-        if (isPersonalized) {
-            // Sort categories in order or shown priority.
-            Collections.sort(categoryList, ExploreSitesSection::compareCategoryPriority);
-        }
-
-        int tileCount = 0;
-        for (final ExploreSitesCategory category : categoryList) {
-            if (tileCount >= MAX_CATEGORIES) break;
-            // Skip empty categories from being shown on NTP.
-            if (!category.isPlaceholder() && category.getNumDisplayed() == 0) continue;
-            createTileView(tileCount, category);
-            // Increment shown count if this is a category that was rotated in.
-            // A rotated in category is defined by having no interaction count
-            // and having a shown count less than the MAX_TIMES_ROTATED.
-            if (isPersonalized && category.getInteractionCount() == 0
-                    && category.getNtpShownCount() < MAX_TIMES_ROTATED
-                    && !category.isPlaceholder()) {
-                ExploreSitesBridge.incrementNtpShownCount(mProfile, category.getId());
-            }
-            tileCount++;
-        }
-        createTileView(tileCount, createMoreTileCategory());
-        if (needIcons) {
-            updateCategoryIcons();
-        }
-    }
-
-    private void onClicked(int tileIndex, ExploreSitesCategory category, View v) {
-        recordOpenedEsp(tileIndex);
-        mNavigationDelegate.openUrl(WindowOpenDisposition.CURRENT_TAB,
-                new LoadUrlParams(category.getUrl(), PageTransition.AUTO_BOOKMARK));
-        final Tracker tracker = TrackerFactory.getTrackerForProfile(mProfile);
-        tracker.notifyEvent(EXPLORE_SITES_TILE_TAPPED);
-    }
-
-    private void recordOpenedEsp(int tileIndex) {
-        // The following must be kept in sync with the "MostVisitedTileIndex" enum in enums.xml.
-        final int kMaxTileCount = 12;
-        RecordHistogram.recordEnumeratedHistogram(
-                "ExploreSites.ClickedNTPCategoryIndex", tileIndex, kMaxTileCount);
-        NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_EXPLORE_SITES_TILE);
-        RecordUserAction.record("MobileNTPExploreSites");
-    }
-
-    @VisibleForTesting
-    static int compareCategoryPriority(ExploreSitesCategory cat1, ExploreSitesCategory cat2) {
-        // First sort by activity count. Most used categories first.
-        if (cat1.getInteractionCount() > cat2.getInteractionCount()) return -1;
-        if (cat1.getInteractionCount() < cat2.getInteractionCount()) return 1;
-        // Category 1 and 2 have the same interaction count. If that is
-        // nonzero, they are equal. Collections.sort is stable, which will preserve input order
-        // for equal categories. Otherwise we want to rotate the categories.
-        if (cat1.getInteractionCount() > 0) return 0;
-
-        // Otherwise activity count is both 0.
-        // We first sort by descending ntp_shown_count mod 3 (TIMES_PER_ROUND).
-        // This is so categories that haven't completed a round are prioritized.
-        int cat1Mod = cat1.getNtpShownCount() % TIMES_PER_ROUND;
-        int cat2Mod = cat2.getNtpShownCount() % TIMES_PER_ROUND;
-        if (cat1Mod > cat2Mod) return -1;
-        if (cat1Mod < cat2Mod) return 1;
-        // If the mods are equal, then we sort by ntp_shown_count / 3 in ascending
-        // order. This is so categories that haven't been shown yet are prioritized.
-        return (cat1.getNtpShownCount() / TIMES_PER_ROUND)
-                - (cat2.getNtpShownCount() / TIMES_PER_ROUND);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index 2b614f8..fc652879 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -36,7 +36,6 @@
 import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.explore_sites.ExperimentalExploreSitesSection;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
-import org.chromium.chrome.browser.explore_sites.ExploreSitesSection;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
@@ -181,10 +180,7 @@
         insertSiteSectionView();
 
         int variation = ExploreSitesBridge.getVariation();
-        if (ExploreSitesBridge.isEnabled(variation)
-                && !ExploreSitesBridge.isIntegratedWithMostLikely(variation)) {
-            mExploreSectionView = ((ViewStub) findViewById(R.id.explore_sites_stub)).inflate();
-        } else if (ExploreSitesBridge.isExperimental(variation)) {
+        if (ExploreSitesBridge.isExperimental(variation)) {
             ViewStub exploreStub = findViewById(R.id.explore_sites_stub);
             exploreStub.setLayoutResource(R.layout.experimental_explore_sites_section);
             mExploreSectionView = exploreStub.inflate();
@@ -236,11 +232,7 @@
         mSiteSectionViewHolder.bindDataSource(mTileGroup, tileRenderer);
 
         int variation = ExploreSitesBridge.getVariation();
-        if (ExploreSitesBridge.isEnabled(variation)
-                && !ExploreSitesBridge.isIntegratedWithMostLikely(variation)) {
-            mExploreSection = new ExploreSitesSection(mExploreSectionView, profile,
-                    mManager.getNavigationDelegate(), SuggestionsConfig.getTileStyle(mUiConfig));
-        } else if (ExploreSitesBridge.isExperimental(variation)) {
+        if (ExploreSitesBridge.isExperimental(variation)) {
             mExploreSection = new ExperimentalExploreSitesSection(
                     mExploreSectionView, profile, mManager.getNavigationDelegate());
         }
@@ -419,14 +411,6 @@
         mSiteSectionView = SiteSection.inflateSiteSection(this);
         ViewGroup.LayoutParams layoutParams = mSiteSectionView.getLayoutParams();
         layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
-        // If the explore sites section exists as its own section, then space it more closely.
-        int variation = ExploreSitesBridge.getVariation();
-        if (ExploreSitesBridge.isEnabled(variation)
-                && !ExploreSitesBridge.isIntegratedWithMostLikely(variation)) {
-            ((MarginLayoutParams) layoutParams).bottomMargin =
-                    getResources().getDimensionPixelOffset(
-                            R.dimen.tile_grid_layout_vertical_spacing);
-        }
         mSiteSectionView.setLayoutParams(layoutParams);
 
         int insertionPoint = indexOfChild(mMiddleSpacer) + 1;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
index 509352de..f5978b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
@@ -97,11 +97,11 @@
      * @return Whether the current Android OS version is supported.
      */
     public static boolean isCurrentOsVersionSupported() {
-        // By default, only Android KitKat and above is supported.
-        int oldestSupportedVersion = Build.VERSION_CODES.KITKAT;
+        // By default, only Android Lollipop and above is supported.
+        int oldestSupportedVersion = Build.VERSION_CODES.LOLLIPOP;
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.JELLY_BEAN_SUPPORTED)) {
-            oldestSupportedVersion = Build.VERSION_CODES.JELLY_BEAN;
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.KITKAT_SUPPORTED)) {
+            oldestSupportedVersion = Build.VERSION_CODES.KITKAT;
         }
         return Build.VERSION.SDK_INT >= oldestSupportedVersion;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
index dc4bcc8..718a63b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
@@ -33,6 +33,7 @@
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -49,6 +50,8 @@
 import org.chromium.base.metrics.CachedMetrics;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.content_public.browser.RenderWidgetHostView;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.UiUtils;
@@ -90,8 +93,8 @@
     public static final String EXTRA_TASK_ID = "org.chromium.chrome.extra.TASK_ID";
 
     private static final String JPEG_EXTENSION = ".jpg";
-    private static final String PACKAGE_NAME_KEY = "last_shared_package_name";
-    private static final String CLASS_NAME_KEY = "last_shared_class_name";
+    private static final String PACKAGE_NAME_KEY_SUFFIX = "last_shared_package_name";
+    private static final String CLASS_NAME_KEY_SUFFIX = "last_shared_class_name";
     private static final String EXTRA_SHARE_SCREENSHOT_AS_STREAM = "share_screenshot_as_stream";
 
     /**
@@ -666,13 +669,26 @@
     @VisibleForTesting
     public static void setLastShareComponentName(
             ComponentName component, @Nullable String sourcePackageName) {
-        SharedPreferences preferences = getSharePreferences(sourcePackageName);
+        if (sourcePackageName == null) {
+            setLastShareComponentNameForChrome(component);
+            return;
+        }
+
+        SharedPreferences preferences = getExternalAppSharingSharedPreferences();
         SharedPreferences.Editor editor = preferences.edit();
         editor.putString(getPackageNameKey(sourcePackageName), component.getPackageName());
         editor.putString(getClassNameKey(sourcePackageName), component.getClassName());
         editor.apply();
     }
 
+    private static void setLastShareComponentNameForChrome(ComponentName component) {
+        SharedPreferencesManager preferencesManager = SharedPreferencesManager.getInstance();
+        preferencesManager.writeString(
+                ChromePreferenceKeys.SHARING_LAST_SHARED_PACKAGE_NAME, component.getPackageName());
+        preferencesManager.writeString(
+                ChromePreferenceKeys.SHARING_LAST_SHARED_CLASS_NAME, component.getClassName());
+    }
+
     @VisibleForTesting
     public static Intent getShareLinkIntent(ShareParams params) {
         final boolean isFileShare = (params.getFileUris() != null);
@@ -756,25 +772,44 @@
      */
     @Nullable
     public static ComponentName getLastShareComponentName(@Nullable String sourcePackageName) {
-        SharedPreferences preferences = getSharePreferences(sourcePackageName);
+        if (sourcePackageName == null) {
+            return getLastShareByChromeComponentName();
+        }
+
+        SharedPreferences preferences = getExternalAppSharingSharedPreferences();
         String packageName = preferences.getString(getPackageNameKey(sourcePackageName), null);
         String className = preferences.getString(getClassNameKey(sourcePackageName), null);
+        return createComponentName(packageName, className);
+    }
+
+    /**
+     * Gets the {@link ComponentName} of the app that was used to last share by Chrome.
+     */
+    @Nullable
+    public static ComponentName getLastShareByChromeComponentName() {
+        SharedPreferencesManager preferencesManager = SharedPreferencesManager.getInstance();
+        String packageName = preferencesManager.readString(
+                ChromePreferenceKeys.SHARING_LAST_SHARED_PACKAGE_NAME, null);
+        String className = preferencesManager.readString(
+                ChromePreferenceKeys.SHARING_LAST_SHARED_CLASS_NAME, null);
+        return createComponentName(packageName, className);
+    }
+
+    private static ComponentName createComponentName(String packageName, String className) {
         if (packageName == null || className == null) return null;
         return new ComponentName(packageName, className);
     }
 
-    private static SharedPreferences getSharePreferences(@Nullable String sourcePackageName) {
-        return sourcePackageName != null
-                ? ContextUtils.getApplicationContext().getSharedPreferences(
-                          EXTERNAL_APP_SHARING_PREF_FILE_NAME, Context.MODE_PRIVATE)
-                : ContextUtils.getAppSharedPreferences();
+    private static SharedPreferences getExternalAppSharingSharedPreferences() {
+        return ContextUtils.getApplicationContext().getSharedPreferences(
+                EXTERNAL_APP_SHARING_PREF_FILE_NAME, Context.MODE_PRIVATE);
     }
 
-    private static String getPackageNameKey(@Nullable String sourcePackageName) {
-        return (TextUtils.isEmpty(sourcePackageName) ? "" : sourcePackageName) + PACKAGE_NAME_KEY;
+    private static String getPackageNameKey(@NonNull String sourcePackageName) {
+        return sourcePackageName + PACKAGE_NAME_KEY_SUFFIX;
     }
 
-    private static String getClassNameKey(@Nullable String sourcePackageName) {
-        return (TextUtils.isEmpty(sourcePackageName) ? "" : sourcePackageName) + CLASS_NAME_KEY;
+    private static String getClassNameKey(@NonNull String sourcePackageName) {
+        return sourcePackageName + CLASS_NAME_KEY_SUFFIX;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/SiteSection.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/SiteSection.java
index 4cb1c2f..4632f48c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/SiteSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/SiteSection.java
@@ -11,8 +11,6 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeFeatureList;
-import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.cards.ItemViewType;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder;
@@ -111,11 +109,6 @@
     }
 
     private static int getMaxTileRows() {
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.EXPLORE_SITES)
-                && !ExploreSitesBridge.isIntegratedWithMostLikely(
-                        ExploreSitesBridge.getVariation())) {
-            return 1;
-        }
         return 2;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
index 8505f83..fe99f84 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
@@ -24,7 +24,6 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
 import org.chromium.chrome.browser.explore_sites.ExploreSitesIPH;
-import org.chromium.chrome.browser.explore_sites.MostLikelyVariation;
 import org.chromium.chrome.browser.favicon.IconType;
 import org.chromium.chrome.browser.favicon.LargeIconBridge;
 import org.chromium.chrome.browser.favicon.RoundedIconGenerator;
@@ -146,28 +145,17 @@
             tileView = (TopSitesTileView) LayoutInflater.from(parentView.getContext())
                                .inflate(mTopSitesLayout, parentView, false);
 
-            int iconVariation = ExploreSitesBridge.getIconVariation();
-            if (iconVariation == MostLikelyVariation.ICON_ARROW) {
-                tile.setIcon(VectorDrawableCompat.create(
-                        mResources, R.drawable.ic_arrow_forward_blue_24dp, mTheme));
-                tile.setType(TileVisualType.ICON_REAL);
-            } else if (iconVariation == MostLikelyVariation.ICON_DOTS) {
-                tile.setIcon(VectorDrawableCompat.create(
-                        mResources, R.drawable.ic_apps_blue_24dp, mTheme));
-                tile.setType(TileVisualType.ICON_REAL);
-            } else if (iconVariation == MostLikelyVariation.ICON_GROUPED) {
-                tile.setIcon(VectorDrawableCompat.create(
-                        mResources, R.drawable.ic_apps_blue_24dp, mTheme));
-                tile.setType(TileVisualType.ICON_DEFAULT);
+            tile.setIcon(
+                    VectorDrawableCompat.create(mResources, R.drawable.ic_apps_blue_24dp, mTheme));
+            tile.setType(TileVisualType.ICON_DEFAULT);
 
-                // One task to load actual icon.
-                LargeIconBridge.LargeIconCallback bridgeCallback =
-                        setupDelegate.createIconLoadCallback(tile);
-                ExploreSitesBridge.getSummaryImage(Profile.getLastUsedProfile(), mDesiredIconSize,
-                        (Bitmap img)
-                                -> bridgeCallback.onLargeIconAvailable(
-                                        img, Color.BLACK, false, IconType.FAVICON));
-            }
+            // One task to load actual icon.
+            LargeIconBridge.LargeIconCallback bridgeCallback =
+                    setupDelegate.createIconLoadCallback(tile);
+            ExploreSitesBridge.getSummaryImage(Profile.getLastUsedProfile(), mDesiredIconSize,
+                    (Bitmap img)
+                            -> bridgeCallback.onLargeIconAvailable(
+                                    img, Color.BLACK, false, IconType.FAVICON));
         } else {
             tileView = (SuggestionsTileView) LayoutInflater.from(parentView.getContext())
                                .inflate(mLayout, parentView, false);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TopSitesTileView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TopSitesTileView.java
index 423058c1..e51973b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TopSitesTileView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TopSitesTileView.java
@@ -8,9 +8,6 @@
 import android.content.res.Resources;
 import android.util.AttributeSet;
 
-import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge;
-import org.chromium.chrome.browser.explore_sites.MostLikelyVariation;
-
 /**
  * The view for a top sites tile. Displays the title of the site beneath a large icon.
  */
@@ -26,8 +23,7 @@
     protected void setIconViewLayoutParams(Tile tile) {
         MarginLayoutParams params = (MarginLayoutParams) mIconView.getLayoutParams();
         Resources resources = getResources();
-        if (tile.getType() == TileVisualType.ICON_REAL
-                && ExploreSitesBridge.getIconVariation() == MostLikelyVariation.ICON_GROUPED) {
+        if (tile.getType() == TileVisualType.ICON_REAL) {
             // Grouped icons have extra large size.
             params.width = resources.getDimensionPixelOffset(
                     org.chromium.chrome.R.dimen.tile_view_icon_size);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArImmersiveOverlay.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArImmersiveOverlay.java
index 665ed3d..f92ac46 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArImmersiveOverlay.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArImmersiveOverlay.java
@@ -292,7 +292,7 @@
         // The JS app may have put an element into fullscreen mode during the immersive session,
         // even if this wasn't visible to the user. Ensure that we fully exit out of any active
         // fullscreen state on session end to avoid being left in a confusing state.
-        if (mActivity.getActivityTab() != null) {
+        if (mActivity.getActivityTab() != null && !mActivity.isActivityFinishingOrDestroyed()) {
             mActivity.getFullscreenManager().onExitFullscreen(mActivity.getActivityTab());
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
index 18a0693..fcbcb7b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
@@ -185,6 +185,7 @@
     @Test
     @MediumTest
     @Feature({"OfflineAutoFetch"})
+    @DisabledTest(message = "https://crbug.com/1041822")
     public void testAutoFetchTriggersOnDNSErrorWhenOffline() {
         attemptLoadPage("http://does.not.resolve.com");
         waitForRequestCount(1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
index 332400b..42ab0bc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
@@ -7,6 +7,7 @@
 import android.app.Activity;
 import android.app.Instrumentation.ActivityResult;
 import android.content.Context;
+import android.os.Build;
 import android.support.test.espresso.intent.Intents;
 import android.support.test.espresso.intent.matcher.IntentMatchers;
 import android.support.test.filters.MediumTest;
@@ -21,6 +22,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -188,6 +190,7 @@
     @MediumTest
     @Feature({"Omaha"})
     @RetryOnFailure
+    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
     public void testCurrentVersionIsSame() throws Exception {
         checkUpdateMenuItemIsNotShowing("1.2.3.4", "1.2.3.4");
     }
@@ -195,6 +198,7 @@
     @Test
     @MediumTest
     @Feature({"Omaha"})
+    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
     public void testCurrentVersionIsNewer() throws Exception {
         checkUpdateMenuItemIsNotShowing("27.0.1453.42", "26.0.1410.49");
     }
@@ -203,6 +207,7 @@
     @MediumTest
     @Feature({"Omaha"})
     @RetryOnFailure
+    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
     public void testNoVersionKnown() throws Exception {
         checkUpdateMenuItemIsNotShowing("1.2.3.4", "0");
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSectionUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSectionUnitTest.java
deleted file mode 100644
index 23a17681..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSectionUnitTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.explore_sites;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-
-/**
- * Unit tests for {@link ExploreSitesSection}
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class ExploreSitesSectionUnitTest {
-    private static final int ID = 1;
-    private static final int TYPE = 2;
-    private static final String TITLE = "foo";
-
-    @Test
-    public void categoriesWithInteractionCount() {
-        ExploreSitesCategory cat1 = new ExploreSitesCategory(ID, TYPE, TITLE, 0, 5);
-        ExploreSitesCategory cat2 = new ExploreSitesCategory(ID, TYPE, TITLE, 0, 4);
-        ExploreSitesCategory cat3 = new ExploreSitesCategory(ID, TYPE, TITLE, 0, 3);
-        ExploreSitesCategory cat4 = new ExploreSitesCategory(ID, TYPE, TITLE, 3, 0);
-
-        // Interaction is sorted in descending order.
-        // 5 > 4, so 5 is first.
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat1, cat2) < 0);
-        // 3 < 4, so 4 is first.
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat3, cat2) > 0);
-        // 5 == 5.
-        assertEquals(0, ExploreSitesSection.compareCategoryPriority(cat1, cat1));
-        // 0s are not treated special.
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat3, cat4) < 0);
-    }
-
-    @Test
-    public void categoriesWithShownCount() {
-        ExploreSitesCategory cat1 = new ExploreSitesCategory(ID, TYPE, TITLE, 2, 0);
-        ExploreSitesCategory cat2 = new ExploreSitesCategory(ID, TYPE, TITLE, 4, 0);
-        ExploreSitesCategory cat3 = new ExploreSitesCategory(ID, TYPE, TITLE, 5, 0);
-        ExploreSitesCategory cat4 = new ExploreSitesCategory(ID, TYPE, TITLE, 3, 0);
-
-        // Mods are sorted first in descending order.
-        // 4 % 3 < 5 % 3, so 5 is first.
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat2, cat3) > 0);
-        // 2 % 3 > 4 % 3, so 2 is first.
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat1, cat2) < 0);
-        // If mods are equal, sort by integer division in ascending order.
-        // 2 / 3 < 5 / 3, so 2 is first
-        assertTrue(ExploreSitesSection.compareCategoryPriority(cat1, cat3) < 0);
-        // If everything is equal, return equal.
-        assertEquals(0, ExploreSitesSection.compareCategoryPriority(cat1, cat1));
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/modules/chrome_feature_module_tmpl.gni b/chrome/android/modules/chrome_feature_module_tmpl.gni
index 55d5324..fbff253 100644
--- a/chrome/android/modules/chrome_feature_module_tmpl.gni
+++ b/chrome/android/modules/chrome_feature_module_tmpl.gni
@@ -87,9 +87,7 @@
                            ])
     android_manifest = _module_desc.android_manifest
     target_sdk_version = android_sdk_version
-    deps = [
-      ":${target_name}__module_desc_java",
-    ]
+    deps = [ ":${target_name}__module_desc_java" ]
     if (defined(_module_desc.pak_deps)) {
       deps += [ ":${target_name}__pak_assets" ]
     }
diff --git a/chrome/android/modules/extra_icu/public/BUILD.gn b/chrome/android/modules/extra_icu/public/BUILD.gn
index 871d14c..cd81faa 100644
--- a/chrome/android/modules/extra_icu/public/BUILD.gn
+++ b/chrome/android/modules/extra_icu/public/BUILD.gn
@@ -5,9 +5,7 @@
 import("//build/config/android/rules.gni")
 
 android_library("java") {
-  sources = [
-    "java/src/org/chromium/chrome/modules/extra_icu/ExtraIcu.java",
-  ]
+  sources = [ "java/src/org/chromium/chrome/modules/extra_icu/ExtraIcu.java" ]
   deps = [
     "//components/module_installer/android:module_installer_java",
     "//components/module_installer/android:module_interface_java",
diff --git a/chrome/android/webapk/libs/common/BUILD.gn b/chrome/android/webapk/libs/common/BUILD.gn
index a2fdc53..349440b 100644
--- a/chrome/android/webapk/libs/common/BUILD.gn
+++ b/chrome/android/webapk/libs/common/BUILD.gn
@@ -15,9 +15,7 @@
 }
 
 android_library("splash_java") {
-  sources = [
-    "src/org/chromium/webapk/lib/common/splash/SplashLayout.java",
-  ]
+  sources = [ "src/org/chromium/webapk/lib/common/splash/SplashLayout.java" ]
   deps = [
     ":splash_resources",
     "//third_party/android_deps:androidx_annotation_annotation_java",
diff --git a/chrome/android/webapk/shell_apk/javatests/dex_optimizer/BUILD.gn b/chrome/android/webapk/shell_apk/javatests/dex_optimizer/BUILD.gn
index df0ded92..d939315 100644
--- a/chrome/android/webapk/shell_apk/javatests/dex_optimizer/BUILD.gn
+++ b/chrome/android/webapk/shell_apk/javatests/dex_optimizer/BUILD.gn
@@ -7,9 +7,7 @@
 android_aidl("dex_optimizer_service_aidl") {
   interface_file =
       "src/org/chromium/webapk/shell_apk/test/dex_optimizer/common.aidl"
-  sources = [
-    "src/org/chromium/webapk/shell_apk/test/dex_optimizer/IDexOptimizerService.aidl",
-  ]
+  sources = [ "src/org/chromium/webapk/shell_apk/test/dex_optimizer/IDexOptimizerService.aidl" ]
 }
 
 android_apk("dex_optimizer_apk") {
@@ -24,9 +22,7 @@
   min_sdk_version = 16
   apk_name = "DexOptimizer"
   chromium_code = false
-  sources = [
-    "src/org/chromium/webapk/shell_apk/test/dex_optimizer/DexOptimizerServiceImpl.java",
-  ]
+  sources = [ "src/org/chromium/webapk/shell_apk/test/dex_optimizer/DexOptimizerServiceImpl.java" ]
   deps = [
     "//base:base_java",
     "//chrome/android/webapk/libs/client:client_java",
diff --git a/chrome/android/webapk/shell_apk/mustache_pass.gni b/chrome/android/webapk/shell_apk/mustache_pass.gni
index 244575cd..5142dd04 100644
--- a/chrome/android/webapk/shell_apk/mustache_pass.gni
+++ b/chrome/android/webapk/shell_apk/mustache_pass.gni
@@ -17,9 +17,7 @@
                              "visibility",
                              "deps",
                            ])
-    sources = [
-      invoker.input,
-    ]
+    sources = [ invoker.input ]
     if (defined(invoker.config_file)) {
       sources += [ invoker.config_file ]
     }
@@ -29,9 +27,7 @@
 
     script = "//chrome/android/webapk/shell_apk/mustache_pass.py"
 
-    outputs = [
-      invoker.output,
-    ]
+    outputs = [ invoker.output ]
 
     args = [
       "--template",
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 6991e7c..491b3e9 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9026,6 +9026,23 @@
         </message>
       </if>
 
+      <!-- WebXr permissions -->
+      <!-- TODO(crbug.com/1041009): Finalize WebXr Permissions strings -->
+      <message name="IDS_VR_PERMISSION_FRAGMENT" desc="Permission request shown if the user is visiting a site that wants to use VR. Follows a prompt: 'This site would like to:">
+        Access Virtual Reality devices and data
+      </message>
+      <message name="IDS_AR_PERMISSION_FRAGMENT" desc="Permission request shown if the user is visiting a site that wants to use AR. Follows a prompt: 'This site would like to:">
+        Track camera position and map your room
+      </message>
+      <if expr="is_android">
+        <message name="IDS_VR_INFOBAR_TEXT" desc="Text requesting permission for a site to use VR">
+          <ph name="URL">$1<ex>google.com</ex></ph> wants to be able to start virtual reality sessions
+        </message>
+        <message name="IDS_AR_INFOBAR_TEXT" desc="Text requesting permission for a site to use AR">
+          <ph name="URL">$1<ex>google.com</ex></ph> wants to track camera position and map your room
+        </message>
+      </if>
+
       <!-- Sensor messages -->
       <message name="IDS_SENSORS_ALLOWED_TOOLTIP" desc="Location bar icon tooltip text when a page is allowed to use device's sensors.">
         This site is using motion or light sensors.
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 83e3418..ec722500 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -730,6 +730,9 @@
     <message name="IDS_SETTINGS_PLUGIN_VM_REMOVE_BUTTON" desc="Label for the button to open a dialog confirming removal of Plugin VM.">
       Remove
     </message>
+    <message name="IDS_SETTINGS_PLUGIN_VM_CONFIRM_REMOVE_DIALOG_BODY" desc="Message of the confirmation dialog displayed before removal begins.">
+      Removing Plugin VM will delete your VM. This includes its applications, settings, and data. Are you sure you wish to continue?
+    </message>
 
     <!-- Android Apps Page -->
     <message name="IDS_SETTINGS_ANDROID_APPS_TITLE" desc="The title of Google Play Store (Arc++ / Android Apps) section.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index d960919..20d2dad 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -169,6 +169,10 @@
     "bitmap_fetcher/bitmap_fetcher_service.h",
     "bitmap_fetcher/bitmap_fetcher_service_factory.cc",
     "bitmap_fetcher/bitmap_fetcher_service_factory.h",
+    "bluetooth/bluetooth_chooser_context.cc",
+    "bluetooth/bluetooth_chooser_context.h",
+    "bluetooth/bluetooth_chooser_context_factory.cc",
+    "bluetooth/bluetooth_chooser_context_factory.h",
     "bookmarks/bookmark_model_factory.cc",
     "bookmarks/bookmark_model_factory.h",
     "bookmarks/chrome_bookmark_client.cc",
@@ -1268,9 +1272,6 @@
     "predictors/loading_predictor_config.h",
     "predictors/loading_predictor_factory.cc",
     "predictors/loading_predictor_factory.h",
-    "predictors/loading_predictor_key_value_data.h",
-    "predictors/loading_predictor_key_value_table.cc",
-    "predictors/loading_predictor_key_value_table.h",
     "predictors/loading_predictor_tab_helper.cc",
     "predictors/loading_predictor_tab_helper.h",
     "predictors/loading_stats_collector.cc",
@@ -1285,8 +1286,6 @@
     "predictors/predictor_database.h",
     "predictors/predictor_database_factory.cc",
     "predictors/predictor_database_factory.h",
-    "predictors/predictor_table_base.cc",
-    "predictors/predictor_table_base.h",
     "predictors/predictors_features.cc",
     "predictors/predictors_features.h",
     "predictors/proxy_lookup_client_impl.cc",
@@ -2094,6 +2093,7 @@
     "//components/signin/public/identity_manager",
     "//components/signin/public/webdata",
     "//components/spellcheck:buildflags",
+    "//components/sqlite_proto",
     "//components/ssl_errors",
     "//components/startup_metric_utils/browser",
     "//components/storage_monitor",
@@ -3060,10 +3060,6 @@
       "badging/badge_manager_factory.h",
       "banners/app_banner_manager_desktop.cc",
       "banners/app_banner_manager_desktop.h",
-      "bluetooth/bluetooth_chooser_context.cc",
-      "bluetooth/bluetooth_chooser_context.h",
-      "bluetooth/bluetooth_chooser_context_factory.cc",
-      "bluetooth/bluetooth_chooser_context_factory.h",
       "bookmarks/bookmark_html_writer.cc",
       "bookmarks/bookmark_html_writer.h",
       "certificate_viewer.h",
@@ -5269,6 +5265,12 @@
     sources += [
       "component_updater/vr_assets_component_installer.cc",
       "component_updater/vr_assets_component_installer.h",
+      "vr/service/browser_xr_runtime.cc",
+      "vr/service/browser_xr_runtime.h",
+      "vr/service/vr_service_impl.cc",
+      "vr/service/vr_service_impl.h",
+      "vr/service/xr_runtime_manager.cc",
+      "vr/service/xr_runtime_manager.h",
     ]
 
     deps += [ "//chrome/browser/vr:vr_common" ]
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 93be4102..fe6865e 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -224,6 +224,7 @@
   "+components/signin/core/browser",
   "+components/signin/public",
   "+components/spellcheck",
+  "+components/sqlite_proto",
   "+components/ssl_errors",
   "+components/startup_metric_utils/browser",
   "+components/storage_monitor",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e177563..08acdcf 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1088,65 +1088,22 @@
 const FeatureEntry::FeatureParam kExploreSitesExperimental = {
     chrome::android::explore_sites::kExploreSitesVariationParameterName,
     chrome::android::explore_sites::kExploreSitesVariationExperimental};
-const FeatureEntry::FeatureParam kExploreSitesPersonalized = {
-    chrome::android::explore_sites::kExploreSitesVariationParameterName,
-    chrome::android::explore_sites::kExploreSitesVariationPersonalized};
 const FeatureEntry::FeatureParam kExploreSitesDenseTitleBottom[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
     {chrome::android::explore_sites::kExploreSitesDenseVariationParameterName,
      chrome::android::explore_sites::
          kExploreSitesDenseVariationDenseTitleBottom},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesMostLikelyVariationIconDots}};
+};
 const FeatureEntry::FeatureParam kExploreSitesDenseTitleRight[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
     {chrome::android::explore_sites::kExploreSitesDenseVariationParameterName,
      chrome::android::explore_sites::
          kExploreSitesDenseVariationDenseTitleRight},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesMostLikelyVariationIconDots}};
-const FeatureEntry::FeatureParam kExploreSitesIconArrow[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationIconArrow}};
-const FeatureEntry::FeatureParam kExploreSitesIconDots[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesMostLikelyVariationIconDots}};
-const FeatureEntry::FeatureParam kExploreSitesIconGrouped[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationIconGrouped}};
+};
 const FeatureEntry::FeatureParam kExploreSitesWithGamesTop[] = {
-    {chrome::android::explore_sites::kExploreSitesVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesVariationMostLikelyTile},
-    {chrome::android::explore_sites::
-         kExploreSitesMostLikelyVariationParameterName,
-     chrome::android::explore_sites::kExploreSitesMostLikelyVariationIconDots},
     {chrome::android::explore_sites::
          kExploreSitesHeadersExperimentParameterName,
      chrome::android::explore_sites::kExploreSitesGamesTopExperiment}};
 const FeatureEntry::FeatureVariation kExploreSitesVariations[] = {
     {"Experimental", &kExploreSitesExperimental, 1, nullptr},
-    {"Personalized", &kExploreSitesPersonalized, 1, nullptr},
-    {"Arrow Icon", kExploreSitesIconArrow, base::size(kExploreSitesIconArrow),
-     nullptr},
-    {"Dots Icon", kExploreSitesIconDots, base::size(kExploreSitesIconDots),
-     nullptr},
-    {"Grouped Icon", kExploreSitesIconGrouped,
-     base::size(kExploreSitesIconGrouped), nullptr},
     {"Games Top", kExploreSitesWithGamesTop,
      base::size(kExploreSitesWithGamesTop), nullptr},
     {"Dense Title Bottom", kExploreSitesDenseTitleBottom,
@@ -2251,6 +2208,12 @@
      flag_descriptions::kDesktopPWAsLocalUpdatingName,
      flag_descriptions::kDesktopPWAsLocalUpdatingDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kDesktopPWAsLocalUpdating)},
+    {"enable-desktop-pwas-local-updating-throttle-persistence",
+     flag_descriptions::kDesktopPWAsLocalUpdatingThrottlePersistenceName,
+     flag_descriptions::kDesktopPWAsLocalUpdatingThrottlePersistenceDescription,
+     kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         features::kDesktopPWAsLocalUpdatingThrottlePersistence)},
     {"enable-desktop-pwas-tab-strip",
      flag_descriptions::kDesktopPWAsTabStripName,
      flag_descriptions::kDesktopPWAsTabStripDescription, kOsDesktop,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 47c700ca..eaeff52 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -160,7 +160,7 @@
     &kImmersiveUiMode,
     &kInlineUpdateFlow,
     &kIntentBlockExternalFormRedirectsNoGesture,
-    &kJellyBeanSupported,
+    &kKitKatSupported,
     &kNewPhotoPicker,
     &kNotificationSuspender,
     &kNTPLaunchAfterInactivity,
@@ -478,8 +478,8 @@
     "IntentBlockExternalFormRedirectsNoGesture",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kJellyBeanSupported{"JellyBeanSupported",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kKitKatSupported{"KitKatSupported",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kSearchEnginePromoExistingDevice{
     "SearchEnginePromo.ExistingDevice", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 392a246..82b0e8d 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -84,7 +84,7 @@
 extern const base::Feature kImprovedA2HS;
 extern const base::Feature kInlineUpdateFlow;
 extern const base::Feature kIntentBlockExternalFormRedirectsNoGesture;
-extern const base::Feature kJellyBeanSupported;
+extern const base::Feature kKitKatSupported;
 extern const base::Feature kLanguagesPreference;
 extern const base::Feature kNewPhotoPicker;
 extern const base::Feature kNotificationSuspender;
diff --git a/chrome/browser/android/explore_sites/explore_sites_bridge.cc b/chrome/browser/android/explore_sites/explore_sites_bridge.cc
index 6366108d..c639f93 100644
--- a/chrome/browser/android/explore_sites/explore_sites_bridge.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_bridge.cc
@@ -149,40 +149,12 @@
 }  // namespace
 
 // static
-void JNI_ExploreSitesBridge_GetEspCatalog(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& j_profile,
-    const JavaParamRef<jobject>& j_result_obj,
-    const JavaParamRef<jobject>& j_callback_obj) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
-  DCHECK(profile);
-
-  ExploreSitesService* service =
-      ExploreSitesServiceFactory::GetForBrowserContext(profile);
-  if (!service) {
-    DLOG(ERROR) << "Unable to create the ExploreSitesService!";
-    base::android::RunObjectCallbackAndroid(j_callback_obj, nullptr);
-    return;
-  }
-
-  service->GetCatalog(
-      base::BindOnce(&CatalogReady, ScopedJavaGlobalRef<jobject>(j_result_obj),
-                     ScopedJavaGlobalRef<jobject>(j_callback_obj)));
-}
-
-// static
 jint JNI_ExploreSitesBridge_GetVariation(JNIEnv* env) {
   return static_cast<jint>(
       chrome::android::explore_sites::GetExploreSitesVariation());
 }
 
 // static
-jint JNI_ExploreSitesBridge_GetIconVariation(JNIEnv* env) {
-  return static_cast<jint>(
-      chrome::android::explore_sites::GetMostLikelyVariation());
-}
-
-// static
 jint JNI_ExploreSitesBridge_GetDenseVariation(JNIEnv* env) {
   return static_cast<jint>(chrome::android::explore_sites::GetDenseVariation());
 }
@@ -320,22 +292,6 @@
   service->RecordClick(url, category_type);
 }
 
-void JNI_ExploreSitesBridge_IncrementNtpShownCount(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& j_profile,
-    const jint j_category_id) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
-  ExploreSitesService* service =
-      ExploreSitesServiceFactory::GetForBrowserContext(profile);
-  if (!service) {
-    DLOG(ERROR) << "Unable to create the ExploreSitesService!";
-    return;
-  }
-
-  int category_id = static_cast<int>(j_category_id);
-  service->IncrementNtpShownCount(category_id);
-}
-
 // static
 void ExploreSitesBridge::ScheduleDailyTask() {
   JNIEnv* env = base::android::AttachCurrentThread();
@@ -349,30 +305,6 @@
 }
 
 // static
-void JNI_ExploreSitesBridge_GetCategoryImage(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& j_profile,
-    const jint j_category_id,
-    const jint j_pixel_size,
-    const JavaParamRef<jobject>& j_callback_obj) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
-  DCHECK(profile);
-
-  ExploreSitesService* service =
-      ExploreSitesServiceFactory::GetForBrowserContext(profile);
-  if (!service) {
-    DLOG(ERROR) << "Unable to create the ExploreSitesService!";
-    base::android::RunBooleanCallbackAndroid(j_callback_obj, false);
-    return;
-  }
-
-  service->GetCategoryImage(
-      j_category_id, j_pixel_size,
-      base::BindOnce(&ImageReady,
-                     ScopedJavaGlobalRef<jobject>(j_callback_obj)));
-}
-
-// static
 void JNI_ExploreSitesBridge_GetSummaryImage(
     JNIEnv* env,
     const JavaParamRef<jobject>& j_profile,
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature.cc b/chrome/browser/android/explore_sites/explore_sites_feature.cc
index fda5c34..aa3ec66 100644
--- a/chrome/browser/android/explore_sites/explore_sites_feature.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_feature.cc
@@ -13,36 +13,22 @@
 namespace explore_sites {
 
 const char kExploreSitesVariationParameterName[] = "variation";
-
 const char kExploreSitesVariationExperimental[] = "experiment";
-const char kExploreSitesVariationPersonalized[] = "personalized";
-const char kExploreSitesVariationMostLikelyTile[] = "mostLikelyTile";
 
-const char kExploreSitesMostLikelyVariationParameterName[] =
-    "mostLikelyVariation";
 const char kExploreSitesHeadersExperimentParameterName[] = "exp";
-
-const char kExploreSitesMostLikelyVariationIconArrow[] = "arrowIcon";
-const char kExploreSitesMostLikelyVariationIconDots[] = "dotsIcon";
-const char kExploreSitesMostLikelyVariationIconGrouped[] = "groupedIcon";
+const char kExploreSitesGamesTopExperiment[] = "games-top";
 
 const char kExploreSitesDenseVariationParameterName[] = "denseVariation";
 const char kExploreSitesDenseVariationOriginal[] = "original";
 const char kExploreSitesDenseVariationDenseTitleBottom[] = "titleBottom";
 const char kExploreSitesDenseVariationDenseTitleRight[] = "titleRight";
 
-const char kExploreSitesGamesTopExperiment[] = "games-top";
-
 ExploreSitesVariation GetExploreSitesVariation() {
   if (base::FeatureList::IsEnabled(kExploreSites)) {
     const std::string feature_param = base::GetFieldTrialParamValueByFeature(
         kExploreSites, kExploreSitesVariationParameterName);
     if (feature_param == kExploreSitesVariationExperimental) {
       return ExploreSitesVariation::EXPERIMENT;
-    } else if (feature_param == kExploreSitesVariationPersonalized) {
-      return ExploreSitesVariation::PERSONALIZED;
-    } else if (feature_param == kExploreSitesVariationMostLikelyTile) {
-      return ExploreSitesVariation::MOST_LIKELY;
     } else {
       return ExploreSitesVariation::ENABLED;
     }
@@ -50,30 +36,6 @@
   return ExploreSitesVariation::DISABLED;
 }
 
-MostLikelyVariation GetMostLikelyVariation() {
-  if (base::FeatureList::IsEnabled(kExploreSites) &&
-      base::GetFieldTrialParamValueByFeature(
-          kExploreSites, kExploreSitesVariationParameterName) ==
-          kExploreSitesVariationMostLikelyTile) {
-    if (base::GetFieldTrialParamValueByFeature(
-            kExploreSites, kExploreSitesMostLikelyVariationParameterName) ==
-        kExploreSitesMostLikelyVariationIconArrow) {
-      return MostLikelyVariation::ICON_ARROW;
-    }
-    if (base::GetFieldTrialParamValueByFeature(
-            kExploreSites, kExploreSitesMostLikelyVariationParameterName) ==
-        kExploreSitesMostLikelyVariationIconDots) {
-      return MostLikelyVariation::ICON_DOTS;
-    }
-    if (base::GetFieldTrialParamValueByFeature(
-            kExploreSites, kExploreSitesMostLikelyVariationParameterName) ==
-        kExploreSitesMostLikelyVariationIconGrouped) {
-      return MostLikelyVariation::ICON_GROUPED;
-    }
-  }
-  return MostLikelyVariation::NONE;
-}
-
 DenseVariation GetDenseVariation() {
   if (base::GetFieldTrialParamValueByFeature(
           kExploreSites, kExploreSitesDenseVariationParameterName) ==
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature.h b/chrome/browser/android/explore_sites/explore_sites_feature.h
index bd28e70..3dd6e51f 100644
--- a/chrome/browser/android/explore_sites/explore_sites_feature.h
+++ b/chrome/browser/android/explore_sites/explore_sites_feature.h
@@ -10,47 +10,30 @@
 namespace explore_sites {
 
 extern const char kExploreSitesVariationParameterName[];
-
 extern const char kExploreSitesVariationExperimental[];
-extern const char kExploreSitesVariationPersonalized[];
-extern const char kExploreSitesVariationMostLikelyTile[];
 
-extern const char kExploreSitesMostLikelyVariationParameterName[];
 extern const char kExploreSitesHeadersExperimentParameterName[];
-
-extern const char kExploreSitesMostLikelyVariationIconArrow[];
-extern const char kExploreSitesMostLikelyVariationIconDots[];
-extern const char kExploreSitesMostLikelyVariationIconGrouped[];
+extern const char kExploreSitesGamesTopExperiment[];
 
 extern const char kExploreSitesDenseVariationParameterName[];
 extern const char kExploreSitesDenseVariationOriginal[];
 extern const char kExploreSitesDenseVariationDenseTitleBottom[];
 extern const char kExploreSitesDenseVariationDenseTitleRight[];
 
-extern const char kExploreSitesGamesTopExperiment[];
-
 // A Java counterpart will be generated for this enum.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.explore_sites
 enum class ExploreSitesVariation {
   ENABLED,
   EXPERIMENT,
-  PERSONALIZED,
-  MOST_LIKELY,
   DISABLED
 };
 
 // A Java counterpart will be generated for this enum.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.explore_sites
-enum class MostLikelyVariation { NONE, ICON_ARROW, ICON_DOTS, ICON_GROUPED };
-
-// A Java counterpart will be generated for this enum.
-// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.explore_sites
 enum class DenseVariation { ORIGINAL, DENSE_TITLE_BOTTOM, DENSE_TITLE_RIGHT };
 
 ExploreSitesVariation GetExploreSitesVariation();
 
-MostLikelyVariation GetMostLikelyVariation();
-
 DenseVariation GetDenseVariation();
 
 }  // namespace explore_sites
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc b/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc
index c416ae2..c2431fb 100644
--- a/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc
@@ -64,45 +64,6 @@
   EXPECT_EQ(DenseVariation::ORIGINAL, GetDenseVariation());
 }
 
-TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithIconArrow) {
-  std::map<std::string, std::string> parameters;
-  parameters[kExploreSitesVariationParameterName] =
-      kExploreSitesVariationMostLikelyTile;
-  parameters[kExploreSitesMostLikelyVariationParameterName] =
-      kExploreSitesMostLikelyVariationIconArrow;
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(kExploreSites,
-                                                         parameters);
-  EXPECT_EQ(ExploreSitesVariation::MOST_LIKELY, GetExploreSitesVariation());
-  EXPECT_EQ(MostLikelyVariation::ICON_ARROW, GetMostLikelyVariation());
-}
-
-TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithIconDots) {
-  std::map<std::string, std::string> parameters;
-  parameters[kExploreSitesVariationParameterName] =
-      kExploreSitesVariationMostLikelyTile;
-  parameters[kExploreSitesMostLikelyVariationParameterName] =
-      kExploreSitesMostLikelyVariationIconDots;
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(kExploreSites,
-                                                         parameters);
-  EXPECT_EQ(ExploreSitesVariation::MOST_LIKELY, GetExploreSitesVariation());
-  EXPECT_EQ(MostLikelyVariation::ICON_DOTS, GetMostLikelyVariation());
-}
-
-TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithIconGrouped) {
-  std::map<std::string, std::string> parameters;
-  parameters[kExploreSitesVariationParameterName] =
-      kExploreSitesVariationMostLikelyTile;
-  parameters[kExploreSitesMostLikelyVariationParameterName] =
-      kExploreSitesMostLikelyVariationIconGrouped;
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(kExploreSites,
-                                                         parameters);
-  EXPECT_EQ(ExploreSitesVariation::MOST_LIKELY, GetExploreSitesVariation());
-  EXPECT_EQ(MostLikelyVariation::ICON_GROUPED, GetMostLikelyVariation());
-}
-
 TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithBogus) {
   const char bogusParamValue[] = "bogus";
   std::map<std::string, std::string> parameters;
diff --git a/chrome/browser/android/explore_sites/explore_sites_service.h b/chrome/browser/android/explore_sites/explore_sites_service.h
index b261617..02f06cab 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service.h
+++ b/chrome/browser/android/explore_sites/explore_sites_service.h
@@ -20,14 +20,6 @@
   // Returns via callback the current catalog stored locally.
   virtual void GetCatalog(CatalogCallback callback) = 0;
 
-  // Returns via callback the image for a category. This image is composed from
-  // multiple site images. The site images are checked against the user
-  // blacklist so that unwanted sites are not represented in the category image.
-  // Returns |nullptr| if there was an error, or no match.
-  virtual void GetCategoryImage(int category_id,
-                                int pixel_size,
-                                BitmapCallback callback) = 0;
-
   // Returns via callback an image representing a summary of the current
   // catalog. This image is composed from multiple site images.
   // Returns |nullptr| if there was an error.
@@ -57,10 +49,6 @@
                                base::Time end,
                                base::OnceClosure callback) = 0;
 
-  // Increment the ntp_shown_count for the particular category.
-  // |category_id| the row id of the category to increment.
-  virtual void IncrementNtpShownCount(int category_id) = 0;
-
   // Controls for use by chrome://explore-sites-internals.
   virtual void ClearCachedCatalogsForDebugging() = 0;
   virtual void OverrideCountryCodeForDebugging(
diff --git a/chrome/browser/android/explore_sites/explore_sites_service_impl.cc b/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
index 0cde3b2..229c4c3ca 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_service_impl.cc
@@ -69,10 +69,7 @@
 
 // static
 bool ExploreSitesServiceImpl::IsExploreSitesEnabled() {
-  ExploreSitesVariation variation = GetExploreSitesVariation();
-  return variation == ExploreSitesVariation::ENABLED ||
-         variation == ExploreSitesVariation::PERSONALIZED ||
-         variation == ExploreSitesVariation::MOST_LIKELY;
+  return GetExploreSitesVariation() == ExploreSitesVariation::ENABLED;
 }
 
 void ExploreSitesServiceImpl::GetCatalog(CatalogCallback callback) {
@@ -86,16 +83,6 @@
       std::move(callback)));
 }
 
-void ExploreSitesServiceImpl::GetCategoryImage(int category_id,
-                                               int pixel_size,
-                                               BitmapCallback callback) {
-  task_queue_.AddTask(std::make_unique<GetImagesTask>(
-      explore_sites_store_.get(), category_id, kFaviconsPerCategoryImage,
-      base::BindOnce(&ExploreSitesServiceImpl::ComposeCategoryImage,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     pixel_size)));
-}
-
 void ExploreSitesServiceImpl::GetSummaryImage(int pixel_size,
                                               BitmapCallback callback) {
   task_queue_.AddTask(std::make_unique<GetImagesTask>(
@@ -166,11 +153,6 @@
           std::move(callback))));
 }
 
-void ExploreSitesServiceImpl::IncrementNtpShownCount(int category_id) {
-  task_queue_.AddTask(std::make_unique<IncrementShownCountTask>(
-      explore_sites_store_.get(), category_id));
-}
-
 void ExploreSitesServiceImpl::ClearCachedCatalogsForDebugging() {
   task_queue_.AddTask(std::make_unique<ClearCatalogTask>(
       explore_sites_store_.get(), base::BindOnce([](bool result) {})));
diff --git a/chrome/browser/android/explore_sites/explore_sites_service_impl.h b/chrome/browser/android/explore_sites/explore_sites_service_impl.h
index 27ea324..1109896 100644
--- a/chrome/browser/android/explore_sites/explore_sites_service_impl.h
+++ b/chrome/browser/android/explore_sites/explore_sites_service_impl.h
@@ -43,9 +43,6 @@
 
   // ExploreSitesService implementation.
   void GetCatalog(CatalogCallback callback) override;
-  void GetCategoryImage(int category_id,
-                        int pixel_size,
-                        BitmapCallback callback) override;
   void GetSummaryImage(int pixel_size, BitmapCallback callback) override;
   void GetSiteImage(int site_id, BitmapCallback callback) override;
   void UpdateCatalogFromNetwork(bool is_immediate_fetch,
@@ -56,7 +53,6 @@
   void ClearActivities(base::Time begin,
                        base::Time end,
                        base::OnceClosure callback) override;
-  void IncrementNtpShownCount(int category_id) override;
   void ClearCachedCatalogsForDebugging() override;
   void OverrideCountryCodeForDebugging(
       const std::string& country_code) override;
diff --git a/chrome/browser/android/explore_sites/most_visited_client.cc b/chrome/browser/android/explore_sites/most_visited_client.cc
index 55face59..dc56d1c 100644
--- a/chrome/browser/android/explore_sites/most_visited_client.cc
+++ b/chrome/browser/android/explore_sites/most_visited_client.cc
@@ -11,13 +11,8 @@
 #include "ui/base/l10n/l10n_util.h"
 
 namespace explore_sites {
-using chrome::android::explore_sites::GetMostLikelyVariation;
-using chrome::android::explore_sites::MostLikelyVariation;
 
 std::unique_ptr<MostVisitedClient> MostVisitedClient::Create() {
-  if (GetMostLikelyVariation() == MostLikelyVariation::NONE)
-    return nullptr;
-
   // note: wrap_unique is used because the constructor is private.
   return base::WrapUnique(new MostVisitedClient());
 }
diff --git a/chrome/browser/android/headers_classifier.cc b/chrome/browser/android/headers_classifier.cc
index 88e4bac..e98c9bb 100644
--- a/chrome/browser/android/headers_classifier.cc
+++ b/chrome/browser/android/headers_classifier.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "base/android/jni_string.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/hash/hash.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_util.h"
@@ -26,9 +28,17 @@
     return true;
 
   if (is_first_party) {
-    base::UmaHistogramSparse(
-        "Android.IntentNonSafelistedHeaderNames",
-        base::PersistentHash(base::ToLowerASCII(header_name)));
+    int hash = base::PersistentHash(base::ToLowerASCII(header_name));
+    base::UmaHistogramSparse("Android.IntentNonSafelistedHeaderNames", hash);
+    if (hash == -240302231) {
+      static base::debug::CrashKeyString* non_safelisted_header_name =
+          base::debug::AllocateCrashKeyString(
+              "intent_non_safelisted_header_name",
+              base::debug::CrashKeySize::Size256);
+      base::debug::ScopedCrashKeyString(non_safelisted_header_name,
+                                        header_name);
+      base::debug::DumpWithoutCrashing();
+    }
   }
 
   return false;
diff --git a/chrome/browser/android/resource_id.h b/chrome/browser/android/resource_id.h
index fe3ef6b4..7fe72d8 100644
--- a/chrome/browser/android/resource_id.h
+++ b/chrome/browser/android/resource_id.h
@@ -64,6 +64,7 @@
                     R.drawable.ic_vpn_key_blue)
 DECLARE_RESOURCE_ID(IDR_ANDROID_INFOBAR_TRANSLATE, R.drawable.infobar_translate)
 DECLARE_RESOURCE_ID(IDR_ANDROID_INFOBAR_WARNING, R.drawable.infobar_warning)
+DECLARE_RESOURCE_ID(IDR_ANDROID_INFOBAR_VR_HEADSET, R.drawable.vr_headset)
 DECLARE_RESOURCE_ID(IDR_ANDROID_INFOBAR_PHONE_ICON,
                     R.drawable.smartphone_black_24dp)
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_WITH_DIVIDER,
diff --git a/chrome/browser/bluetooth/bluetooth_chooser_context.cc b/chrome/browser/bluetooth/bluetooth_chooser_context.cc
index bf815ff..e7519eb 100644
--- a/chrome/browser/bluetooth/bluetooth_chooser_context.cc
+++ b/chrome/browser/bluetooth/bluetooth_chooser_context.cc
@@ -4,15 +4,88 @@
 
 #include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
 
+#include <memory>
+#include <utility>
+#include <vector>
+
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
 #include "url/origin.h"
 
 using blink::WebBluetoothDeviceId;
 using device::BluetoothUUID;
 using device::BluetoothUUIDHash;
 
+namespace {
+
+// The Bluetooth device permission objects are dictionary type base::Values. The
+// object contains keys for the device address, device name, services that can
+// be accessed, and the generated web bluetooth device ID. Since base::Value
+// does not have a set type, the services key contains another dictionary type
+// base::Value object where each key is a UUID for a service and the value is a
+// boolean that is never used. This allows for service permissions to be queried
+// quickly and for new service permissions to added without duplicating existing
+// service permissions. The following is an example of how a device permission
+// is formatted using JSON notation:
+// {
+//   "device-address": "00:00:00:00:00:00",
+//   "name": "Wireless Device",
+//   "services": {
+//     "0xabcd": "true",
+//     "0x1234": "true",
+//   },
+//   "web-bluetooth-device-id": "4ik7W0WVaGFY6zXxJqdAKw==",
+// }
+constexpr char kDeviceAddressKey[] = "device-address";
+constexpr char kDeviceNameKey[] = "name";
+constexpr char kServicesKey[] = "services";
+constexpr char kWebBluetoothDeviceIdKey[] = "web-bluetooth-device-id";
+
+// The Web Bluetooth API spec states that when the user selects a device to
+// pair with the origin, the origin is allowed to access any service listed in
+// |options->filters| and |options->optional_services|.
+// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
+void AddUnionOfServicesTo(
+    const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options,
+    base::Value* permission_object) {
+  DCHECK(!!permission_object->FindDictKey(kServicesKey));
+  auto& services_dict = *permission_object->FindDictKey(kServicesKey);
+  if (options->filters) {
+    for (const blink::mojom::WebBluetoothLeScanFilterPtr& filter :
+         options->filters.value()) {
+      if (!filter->services)
+        continue;
+
+      for (const BluetoothUUID& uuid : filter->services.value())
+        services_dict.SetBoolKey(uuid.canonical_value(), /*val=*/true);
+    }
+  }
+
+  for (const BluetoothUUID& uuid : options->optional_services)
+    services_dict.SetBoolKey(uuid.canonical_value(), /*val=*/true);
+}
+
+base::Value DeviceInfoToDeviceObject(
+    const device::BluetoothDevice* device,
+    const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options,
+    const WebBluetoothDeviceId& device_id) {
+  base::Value device_value(base::Value::Type::DICTIONARY);
+  device_value.SetStringKey(kDeviceAddressKey, device->GetAddress());
+  device_value.SetStringKey(kWebBluetoothDeviceIdKey, device_id.str());
+  device_value.SetStringKey(kDeviceNameKey, device->GetNameForDisplay());
+
+  base::Value services_value(base::Value::Type::DICTIONARY);
+  device_value.SetKey(kServicesKey, std::move(services_value));
+  AddUnionOfServicesTo(options, &device_value);
+
+  return device_value;
+}
+
+}  // namespace
+
 BluetoothChooserContext::BluetoothChooserContext(Profile* profile)
     : ChooserContextBase(profile,
                          ContentSettingsType::BLUETOOTH_GUARD,
@@ -20,36 +93,152 @@
 
 BluetoothChooserContext::~BluetoothChooserContext() = default;
 
-const WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
+WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const std::string& device_address) {
-  NOTIMPLEMENTED();
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  for (const auto& object : object_list) {
+    const base::Value& device = object->value;
+    DCHECK(IsValidObject(device));
+
+    if (device_address == *device.FindStringKey(kDeviceAddressKey)) {
+      return WebBluetoothDeviceId(
+          *device.FindStringKey(kWebBluetoothDeviceIdKey));
+    }
+  }
+
+  // Check if the device has been assigned an ID through an LE scan.
+  auto scanned_devices_it =
+      scanned_devices_.find({requesting_origin, embedding_origin});
+  if (scanned_devices_it == scanned_devices_.end())
+    return {};
+
+  auto address_to_id_it = scanned_devices_it->second.find(device_address);
+  if (address_to_id_it != scanned_devices_it->second.end())
+    return address_to_id_it->second;
   return {};
 }
 
-const std::string BluetoothChooserContext::GetDeviceAddress(
+std::string BluetoothChooserContext::GetDeviceAddress(
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const WebBluetoothDeviceId& device_id) {
-  NOTIMPLEMENTED();
-  return "";
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  for (const auto& object : object_list) {
+    const base::Value& device = object->value;
+    DCHECK(IsValidObject(device));
+
+    const WebBluetoothDeviceId web_bluetooth_device_id(
+        *device.FindStringKey(kWebBluetoothDeviceIdKey));
+    if (device_id == web_bluetooth_device_id)
+      return *device.FindStringKey(kDeviceAddressKey);
+  }
+
+  // Check if the device ID corresponds to a device detected via an LE scan.
+  auto scanned_devices_it =
+      scanned_devices_.find({requesting_origin, embedding_origin});
+  if (scanned_devices_it == scanned_devices_.end())
+    return std::string();
+
+  for (const auto& entry : scanned_devices_it->second) {
+    if (entry.second == device_id)
+      return entry.first;
+  }
+  return std::string();
 }
 
-const WebBluetoothDeviceId BluetoothChooserContext::GrantDevicePermission(
+WebBluetoothDeviceId BluetoothChooserContext::AddScannedDevice(
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
-    const std::string& device_address,
-    base::flat_set<BluetoothUUID, BluetoothUUIDHash>& services) {
-  NOTIMPLEMENTED();
-  return {};
+    const std::string& device_address) {
+  // Check if a WebBluetoothDeviceId already exists for the device with
+  // |device_address| for the current origin pair.
+  const auto granted_id = GetWebBluetoothDeviceId(
+      requesting_origin, embedding_origin, device_address);
+  if (granted_id.IsValid())
+    return granted_id;
+
+  DeviceAddressToIdMap& address_to_id_map =
+      scanned_devices_[{requesting_origin, embedding_origin}];
+  auto scanned_id = WebBluetoothDeviceId::Create();
+  address_to_id_map.emplace(device_address, scanned_id);
+  return scanned_id;
+}
+
+WebBluetoothDeviceId BluetoothChooserContext::GrantServiceAccessPermission(
+    const url::Origin& requesting_origin,
+    const url::Origin& embedding_origin,
+    const device::BluetoothDevice* device,
+    const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
+  // If |requesting_origin| and |embedding_origin| already have permission to
+  // access the device with |device_address|, update the allowed GATT services
+  // by performing a union of |services|.
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  const std::string& device_address = device->GetAddress();
+  for (const auto& object : object_list) {
+    base::Value& device_object = object->value;
+    DCHECK(IsValidObject(device_object));
+    if (device_address == *device_object.FindStringKey(kDeviceAddressKey)) {
+      auto new_device_object = device_object.Clone();
+      WebBluetoothDeviceId device_id(
+          *new_device_object.FindStringKey(kWebBluetoothDeviceIdKey));
+
+      AddUnionOfServicesTo(options, &new_device_object);
+      UpdateObjectPermission(requesting_origin, embedding_origin, device_object,
+                             std::move(new_device_object));
+      return device_id;
+    }
+  }
+
+  // If the device has been detected through the Web Bluetooth Scanning API,
+  // grant permission using the WebBluetoothDeviceId generated through that API.
+  // Remove the ID from the temporary |scanned_devices_| map to avoid
+  // duplication, since the ID will now be stored in HostContentSettingsMap.
+  WebBluetoothDeviceId device_id;
+  auto scanned_devices_it =
+      scanned_devices_.find({requesting_origin, embedding_origin});
+  if (scanned_devices_it != scanned_devices_.end()) {
+    auto& address_to_id_map = scanned_devices_it->second;
+    auto address_to_id_it = address_to_id_map.find(device_address);
+
+    if (address_to_id_it != address_to_id_map.end()) {
+      device_id = address_to_id_it->second;
+      address_to_id_map.erase(address_to_id_it);
+
+      if (scanned_devices_it->second.empty())
+        scanned_devices_.erase(scanned_devices_it);
+    }
+  }
+
+  if (!device_id.IsValid())
+    device_id = WebBluetoothDeviceId::Create();
+
+  base::Value permission_object =
+      DeviceInfoToDeviceObject(device, options, device_id);
+  GrantObjectPermission(requesting_origin, embedding_origin,
+                        std::move(permission_object));
+  return device_id;
 }
 
 bool BluetoothChooserContext::HasDevicePermission(
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const WebBluetoothDeviceId& device_id) {
-  NOTIMPLEMENTED();
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  for (const auto& object : object_list) {
+    const base::Value& device = object->value;
+    DCHECK(IsValidObject(device));
+
+    const WebBluetoothDeviceId web_bluetooth_device_id(
+        *device.FindStringKey(kWebBluetoothDeviceIdKey));
+    if (device_id == web_bluetooth_device_id)
+      return true;
+  }
   return false;
 }
 
@@ -57,7 +246,17 @@
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
     const WebBluetoothDeviceId& device_id) {
-  NOTIMPLEMENTED();
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  for (const auto& object : object_list) {
+    const base::Value& device = object->value;
+    DCHECK(IsValidObject(device));
+
+    const WebBluetoothDeviceId web_bluetooth_device_id(
+        *device.FindStringKey(kWebBluetoothDeviceIdKey));
+    if (device_id == web_bluetooth_device_id)
+      return !device.FindDictKey(kServicesKey)->DictEmpty();
+  }
   return false;
 }
 
@@ -66,32 +265,27 @@
     const url::Origin& embedding_origin,
     const WebBluetoothDeviceId& device_id,
     BluetoothUUID service) {
-  NOTIMPLEMENTED();
+  const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
+      GetGrantedObjects(requesting_origin, embedding_origin);
+  for (const auto& object : object_list) {
+    const base::Value& device = object->value;
+    DCHECK(IsValidObject(device));
+
+    const WebBluetoothDeviceId web_bluetooth_device_id(
+        *device.FindStringKey(kWebBluetoothDeviceIdKey));
+    if (device_id == web_bluetooth_device_id) {
+      const auto& services_dict = *device.FindDictKey(kServicesKey);
+      return !!services_dict.FindKey(service.canonical_value());
+    }
+  }
   return false;
 }
 
 bool BluetoothChooserContext::IsValidObject(const base::Value& object) {
-  NOTIMPLEMENTED();
-  return false;
-}
-
-std::vector<std::unique_ptr<ChooserContextBase::Object>>
-BluetoothChooserContext::GetGrantedObjects(
-    const url::Origin& requesting_origin,
-    const url::Origin& embedding_origin) {
-  NOTIMPLEMENTED();
-  return {};
-}
-
-std::vector<std::unique_ptr<ChooserContextBase::Object>>
-BluetoothChooserContext::GetAllGrantedObjects() {
-  NOTIMPLEMENTED();
-  return {};
-}
-
-void BluetoothChooserContext::RevokeObjectPermission(
-    const url::Origin& requesting_origin,
-    const url::Origin& embedding_origin,
-    const base::Value& object) {
-  NOTIMPLEMENTED();
+  return object.FindStringKey(kDeviceAddressKey) &&
+         object.FindStringKey(kDeviceNameKey) &&
+         object.FindStringKey(kWebBluetoothDeviceIdKey) &&
+         WebBluetoothDeviceId::IsValid(
+             *object.FindStringKey(kWebBluetoothDeviceIdKey)) &&
+         object.FindDictKey(kServicesKey);
 }
diff --git a/chrome/browser/bluetooth/bluetooth_chooser_context.h b/chrome/browser/bluetooth/bluetooth_chooser_context.h
index 700204d1..a04ab4be 100644
--- a/chrome/browser/bluetooth/bluetooth_chooser_context.h
+++ b/chrome/browser/bluetooth/bluetooth_chooser_context.h
@@ -5,12 +5,16 @@
 #ifndef CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
 #define CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
 
+#include <map>
 #include <string>
 
 #include "base/containers/flat_set.h"
 #include "chrome/browser/permissions/chooser_context_base.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 #include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-forward.h"
 
 namespace base {
 class Value;
@@ -37,22 +41,27 @@
 
   // Helper methods for converting between a WebBluetoothDeviceId and a
   // Bluetooth device address string for a given origin pair.
-  const blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
+  blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
       const url::Origin& requesting_origin,
       const url::Origin& embedding_origin,
       const std::string& device_address);
-  const std::string GetDeviceAddress(
+  std::string GetDeviceAddress(const url::Origin& requesting_origin,
+                               const url::Origin& embedding_origin,
+                               const blink::WebBluetoothDeviceId& device_id);
+
+  // Bluetooth scanning specific interface for generating WebBluetoothDeviceIds
+  // for scanned devices.
+  blink::WebBluetoothDeviceId AddScannedDevice(
       const url::Origin& requesting_origin,
       const url::Origin& embedding_origin,
-      const blink::WebBluetoothDeviceId& device_id);
+      const std::string& device_address);
 
   // Bluetooth-specific interface for granting and checking permissions.
-  const blink::WebBluetoothDeviceId GrantDevicePermission(
+  blink::WebBluetoothDeviceId GrantServiceAccessPermission(
       const url::Origin& requesting_origin,
       const url::Origin& embedding_origin,
-      const std::string& device_address,
-      base::flat_set<device::BluetoothUUID, device::BluetoothUUIDHash>&
-          services);
+      const device::BluetoothDevice* device,
+      const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options);
   bool HasDevicePermission(const url::Origin& requesting_origin,
                            const url::Origin& embedding_origin,
                            const blink::WebBluetoothDeviceId& device_id);
@@ -68,13 +77,15 @@
  protected:
   // ChooserContextBase implementation;
   bool IsValidObject(const base::Value& object) override;
-  std::vector<std::unique_ptr<Object>> GetGrantedObjects(
-      const url::Origin& requesting_origin,
-      const url::Origin& embedding_origin) override;
-  std::vector<std::unique_ptr<Object>> GetAllGrantedObjects() override;
-  void RevokeObjectPermission(const url::Origin& requesting_origin,
-                              const url::Origin& embedding_origin,
-                              const base::Value& object) override;
+
+ private:
+  // This map records the generated Web Bluetooth IDs for devices discovered via
+  // the Scanning API. Each requesting/embedding origin pair has its own version
+  // of this map so that IDs cannot be correlated between cross-origin sites.
+  using DeviceAddressToIdMap =
+      std::map<std::string, blink::WebBluetoothDeviceId>;
+  std::map<std::pair<url::Origin, url::Origin>, DeviceAddressToIdMap>
+      scanned_devices_;
 };
 
 #endif  // CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
diff --git a/chrome/browser/bluetooth/bluetooth_chooser_context_unittest.cc b/chrome/browser/bluetooth/bluetooth_chooser_context_unittest.cc
new file mode 100644
index 0000000..a098d234
--- /dev/null
+++ b/chrome/browser/bluetooth/bluetooth_chooser_context_unittest.cc
@@ -0,0 +1,547 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
+#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/permissions/chooser_context_base.h"
+#include "chrome/browser/permissions/chooser_context_base_mock_permission_observer.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/test/browser_task_environment.h"
+#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+using blink::mojom::WebBluetoothRequestDeviceOptionsPtr;
+using device::BluetoothUUID;
+using device::BluetoothUUIDHash;
+
+namespace {
+
+constexpr char kDeviceAddressKey[] = "device-address";
+constexpr char kDeviceNameKey[] = "name";
+constexpr char kServicesKey[] = "services";
+constexpr char kWebBluetoothDeviceIdKey[] = "web-bluetooth-device-id";
+
+const uint32_t kGamepadBluetoothClass = 0x0508;
+
+constexpr char kDeviceAddress1[] = "00:00:00:00:00:00";
+constexpr char kDeviceAddress2[] = "11:11:11:11:11:11";
+
+constexpr char kGlucoseUUIDString[] = "00001808-0000-1000-8000-00805f9b34fb";
+constexpr char kHeartRateUUIDString[] = "0000180d-0000-1000-8000-00805f9b34fb";
+constexpr char kBatteryServiceUUIDString[] =
+    "0000180f-0000-1000-8000-00805f9b34fb";
+constexpr char kBloodPressureUUIDString[] =
+    "00001813-0000-1000-8000-00805f9b34fb";
+constexpr char kCyclingPowerUUIDString[] =
+    "00001818-0000-1000-8000-00805f9b34fb";
+const BluetoothUUID kGlucoseUUID(kGlucoseUUIDString);
+const BluetoothUUID kHeartRateUUID(kHeartRateUUIDString);
+const BluetoothUUID kBatteryServiceUUID(kBatteryServiceUUIDString);
+const BluetoothUUID kBloodPressureUUID(kBloodPressureUUIDString);
+const BluetoothUUID kCyclingPowerUUID(kCyclingPowerUUIDString);
+
+WebBluetoothRequestDeviceOptionsPtr CreateOptionsForServices(
+    const std::vector<BluetoothUUID>& filter_services,
+    const std::vector<BluetoothUUID>& optional_services) {
+  auto filter = blink::mojom::WebBluetoothLeScanFilter::New();
+  filter->services = filter_services;
+
+  std::vector<blink::mojom::WebBluetoothLeScanFilterPtr> scan_filters;
+  scan_filters.push_back(std::move(filter));
+
+  auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
+  options->filters = std::move(scan_filters);
+  options->optional_services = optional_services;
+  return options;
+}
+
+WebBluetoothRequestDeviceOptionsPtr CreateOptionsForServices(
+    const std::vector<BluetoothUUID>& filter_services) {
+  return CreateOptionsForServices(filter_services, {});
+}
+
+}  // namespace
+
+class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
+ public:
+  FakeBluetoothAdapter() = default;
+
+  // Move-only class.
+  FakeBluetoothAdapter(const FakeBluetoothAdapter&) = delete;
+  FakeBluetoothAdapter& operator=(const FakeBluetoothAdapter&) = delete;
+
+ private:
+  ~FakeBluetoothAdapter() override = default;
+};
+
+class FakeBluetoothDevice : public device::MockBluetoothDevice {
+ public:
+  FakeBluetoothDevice(device::MockBluetoothAdapter* adapter,
+                      const char* name,
+                      const std::string& address)
+      : device::MockBluetoothDevice(adapter,
+                                    kGamepadBluetoothClass,
+                                    name,
+                                    address,
+                                    /*paired=*/true,
+                                    /*connected=*/true) {}
+
+  // Move-only class.
+  FakeBluetoothDevice(const FakeBluetoothDevice&) = delete;
+  FakeBluetoothDevice& operator=(const FakeBluetoothDevice&) = delete;
+};
+
+class BluetoothChooserContextTest : public testing::Test {
+ public:
+  BluetoothChooserContextTest()
+      : foo_url_("https://foo.com"),
+        bar_url_("https://bar.com"),
+        foo_origin_(url::Origin::Create(foo_url_)),
+        bar_origin_(url::Origin::Create(bar_url_)) {}
+
+  ~BluetoothChooserContextTest() override = default;
+
+  // Move-only class.
+  BluetoothChooserContextTest(const BluetoothChooserContextTest&) = delete;
+  BluetoothChooserContextTest& operator=(const BluetoothChooserContextTest&) =
+      delete;
+
+  void SetUp() override {
+    fake_adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
+    fake_device1_ = GetBluetoothDevice("Wireless Gizmo", kDeviceAddress1);
+    fake_device2_ = GetBluetoothDevice("Wireless Gadget", kDeviceAddress2);
+  }
+
+ protected:
+  Profile* profile() { return &profile_; }
+
+  BluetoothChooserContext* GetChooserContext(Profile* profile) {
+    auto* chooser_context =
+        BluetoothChooserContextFactory::GetForProfile(profile);
+    chooser_context->AddObserver(&mock_permission_observer_);
+    return chooser_context;
+  }
+
+  std::unique_ptr<FakeBluetoothDevice> GetBluetoothDevice(const char* name,
+                                                          std::string address) {
+    return std::make_unique<FakeBluetoothDevice>(fake_adapter_.get(), name,
+                                                 address);
+  }
+
+  // Mock Observer
+  MockPermissionObserver mock_permission_observer_;
+
+  const GURL foo_url_;
+  const GURL bar_url_;
+  const url::Origin foo_origin_;
+  const url::Origin bar_origin_;
+  std::unique_ptr<FakeBluetoothDevice> fake_device1_;
+  std::unique_ptr<FakeBluetoothDevice> fake_device2_;
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  scoped_refptr<FakeBluetoothAdapter> fake_adapter_;
+  TestingProfile profile_;
+};
+
+// Check that Web Bluetooth device permissions are granted and revoked properly,
+// and that the WebBluetoothDeviceId and device address can be retrieved using
+// each other.
+TEST_F(BluetoothChooserContextTest, CheckGrantAndRevokePermission) {
+  const std::vector<BluetoothUUID> services = {kGlucoseUUID,
+                                               kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options =
+      CreateOptionsForServices(services);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+
+  EXPECT_FALSE(context
+                   ->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress())
+                   .IsValid());
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+
+  blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
+      foo_origin_, foo_origin_, fake_device1_.get(), options);
+
+  EXPECT_TRUE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
+  EXPECT_EQ(context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress()),
+            device_id);
+  EXPECT_EQ(context->GetDeviceAddress(foo_origin_, foo_origin_, device_id),
+            fake_device1_->GetAddress());
+  EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, device_id));
+  for (const auto& service : services) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id, service));
+  }
+
+  base::Value expected_object(base::Value::Type::DICTIONARY);
+  expected_object.SetStringKey(kDeviceAddressKey, kDeviceAddress1);
+  expected_object.SetStringKey(kDeviceNameKey,
+                               fake_device1_->GetNameForDisplay());
+  expected_object.SetStringKey(kWebBluetoothDeviceIdKey, device_id.str());
+  base::Value expected_services(base::Value::Type::DICTIONARY);
+  expected_services.SetBoolKey(kGlucoseUUIDString, /*val=*/true);
+  expected_services.SetBoolKey(kBloodPressureUUIDString, /*val=*/true);
+  expected_object.SetKey(kServicesKey, std::move(expected_services));
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+      context->GetGrantedObjects(foo_origin_, foo_origin_);
+  ASSERT_EQ(1u, origin_objects.size());
+  EXPECT_EQ(expected_object, origin_objects[0]->value);
+  EXPECT_FALSE(origin_objects[0]->incognito);
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> all_origin_objects =
+      context->GetAllGrantedObjects();
+  ASSERT_EQ(1u, all_origin_objects.size());
+  EXPECT_EQ(foo_origin_.GetURL(), all_origin_objects[0]->requesting_origin);
+  EXPECT_EQ(foo_origin_.GetURL(), all_origin_objects[0]->embedding_origin);
+  EXPECT_EQ(expected_object, all_origin_objects[0]->value);
+  EXPECT_FALSE(all_origin_objects[0]->incognito);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  EXPECT_CALL(mock_permission_observer_,
+              OnPermissionRevoked(foo_origin_, foo_origin_));
+
+  context->RevokeObjectPermission(foo_origin_, foo_origin_,
+                                  origin_objects[0]->value);
+
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
+  EXPECT_FALSE(context
+                   ->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress())
+                   .IsValid());
+
+  origin_objects = context->GetGrantedObjects(foo_origin_, foo_origin_);
+  EXPECT_EQ(0u, origin_objects.size());
+
+  all_origin_objects = context->GetAllGrantedObjects();
+  EXPECT_EQ(0u, all_origin_objects.size());
+}
+
+// Check that Web Bluetooth permissions granted in incognito mode remain only
+// in the incognito session.
+TEST_F(BluetoothChooserContextTest, GrantPermissionInIncognito) {
+  const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options =
+      CreateOptionsForServices(services);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+  BluetoothChooserContext* incognito_context =
+      GetChooserContext(profile()->GetOffTheRecordProfile());
+
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
+      foo_origin_, foo_origin_, fake_device1_.get(), options);
+
+  EXPECT_TRUE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
+  EXPECT_EQ(device_id,
+            context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress()));
+  EXPECT_EQ(context->GetDeviceAddress(foo_origin_, foo_origin_, device_id),
+            fake_device1_->GetAddress());
+  EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, device_id));
+  for (const auto& service : services) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id, service));
+  }
+
+  EXPECT_FALSE(incognito_context->HasDevicePermission(foo_origin_, foo_origin_,
+                                                      device_id));
+  EXPECT_FALSE(incognito_context
+                   ->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress())
+                   .IsValid());
+
+  testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId incognito_device_id =
+      incognito_context->GrantServiceAccessPermission(
+          foo_origin_, foo_origin_, fake_device1_.get(), options);
+
+  EXPECT_FALSE(context->HasDevicePermission(foo_origin_, foo_origin_,
+                                            incognito_device_id));
+  EXPECT_NE(incognito_device_id,
+            context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress()));
+  EXPECT_TRUE(incognito_context->HasDevicePermission(foo_origin_, foo_origin_,
+                                                     incognito_device_id));
+  EXPECT_EQ(incognito_device_id,
+            incognito_context->GetWebBluetoothDeviceId(
+                foo_origin_, foo_origin_, fake_device1_->GetAddress()));
+  EXPECT_EQ(incognito_context->GetDeviceAddress(foo_origin_, foo_origin_,
+                                                incognito_device_id),
+            fake_device1_->GetAddress());
+  EXPECT_TRUE(incognito_context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, incognito_device_id));
+  for (const auto& service : services) {
+    EXPECT_TRUE(incognito_context->IsAllowedToAccessService(
+        foo_origin_, foo_origin_, incognito_device_id, service));
+  }
+
+  {
+    std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+        context->GetGrantedObjects(foo_origin_, foo_origin_);
+    EXPECT_EQ(1u, origin_objects.size());
+
+    std::vector<std::unique_ptr<ChooserContextBase::Object>>
+        all_origin_objects = context->GetAllGrantedObjects();
+    ASSERT_EQ(1u, all_origin_objects.size());
+    EXPECT_FALSE(all_origin_objects[0]->incognito);
+  }
+  {
+    std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+        incognito_context->GetGrantedObjects(foo_origin_, foo_origin_);
+    EXPECT_EQ(1u, origin_objects.size());
+
+    // GetAllGrantedObjects() on an incognito session also returns the
+    // permission objects granted in the non-incognito session.
+    std::vector<std::unique_ptr<ChooserContextBase::Object>>
+        all_origin_objects = incognito_context->GetAllGrantedObjects();
+    ASSERT_EQ(2u, all_origin_objects.size());
+    EXPECT_TRUE(all_origin_objects[0]->incognito ^
+                all_origin_objects[1]->incognito);
+  }
+}
+
+// Check that granting device permission with new services updates the
+// permission.
+TEST_F(BluetoothChooserContextTest, CheckGrantWithServiceUpdates) {
+  const std::vector<BluetoothUUID> services1{kGlucoseUUID, kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options1 =
+      CreateOptionsForServices(services1);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId device_id1 =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options1);
+  EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, device_id1));
+  for (const auto& service : services1) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id1, service));
+  }
+
+  const std::vector<BluetoothUUID> services2{kHeartRateUUID, kBloodPressureUUID,
+                                             kCyclingPowerUUID};
+  WebBluetoothRequestDeviceOptionsPtr options2 =
+      CreateOptionsForServices(services2);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId device_id2 =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options2);
+  EXPECT_EQ(device_id2, device_id1);
+
+  base::flat_set<BluetoothUUID> services_set(services1);
+  services_set.insert(services2.begin(), services2.end());
+  for (const auto& service : services_set) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id2, service));
+  }
+}
+
+// Check that permissions are granted to the union of filtered and optional
+// services.
+TEST_F(BluetoothChooserContextTest, CheckGrantWithOptionalServices) {
+  const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
+  const std::vector<BluetoothUUID> optional_services{kBatteryServiceUUID};
+  WebBluetoothRequestDeviceOptionsPtr options =
+      CreateOptionsForServices(services, optional_services);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
+      foo_origin_, foo_origin_, fake_device1_.get(), options);
+
+  EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, device_id));
+  for (const auto& service : services) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id, service));
+  }
+  for (const auto& service : optional_services) {
+    EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
+                                                  device_id, service));
+  }
+}
+
+// Check that the Bluetooth guard permission prevents Web Bluetooth from being
+// used even if permissions exist for a pair of origins.
+TEST_F(BluetoothChooserContextTest, BluetoothGuardPermission) {
+  const std::vector<BluetoothUUID> services1{kGlucoseUUID, kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options1 =
+      CreateOptionsForServices(services1);
+  const std::vector<BluetoothUUID> services2{kHeartRateUUID, kCyclingPowerUUID};
+  WebBluetoothRequestDeviceOptionsPtr options2 =
+      CreateOptionsForServices(services2);
+
+  auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
+  map->SetContentSettingDefaultScope(
+      foo_url_, foo_url_, ContentSettingsType::BLUETOOTH_GUARD,
+      /*resource_identifier=*/std::string(), CONTENT_SETTING_BLOCK);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA))
+      .Times(4);
+
+  blink::WebBluetoothDeviceId foo_device_id1 =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options1);
+  blink::WebBluetoothDeviceId foo_device_id2 =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options2);
+  blink::WebBluetoothDeviceId bar_device_id1 =
+      context->GrantServiceAccessPermission(bar_origin_, bar_origin_,
+                                            fake_device1_.get(), options1);
+  blink::WebBluetoothDeviceId bar_device_id2 =
+      context->GrantServiceAccessPermission(bar_origin_, bar_origin_,
+                                            fake_device2_.get(), options2);
+
+  {
+    std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+        context->GetGrantedObjects(foo_origin_, foo_origin_);
+    EXPECT_EQ(0u, origin_objects.size());
+  }
+  {
+    std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+        context->GetGrantedObjects(bar_origin_, bar_origin_);
+    EXPECT_EQ(2u, origin_objects.size());
+  }
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> all_origin_objects =
+      context->GetAllGrantedObjects();
+  EXPECT_EQ(2u, all_origin_objects.size());
+  for (const auto& object : all_origin_objects) {
+    EXPECT_EQ(object->requesting_origin, bar_origin_.GetURL());
+    EXPECT_EQ(object->embedding_origin, bar_origin_.GetURL());
+  }
+
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, foo_device_id1));
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, foo_device_id2));
+  EXPECT_TRUE(
+      context->HasDevicePermission(bar_origin_, bar_origin_, bar_device_id1));
+  EXPECT_TRUE(
+      context->HasDevicePermission(bar_origin_, bar_origin_, bar_device_id2));
+}
+
+// Check that a valid WebBluetoothDeviceId is produced for Bluetooth LE
+// scanned devices. When permission is granted to one of these devices, the
+// previously generated WebBluetoothDeviceId should be remembered.
+TEST_F(BluetoothChooserContextTest, BluetoothLEScannedDevices) {
+  BluetoothChooserContext* context = GetChooserContext(profile());
+
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA))
+      .Times(0);
+  blink::WebBluetoothDeviceId scanned_id = context->AddScannedDevice(
+      foo_origin_, foo_origin_, fake_device1_->GetAddress());
+
+  EXPECT_EQ(scanned_id,
+            context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
+                                             fake_device1_->GetAddress()));
+  EXPECT_EQ(fake_device1_->GetAddress(),
+            context->GetDeviceAddress(foo_origin_, foo_origin_, scanned_id));
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, scanned_id));
+  EXPECT_FALSE(context->IsAllowedToAccessAtLeastOneService(
+      foo_origin_, foo_origin_, scanned_id));
+
+  const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options =
+      CreateOptionsForServices(services);
+  testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
+  EXPECT_CALL(mock_permission_observer_,
+              OnChooserObjectPermissionChanged(
+                  ContentSettingsType::BLUETOOTH_GUARD,
+                  ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
+  blink::WebBluetoothDeviceId granted_id =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options);
+
+  EXPECT_EQ(scanned_id, granted_id);
+}
+
+// Granted devices should return the same ID when detected via a Bluetooth LE
+// scan. If the permission is revoked, then a new ID should be generated for the
+// device when detected via a Bluetooth LE scan.
+TEST_F(BluetoothChooserContextTest, BluetoothLEScanWithGrantedDevices) {
+  const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
+  WebBluetoothRequestDeviceOptionsPtr options =
+      CreateOptionsForServices(services);
+
+  BluetoothChooserContext* context = GetChooserContext(profile());
+
+  blink::WebBluetoothDeviceId granted_id =
+      context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
+                                            fake_device1_.get(), options);
+  blink::WebBluetoothDeviceId scanned_id = context->AddScannedDevice(
+      foo_origin_, foo_origin_, fake_device1_->GetAddress());
+  EXPECT_EQ(granted_id, scanned_id);
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+      context->GetGrantedObjects(foo_origin_, foo_origin_);
+  ASSERT_EQ(1u, origin_objects.size());
+  context->RevokeObjectPermission(foo_origin_, foo_origin_,
+                                  origin_objects[0]->value);
+
+  scanned_id = context->AddScannedDevice(foo_origin_, foo_origin_,
+                                         fake_device1_->GetAddress());
+  EXPECT_NE(scanned_id, granted_id);
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, scanned_id));
+  EXPECT_FALSE(
+      context->HasDevicePermission(foo_origin_, foo_origin_, granted_id));
+}
diff --git a/chrome/browser/chromeos/arc/print_spooler/print_session_impl.cc b/chrome/browser/chromeos/arc/print_spooler/print_session_impl.cc
index 118d888..c8ed547 100644
--- a/chrome/browser/chromeos/arc/print_spooler/print_session_impl.cc
+++ b/chrome/browser/chromeos/arc/print_spooler/print_session_impl.cc
@@ -28,7 +28,6 @@
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/c/system/types.h"
 #include "mojo/public/cpp/base/shared_memory_utils.h"
-#include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "net/base/filename_util.h"
 #include "printing/page_range.h"
 #include "printing/print_job_constants.h"
@@ -265,13 +264,16 @@
     return;
   }
 
+  int request_id = job_settings.FindIntKey(printing::kPreviewRequestID).value();
   instance_->CreatePreviewDocument(
       std::move(request),
       base::BindOnce(&PrintSessionImpl::OnPreviewDocumentCreated,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), request_id,
+                     std::move(callback)));
 }
 
 void PrintSessionImpl::OnPreviewDocumentCreated(
+    int request_id,
     CreatePreviewDocumentCallback callback,
     mojo::ScopedHandle preview_document,
     int64_t data_size) {
@@ -286,10 +288,12 @@
       base::BindOnce(&ReadPreviewDocument, std::move(preview_document),
                      static_cast<size_t>(data_size)),
       base::BindOnce(&PrintSessionImpl::OnPreviewDocumentRead,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), request_id,
+                     std::move(callback)));
 }
 
 void PrintSessionImpl::OnPreviewDocumentRead(
+    int request_id,
     CreatePreviewDocumentCallback callback,
     base::ReadOnlySharedMemoryRegion preview_document_region) {
   if (!preview_document_region.IsValid()) {
@@ -300,11 +304,34 @@
   if (!pdf_flattener_.is_bound()) {
     GetPrintingService()->BindPdfFlattener(
         pdf_flattener_.BindNewPipeAndPassReceiver());
+    pdf_flattener_.set_disconnect_handler(
+        base::BindOnce(&PrintSessionImpl::OnPdfFlattenerDisconnected,
+                       weak_ptr_factory_.GetWeakPtr()));
   }
+
+  bool inserted =
+      callbacks_.emplace(std::make_pair(request_id, std::move(callback)))
+          .second;
+  DCHECK(inserted);
+
   pdf_flattener_->FlattenPdf(
       std::move(preview_document_region),
-      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
-          std::move(callback), base::ReadOnlySharedMemoryRegion()));
+      base::BindOnce(&PrintSessionImpl::OnPdfFlattened,
+                     weak_ptr_factory_.GetWeakPtr(), request_id));
+}
+
+void PrintSessionImpl::OnPdfFlattened(
+    int request_id,
+    base::ReadOnlySharedMemoryRegion flattened_document_region) {
+  auto it = callbacks_.find(request_id);
+  std::move(it->second).Run(std::move(flattened_document_region));
+  callbacks_.erase(it);
+}
+
+void PrintSessionImpl::OnPdfFlattenerDisconnected() {
+  for (auto& it : callbacks_)
+    std::move(it.second).Run(base::ReadOnlySharedMemoryRegion());
+  callbacks_.clear();
 }
 
 void PrintSessionImpl::Close() {
diff --git a/chrome/browser/chromeos/arc/print_spooler/print_session_impl.h b/chrome/browser/chromeos/arc/print_spooler/print_session_impl.h
index 216f1a0..8979ada 100644
--- a/chrome/browser/chromeos/arc/print_spooler/print_session_impl.h
+++ b/chrome/browser/chromeos/arc/print_spooler/print_session_impl.h
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/macros.h"
+#include "base/containers/flat_map.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
@@ -44,6 +44,8 @@
       std::unique_ptr<ash::ArcCustomTab> custom_tab,
       mojom::PrintSessionInstancePtr instance);
 
+  PrintSessionImpl(const PrintSessionImpl&) = delete;
+  PrintSessionImpl& operator=(const PrintSessionImpl&) = delete;
   ~PrintSessionImpl() override;
 
   // Called when print preview is closed.
@@ -63,16 +65,24 @@
   // Called once the preview document has been created by ARC. The preview
   // document must be read and flattened before being returned by the
   // PrintRenderer.
-  void OnPreviewDocumentCreated(CreatePreviewDocumentCallback callback,
+  void OnPreviewDocumentCreated(int request_id,
+                                CreatePreviewDocumentCallback callback,
                                 mojo::ScopedHandle preview_document,
                                 int64_t data_size);
 
   // Called once the preview document from ARC has been read. The preview
   // document must be flattened before being returned by the PrintRenderer.
   void OnPreviewDocumentRead(
+      int request_id,
       CreatePreviewDocumentCallback callback,
       base::ReadOnlySharedMemoryRegion preview_document_region);
 
+  void OnPdfFlattened(
+      int request_id,
+      base::ReadOnlySharedMemoryRegion flattened_document_region);
+
+  void OnPdfFlattenerDisconnected();
+
   // Used to close the ARC Custom Tab used for printing. If the remote end
   // closes the connection, the ARC Custom Tab and print preview will be closed.
   // If printing has already started, this will not cancel any active print job.
@@ -98,13 +108,14 @@
   // Remote interface used to flatten a PDF (preview document).
   mojo::Remote<printing::mojom::PdfFlattener> pdf_flattener_;
 
+  // In flight callbacks to |pdf_flattener_|, with their request IDs as the key.
+  base::flat_map<int, CreatePreviewDocumentCallback> callbacks_;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
   base::WeakPtrFactory<PrintSessionImpl> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(PrintSessionImpl);
 };
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/crostini/crostini_installer_ui_delegate.h b/chrome/browser/chromeos/crostini/crostini_installer_ui_delegate.h
index 12174cb..6b6ae2f 100644
--- a/chrome/browser/chromeos/crostini/crostini_installer_ui_delegate.h
+++ b/chrome/browser/chromeos/crostini/crostini_installer_ui_delegate.h
@@ -16,14 +16,16 @@
  public:
   // The size of the download for the VM image.
   // TODO(timloh): This is just a placeholder.
-  static constexpr int64_t kDownloadSizeInBytes = 300 * 1024 * 1024;
-  // The minimum feasible size for a VM disk image.
+  // As of 2020-01-10 the Termina files.zip is ~90MiB and the squashfs container
+  // is ~330MiB.
+  static constexpr int64_t kDownloadSizeInBytes = 450 * 1024 * 1024;  // 450 MiB
+
+  // As of 2020-01-10 Crostini once installed uses ~1.8GiB, and metrics show
+  // that install success rate plummets when users have less than that much free
+  // space.
   static constexpr int64_t kMinimumDiskSize =
-      1ll * 1024 * 1024 * 1024;  // 1 GiB
-  // Minimum amount of free disk space to install crostini successfully.
-  static constexpr int64_t kMinimumFreeDiskSpace =
-      crostini::CrostiniInstallerUIDelegate::kDownloadSizeInBytes +
-      kMinimumDiskSize;
+      1.8l * 1024 * 1024 * 1024;  // 1.8 GiB
+  static constexpr int64_t kMinimumFreeDiskSpace = kMinimumDiskSize;
 
   // |progress_fraction| ranges from 0.0 to 1.0.
   using ProgressCallback =
diff --git a/chrome/browser/chromeos/extensions/autotest_private/OWNERS b/chrome/browser/chromeos/extensions/autotest_private/OWNERS
index eb940ade..db4f4f0 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/OWNERS
+++ b/chrome/browser/chromeos/extensions/autotest_private/OWNERS
@@ -1,2 +1,2 @@
 achuith@chromium.org
-stevenjb@chromium.org
+tbarzic@chromium.org
diff --git a/chrome/browser/chromeos/extensions/quick_unlock_private/OWNERS b/chrome/browser/chromeos/extensions/quick_unlock_private/OWNERS
index 940c88e..af0efcd 100644
--- a/chrome/browser/chromeos/extensions/quick_unlock_private/OWNERS
+++ b/chrome/browser/chromeos/extensions/quick_unlock_private/OWNERS
@@ -1,4 +1,5 @@
 alemate@chromium.org
-stevenjb@chromium.org
+
+file://chrome/browser/resources/settings/chromeos/OWNERS
 
 # COMPONENT: UI>Settings
diff --git a/chrome/browser/chromeos/extensions/users_private/OWNERS b/chrome/browser/chromeos/extensions/users_private/OWNERS
index 268f771..7f062de0 100644
--- a/chrome/browser/chromeos/extensions/users_private/OWNERS
+++ b/chrome/browser/chromeos/extensions/users_private/OWNERS
@@ -2,4 +2,3 @@
 alemate@chromium.org
 emaxx@chromium.org
 michaelpg@chromium.org
-stevenjb@chromium.org
diff --git a/chrome/browser/chromeos/login/version_info_updater.cc b/chrome/browser/chromeos/login/version_info_updater.cc
index 515fbf2c..3b51d9cf 100644
--- a/chrome/browser/chromeos/login/version_info_updater.cc
+++ b/chrome/browser/chromeos/login/version_info_updater.cc
@@ -190,10 +190,20 @@
 void VersionInfoUpdater::OnQueryAdbSideload(
     SessionManagerClient::AdbSideloadResponseCode response_code,
     bool enabled) {
-  if (response_code != SessionManagerClient::AdbSideloadResponseCode::SUCCESS) {
-    LOG(WARNING) << "Failed to query adb sideload status";
-    // Pretend to be enabled to show warning at login screen conservatively.
-    enabled = true;
+  switch (response_code) {
+    case SessionManagerClient::AdbSideloadResponseCode::SUCCESS:
+      break;
+    case SessionManagerClient::AdbSideloadResponseCode::FAILED:
+      // Pretend to be enabled to show warning at login screen conservatively.
+      LOG(WARNING) << "Failed to query adb sideload status";
+      enabled = true;
+      break;
+    case SessionManagerClient::AdbSideloadResponseCode::NEED_POWERWASH:
+      // This can only happen on device initialized before M74, i.e. not
+      // powerwashed since then. Treat it as powerwash disabled to not show the
+      // message.
+      enabled = false;
+      break;
   }
 
   if (delegate_)
diff --git a/chrome/browser/chromeos/status/OWNERS b/chrome/browser/chromeos/status/OWNERS
deleted file mode 100644
index 72b2844..0000000
--- a/chrome/browser/chromeos/status/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-sadrul@chromium.org
-stevenjb@chromium.org
diff --git a/chrome/browser/content_settings/chrome_content_settings_utils.cc b/chrome/browser/content_settings/chrome_content_settings_utils.cc
index 12037091..dace9ca 100644
--- a/chrome/browser/content_settings/chrome_content_settings_utils.cc
+++ b/chrome/browser/content_settings/chrome_content_settings_utils.cc
@@ -17,11 +17,6 @@
 
 namespace content_settings {
 
-void RecordMixedScriptAction(MixedScriptAction action) {
-  UMA_HISTOGRAM_ENUMERATION("ContentSettings.MixedScript", action,
-                            MIXED_SCRIPT_ACTION_COUNT);
-}
-
 void RecordPluginsAction(PluginsAction action) {
   UMA_HISTOGRAM_ENUMERATION("ContentSettings.Plugins", action,
                             PLUGINS_ACTION_COUNT);
diff --git a/chrome/browser/content_settings/chrome_content_settings_utils.h b/chrome/browser/content_settings/chrome_content_settings_utils.h
index de33fa2..e01e285 100644
--- a/chrome/browser/content_settings/chrome_content_settings_utils.h
+++ b/chrome/browser/content_settings/chrome_content_settings_utils.h
@@ -17,18 +17,6 @@
 
 namespace content_settings {
 
-// UMA histogram for the mixed script shield. The enum values correspond to
-// histogram entries, so do not remove any existing values.
-enum MixedScriptAction {
-  MIXED_SCRIPT_ACTION_DISPLAYED_SHIELD = 0,
-  MIXED_SCRIPT_ACTION_DISPLAYED_BUBBLE,
-  MIXED_SCRIPT_ACTION_CLICKED_ALLOW,
-  MIXED_SCRIPT_ACTION_CLICKED_LEARN_MORE,
-  MIXED_SCRIPT_ACTION_COUNT
-};
-
-void RecordMixedScriptAction(MixedScriptAction action);
-
 // UMA histogram for the plugins broken puzzle piece. The enum values
 // correspond to histogram entries, so do not remove any existing values.
 enum PluginsAction {
diff --git a/chrome/browser/content_settings/tab_specific_content_settings.cc b/chrome/browser/content_settings/tab_specific_content_settings.cc
index 58465b5..df87f900 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings.cc
+++ b/chrome/browser/content_settings/tab_specific_content_settings.cc
@@ -288,10 +288,7 @@
     status.blocked = true;
     content_settings::UpdateLocationBarUiForWebContents(web_contents());
 
-    if (type == ContentSettingsType::MIXEDSCRIPT) {
-      content_settings::RecordMixedScriptAction(
-          content_settings::MIXED_SCRIPT_ACTION_DISPLAYED_SHIELD);
-    } else if (type == ContentSettingsType::PLUGINS) {
+    if (type == ContentSettingsType::PLUGINS) {
       content_settings::RecordPluginsAction(
           content_settings::PLUGINS_ACTION_DISPLAYED_BLOCKED_ICON_IN_OMNIBOX);
     } else if (type == ContentSettingsType::POPUPS) {
diff --git a/chrome/browser/extensions/api/autofill_private/OWNERS b/chrome/browser/extensions/api/autofill_private/OWNERS
index aa215c7..03050fe 100644
--- a/chrome/browser/extensions/api/autofill_private/OWNERS
+++ b/chrome/browser/extensions/api/autofill_private/OWNERS
@@ -1 +1,3 @@
-stevenjb@chromium.org
+file://chrome/browser/resources/settings/OWNERS
+
+# COMPONENT: UI>Settings
diff --git a/chrome/browser/extensions/api/language_settings_private/OWNERS b/chrome/browser/extensions/api/language_settings_private/OWNERS
index 057813143..14a91b0 100644
--- a/chrome/browser/extensions/api/language_settings_private/OWNERS
+++ b/chrome/browser/extensions/api/language_settings_private/OWNERS
@@ -1,2 +1,5 @@
 michaelpg@chromium.org
-stevenjb@chromium.org
+
+file://chrome/browser/resources/settings/OWNERS
+
+# COMPONENT: UI>Settings
diff --git a/chrome/browser/extensions/api/passwords_private/OWNERS b/chrome/browser/extensions/api/passwords_private/OWNERS
index aa215c7..03050fe 100644
--- a/chrome/browser/extensions/api/passwords_private/OWNERS
+++ b/chrome/browser/extensions/api/passwords_private/OWNERS
@@ -1 +1,3 @@
-stevenjb@chromium.org
+file://chrome/browser/resources/settings/OWNERS
+
+# COMPONENT: UI>Settings
diff --git a/chrome/browser/extensions/api/settings_private/OWNERS b/chrome/browser/extensions/api/settings_private/OWNERS
index 5f9293f..599493a 100644
--- a/chrome/browser/extensions/api/settings_private/OWNERS
+++ b/chrome/browser/extensions/api/settings_private/OWNERS
@@ -1,5 +1,6 @@
 dbeam@chromium.org
 michaelpg@chromium.org
-stevenjb@chromium.org
+
+file://chrome/browser/resources/settings/OWNERS
 
 # COMPONENT: UI>Settings
diff --git a/chrome/browser/extensions/system_display/OWNERS b/chrome/browser/extensions/system_display/OWNERS
index d418f5f..671dbe7f 100644
--- a/chrome/browser/extensions/system_display/OWNERS
+++ b/chrome/browser/extensions/system_display/OWNERS
@@ -1,3 +1,5 @@
-stevenjb@chromium.org
+afakhry@chromium.org
+
+file://chrome/browser/resources/settings/OWNERS
 
 # COMPONENT: OS>Systems>Display
diff --git a/chrome/browser/favicon/OWNERS b/chrome/browser/favicon/OWNERS
index 77ebe9f..9a01ac2f 100644
--- a/chrome/browser/favicon/OWNERS
+++ b/chrome/browser/favicon/OWNERS
@@ -1,6 +1,5 @@
 pkotwicz@chromium.org
 sky@chromium.org
-stevenjb@chromium.org
 
 # Temporary owner, for refactoring changes only.
 caitkp@chromium.org
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4a50ba4..45c7b0d 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1259,6 +1259,11 @@
     "expiry_milestone": 83
   },
   {
+    "name": "enable-desktop-pwas-local-updating-throttle-persistence",
+    "owners": [ "desktop-pwas-team@google.com" ],
+    "expiry_milestone": 83
+  },
+  {
     "name": "enable-desktop-pwas-tab-strip",
     "owners": [ "desktop-pwas-team@google.com" ],
     "expiry_milestone": 84
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 553617ff..f2d7ab9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -649,6 +649,12 @@
     "Enable installed PWAs to update their app manifest data when the site "
     "manifest data has changed.";
 
+extern const char kDesktopPWAsLocalUpdatingThrottlePersistenceName[] =
+    "Desktop PWAs local updating throttle persistence";
+extern const char kDesktopPWAsLocalUpdatingThrottlePersistenceDescription[] =
+    "Persist the throttling of local PWA manifest update checks across browser "
+    "restarts.";
+
 extern const char kDesktopPWAsTabStripName[] = "Desktop PWA tab strips";
 extern const char kDesktopPWAsTabStripDescription[] =
     "Experimental UI for exploring what PWA windows would look like with a tab "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1e83f7ff..e00d974 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -401,6 +401,9 @@
 extern const char kDesktopPWAsLocalUpdatingName[];
 extern const char kDesktopPWAsLocalUpdatingDescription[];
 
+extern const char kDesktopPWAsLocalUpdatingThrottlePersistenceName[];
+extern const char kDesktopPWAsLocalUpdatingThrottlePersistenceDescription[];
+
 extern const char kDesktopPWAsTabStripName[];
 extern const char kDesktopPWAsTabStripDescription[];
 
diff --git a/chrome/browser/notifications/OWNERS b/chrome/browser/notifications/OWNERS
index 2ae91f0..1ceea9f 100644
--- a/chrome/browser/notifications/OWNERS
+++ b/chrome/browser/notifications/OWNERS
@@ -3,7 +3,6 @@
 knollr@chromium.org
 mukai@chromium.org
 peter@chromium.org
-stevenjb@chromium.org
 yoshiki@chromium.org
 
 # Linux files
diff --git a/chrome/browser/permissions/chooser_context_base.cc b/chrome/browser/permissions/chooser_context_base.cc
index 9c7bfcf..603d3c6 100644
--- a/chrome/browser/permissions/chooser_context_base.cc
+++ b/chrome/browser/permissions/chooser_context_base.cc
@@ -153,6 +153,27 @@
   NotifyPermissionChanged();
 }
 
+void ChooserContextBase::UpdateObjectPermission(
+    const url::Origin& requesting_origin,
+    const url::Origin& embedding_origin,
+    base::Value& old_object,
+    base::Value new_object) {
+  base::Value setting =
+      GetWebsiteSetting(requesting_origin, embedding_origin, /*info=*/nullptr);
+  base::Value* objects = setting.FindListKey(kObjectListKey);
+  if (!objects)
+    return;
+
+  base::Value::ListView object_list = objects->GetList();
+  auto it = std::find(object_list.begin(), object_list.end(), old_object);
+  if (it == object_list.end())
+    return;
+
+  *it = std::move(new_object);
+  SetWebsiteSetting(requesting_origin, embedding_origin, std::move(setting));
+  NotifyPermissionChanged();
+}
+
 void ChooserContextBase::RevokeObjectPermission(
     const url::Origin& requesting_origin,
     const url::Origin& embedding_origin,
diff --git a/chrome/browser/permissions/chooser_context_base.h b/chrome/browser/permissions/chooser_context_base.h
index 61c6360..1f322b0 100644
--- a/chrome/browser/permissions/chooser_context_base.h
+++ b/chrome/browser/permissions/chooser_context_base.h
@@ -98,6 +98,14 @@
                              const url::Origin& embedding_origin,
                              base::Value object);
 
+  // Updates |old_object| with |new_object| for |requesting_origin| when
+  // embedded within |embedding_origin|, and writes the value into
+  // |host_content_settings_map_|.
+  void UpdateObjectPermission(const url::Origin& requesting_origin,
+                              const url::Origin& embedding_origin,
+                              base::Value& old_object,
+                              base::Value new_object);
+
   // Revokes |requesting_origin|'s permission to access |object| when embedded
   // within |embedding_origin|.
   //
diff --git a/chrome/browser/permissions/chooser_context_base_unittest.cc b/chrome/browser/permissions/chooser_context_base_unittest.cc
index 46607ec..1c7d91b 100644
--- a/chrome/browser/permissions/chooser_context_base_unittest.cc
+++ b/chrome/browser/permissions/chooser_context_base_unittest.cc
@@ -141,6 +141,58 @@
   EXPECT_EQ(0u, objects.size());
 }
 
+TEST_F(ChooserContextBaseTest, GrantAndUpdateObjectPermission) {
+  TestChooserContext context(profile());
+  MockPermissionObserver mock_observer;
+  context.AddObserver(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
+  context.GrantObjectPermission(origin1_, origin1_, object1_.Clone());
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
+      context.GetGrantedObjects(origin1_, origin1_);
+  EXPECT_EQ(1u, objects.size());
+  EXPECT_EQ(object1_, objects[0]->value);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+  EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
+  context.UpdateObjectPermission(origin1_, origin1_, objects[0]->value,
+                                 object2_.Clone());
+
+  objects = context.GetGrantedObjects(origin1_, origin1_);
+  EXPECT_EQ(1u, objects.size());
+  EXPECT_EQ(object2_, objects[0]->value);
+}
+
+// UpdateObjectPermission() should not grant new permissions.
+TEST_F(ChooserContextBaseTest,
+       UpdateObjectPermissionWithNonExistentPermission) {
+  TestChooserContext context(profile());
+  MockPermissionObserver mock_observer;
+  context.AddObserver(&mock_observer);
+
+  // Attempt to update permission for non-existent |object1_| permission.
+  EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _)).Times(0);
+  context.UpdateObjectPermission(origin1_, origin1_, object1_,
+                                 object2_.Clone());
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
+      context.GetGrantedObjects(origin1_, origin1_);
+  EXPECT_TRUE(objects.empty());
+
+  // Grant permission for |object2_| but attempt to update permission for
+  // non-existent |object1_| permission again.
+  EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
+  context.GrantObjectPermission(origin1_, origin1_, object2_.Clone());
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+  EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _)).Times(0);
+  context.UpdateObjectPermission(origin1_, origin1_, object1_,
+                                 object2_.Clone());
+  testing::Mock::VerifyAndClearExpectations(&mock_observer);
+}
+
 TEST_F(ChooserContextBaseTest, GetAllGrantedObjects) {
   TestChooserContext context(profile());
   MockPermissionObserver mock_observer;
diff --git a/chrome/browser/permissions/permission_request_impl.cc b/chrome/browser/permissions/permission_request_impl.cc
index 4182ddc..9318a81 100644
--- a/chrome/browser/permissions/permission_request_impl.cc
+++ b/chrome/browser/permissions/permission_request_impl.cc
@@ -58,6 +58,9 @@
       return IDR_ANDROID_INFOBAR_CLIPBOARD;
     case ContentSettingsType::NFC:
       return IDR_ANDROID_INFOBAR_NFC;
+    case ContentSettingsType::VR:
+    case ContentSettingsType::AR:
+      return IDR_ANDROID_INFOBAR_VR_HEADSET;
     default:
       NOTREACHED();
       return IDR_ANDROID_INFOBAR_WARNING;
@@ -85,6 +88,9 @@
       return vector_icons::kAccessibilityIcon;
     case ContentSettingsType::CLIPBOARD_READ_WRITE:
       return kContentPasteIcon;
+    case ContentSettingsType::VR:
+    case ContentSettingsType::AR:
+      return kVrHeadsetIcon;
     default:
       NOTREACHED();
       return kExtensionIcon;
@@ -123,6 +129,12 @@
     case ContentSettingsType::NFC:
       message_id = IDS_NFC_PERMISSION_TITLE;
       break;
+    // TODO(andypaicu): GetTitleText is no longer used, but we have to return
+    // something at present to avoid crashing. This both avoids the crash and
+    // avoids adding an unused string.
+    case ContentSettingsType::VR:
+    case ContentSettingsType::AR:
+      return base::string16();
     default:
       NOTREACHED();
       return base::string16();
@@ -163,6 +175,12 @@
     case ContentSettingsType::NFC:
       message_id = IDS_NFC_INFOBAR_TEXT;
       break;
+    case ContentSettingsType::VR:
+      message_id = IDS_VR_INFOBAR_TEXT;
+      break;
+    case ContentSettingsType::AR:
+      message_id = IDS_AR_INFOBAR_TEXT;
+      break;
     default:
       NOTREACHED();
       return base::string16();
@@ -231,6 +249,12 @@
     case ContentSettingsType::NFC:
       message_id = IDS_NFC_PERMISSION_FRAGMENT;
       break;
+    case ContentSettingsType::VR:
+      message_id = IDS_VR_PERMISSION_FRAGMENT;
+      break;
+    case ContentSettingsType::AR:
+      message_id = IDS_AR_PERMISSION_FRAGMENT;
+      break;
     default:
       NOTREACHED();
       return base::string16();
diff --git a/chrome/browser/permissions/permission_uma_util.cc b/chrome/browser/permissions/permission_uma_util.cc
index ece9ff4..8741078 100644
--- a/chrome/browser/permissions/permission_uma_util.cc
+++ b/chrome/browser/permissions/permission_uma_util.cc
@@ -98,9 +98,9 @@
     case PermissionRequestType::PERMISSION_CLIPBOARD_READ_WRITE:
       return "ClipboardReadWrite";
     case PermissionRequestType::PERMISSION_VR:
-      return "Vr";
+      return "VR";
     case PermissionRequestType::PERMISSION_AR:
-      return "Ar";
+      return "AR";
     default:
       NOTREACHED();
       return "";
@@ -427,7 +427,7 @@
   bool secure_origin = content::IsOriginSecure(requesting_origin);
 
   switch (permission) {
-    // Geolocation, MidiSysEx, Push, Media and Clipboard permissions are
+    // Geolocation, MidiSysEx, Push, Media, Clipboard, and AR/VR permissions are
     // disabled on insecure origins, so there's no need to record separate
     // metrics for secure/insecure.
     case ContentSettingsType::GEOLOCATION:
@@ -475,6 +475,14 @@
       base::UmaHistogramEnumeration("Permissions.Action.Nfc", action,
                                     PermissionAction::NUM);
       break;
+    case ContentSettingsType::VR:
+      base::UmaHistogramEnumeration("Permissions.Action.VR", action,
+                                    PermissionAction::NUM);
+      break;
+    case ContentSettingsType::AR:
+      base::UmaHistogramEnumeration("Permissions.Action.AR", action,
+                                    PermissionAction::NUM);
+      break;
     // The user is not prompted for these permissions, thus there is no
     // permission action recorded for them.
     default:
diff --git a/chrome/browser/policy/browser_dm_token_storage_win.cc b/chrome/browser/policy/browser_dm_token_storage_win.cc
index 6387dba..157f19f 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_win.cc
@@ -15,6 +15,7 @@
 #include <wrl/client.h>
 
 #include <memory>
+#include <tuple>
 #include <utility>
 #include <vector>
 
@@ -25,6 +26,7 @@
 #include "base/logging.h"
 #include "base/no_destructor.h"
 #include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
@@ -155,41 +157,46 @@
 }
 
 std::string BrowserDMTokenStorageWin::InitDMToken() {
-  base::win::RegKey key;
-  base::string16 dm_token_key_path;
-  base::string16 dm_token_value_name;
-  InstallUtil::GetMachineLevelUserCloudPolicyDMTokenRegistryPath(
-      &dm_token_key_path, &dm_token_value_name);
-  LONG result = key.Open(HKEY_LOCAL_MACHINE, dm_token_key_path.c_str(),
-                         KEY_QUERY_VALUE | KEY_WOW64_64KEY);
-  if (result != ERROR_SUCCESS)
-    return std::string();
-
   // At the time of writing (January 2018), the DM token is about 200 bytes
   // long. The initial size of the buffer should be enough to cover most
   // realistic future size-increase scenarios, although we still make an effort
   // to support somewhat larger token sizes just to be safe.
   constexpr size_t kInitialDMTokenSize = 512;
 
-  DWORD size = kInitialDMTokenSize;
-  std::vector<char> raw_value(size);
-  DWORD dtype = REG_NONE;
-  result = key.ReadValue(dm_token_value_name.c_str(), raw_value.data(), &size,
-                         &dtype);
-  if (result == ERROR_MORE_DATA && size <= installer::kMaxDMTokenLength) {
-    raw_value.resize(size);
-    result = key.ReadValue(dm_token_value_name.c_str(), raw_value.data(), &size,
-                           &dtype);
+  base::win::RegKey key;
+  base::string16 dm_token_value_name;
+  std::vector<char> raw_value(kInitialDMTokenSize);
+
+  // Prefer the app-neutral location over the browser's to match Google Update's
+  // behavior.
+  for (const auto& location : {InstallUtil::BrowserLocation(false),
+                               InstallUtil::BrowserLocation(true)}) {
+    std::tie(key, dm_token_value_name) =
+        InstallUtil::GetCloudManagementDmTokenLocation(
+            InstallUtil::ReadOnly(true), location);
+    if (!key.Valid())
+      continue;
+
+    DWORD dtype = REG_NONE;
+    DWORD size = DWORD{raw_value.size()};
+    auto result = key.ReadValue(dm_token_value_name.c_str(), raw_value.data(),
+                                &size, &dtype);
+    if (result == ERROR_MORE_DATA && size <= installer::kMaxDMTokenLength) {
+      raw_value.resize(size);
+      result = key.ReadValue(dm_token_value_name.c_str(), raw_value.data(),
+                             &size, &dtype);
+    }
+    if (result != ERROR_SUCCESS || dtype != REG_BINARY || size == 0)
+      continue;
+
+    DCHECK_LE(size, installer::kMaxDMTokenLength);
+    return base::TrimWhitespaceASCII(base::StringPiece(raw_value.data(), size),
+                                     base::TRIM_ALL)
+        .as_string();
   }
 
-  if (result != ERROR_SUCCESS || dtype != REG_BINARY || size == 0) {
-    DVLOG(1) << "Failed to get DMToken from Registry.";
-    return std::string();
-  }
-  DCHECK_LE(size, installer::kMaxDMTokenLength);
-  std::string dm_token;
-  dm_token.assign(raw_value.data(), size);
-  return base::TrimWhitespaceASCII(dm_token, base::TRIM_ALL).as_string();
+  DVLOG(1) << "Failed to get DMToken from Registry.";
+  return std::string();
 }
 
 bool BrowserDMTokenStorageWin::InitEnrollmentErrorOption() {
diff --git a/chrome/browser/policy/browser_dm_token_storage_win.h b/chrome/browser/policy/browser_dm_token_storage_win.h
index fff7473..9a5ec92 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win.h
+++ b/chrome/browser/policy/browser_dm_token_storage_win.h
@@ -44,6 +44,8 @@
   FRIEND_TEST_ALL_PREFIXES(BrowserDMTokenStorageWinTest,
                            InitEnrollmentTokenFromSecondary);
   FRIEND_TEST_ALL_PREFIXES(BrowserDMTokenStorageWinTest, InitDMToken);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDMTokenStorageWinTest,
+                           InitDMTokenFromBrowserLocation);
 
   DISALLOW_COPY_AND_ASSIGN(BrowserDMTokenStorageWin);
 };
diff --git a/chrome/browser/policy/browser_dm_token_storage_win_unittest.cc b/chrome/browser/policy/browser_dm_token_storage_win_unittest.cc
index 9fe06c5..2e0bf5e07 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win_unittest.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_win_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/policy/browser_dm_token_storage_win.h"
 
+#include <tuple>
+
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/test_reg_util_win.h"
@@ -23,6 +25,7 @@
 constexpr wchar_t kEnrollmentToken1[] = L"fake-enrollment-token-1";
 constexpr wchar_t kEnrollmentToken2[] = L"fake-enrollment-token-2";
 constexpr char kDMToken1[] = "fake-dm-token-1";
+constexpr char kDMToken2[] = "fake-dm-token-2";
 
 }  // namespace
 
@@ -66,16 +69,18 @@
                ERROR_SUCCESS;
   }
 
-  bool SetDMToken(const std::string& dm_token) {
+  // Sets a DM token in either the app-neutral or browser-specific location in
+  // the registry.
+  bool SetDMToken(const std::string& dm_token,
+                  InstallUtil::BrowserLocation browser_location) {
     base::win::RegKey key;
-    base::string16 dm_token_key_path;
     base::string16 dm_token_value_name;
-    InstallUtil::GetMachineLevelUserCloudPolicyDMTokenRegistryPath(
-        &dm_token_key_path, &dm_token_value_name);
-    return (key.Create(HKEY_LOCAL_MACHINE, dm_token_key_path.c_str(),
-                       KEY_SET_VALUE | KEY_WOW64_64KEY) == ERROR_SUCCESS) &&
-           (key.WriteValue(dm_token_value_name.c_str(), dm_token.c_str(),
-                           dm_token.size(), REG_BINARY) == ERROR_SUCCESS);
+    std::tie(key, dm_token_value_name) =
+        InstallUtil::GetCloudManagementDmTokenLocation(
+            InstallUtil::ReadOnly(false), browser_location);
+    return key.Valid() &&
+           key.WriteValue(dm_token_value_name.c_str(), dm_token.c_str(),
+                          dm_token.size(), REG_BINARY) == ERROR_SUCCESS;
   }
 
   content::BrowserTaskEnvironment task_environment_;
@@ -108,9 +113,20 @@
 TEST_F(BrowserDMTokenStorageWinTest, InitDMToken) {
   ASSERT_TRUE(SetMachineGuid(kClientId1));
 
-  ASSERT_TRUE(SetDMToken(kDMToken1));
+  // The app-neutral location should be preferred.
+  ASSERT_TRUE(SetDMToken(kDMToken1, InstallUtil::BrowserLocation(false)));
+  ASSERT_TRUE(SetDMToken(kDMToken2, InstallUtil::BrowserLocation(true)));
   BrowserDMTokenStorageWin storage;
   EXPECT_EQ(std::string(kDMToken1), storage.InitDMToken());
 }
 
+TEST_F(BrowserDMTokenStorageWinTest, InitDMTokenFromBrowserLocation) {
+  ASSERT_TRUE(SetMachineGuid(kClientId1));
+
+  // If there's no app-neutral token, the browser-specific one should be used.
+  ASSERT_TRUE(SetDMToken(kDMToken2, InstallUtil::BrowserLocation(true)));
+  BrowserDMTokenStorageWin storage;
+  EXPECT_EQ(std::string(kDMToken2), storage.InitDMToken());
+}
+
 }  // namespace policy
diff --git a/chrome/browser/predictors/autocomplete_action_predictor_table.h b/chrome/browser/predictors/autocomplete_action_predictor_table.h
index e4622db..913f36c 100644
--- a/chrome/browser/predictors/autocomplete_action_predictor_table.h
+++ b/chrome/browser/predictors/autocomplete_action_predictor_table.h
@@ -10,7 +10,7 @@
 
 #include "base/macros.h"
 #include "base/strings/string16.h"
-#include "chrome/browser/predictors/predictor_table_base.h"
+#include "components/sqlite_proto/predictor_table_base.h"
 #include "url/gurl.h"
 
 namespace predictors {
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.h b/chrome/browser/predictors/resource_prefetch_predictor.h
index de24022..36f1285 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.h
+++ b/chrome/browser/predictors/resource_prefetch_predictor.h
@@ -21,7 +21,6 @@
 #include "base/task/cancelable_task_tracker.h"
 #include "base/time/time.h"
 #include "chrome/browser/predictors/loading_predictor_config.h"
-#include "chrome/browser/predictors/loading_predictor_key_value_data.h"
 #include "chrome/browser/predictors/navigation_id.h"
 #include "chrome/browser/predictors/resource_prefetch_predictor_tables.h"
 #include "components/history/core/browser/history_db_task.h"
@@ -29,6 +28,7 @@
 #include "components/history/core/browser/history_service_observer.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/sqlite_proto/loading_predictor_key_value_data.h"
 #include "content/public/common/resource_type.h"
 #include "net/base/network_isolation_key.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_tables.cc b/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
index 268d1f4..be452ad 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
@@ -14,8 +14,6 @@
 #include "chrome/browser/predictors/predictors_features.h"
 #include "sql/statement.h"
 
-using google::protobuf::MessageLite;
-
 namespace {
 
 const char kMetadataTableName[] = "resource_prefetch_predictor_metadata";
@@ -122,24 +120,6 @@
   return score;
 }
 
-void ResourcePrefetchPredictorTables::ScheduleDBTask(
-    const base::Location& from_here,
-    DBTask task) {
-  GetTaskRunner()->PostTask(
-      from_here,
-      base::BindOnce(
-          &ResourcePrefetchPredictorTables::ExecuteDBTaskOnDBSequence, this,
-          std::move(task)));
-}
-
-void ResourcePrefetchPredictorTables::ExecuteDBTaskOnDBSequence(DBTask task) {
-  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
-  if (CantAccessDatabase())
-    return;
-
-  std::move(task).Run(DB());
-}
-
 LoadingPredictorKeyValueTable<RedirectData>*
 ResourcePrefetchPredictorTables::host_redirect_table() {
   return host_redirect_table_.get();
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_tables.h b/chrome/browser/predictors/resource_prefetch_predictor_tables.h
index 21af1f0..64089ac 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_tables.h
+++ b/chrome/browser/predictors/resource_prefetch_predictor_tables.h
@@ -14,13 +14,9 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/sequenced_task_runner.h"
-#include "chrome/browser/predictors/loading_predictor_key_value_table.h"
-#include "chrome/browser/predictors/predictor_table_base.h"
 #include "chrome/browser/predictors/resource_prefetch_predictor.pb.h"
-
-namespace base {
-class Location;
-}
+#include "components/sqlite_proto/loading_predictor_key_value_table.h"
+#include "components/sqlite_proto/predictor_table_base.h"
 
 namespace predictors {
 
@@ -33,12 +29,6 @@
 //  - OriginTable - key: host, value: OriginData
 class ResourcePrefetchPredictorTables : public PredictorTableBase {
  public:
-  typedef base::OnceCallback<void(sql::Database*)> DBTask;
-
-  virtual void ScheduleDBTask(const base::Location& from_here, DBTask task);
-
-  virtual void ExecuteDBTaskOnDBSequence(DBTask task);
-
   virtual LoadingPredictorKeyValueTable<RedirectData>* host_redirect_table();
   virtual LoadingPredictorKeyValueTable<OriginData>* origin_table();
 
@@ -64,7 +54,7 @@
  protected:
   // Protected for testing. Use PredictorDatabase::resource_prefetch_tables()
   // instead of this constructor.
-  ResourcePrefetchPredictorTables(
+  explicit ResourcePrefetchPredictorTables(
       scoped_refptr<base::SequencedTaskRunner> db_task_runner);
   ~ResourcePrefetchPredictorTables() override;
 
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
index e47aec9..7ae28bb 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
@@ -182,9 +182,9 @@
 }
 
 void ResourcePrefetchPredictorTest::TearDown() {
-  EXPECT_EQ(*predictor_->host_redirect_data_->data_cache_,
+  EXPECT_EQ(*predictor_->host_redirect_data_->DataCacheForTesting(),
             mock_tables_->host_redirect_table_.data_);
-  EXPECT_EQ(*predictor_->origin_data_->data_cache_,
+  EXPECT_EQ(*predictor_->origin_data_->DataCacheForTesting(),
             mock_tables_->origin_table_.data_);
   loading_predictor_->Shutdown();
 }
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 102bedc..0c66b74 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -457,6 +457,9 @@
     public static final String SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP =
             "search_engine_choice_requested_timestamp";
 
+    public static final String SHARING_LAST_SHARED_CLASS_NAME = "last_shared_class_name";
+    public static final String SHARING_LAST_SHARED_PACKAGE_NAME = "last_shared_package_name";
+
     /**
      * Generic signin and sync promo preferences.
      */
@@ -728,6 +731,8 @@
                 SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE,
                 SEARCH_ENGINE_CHOICE_PRESENTED_VERSION,
                 SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP,
+                SHARING_LAST_SHARED_CLASS_NAME,
+                SHARING_LAST_SHARED_PACKAGE_NAME,
                 SIGNIN_AND_SYNC_PROMO_SHOW_COUNT,
                 SIGNIN_PROMO_IMPRESSIONS_COUNT_BOOKMARKS,
                 SIGNIN_PROMO_IMPRESSIONS_COUNT_SETTINGS,
diff --git a/chrome/browser/resources/settings/OWNERS b/chrome/browser/resources/settings/OWNERS
index 586b9103..b887eda 100644
--- a/chrome/browser/resources/settings/OWNERS
+++ b/chrome/browser/resources/settings/OWNERS
@@ -4,7 +4,6 @@
 hcarmona@chromium.org
 khorimoto@chromium.org
 michaelpg@chromium.org
-stevenjb@chromium.org
 tommycli@chromium.org
 zentaro@chromium.org
 
diff --git a/chrome/browser/resources/settings/chromeos/plugin_vm_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/plugin_vm_page/BUILD.gn
index 764cb05..1d0afe1 100644
--- a/chrome/browser/resources/settings/chromeos/plugin_vm_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/plugin_vm_page/BUILD.gn
@@ -8,15 +8,14 @@
   deps = [
     ":plugin_vm_browser_proxy",
     ":plugin_vm_page",
+    ":plugin_vm_remove_confirmation_dialog",
     ":plugin_vm_shared_paths",
     ":plugin_vm_subpage",
   ]
 }
 
 js_library("plugin_vm_browser_proxy") {
-  deps = [
-    "//ui/webui/resources/js:cr",
-  ]
+  deps = [ "//ui/webui/resources/js:cr" ]
 }
 
 js_library("plugin_vm_page") {
@@ -35,7 +34,9 @@
 }
 
 js_library("plugin_vm_subpage") {
-  deps = [
-    "../..:route",
-  ]
+  deps = [ "../..:route" ]
+}
+
+js_library("plugin_vm_remove_confirmation_dialog") {
+  deps = [ ":plugin_vm_browser_proxy" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.html b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.html
new file mode 100644
index 0000000..48cc54ee
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.html
@@ -0,0 +1,23 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
+<link rel="import" href="plugin_vm_browser_proxy.html">
+<link rel="import" href="../../settings_shared_css.html">
+
+<dom-module id="settings-plugin-vm-remove-confirmation-dialog">
+  <template>
+    <style include="settings-shared"></style>
+    <cr-dialog id="dialog" close-text="$i18n{close}">
+      <div slot="title">$i18n{pluginVmRemove}</div>
+      <div slot="body">$i18n{pluginVmRemoveConfirmationDialogMessage}</div>
+      <div slot="button-container">
+        <cr-button id="cancel" class="cancel-button"
+            on-click="onCancelClick_">$i18n{cancel}</cr-button>
+        <cr-button id="continue" class="action-button"
+            on-click="onContinueClick_">$i18n{pluginVmRemoveButton}</cr-button>
+      </div>
+    </cr-dialog>
+  </template>
+  <script src="plugin_vm_remove_confirmation_dialog.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.js b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.js
new file mode 100644
index 0000000..b71af55
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.js
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview 'settings-plugin-vm-remove-confirmation-dialog' is a component
+ * warning the user that remove plugin vm removes their vm's data and settings.
+ */
+Polymer({
+  is: 'settings-plugin-vm-remove-confirmation-dialog',
+
+  /** @override */
+  attached: function() {
+    this.$.dialog.showModal();
+  },
+
+  /** @private */
+  onCancelClick_: function() {
+    this.$.dialog.cancel();
+  },
+
+  /** @private */
+  onContinueClick_: function() {
+    settings.PluginVmBrowserProxyImpl.getInstance().removePluginVm();
+    this.$.dialog.close();
+  },
+});
diff --git a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.html b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.html
index 5142ce5..dcd4d1b 100644
--- a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
+<link rel="import" href="plugin_vm_remove_confirmation_dialog.html">
 <link rel="import" href="../../settings_shared_css.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
 
@@ -19,11 +20,16 @@
     <settings-toggle-button label="$i18n{pluginVmPrinterAccess}">
     </settings-toggle-button>
     <div id="plugin-vm-remove" class="settings-box">
-      <div id="plugin-vm-remove-label" class="start">$i18n{pluginVmRemove}</div>
-      <cr-button on-click="onRemoveClick_"
-        aria-labelledby="plugin-vm-remove-label">
-          $i18n{pluginVmRemoveButton}
+      <div id="pluginVmRemoveLabel" class="start">$i18n{pluginVmRemove}</div>
+      <cr-button on-click="onRemoveClick_" id="pluginVmRemoveButton"
+          aria-labelledby="pluginVmRemoveLabel">
+        $i18n{pluginVmRemoveButton}
       </cr-button>
+      <template is="dom-if" if="[[showRemoveConfirmationDialog_]]" restamp>
+        <settings-plugin-vm-remove-confirmation-dialog
+            on-close="onRemoveConfirmationDialogClose_">
+        </settings-plugin-vm-remove-confirmation-dialog>
+      </template>
     </div>
   </template>
   <script src="plugin_vm_subpage.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.js b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.js
index c5faf56..82ddc63 100644
--- a/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/plugin_vm_page/plugin_vm_subpage.js
@@ -16,6 +16,12 @@
       type: Object,
       notify: true,
     },
+
+    /** @private */
+    showRemoveConfirmationDialog_: {
+      type: Boolean,
+      value: false,
+    },
   },
 
   // TODO(juwa@google.com): Navigate back if plugin vm uninstalled.
@@ -26,11 +32,19 @@
   },
 
   /**
-   * Removes PluginVm.
-   * TODO(juwa@google.com): Show a confirmation dialog before removing.
+   * Shows a confirmation dialog, which if accepted will remove PluginVm.
    * @private
    */
   onRemoveClick_() {
-    settings.PluginVmBrowserProxyImpl.getInstance().removePluginVm();
+    this.showRemoveConfirmationDialog_ = true;
+  },
+
+  /**
+   * Hides the remove confirmation dialog.
+   * @private
+   */
+  onRemoveConfirmationDialogClose_: function() {
+    this.showRemoveConfirmationDialog_ = false;
+    this.$.pluginVmRemoveButton.focus();
   },
 });
diff --git a/chrome/browser/resources/settings/os_settings_resources.grd b/chrome/browser/resources/settings/os_settings_resources.grd
index d0f3efa0..d7d78536 100644
--- a/chrome/browser/resources/settings/os_settings_resources.grd
+++ b/chrome/browser/resources/settings/os_settings_resources.grd
@@ -1193,6 +1193,12 @@
       <structure name="IDR_OS_SETTINGS_PLUGIN_VM_BROWSER_PROXY_HTML"
                  file="chromeos/plugin_vm_page/plugin_vm_browser_proxy.html"
                  type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_PLUGIN_VM_REMOVE_CONFIRMATION_DIALOG_JS"
+                 file="chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.js"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_PLUGIN_VM_REMOVE_CONFIRMATION_DIALOG_HTML"
+                 file="chromeos/plugin_vm_page/plugin_vm_remove_confirmation_dialog.html"
+                 type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_USERS_PAGE_ADD_USER_DIALOG_JS"
                  file="chromeos/os_people_page/users_add_user_dialog.js"
                  type="chrome_html" />
diff --git a/chrome/browser/resources/settings/printing_page/OWNERS b/chrome/browser/resources/settings/printing_page/OWNERS
index 1100e97..23c360a8 100644
--- a/chrome/browser/resources/settings/printing_page/OWNERS
+++ b/chrome/browser/resources/settings/printing_page/OWNERS
@@ -1,2 +1,6 @@
 khorimoto@chromium.org
-stevenjb@chromium.org
+
+file://chrome/browser/resources/settings/OWNERS
+
+# COMPONENT: UI>Settings
+
diff --git a/chrome/browser/resources/tab_strip/tabs_api_proxy.js b/chrome/browser/resources/tab_strip/tabs_api_proxy.js
index 6b12e20..1ebb902 100644
--- a/chrome/browser/resources/tab_strip/tabs_api_proxy.js
+++ b/chrome/browser/resources/tab_strip/tabs_api_proxy.js
@@ -51,6 +51,7 @@
  *    blocked: boolean,
  *    crashed: boolean,
  *    favIconUrl: (string|undefined),
+ *    groupId: (string|undefined),
  *    id: number,
  *    index: number,
  *    isDefaultFavicon: boolean,
diff --git a/chrome/browser/settings/BUILD.gn b/chrome/browser/settings/BUILD.gn
index f3fe65cf..7834513 100644
--- a/chrome/browser/settings/BUILD.gn
+++ b/chrome/browser/settings/BUILD.gn
@@ -15,6 +15,7 @@
     "android/widget/java/src/org/chromium/chrome/browser/settings/ChromeBasePreference.java",
     "android/widget/java/src/org/chromium/chrome/browser/settings/ChromeImageViewPreference.java",
     "android/widget/java/src/org/chromium/chrome/browser/settings/ChromeSwitchPreference.java",
+    "android/widget/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java",
     "android/widget/java/src/org/chromium/chrome/browser/settings/SpinnerPreference.java",
     "android/widget/java/src/org/chromium/chrome/browser/settings/TextMessagePreference.java",
   ]
@@ -27,7 +28,8 @@
     "//third_party/android_deps:androidx_annotation_annotation_java",
     "//third_party/android_deps:com_android_support_preference_v7_java",
 
-    # TODO(crbug.com/1017190): Remove these 2 deps once we stop linting individual targets.
+    # TODO(crbug.com/1017190): Remove the following deps once we stop linting individual targets.
+    "//components/browser_ui/styles/android:java_resources",
     "//third_party/android_deps:com_android_support_design_java",
     "//ui/android:ui_java",
   ]
@@ -36,7 +38,9 @@
 android_resources("java_resources") {
   deps = [
     "//chrome/browser/ui/android/strings:ui_strings_grd",
+    "//components/browser_ui/styles/android:java_resources",
     "//third_party/android_deps:com_android_support_design_java",
+    "//third_party/android_deps:com_android_support_preference_v7_java",
     "//ui/android:ui_java_resources",
   ]
   resource_dirs = [ "android/java/res" ]
diff --git a/chrome/android/java/res/layout/checkable_image_view_widget.xml b/chrome/browser/settings/android/java/res/layout/checkable_image_view_widget.xml
similarity index 100%
rename from chrome/android/java/res/layout/checkable_image_view_widget.xml
rename to chrome/browser/settings/android/java/res/layout/checkable_image_view_widget.xml
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java b/chrome/browser/settings/android/widget/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java
similarity index 93%
rename from chrome/android/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java
rename to chrome/browser/settings/android/widget/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java
index bb179137..17be032 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java
+++ b/chrome/browser/settings/android/widget/java/src/org/chromium/chrome/browser/settings/ExpandablePreferenceGroup.java
@@ -13,7 +13,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 
-import org.chromium.chrome.R;
 import org.chromium.ui.drawable.StateListDrawableBuilder;
 import org.chromium.ui.widget.CheckableImageView;
 
@@ -66,9 +65,10 @@
 
         // For accessibility, read out the whole title and whether the group is collapsed/expanded.
         View view = holder.itemView;
-        String description = getTitle() + getContext().getResources().getString(mExpanded
-                ? R.string.accessibility_expanded_group
-                : R.string.accessibility_collapsed_group);
+        String description = getTitle()
+                + getContext().getResources().getString(mExpanded
+                                ? R.string.accessibility_expanded_group
+                                : R.string.accessibility_collapsed_group);
         view.setContentDescription(description);
     }
 
diff --git a/chrome/browser/sync/test/integration/sync_auth_test.cc b/chrome/browser/sync/test/integration/sync_auth_test.cc
index c5f8495..acc88a170 100644
--- a/chrome/browser/sync/test/integration/sync_auth_test.cc
+++ b/chrome/browser/sync/test/integration/sync_auth_test.cc
@@ -4,6 +4,8 @@
 
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -20,6 +22,7 @@
 #include "components/sync/driver/profile_sync_service.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_token_status.h"
+#include "content/public/test/test_launcher.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
@@ -91,6 +94,33 @@
   }
 };
 
+class BooleanHistogramTotalCountChecker : public StatusChangeChecker {
+ public:
+  BooleanHistogramTotalCountChecker(const std::string& histogram_name,
+                                    int expected_count)
+      : histogram_name_(histogram_name), expected_count_(expected_count) {}
+
+  BooleanHistogramTotalCountChecker(
+      const BooleanHistogramTotalCountChecker& other) = delete;
+  ~BooleanHistogramTotalCountChecker() override = default;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override {
+    int current_count =
+        histogram_tester_.GetBucketCount(histogram_name_, /*sample=*/0) +
+        histogram_tester_.GetBucketCount(histogram_name_, /*sample=*/1);
+    *os << "Waiting for " << histogram_name_ << " total count to be "
+        << expected_count_ << ". Current total count is " << current_count
+        << ".";
+    return current_count == expected_count_;
+  }
+
+ private:
+  base::HistogramTester histogram_tester_;
+  std::string histogram_name_;
+  int expected_count_;
+};
+
 class SyncAuthTest : public SyncTest {
  public:
   SyncAuthTest() : SyncTest(SINGLE_CLIENT), bookmark_index_(0) {}
@@ -137,6 +167,27 @@
   DISALLOW_COPY_AND_ASSIGN(SyncAuthTest);
 };
 
+class SyncAuthTestWithDirectoryNigoriPreTest : public SyncAuthTest {
+ public:
+  SyncAuthTestWithDirectoryNigoriPreTest() {
+    if (content::IsPreTest()) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{},
+          /*disabled_features=*/{switches::kSyncUSSNigori,
+                                 switches::kStopSyncInPausedState});
+    } else {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{switches::kSyncUSSNigori},
+          /*disabled_features=*/{switches::kStopSyncInPausedState});
+    }
+  }
+
+  ~SyncAuthTestWithDirectoryNigoriPreTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Verify that sync works with a valid OAuth2 token.
 IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) {
   ASSERT_TRUE(SetupSync());
@@ -447,4 +498,22 @@
   EXPECT_FALSE(HasUserPrefValue(pref_service, prefs::kHomePageIsNewTabPage));
 }
 
+IN_PROC_BROWSER_TEST_F(
+    SyncAuthTestWithDirectoryNigoriPreTest,
+    PRE_ShouldRecordNigoriConfigurationWithInvalidatedCredentials) {
+  ASSERT_TRUE(SetupSync());
+  GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
+  ASSERT_TRUE(GetSyncService(0)->GetAuthError().IsPersistentError());
+}
+
+IN_PROC_BROWSER_TEST_F(
+    SyncAuthTestWithDirectoryNigoriPreTest,
+    ShouldRecordNigoriConfigurationWithInvalidatedCredentials) {
+  BooleanHistogramTotalCountChecker histogram_status_checker(
+      "Sync.NigoriConfigurationWithInvalidatedCredentials",
+      /*expected_count=*/1);
+  ASSERT_TRUE(SetupClients());
+  EXPECT_TRUE(histogram_status_checker.Wait());
+}
+
 }  // namespace
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index f30cf44..0383de03 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2703,15 +2703,6 @@
       <message name="IDS_NTP_EXPLORE_SITES_MORE" desc="Text on a button leading to a complete view of all categories for all popular websites">
         More categories
       </message>
-      <message name="IDS_EXPLORE_SITES_DEFAULT_CATEGORY_NEWS" desc="The caption for a button that when clicked opens a UI in this tab that shows a list of websites with content related to news. [CHAR-LIMIT=12]">
-        News
-      </message>
-      <message name="IDS_EXPLORE_SITES_DEFAULT_CATEGORY_SPORTS" desc="The caption for a button that when clicked opens a UI in this tab that shows a list of websites with content related to sports. For example, soccer, cricket, football, running [CHAR-LIMIT=12]">
-        Sports
-      </message>
-      <message name="IDS_EXPLORE_SITES_DEFAULT_CATEGORY_SHOPPING" desc="The caption for a button that when clicked opens a UI in this tab that shows a list of websites with content related to shopping. [CHAR-LIMIT=12]">
-        Shopping
-      </message>
       <message name="IDS_EXPLORE_SITES_LOADING_FROM_NET" desc="Caption for an indeterminate loading spinner indicating that we are loading the data from the internet">
         Finding the best from the web…
       </message>
@@ -2727,9 +2718,6 @@
       <message name="IDS_EXPLORE_SITES_LOADING_ERROR_NEXT_STEPS_CHECK_CONNECTION" desc="Text on an error screen indicating that checking network connection can fix this error.">
         Checking your internet connection
       </message>
-      <message name="IDS_EXPLORE_SITES_TOP_SITES_TILE" desc="A label for a button that when clicked takes the user to a tab that contains categorized links to websites that are popular in their country. [CHAR_LIMIT=12]">
-        Top sites
-      </message>
       <message name="IDS_EXPLORE_SITES_IPH" desc="An in-product-help message for the explore sites feature on Android. The message encourages users to open a tab that contains categorized links to websites that are popular in their country.">
         See popular websites
       </message>
diff --git a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc
index a6c61ea..6359746 100644
--- a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc
@@ -31,6 +31,10 @@
   return "AssistantWebViewImpl";
 }
 
+gfx::NativeView AssistantWebViewImpl::GetNativeView() {
+  return web_contents_->GetNativeView();
+}
+
 void AssistantWebViewImpl::ChildPreferredSizeChanged(views::View* child) {
   DCHECK_EQ(web_view_.get(), child);
   SetPreferredSize(web_view_->GetPreferredSize());
@@ -109,6 +113,13 @@
   return false;
 }
 
+void AssistantWebViewImpl::NavigationStateChanged(
+    content::WebContents* web_contents,
+    content::InvalidateTypes changed_flags) {
+  DCHECK_EQ(web_contents_.get(), web_contents);
+  UpdateCanGoBack();
+}
+
 void AssistantWebViewImpl::DidStopLoading() {
   for (auto& observer : observers_)
     observer.DidStopLoading();
@@ -141,6 +152,18 @@
                                                              max_size);
 }
 
+void AssistantWebViewImpl::NavigationEntriesDeleted() {
+  UpdateCanGoBack();
+}
+
+void AssistantWebViewImpl::DidAttachInterstitialPage() {
+  UpdateCanGoBack();
+}
+
+void AssistantWebViewImpl::DidDetachInterstitialPage() {
+  UpdateCanGoBack();
+}
+
 void AssistantWebViewImpl::InitWebContents(Profile* profile) {
   web_contents_ =
       content::WebContents::Create(content::WebContents::CreateParams(
@@ -168,3 +191,14 @@
   web_view_->SetWebContents(web_contents_.get());
   AddChildView(web_view_.get());
 }
+
+void AssistantWebViewImpl::UpdateCanGoBack() {
+  const bool can_go_back = web_contents_->GetController().CanGoBack();
+  if (can_go_back_ == can_go_back)
+    return;
+
+  can_go_back_ = can_go_back;
+
+  for (auto& observer : observers_)
+    observer.DidChangeCanGoBack(can_go_back_);
+}
diff --git a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h
index 0db9b331..f2d502d 100644
--- a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h
+++ b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h
@@ -33,6 +33,7 @@
 
   // ash::AssistantWebView2:
   const char* GetClassName() const override;
+  gfx::NativeView GetNativeView() override;
   void ChildPreferredSizeChanged(views::View* child) override;
   void Layout() override;
   void AddObserver(Observer* observer) override;
@@ -53,22 +54,32 @@
   void ResizeDueToAutoResize(content::WebContents* web_contents,
                              const gfx::Size& new_size) override;
   bool TakeFocus(content::WebContents* web_contents, bool reverse) override;
+  void NavigationStateChanged(content::WebContents* web_contents,
+                              content::InvalidateTypes changed_flags) override;
 
   // content::WebContentsObserver:
   void DidStopLoading() override;
   void OnFocusChangedInPage(content::FocusedNodeDetails* details) override;
   void RenderViewHostChanged(content::RenderViewHost* old_host,
                              content::RenderViewHost* new_host) override;
+  void NavigationEntriesDeleted() override;
+  void DidAttachInterstitialPage() override;
+  void DidDetachInterstitialPage() override;
 
  private:
   void InitWebContents(Profile* profile);
   void InitLayout(Profile* profile);
 
+  void UpdateCanGoBack();
+
   const InitParams params_;
 
   std::unique_ptr<content::WebContents> web_contents_;
   std::unique_ptr<views::WebView> web_view_;
 
+  // Whether or not the embedded |web_contents_| can go back.
+  bool can_go_back_ = false;
+
   base::ObserverList<Observer> observers_;
 };
 
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
index 80bae50..8fa57ba 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
@@ -336,8 +336,6 @@
     : ContentSettingSimpleBubbleModel(delegate,
                                       web_contents,
                                       ContentSettingsType::MIXEDSCRIPT) {
-  content_settings::RecordMixedScriptAction(
-      content_settings::MIXED_SCRIPT_ACTION_DISPLAYED_BUBBLE);
   set_custom_link_enabled(true);
   set_show_learn_more(true);
   SetManageText();
@@ -346,9 +344,6 @@
 void ContentSettingMixedScriptBubbleModel::OnLearnMoreClicked() {
   if (delegate())
     delegate()->ShowLearnMorePage(content_type());
-
-  content_settings::RecordMixedScriptAction(
-      content_settings::MIXED_SCRIPT_ACTION_CLICKED_LEARN_MORE);
 }
 
 void ContentSettingMixedScriptBubbleModel::OnCustomLinkClicked() {
@@ -363,9 +358,6 @@
   // Update renderer side settings to allow active mixed content.
   web_contents()->ForEachFrame(
       base::BindRepeating(&::SetAllowRunningInsecureContent));
-
-  content_settings::RecordMixedScriptAction(
-      content_settings::MIXED_SCRIPT_ACTION_CLICKED_ALLOW);
 }
 
 // Don't set any manage text since none is displayed.
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
index 15c05ff..1700461 100644
--- a/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
+++ b/chrome/browser/ui/content_settings/content_setting_bubble_model_browsertest.cc
@@ -128,11 +128,6 @@
 
   EXPECT_FALSE(GetActiveTabSpecificContentSettings()->IsContentBlocked(
       ContentSettingsType::MIXEDSCRIPT));
-
-  // Check that the UMA counts are as expected.
-  histograms.ExpectBucketCount(
-      "ContentSettings.MixedScript",
-      content_settings::MIXED_SCRIPT_ACTION_CLICKED_ALLOW, 1);
 }
 
 // Tests that a MIXEDSCRIPT type ContentSettingBubbleModel does not work
diff --git a/chrome/browser/ui/webui/explore_sites_internals/explore_sites_internals_page_handler.cc b/chrome/browser/ui/webui/explore_sites_internals/explore_sites_internals_page_handler.cc
index b9a460d..0963163 100644
--- a/chrome/browser/ui/webui/explore_sites_internals/explore_sites_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/explore_sites_internals/explore_sites_internals_page_handler.cc
@@ -30,10 +30,6 @@
       return "Enabled";
     case ExploreSitesVariation::EXPERIMENT:
       return "Experiment";
-    case ExploreSitesVariation::PERSONALIZED:
-      return "Personalized";
-    case ExploreSitesVariation::MOST_LIKELY:
-      return "Most Likely";
     case ExploreSitesVariation::DISABLED:
       return "Disabled";
   }
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index f3c411e6..a75ce31 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -670,6 +670,8 @@
        IDS_SETTINGS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING},
       {"pluginVmRemove", IDS_SETTINGS_PLUGIN_VM_REMOVE_LABEL},
       {"pluginVmRemoveButton", IDS_SETTINGS_PLUGIN_VM_REMOVE_BUTTON},
+      {"pluginVmRemoveConfirmationDialogMessage",
+       IDS_SETTINGS_PLUGIN_VM_CONFIRM_REMOVE_DIALOG_BODY},
   };
   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
 }
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
index 8734632..9597a678 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
@@ -15,11 +15,14 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_group.h"
+#include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h"
 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_metrics.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/tab_groups/tab_group_id.h"
 #include "third_party/skia/include/core/SkStream.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -212,6 +215,45 @@
 }
 
 // TabStripModelObserver:
+void TabStripUIHandler::OnTabGroupChanged(const TabGroupChange& change) {
+  switch (change.type) {
+    case TabGroupChange::kCreated:
+    case TabGroupChange::kContentsChanged: {
+      // TabGroupChange::kCreated events are unnecessary as the front-end will
+      // assume a group was created if there is a tab-group-state-changed event
+      // with a new group ID. TabGroupChange::kContentsChanged events are
+      // handled by TabGroupStateChanged.
+      break;
+    }
+
+    case TabGroupChange::kVisualsChanged: {
+      // TODO(johntlee): Send visuals to front-end.
+      break;
+    }
+
+    case TabGroupChange::kClosed: {
+      FireWebUIListener("tab-group-closed",
+                        base::Value(change.group.ToString()));
+      break;
+    }
+  }
+}
+
+void TabStripUIHandler::TabGroupedStateChanged(
+    base::Optional<tab_groups::TabGroupId> group,
+    int index) {
+  int tab_id = extensions::ExtensionTabUtil::GetTabId(
+      browser_->tab_strip_model()->GetWebContentsAt(index));
+  if (group.has_value()) {
+    FireWebUIListener("tab-group-state-changed", base::Value(tab_id),
+                      base::Value(index),
+                      base::Value(group.value().ToString()));
+  } else {
+    FireWebUIListener("tab-group-state-changed", base::Value(tab_id),
+                      base::Value(index));
+  }
+}
+
 void TabStripUIHandler::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
@@ -342,6 +384,12 @@
   tab_data.SetInteger("id", extensions::ExtensionTabUtil::GetTabId(contents));
   tab_data.SetInteger("index", index);
 
+  const base::Optional<tab_groups::TabGroupId> group_id =
+      browser_->tab_strip_model()->GetTabGroupForTab(index);
+  if (group_id.has_value()) {
+    tab_data.SetString("groupId", group_id.value().ToString());
+  }
+
   TabRendererData tab_renderer_data =
       TabRendererData::FromTabInModel(browser_->tab_strip_model(), index);
   tab_data.SetBoolean("pinned", tab_renderer_data.pinned);
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h
index a667b6dbb..41be21d 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h
@@ -26,6 +26,9 @@
   void NotifyReceivedKeyboardFocus();
 
   // TabStripModelObserver:
+  void OnTabGroupChanged(const TabGroupChange& change) override;
+  void TabGroupedStateChanged(base::Optional<tab_groups::TabGroupId> group,
+                              int index) override;
   void OnTabStripModelChanged(
       TabStripModel* tab_strip_model,
       const TabStripModelChange& change,
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc
new file mode 100644
index 0000000..0678124
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h"
+#include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_layout.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "components/tab_groups/tab_group_id.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/test/test_web_ui.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/default_theme_provider.h"
+#include "ui/base/theme_provider.h"
+#include "ui/gfx/geometry/point.h"
+
+namespace {
+
+class TestTabStripUIHandler : public TabStripUIHandler {
+ public:
+  explicit TestTabStripUIHandler(content::WebUI* web_ui,
+                                 Browser* browser,
+                                 TabStripUIEmbedder* embedder)
+      : TabStripUIHandler(browser, embedder) {
+    set_web_ui(web_ui);
+  }
+};
+
+class MockTabStripUIEmbedder : public TabStripUIEmbedder {
+ public:
+  MockTabStripUIEmbedder() {}
+  MOCK_CONST_METHOD0(GetAcceleratorProvider, const ui::AcceleratorProvider*());
+  MOCK_METHOD0(CloseContainer, void());
+  MOCK_METHOD2(ShowContextMenuAtPoint,
+               void(gfx::Point, std::unique_ptr<ui::MenuModel>));
+  MOCK_METHOD0(GetLayout, TabStripUILayout());
+  MOCK_METHOD0(GetThemeProvider, const ui::ThemeProvider*());
+};
+
+}  // namespace
+
+class TabStripUIHandlerTest : public BrowserWithTestWindowTest {
+ public:
+  TabStripUIHandlerTest() : web_ui_(new content::TestWebUI) {}
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+    handler_ = std::make_unique<TestTabStripUIHandler>(web_ui(), browser(),
+                                                       &mock_embedder_);
+    handler()->AllowJavascriptForTesting();
+    web_ui()->ClearTrackedCalls();
+  }
+
+  TabStripUIHandler* handler() { return handler_.get(); }
+  content::TestWebUI* web_ui() { return web_ui_.get(); }
+
+ protected:
+  ::testing::NiceMock<MockTabStripUIEmbedder> mock_embedder_;
+
+ private:
+  std::unique_ptr<content::TestWebUI> web_ui_;
+  std::unique_ptr<TestTabStripUIHandler> handler_;
+  DISALLOW_COPY_AND_ASSIGN(TabStripUIHandlerTest);
+};
+
+TEST_F(TabStripUIHandlerTest, GroupClosedEvent) {
+  AddTab(browser(), GURL("http://foo"));
+  tab_groups::TabGroupId expected_group_id =
+      browser()->tab_strip_model()->AddToNewGroup({0});
+  browser()->tab_strip_model()->RemoveFromGroup({0});
+
+  const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
+  EXPECT_EQ("cr.webUIListenerCallback", data.function_name());
+
+  std::string event_name;
+  ASSERT_TRUE(data.arg1()->GetAsString(&event_name));
+  EXPECT_EQ("tab-group-closed", event_name);
+
+  std::string actual_group_id;
+  ASSERT_TRUE(data.arg2()->GetAsString(&actual_group_id));
+  EXPECT_EQ(expected_group_id.ToString(), actual_group_id);
+}
+
+TEST_F(TabStripUIHandlerTest, GroupStateChangedEvents) {
+  AddTab(browser(), GURL("http://foo/1"));
+  AddTab(browser(), GURL("http://foo/2"));
+
+  // Add one of the tabs to a group to test for a tab-group-state-changed event.
+  tab_groups::TabGroupId expected_group_id =
+      browser()->tab_strip_model()->AddToNewGroup({0, 1});
+
+  const content::TestWebUI::CallData& grouped_data =
+      *web_ui()->call_data().back();
+  EXPECT_EQ("cr.webUIListenerCallback", grouped_data.function_name());
+
+  std::string event_name;
+  ASSERT_TRUE(grouped_data.arg1()->GetAsString(&event_name));
+  EXPECT_EQ("tab-group-state-changed", event_name);
+
+  int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
+      browser()->tab_strip_model()->GetWebContentsAt(1));
+  int actual_tab_id;
+  ASSERT_TRUE(grouped_data.arg2()->GetAsInteger(&actual_tab_id));
+  EXPECT_EQ(expected_tab_id, actual_tab_id);
+
+  int index;
+  ASSERT_TRUE(grouped_data.arg3()->GetAsInteger(&index));
+  EXPECT_EQ(1, index);
+
+  std::string actual_group_id;
+  ASSERT_TRUE(grouped_data.arg4()->GetAsString(&actual_group_id));
+  EXPECT_EQ(expected_group_id.ToString(), actual_group_id);
+
+  // Remove the tab from the group to test for a tab-group-state-changed event.
+  browser()->tab_strip_model()->RemoveFromGroup({1});
+  const content::TestWebUI::CallData& ungrouped_data =
+      *web_ui()->call_data().back();
+  EXPECT_EQ("cr.webUIListenerCallback", ungrouped_data.function_name());
+  ASSERT_TRUE(ungrouped_data.arg1()->GetAsString(&event_name));
+  EXPECT_EQ("tab-group-state-changed", event_name);
+  ASSERT_TRUE(ungrouped_data.arg2()->GetAsInteger(&actual_tab_id));
+  EXPECT_EQ(expected_tab_id, actual_tab_id);
+  ASSERT_TRUE(ungrouped_data.arg3()->GetAsInteger(&index));
+  EXPECT_EQ(1, index);
+  EXPECT_EQ(nullptr, ungrouped_data.arg4());
+}
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 4028d207..c8bd67f7 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -244,16 +244,10 @@
     "platform_controller.h",
     "scheduler_browser_renderer_interface.h",
     "scheduler_delegate.h",
-    "service/browser_xr_runtime.cc",
-    "service/browser_xr_runtime.h",
     "service/gvr_consent_helper.cc",
     "service/gvr_consent_helper.h",
-    "service/vr_service_impl.cc",
-    "service/vr_service_impl.h",
     "service/xr_device_service.cc",
     "service/xr_device_service.h",
-    "service/xr_runtime_manager.cc",
-    "service/xr_runtime_manager.h",
     "service/xr_runtime_manager_observer.h",
     "service/xr_session_request_consent_manager.cc",
     "service/xr_session_request_consent_manager.h",
@@ -516,15 +510,21 @@
 
   deps = [
     ":vr_test_support",
+    "//chrome/browser",
+    "//chrome/test:test_support",
     "//components/url_formatter",
     "//components/vector_icons",
+    "//content/test:test_support",
     "//mojo/public/cpp/bindings",
     "//services/network:test_support",
     "//testing/gmock",
     "//ui/gfx/geometry",
   ]
   if (is_android) {
-    deps += [ "//ui/android:ui_java" ]
+    deps += [
+      "//chrome:chrome_android_core",
+      "//ui/android:ui_java",
+    ]
   }
 }
 
diff --git a/chrome/browser/vr/elements/vector_icon_unittest.cc b/chrome/browser/vr/elements/vector_icon_unittest.cc
index 5c4591aa..06f0f43 100644
--- a/chrome/browser/vr/elements/vector_icon_unittest.cc
+++ b/chrome/browser/vr/elements/vector_icon_unittest.cc
@@ -52,7 +52,7 @@
   EXPECT_CALL(canvas, willSave());
 
   // This matrix is concatenated to apply to the vector icon.
-  EXPECT_CALL(canvas, didConcat(_));
+  EXPECT_CALL(canvas, didScale(_, _));
 
   // This is the call to draw the path comprising the vector icon.
   EXPECT_CALL(canvas, onDrawPath(_, _));
diff --git a/chrome/browser/vr/service/isolated_device_provider.h b/chrome/browser/vr/service/isolated_device_provider.h
index 89e3a8e..210f3b0c 100644
--- a/chrome/browser/vr/service/isolated_device_provider.h
+++ b/chrome/browser/vr/service/isolated_device_provider.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_VR_SERVICE_ISOLATED_DEVICE_PROVIDER_H_
 
 #include "base/containers/flat_map.h"
+#include "chrome/browser/vr/vr_export.h"
 #include "device/vr/public/mojom/isolated_xr_service.mojom.h"
 #include "device/vr/vr_device.h"
 #include "device/vr/vr_device_provider.h"
@@ -17,7 +18,7 @@
 
 class VRUiHost;
 
-class IsolatedVRDeviceProvider
+class VR_EXPORT IsolatedVRDeviceProvider
     : public device::VRDeviceProvider,
       public device::mojom::IsolatedXRRuntimeProviderClient {
  public:
diff --git a/chrome/browser/vr/service/vr_service_impl.cc b/chrome/browser/vr/service/vr_service_impl.cc
index 3a75036..4777852f 100644
--- a/chrome/browser/vr/service/vr_service_impl.cc
+++ b/chrome/browser/vr/service/vr_service_impl.cc
@@ -8,15 +8,19 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "build/build_config.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/vr/metrics/session_metrics_helper.h"
 #include "chrome/browser/vr/mode.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
 #include "chrome/browser/vr/service/xr_runtime_manager.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/ukm/content/source_url_recorder.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/browser/render_widget_host.h"
@@ -85,6 +89,17 @@
   return vr::XrConsentPromptLevel::kDefault;
 }
 
+ContentSettingsType GetRequiredPermission(device::mojom::XRSessionMode mode) {
+  switch (mode) {
+    case device::mojom::XRSessionMode::kInline:
+      return ContentSettingsType::SENSORS;
+    case device::mojom::XRSessionMode::kImmersiveVr:
+      return ContentSettingsType::VR;
+    case device::mojom::XRSessionMode::kImmersiveAr:
+      return ContentSettingsType::AR;
+  }
+}
+
 }  // namespace
 
 namespace vr {
@@ -409,19 +424,41 @@
   DCHECK_NE(options->mode, device::mojom::XRSessionMode::kImmersiveAr);
 #endif
 
+  bool consent_granted = false;
   XrConsentPromptLevel consent_level =
       GetRequiredConsentLevel(options->mode, runtime, requested_features);
+  if (!base::FeatureList::IsEnabled(features::kWebXrPermissionsApi)) {
+    consent_granted =
+        ((consent_level == XrConsentPromptLevel::kNone) ||
+         IsConsentGrantedForDevice(runtime->GetId(), consent_level));
+  }
 
   // Skip the consent prompt if the user has already consented for this device,
   // or if consent is not needed.
-  if (consent_level == XrConsentPromptLevel::kNone ||
-      IsConsentGrantedForDevice(runtime->GetId(), consent_level) ||
-      IsXrDeviceConsentPromptDisabledForTesting()) {
+  if (consent_granted || IsXrDeviceConsentPromptDisabledForTesting()) {
     DoRequestSession(std::move(options), std::move(callback), runtime,
                      std::move(requested_features));
     return;
   }
 
+  if (base::FeatureList::IsEnabled(features::kWebXrPermissionsApi)) {
+    PermissionManager* permission_manager = PermissionManager::Get(
+        Profile::FromBrowserContext(GetWebContents()->GetBrowserContext()));
+    DCHECK(permission_manager);
+
+    // Need to calculate the permission before the call below, as otherwise
+    // std::move nulls options out before GetRequiredPermission runs.
+    ContentSettingsType permission = GetRequiredPermission(options->mode);
+    permission_manager->RequestPermission(
+        permission, render_frame_host_,
+        render_frame_host_->GetLastCommittedURL(), true,
+        base::BindOnce(&VRServiceImpl::OnPermissionResult,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(options),
+                       std::move(callback), runtime->GetId(),
+                       std::move(requested_features), consent_level));
+    return;
+  }
+
   // TODO(crbug.com/968233): Unify the below consent flow.
 #if defined(OS_ANDROID)
   if (options->mode == device::mojom::XRSessionMode::kImmersiveAr) {
@@ -462,6 +499,20 @@
   NOTREACHED();
 }
 
+// TODO(alcooper): Once the ConsentFlow can be removed expected_runtime_id and
+// consent_level shouldn't be needed.
+void VRServiceImpl::OnPermissionResult(
+    device::mojom::XRSessionOptionsPtr options,
+    device::mojom::VRService::RequestSessionCallback callback,
+    device::mojom::XRDeviceId expected_runtime_id,
+    std::set<device::mojom::XRSessionFeature> enabled_features,
+    XrConsentPromptLevel consent_level,
+    ContentSetting setting_value) {
+  OnConsentResult(std::move(options), std::move(callback), expected_runtime_id,
+                  std::move(enabled_features), consent_level,
+                  setting_value == ContentSetting::CONTENT_SETTING_ALLOW);
+}
+
 void VRServiceImpl::OnConsentResult(
     device::mojom::XRSessionOptionsPtr options,
     device::mojom::VRService::RequestSessionCallback callback,
diff --git a/chrome/browser/vr/service/vr_service_impl.h b/chrome/browser/vr/service/vr_service_impl.h
index 4631503..3ef3f21 100644
--- a/chrome/browser/vr/service/vr_service_impl.h
+++ b/chrome/browser/vr/service/vr_service_impl.h
@@ -15,7 +15,7 @@
 
 #include "chrome/browser/vr/metrics/session_metrics_helper.h"
 #include "chrome/browser/vr/service/xr_consent_prompt_level.h"
-#include "chrome/browser/vr/vr_export.h"
+#include "components/content_settings/core/common/content_settings.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device.h"
@@ -38,8 +38,8 @@
 
 // Browser process implementation of the VRService mojo interface. Instantiated
 // through Mojo once the user loads a page containing WebXR.
-class VR_EXPORT VRServiceImpl : public device::mojom::VRService,
-                                content::WebContentsObserver {
+class VRServiceImpl : public device::mojom::VRService,
+                      content::WebContentsObserver {
  public:
   static bool IsXrDeviceConsentPromptDisabledForTesting();
 
@@ -139,6 +139,13 @@
       std::set<device::mojom::XRSessionFeature> enabled_features,
       XrConsentPromptLevel consent_level,
       bool is_consent_granted);
+  void OnPermissionResult(
+      device::mojom::XRSessionOptionsPtr options,
+      device::mojom::VRService::RequestSessionCallback callback,
+      device::mojom::XRDeviceId expected_runtime_id,
+      std::set<device::mojom::XRSessionFeature> enabled_features,
+      XrConsentPromptLevel consent_level,
+      ContentSetting setting_value);
 
   bool IsConsentGrantedForDevice(device::mojom::XRDeviceId device_id,
                                  XrConsentPromptLevel consent_level);
diff --git a/chrome/browser/vr/service/xr_runtime_manager.h b/chrome/browser/vr/service/xr_runtime_manager.h
index 871fc29d..ae68886 100644
--- a/chrome/browser/vr/service/xr_runtime_manager.h
+++ b/chrome/browser/vr/service/xr_runtime_manager.h
@@ -20,7 +20,6 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/vr/service/vr_service_impl.h"
 #include "chrome/browser/vr/service/xr_runtime_manager_observer.h"
-#include "chrome/browser/vr/vr_export.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -36,7 +35,7 @@
 
 // Singleton used to provide the platform's XR Runtimes to VRServiceImpl
 // instances.
-class VR_EXPORT XRRuntimeManager : public base::RefCounted<XRRuntimeManager> {
+class XRRuntimeManager : public base::RefCounted<XRRuntimeManager> {
  public:
   friend base::RefCounted<XRRuntimeManager>;
   static constexpr auto kRefCountPreference =
diff --git a/chrome/browser/vr/test/webvr_browser_test.h b/chrome/browser/vr/test/webvr_browser_test.h
index d439cbb6..9212e86 100644
--- a/chrome/browser/vr/test/webvr_browser_test.h
+++ b/chrome/browser/vr/test/webvr_browser_test.h
@@ -49,6 +49,9 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
     disable_features_.push_back(features::kWindowsMixedReality);
 #endif
+#if BUILDFLAG(ENABLE_OPENXR)
+    disable_features_.push_back(features::kOpenXR);
+#endif
   }
 };
 
@@ -66,6 +69,9 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
     disable_features_.push_back(features::kWindowsMixedReality);
 #endif
+#if BUILDFLAG(ENABLE_OPENXR)
+    disable_features_.push_back(features::kOpenXR);
+#endif
   }
 };
 
@@ -78,6 +84,9 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
     disable_features_.push_back(features::kWindowsMixedReality);
 #endif
+#if BUILDFLAG(ENABLE_OPENXR)
+    disable_features_.push_back(features::kOpenXR);
+#endif
   }
 };
 
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index c98b69e4..e3857df9 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -98,6 +98,9 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
   disable_features_.push_back(features::kWindowsMixedReality);
 #endif
+#if BUILDFLAG(ENABLE_OPENXR)
+  disable_features_.push_back(features::kOpenXR);
+#endif
 }
 
 WebXrVrRuntimelessBrowserTestSensorless::
@@ -116,6 +119,9 @@
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
   disable_features_.push_back(features::kWindowsMixedReality);
 #endif
+#if BUILDFLAG(ENABLE_OPENXR)
+  disable_features_.push_back(features::kOpenXR);
+#endif
 }
 
 XrBrowserTestBase::RuntimeType WebXrVrOpenVrBrowserTestBase::GetRuntimeType()
@@ -130,7 +136,11 @@
   return gfx::Vector3dF(0, 0, 0.08f);
 }
 
-WebXrVrWmrBrowserTestBase::WebXrVrWmrBrowserTestBase() {}
+WebXrVrWmrBrowserTestBase::WebXrVrWmrBrowserTestBase() {
+#if BUILDFLAG(ENABLE_OPENXR)
+  disable_features_.push_back(features::kOpenXR);
+#endif
+}
 
 WebXrVrWmrBrowserTestBase::~WebXrVrWmrBrowserTestBase() = default;
 
diff --git a/chrome/browser/vr/webxr_permission_context.cc b/chrome/browser/vr/webxr_permission_context.cc
index f7bddb7..637601d 100644
--- a/chrome/browser/vr/webxr_permission_context.cc
+++ b/chrome/browser/vr/webxr_permission_context.cc
@@ -20,25 +20,6 @@
 
 WebXrPermissionContext::~WebXrPermissionContext() = default;
 
-ContentSetting WebXrPermissionContext::GetPermissionStatusInternal(
-    content::RenderFrameHost* render_frame_host,
-    const GURL& requesting_origin,
-    const GURL& embedding_origin) const {
-  return CONTENT_SETTING_BLOCK;
-}
-
-void WebXrPermissionContext::DecidePermission(
-    content::WebContents* web_contents,
-    const PermissionRequestID& id,
-    const GURL& requesting_origin,
-    const GURL& embedding_origin,
-    bool user_gesture,
-    BrowserPermissionCallback callback) {
-  // The user shouldn't be prompted to authorize AR/VR while it's being
-  // implemented.
-  NOTREACHED();
-}
-
 bool WebXrPermissionContext::IsRestrictedToSecureOrigins() const {
   return true;
 }
diff --git a/chrome/browser/vr/webxr_permission_context.h b/chrome/browser/vr/webxr_permission_context.h
index 159e205..d6278df 100644
--- a/chrome/browser/vr/webxr_permission_context.h
+++ b/chrome/browser/vr/webxr_permission_context.h
@@ -18,16 +18,6 @@
 
  private:
   // PermissionContextBase:
-  ContentSetting GetPermissionStatusInternal(
-      content::RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin,
-      const GURL& embedding_origin) const override;
-  void DecidePermission(content::WebContents* web_contents,
-                        const PermissionRequestID& id,
-                        const GURL& requesting_origin,
-                        const GURL& embedding_origin,
-                        bool user_gesture,
-                        BrowserPermissionCallback callback) override;
   bool IsRestrictedToSecureOrigins() const override;
 
   ContentSettingsType content_settings_type_;
diff --git a/chrome/browser/vr/win/vr_browser_renderer_thread_win.h b/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
index 981a7029..6e7e7e61 100644
--- a/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
+++ b/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
@@ -11,7 +11,6 @@
 #include "chrome/browser/vr/browser_renderer.h"
 #include "chrome/browser/vr/model/capturing_state_model.h"
 #include "chrome/browser/vr/model/web_vr_model.h"
-#include "chrome/browser/vr/service/browser_xr_runtime.h"
 #include "chrome/browser/vr/vr_export.h"
 #include "content/public/browser/web_contents.h"
 #include "device/vr/public/mojom/isolated_xr_service.mojom.h"
diff --git a/chrome/browser/web_applications/components/manifest_update_manager.cc b/chrome/browser/web_applications/components/manifest_update_manager.cc
index 4b4a959..1d67cabb 100644
--- a/chrome/browser/web_applications/components/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/components/manifest_update_manager.cc
@@ -128,6 +128,7 @@
     tasks_.erase(it);
   }
   DCHECK(!tasks_.contains(app_id));
+  last_update_check_.erase(app_id);
 }
 
 bool ManifestUpdateManager::MaybeConsumeUpdateCheck(const GURL& origin,
@@ -151,6 +152,12 @@
 base::Optional<base::Time> ManifestUpdateManager::GetLastUpdateCheckTime(
     const GURL& origin,
     const AppId& app_id) const {
+  if (!base::FeatureList::IsEnabled(
+          features::kDesktopPWAsLocalUpdatingThrottlePersistence)) {
+    auto it = last_update_check_.find(app_id);
+    return it != last_update_check_.end() ? it->second : base::Time();
+  }
+
   AppPrefs app_prefs(profile_, origin);
   if (!app_prefs.IsAvailable())
     return base::nullopt;
@@ -164,6 +171,12 @@
 void ManifestUpdateManager::SetLastUpdateCheckTime(const GURL& origin,
                                                    const AppId& app_id,
                                                    base::Time time) {
+  if (!base::FeatureList::IsEnabled(
+          features::kDesktopPWAsLocalUpdatingThrottlePersistence)) {
+    last_update_check_[app_id] = time;
+    return;
+  }
+
   AppPrefs app_prefs(profile_, origin);
   if (!app_prefs.IsAvailable())
     return;
diff --git a/chrome/browser/web_applications/components/manifest_update_manager.h b/chrome/browser/web_applications/components/manifest_update_manager.h
index c6430a6..4160000 100644
--- a/chrome/browser/web_applications/components/manifest_update_manager.h
+++ b/chrome/browser/web_applications/components/manifest_update_manager.h
@@ -82,6 +82,8 @@
 
   base::flat_map<AppId, std::unique_ptr<ManifestUpdateTask>> tasks_;
 
+  base::flat_map<AppId, base::Time> last_update_check_;
+
   base::Optional<base::Time> time_override_for_testing_;
   ResultCallback result_callback_for_testing_;
 
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index a979974..80bca8a 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -271,6 +271,11 @@
 const base::Feature kDesktopPWAsLocalUpdating{"DesktopPWAsLocalUpdating",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Whether PWA update check throttling persists across browser restarts.
+const base::Feature kDesktopPWAsLocalUpdatingThrottlePersistence{
+    "DesktopPWAsLocalUpdatingThrottlePersistence",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Adds a tab strip to PWA windows, used for UI experimentation.
 const base::Feature kDesktopPWAsTabStrip{"DesktopPWAsTabStrip",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index b3133c2b..3cfc15b 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -154,6 +154,9 @@
 extern const base::Feature kDesktopPWAsLocalUpdating;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsLocalUpdatingThrottlePersistence;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDesktopPWAsTabStrip;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/installer/linux/common/installer.include b/chrome/installer/linux/common/installer.include
index 21ae329..48913a4 100644
--- a/chrome/installer/linux/common/installer.include
+++ b/chrome/installer/linux/common/installer.include
@@ -184,7 +184,7 @@
   strippedfile="${buildfile}.stripped"
   debugfile="${buildfile}.debug"
   "${BUILDDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}"
-  install -m 4755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/${PROGNAME}-sandbox"
+  install -m 4755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/chrome-sandbox"
 
   # l10n paks
   install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/locales/"
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc
index 0af812a..36aa9ab 100644
--- a/chrome/installer/setup/setup_util.cc
+++ b/chrome/installer/setup/setup_util.cc
@@ -879,31 +879,37 @@
     return false;
   }
 
-  std::wstring path;
-  std::wstring name;
-  InstallUtil::GetMachineLevelUserCloudPolicyDMTokenRegistryPath(&path,
-                                                                 &name);
-
+  // Write the token both to the app-neutral and browser-specific locations.
+  // Only the former is mandatory -- the latter is best-effort.
   base::win::RegKey key;
-  LONG result = key.Create(HKEY_LOCAL_MACHINE, path.c_str(),
-                           KEY_WRITE | KEY_WOW64_64KEY);
-  if (result != ERROR_SUCCESS) {
-    LOG(ERROR) << "Unable to create/open registry key HKLM\\" << path
-               << " for writing result=" << result;
-    return false;
-  }
+  std::wstring value_name;
+  bool succeeded = false;
+  for (const auto& is_browser_location : {InstallUtil::BrowserLocation(false),
+                                          InstallUtil::BrowserLocation(true)}) {
+    std::tie(key, value_name) = InstallUtil::GetCloudManagementDmTokenLocation(
+        InstallUtil::ReadOnly(false), is_browser_location);
+    // If the key couldn't be opened on the first iteration (the mandatory
+    // location), return failure straight away. Otherwise, continue iterating.
+    if (!key.Valid()) {
+      if (succeeded)
+        continue;
+      // Logging already performed in GetCloudManagementDmTokenLocation.
+      return false;
+    }
 
-  result =
-      key.WriteValue(name.c_str(), token.data(),
-                     base::saturated_cast<DWORD>(token.size()), REG_BINARY);
-  if (result != ERROR_SUCCESS) {
-    LOG(ERROR) << "Unable to write specified DMToken to the registry at HKLM\\"
-               << path << "\\" << name << " result=" << result;
-    return false;
+    auto result =
+        key.WriteValue(value_name.c_str(), token.data(),
+                       base::saturated_cast<DWORD>(token.size()), REG_BINARY);
+    if (result == ERROR_SUCCESS) {
+      succeeded = true;
+    } else if (!succeeded) {
+      ::SetLastError(result);
+      PLOG(ERROR) << "Unable to write specified DMToken to the registry";
+      return false;
+    }  // Else ignore the failure to write to the best-effort location.
   }
 
   VLOG(1) << "Successfully stored specified DMToken in the registry.";
-
   return true;
 }
 
diff --git a/chrome/installer/setup/setup_util_unittest.cc b/chrome/installer/setup/setup_util_unittest.cc
index 3426dc4f..b43216c 100644
--- a/chrome/installer/setup/setup_util_unittest.cc
+++ b/chrome/installer/setup/setup_util_unittest.cc
@@ -10,6 +10,7 @@
 #include <ios>
 #include <memory>
 #include <string>
+#include <tuple>
 
 #include "base/base64.h"
 #include "base/command_line.h"
@@ -488,12 +489,11 @@
   ASSERT_EQ(kExpectedSize, token.length());
   EXPECT_TRUE(installer::StoreDMToken(token));
 
-  std::wstring path;
-  std::wstring name;
-  InstallUtil::GetMachineLevelUserCloudPolicyDMTokenRegistryPath(&path, &name);
   base::win::RegKey key;
-  ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, path.c_str(),
-                                    KEY_QUERY_VALUE | KEY_WOW64_64KEY));
+  std::wstring name;
+  std::tie(key, name) = InstallUtil::GetCloudManagementDmTokenLocation(
+      InstallUtil::ReadOnly(true), InstallUtil::BrowserLocation(false));
+  ASSERT_TRUE(key.Valid());
 
   DWORD size = kExpectedSize;
   std::vector<char> raw_value(size);
@@ -503,6 +503,17 @@
   EXPECT_EQ(REG_BINARY, dtype);
   ASSERT_EQ(kExpectedSize, size);
   EXPECT_EQ(0, memcmp(token.data(), raw_value.data(), kExpectedSize));
+
+  std::tie(key, name) = InstallUtil::GetCloudManagementDmTokenLocation(
+      InstallUtil::ReadOnly(true), InstallUtil::BrowserLocation(true));
+  ASSERT_TRUE(key.Valid());
+
+  size = kExpectedSize;
+  ASSERT_EQ(ERROR_SUCCESS,
+            key.ReadValue(name.c_str(), raw_value.data(), &size, &dtype));
+  EXPECT_EQ(REG_BINARY, dtype);
+  ASSERT_EQ(kExpectedSize, size);
+  EXPECT_EQ(0, memcmp(token.data(), raw_value.data(), kExpectedSize));
 }
 
 TEST(SetupUtilTest, StoreDMTokenToRegistryShouldFailWhenDMTokenTooLarge) {
diff --git a/chrome/installer/util/install_util.cc b/chrome/installer/util/install_util.cc
index 8c42ab9..b9b7453e 100644
--- a/chrome/installer/util/install_util.cc
+++ b/chrome/installer/util/install_util.cc
@@ -24,7 +24,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
 #include "base/values.h"
-#include "base/win/registry.h"
 #include "base/win/shlwapi.h"
 #include "base/win/shortcut.h"
 #include "base/win/win_util.h"
@@ -599,18 +598,39 @@
 }
 
 // static
-void InstallUtil::GetMachineLevelUserCloudPolicyDMTokenRegistryPath(
-    base::string16* key_path,
-    base::string16* value_name) {
-  // This token applies to all installs on the machine, even though only a
-  // system install can set it.  This is to prevent users from doing a user
-  // install of chrome to get around policies.
-  *key_path = L"SOFTWARE\\";
-  install_static::AppendChromeInstallSubDirectory(
-      install_static::InstallDetails::Get().mode(), false /* !include_suffix */,
-      key_path);
-  key_path->append(L"\\Enrollment");
-  *value_name = L"dmtoken";
+std::pair<base::win::RegKey, std::wstring>
+InstallUtil::GetCloudManagementDmTokenLocation(
+    ReadOnly read_only,
+    BrowserLocation browser_location) {
+  // The location dictates the path and WoW bit.
+  REGSAM wow_access = 0;
+  std::wstring key_path(L"SOFTWARE\\");
+  if (browser_location) {
+    wow_access |= KEY_WOW64_64KEY;
+    install_static::AppendChromeInstallSubDirectory(
+        install_static::InstallDetails::Get().mode(), /*include_suffix=*/false,
+        &key_path);
+  } else {
+    wow_access |= KEY_WOW64_32KEY;
+    key_path.append(install_static::kCompanyPathName);
+  }
+  key_path.append(L"\\Enrollment");
+
+  base::win::RegKey key;
+  if (read_only) {
+    key.Open(HKEY_LOCAL_MACHINE, key_path.c_str(),
+             KEY_QUERY_VALUE | wow_access);
+  } else {
+    auto result = key.Create(HKEY_LOCAL_MACHINE, key_path.c_str(),
+                             KEY_SET_VALUE | wow_access);
+    if (result != ERROR_SUCCESS) {
+      ::SetLastError(result);
+      PLOG(ERROR) << "Failed to create/open registry key HKLM\\" << key_path
+                  << " for writing";
+    }
+  }
+
+  return {std::move(key), L"dmtoken"};
 }
 
 // static
diff --git a/chrome/installer/util/install_util.h b/chrome/installer/util/install_util.h
index 27964fb6..6582817d 100644
--- a/chrome/installer/util/install_util.h
+++ b/chrome/installer/util/install_util.h
@@ -21,7 +21,9 @@
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
+#include "base/util/type_safety/strong_alias.h"
 #include "base/version.h"
+#include "base/win/registry.h"
 #include "base/win/scoped_handle.h"
 #include "chrome/installer/util/util_constants.h"
 
@@ -183,11 +185,18 @@
   static std::vector<std::pair<std::wstring, std::wstring>>
   GetCloudManagementEnrollmentTokenRegistryPaths();
 
-  // Returns the registry key path and value name where the DM token is stored
-  // for machine level user cloud policies.
-  static void GetMachineLevelUserCloudPolicyDMTokenRegistryPath(
-      base::string16* key_path,
-      base::string16* value_name);
+  using ReadOnly = util::StrongAlias<class ReadOnlyTag, bool>;
+  using BrowserLocation = util::StrongAlias<class BrowserLocationTag, bool>;
+
+  // Returns the registry key and value name from/to which a cloud management DM
+  // token may be read/written. |read_only| indicates whether they key is opened
+  // for reading the value or writing it. |browser_location| indicates whether
+  // the legacy browser-specific location is returned rather than the
+  // app-neutral location. The returned key will be invalid if it could not be
+  // opened/created.
+  static std::pair<base::win::RegKey, std::wstring>
+  GetCloudManagementDmTokenLocation(ReadOnly read_only,
+                                    BrowserLocation browser_location);
 
   // Returns the token used to enroll this chrome instance for machine level
   // user cloud policies.  Returns an empty string if this machine should not
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 410e207..59692ae0 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3025,6 +3025,7 @@
     "../browser/background_sync/periodic_background_sync_permission_context_unittest.cc",
     "../browser/banners/app_banner_settings_helper_unittest.cc",
     "../browser/bitmap_fetcher/bitmap_fetcher_service_unittest.cc",
+    "../browser/bluetooth/bluetooth_chooser_context_unittest.cc",
     "../browser/bookmarks/managed_bookmark_service_unittest.cc",
     "../browser/browser_about_handler_unittest.cc",
     "../browser/browsing_data/browsing_data_appcache_helper_unittest.cc",
diff --git a/chrome/test/base/in_process_browser_test_browsertest.cc b/chrome/test/base/in_process_browser_test_browsertest.cc
index cb2ed68..c8f712a 100644
--- a/chrome/test/base/in_process_browser_test_browsertest.cc
+++ b/chrome/test/base/in_process_browser_test_browsertest.cc
@@ -21,7 +21,6 @@
 #include "content/public/common/content_switches.h"
 #include "net/base/filename_util.h"
 #include "net/base/net_errors.h"
-#include "net/dns/public/resolve_error_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -45,8 +44,7 @@
   explicit LoadFailObserver(content::WebContents* contents)
       : content::WebContentsObserver(contents),
         failed_load_(false),
-        error_code_(net::OK),
-        resolve_error_info_(net::ResolveErrorInfo(net::OK)) {}
+        error_code_(net::OK) { }
 
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override {
@@ -55,21 +53,16 @@
 
     failed_load_ = true;
     error_code_ = navigation_handle->GetNetErrorCode();
-    resolve_error_info_ = navigation_handle->GetResolveErrorInfo();
     validated_url_ = navigation_handle->GetURL();
   }
 
   bool failed_load() const { return failed_load_; }
   net::Error error_code() const { return error_code_; }
-  net::ResolveErrorInfo resolve_error_info() const {
-    return resolve_error_info_;
-  }
   const GURL& validated_url() const { return validated_url_; }
 
  private:
   bool failed_load_;
   net::Error error_code_;
-  net::ResolveErrorInfo resolve_error_info_;
   GURL validated_url_;
 
   DISALLOW_COPY_AND_ASSIGN(LoadFailObserver);
@@ -92,7 +85,6 @@
     ui_test_utils::NavigateToURL(browser(), url);
     EXPECT_TRUE(observer.failed_load());
     EXPECT_EQ(net::ERR_NOT_IMPLEMENTED, observer.error_code());
-    EXPECT_EQ(net::ERR_NOT_IMPLEMENTED, observer.resolve_error_info().error);
     EXPECT_EQ(url, observer.validated_url());
   }
 }
diff --git a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
index 8871538b..bf3dad2 100644
--- a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
@@ -54,19 +54,6 @@
 }
 
 /**
- * Helper method to pull an array of CupsPrinterEntry out of a
- * |printersElement|.
- * @param {!HTMLElement} printersElement
- * @return {!Array<!HTMLElement>}
- * @private
- */
-function getPrinterEntries(printersElement) {
-  const entryList = printersElement.$$('#printerEntryList');
-  return entryList.querySelectorAll(
-      'settings-cups-printers-entry:not([hidden])');
-}
-
-/**
  * @param {!HTMLElement} page
  * @return {!HTMLElement}
  * @private
@@ -194,7 +181,8 @@
  */
 function removePrinter(cupsPrintersBrowserProxy, savedPrintersElement, index) {
   const printerList = cupsPrintersBrowserProxy.printerList.printerList;
-  const savedPrinterEntries = getPrinterEntries(savedPrintersElement);
+  const savedPrinterEntries =
+      cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
   clickThreeDotMenu(savedPrinterEntries[index]);
   savedPrintersElement.$$('#removeButton').click();
@@ -219,7 +207,8 @@
  */
 function removeAllPrinters(cupsPrintersBrowserProxy, savedPrintersElement) {
   const printerList = cupsPrintersBrowserProxy.printerList.printerList;
-  const savedPrinterEntries = getPrinterEntries(savedPrintersElement);
+  const savedPrinterEntries =
+      cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
   if (!printerList.length) {
     return Promise.resolve();
@@ -232,73 +221,6 @@
           this, cupsPrintersBrowserProxy, savedPrintersElement));
 }
 
-/**
- * @param {string} printerName
- * @param {string} printerAddress
- * @param {string} printerId
- * @return {!CupsPrinterInfo}
- * @private
- */
-function createCupsPrinterInfo(printerName, printerAddress, printerId) {
-  const printer = {
-    ppdManufacturer: 'make',
-    ppdModel: 'model',
-    printerAddress: printerAddress,
-    printerDescription: '',
-    printerId: printerId,
-    printerManufacturer: 'make',
-    printerModel: 'model',
-    printerMakeAndModel: '',
-    printerName: printerName,
-    printerPPDPath: '',
-    printerPpdReference: {
-      userSuppliedPpdUrl: '',
-      effectiveMakeAndModel: '',
-      autoconf: false,
-    },
-    printerProtocol: 'ipp',
-    printerQueue: 'moreinfohere',
-    printerStatus: '',
-  };
-  return printer;
-}
-
-/**
- * Helper function that creates a new PrinterListEntry.
- * @param {string} printerName
- * @param {string} printerAddress
- * @param {string} printerId
- * @param {string} printerType
- * @return {!PrinterListEntry}
- */
-function createPrinterListEntry(
-    printerName, printerAddress, printerId, printerType) {
-  const entry = {
-    printerInfo: {
-      ppdManufacturer: '',
-      ppdModel: '',
-      printerAddress: printerAddress,
-      printerDescription: '',
-      printerId: printerId,
-      printerManufacturer: '',
-      printerModel: '',
-      printerMakeAndModel: '',
-      printerName: printerName,
-      printerPPDPath: '',
-      printerPpdReference: {
-        userSuppliedPpdUrl: '',
-        effectiveMakeAndModel: '',
-        autoconf: false,
-      },
-      printerProtocol: 'ipp',
-      printerQueue: 'moreinfohere',
-      printerStatus: '',
-    },
-    printerType: printerType,
-  };
-  return entry;
-}
-
 suite('CupsSavedPrintersTests', function() {
   let page = null;
   let savedPrintersElement = null;
@@ -380,9 +302,9 @@
 
   test('SavedPrintersSuccessfullyPopulates', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -396,7 +318,8 @@
           const savedPrintersList =
               savedPrintersElement.$$('settings-cups-printers-entry-list');
 
-          const printerListEntries = getPrinterEntries(savedPrintersElement);
+          const printerListEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
           verifyPrintersList(printerListEntries, printerList);
         });
@@ -406,9 +329,9 @@
     const savedPrinterEntries = [];
 
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -422,7 +345,8 @@
               cupsPrintersBrowserProxy, savedPrintersElement);
         })
         .then(() => {
-          const entryList = getPrinterEntries(savedPrintersElement);
+          const entryList =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
           verifyPrintersList(entryList, printerList);
         });
   });
@@ -433,9 +357,9 @@
     let savedPrinterEntries = [];
 
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -447,7 +371,8 @@
 
           savedPrintersList =
               savedPrintersElement.$$('settings-cups-printers-entry-list');
-          savedPrinterEntries = getPrinterEntries(savedPrintersElement);
+          savedPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
           verifyPrintersList(savedPrinterEntries, printerList);
 
@@ -468,9 +393,9 @@
     let savedPrinterEntries = null;
 
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -480,7 +405,8 @@
           savedPrintersElement = page.$$('settings-cups-saved-printers');
           assertTrue(!!savedPrintersElement);
 
-          savedPrinterEntries = getPrinterEntries(savedPrintersElement);
+          savedPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
           // Update the printer name of the first entry.
           clickThreeDotMenu(savedPrinterEntries[0]);
@@ -520,9 +446,9 @@
     let editDialog = null;
 
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -532,7 +458,8 @@
           savedPrintersElement = page.$$('settings-cups-saved-printers');
           assertTrue(!!savedPrintersElement);
 
-          savedPrinterEntries = getPrinterEntries(savedPrintersElement);
+          savedPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
 
           // Edit the first entry.
           clickThreeDotMenu(savedPrinterEntries[0]);
@@ -573,9 +500,9 @@
 
   test('SavedPrintersSearchTermFiltersCorrectPrinters', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -585,7 +512,8 @@
           savedPrintersElement = page.$$('settings-cups-saved-printers');
           assertTrue(!!savedPrintersElement);
 
-          const printerListEntries = getPrinterEntries(savedPrintersElement);
+          const printerListEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
           verifyPrintersList(printerListEntries, printerList);
 
           searchTerm = 'google';
@@ -596,7 +524,8 @@
           // hidden entries.
           verifySearchQueryResults(
               savedPrintersElement,
-              [createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED)],
+              [cups_printer_test_util.createPrinterListEntry(
+                  'google', '4', 'id4', PrinterType.SAVED)],
               searchTerm);
 
           // Change the search term and assert that entries are filtered
@@ -609,22 +538,29 @@
           verifySearchQueryResults(
               savedPrintersElement,
               [
-                createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-                createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
+                    'test1', '1', 'id1', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
+                    'test2', '2', 'id2', PrinterType.SAVED),
               ],
               searchTerm);
 
           // Add more printers and assert that they are correctly filtered.
-          addNewSavedPrinter(createCupsPrinterInfo('test3', '3', 'id3'));
-          addNewSavedPrinter(createCupsPrinterInfo('google2', '6', 'id6'));
+          addNewSavedPrinter(cups_printer_test_util.createCupsPrinterInfo(
+              'test3', '3', 'id3'));
+          addNewSavedPrinter(cups_printer_test_util.createCupsPrinterInfo(
+              'google2', '6', 'id6'));
           Polymer.dom.flush();
 
           verifySearchQueryResults(
               savedPrintersElement,
               [
-                createPrinterListEntry('test3', '3', 'id3', PrinterType.SAVED),
-                createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-                createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+                cups_printer_test_util.createPrinterListEntry(
+                    'test3', '3', 'id3', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
+                    'test1', '1', 'id1', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
+                    'test2', '2', 'id2', PrinterType.SAVED)
               ],
               searchTerm);
         });
@@ -632,9 +568,9 @@
 
   test('SavedPrintersNoSearchFound', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -644,7 +580,8 @@
           savedPrintersElement = page.$$('settings-cups-saved-printers');
           assertTrue(!!savedPrintersElement);
 
-          const printerListEntries = getPrinterEntries(savedPrintersElement);
+          const printerListEntries =
+              cups_printer_test_util.getPrinterEntries(savedPrintersElement);
           verifyPrintersList(printerListEntries, printerList);
 
           searchTerm = 'google';
@@ -655,7 +592,8 @@
           // hidden entries.
           verifySearchQueryResults(
               savedPrintersElement,
-              [createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED)],
+              [cups_printer_test_util.createPrinterListEntry(
+                  'google', '4', 'id4', PrinterType.SAVED)],
               searchTerm);
 
           // Change search term to something that has no matches.
@@ -673,16 +611,17 @@
 
           verifySearchQueryResults(
               savedPrintersElement,
-              [createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED)],
+              [cups_printer_test_util.createPrinterListEntry(
+                  'google', '4', 'id4', PrinterType.SAVED)],
               searchTerm);
         });
   });
 
   test('ShowMoreButtonIsInitiallyHiddenAndANewPrinterIsAdded', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -696,9 +635,12 @@
               savedPrintersElement.$$('#printerEntryList');
 
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED)
           ]);
           // Assert that the Show more button is hidden because printer list
           // length is <= 3.
@@ -706,12 +648,17 @@
 
           // Newly added printers will always be visible and inserted to the
           // top of the list.
-          addNewSavedPrinter(createCupsPrinterInfo('test3', '3', 'id3'));
+          addNewSavedPrinter(cups_printer_test_util.createCupsPrinterInfo(
+              'test3', '3', 'id3'));
           expectedVisiblePrinters = [
-            createPrinterListEntry('test3', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'test3', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED)
           ];
           verifyVisiblePrinters(
               printerEntryListTestElement, expectedVisiblePrinters);
@@ -723,10 +670,10 @@
 
   test('PressShowMoreButton', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
-      createCupsPrinterInfo('test3', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('test3', '3', 'id3'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -742,9 +689,12 @@
           // There are 4 total printers but only 3 printers are visible and 1 is
           // hidden underneath the Show more section.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED),
           ]);
           // Assert that the Show more button is shown since printer list length
           // is > 3.
@@ -756,20 +706,24 @@
           assertFalse(!!savedPrintersElement.$$('#show-more-container'));
           // Clicking on the Show more button reveals all hidden printers.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED),
-            createPrinterListEntry('test3', '3', 'id3', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test3', '3', 'id3', PrinterType.SAVED)
           ]);
         });
   });
 
   test('ShowMoreButtonIsInitiallyShownAndWithANewPrinterAdded', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
-      createCupsPrinterInfo('test3', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('test3', '3', 'id3'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -785,21 +739,29 @@
           // There are 4 total printers but only 3 printers are visible and 1 is
           // hidden underneath the Show more section.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED),
           ]);
           // Assert that the Show more button is shown since printer list length
           // is > 3.
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
 
           // Newly added printers will always be visible.
-          addNewSavedPrinter(createCupsPrinterInfo('test5', '5', 'id5'));
+          addNewSavedPrinter(cups_printer_test_util.createCupsPrinterInfo(
+              'test5', '5', 'id5'));
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('test5', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('google', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'test5', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED)
           ]);
           // Assert that the Show more button is still shown.
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
@@ -808,11 +770,11 @@
 
   test('ShowMoreButtonIsShownAndRemovePrinters', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '3', 'id3'),
-      createCupsPrinterInfo('google2', '4', 'id4'),
-      createCupsPrinterInfo('google3', '5', 'id5'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('google2', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('google3', '5', 'id5'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -828,9 +790,12 @@
           // There are 5 total printers but only 3 printers are visible and 2
           // are hidden underneath the Show more section.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('google3', '5', 'id5', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google3', '5', 'id5', PrinterType.SAVED)
           ]);
           // Assert that the Show more button is shown since printer list length
           // is > 3.
@@ -843,9 +808,12 @@
           // Since printers were initially alphabetically sorted, we should
           // expect 'test1' to be the next visible printer.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('google3', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google3', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED)
           ]);
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
 
@@ -854,9 +822,12 @@
           // Printer list has 3 elements now, the Show more button should be
           // hidden.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google3', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google3', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED)
           ]);
           assertFalse(!!savedPrintersElement.$$('#show-more-container'));
         });
@@ -864,12 +835,12 @@
 
   test('ShowMoreButtonIsShownAndSearchQueryFiltersCorrectly', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '3', 'id3'),
-      createCupsPrinterInfo('google2', '4', 'id4'),
-      createCupsPrinterInfo('google3', '5', 'id5'),
-      createCupsPrinterInfo('google4', '6', 'id6'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('google2', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('google3', '5', 'id5'),
+      cups_printer_test_util.createCupsPrinterInfo('google4', '6', 'id6'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -885,9 +856,12 @@
           // There are 6 total printers but only 3 printers are visible and 3
           // are hidden underneath the Show more section.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('google3', '5', 'id5', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google3', '5', 'id5', PrinterType.SAVED)
           ]);
           // Assert that the Show more button is shown since printer list length
           // is > 3.
@@ -900,12 +874,14 @@
           verifySearchQueryResults(
               savedPrintersElement,
               [
-                createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-                createPrinterListEntry(
+                cups_printer_test_util.createPrinterListEntry(
+                    'google', '3', 'id3', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
                     'google2', '4', 'id4', PrinterType.SAVED),
-                createPrinterListEntry(
+                cups_printer_test_util.createPrinterListEntry(
                     'google3', '5', 'id5', PrinterType.SAVED),
-                createPrinterListEntry('google4', '6', 'id6', PrinterType.SAVED)
+                cups_printer_test_util.createPrinterListEntry(
+                    'google4', '6', 'id6', PrinterType.SAVED)
               ],
               searchTerm);
           // Having a search term should hide the Show more button.
@@ -927,8 +903,10 @@
           verifySearchQueryResults(
               savedPrintersElement,
               [
-                createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED),
-                createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+                cups_printer_test_util.createPrinterListEntry(
+                    'test1', '1', 'id1', PrinterType.SAVED),
+                cups_printer_test_util.createPrinterListEntry(
+                    'test2', '2', 'id2', PrinterType.SAVED)
               ],
               searchTerm);
           assertFalse(!!savedPrintersElement.$$('#show-more-container'));
@@ -939,9 +917,12 @@
           savedPrintersElement.searchTerm = searchTerm;
           Polymer.dom.flush();
           const expectedVisiblePrinters = [
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('google3', '5', 'id5', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google3', '5', 'id5', PrinterType.SAVED)
           ];
           verifySearchQueryResults(
               savedPrintersElement, expectedVisiblePrinters, searchTerm);
@@ -953,10 +934,10 @@
 
   test('ShowMoreButtonAddAndRemovePrinters', function() {
     createCupsPrinterPage([
-      createCupsPrinterInfo('google', '3', 'id3'),
-      createCupsPrinterInfo('google2', '4', 'id4'),
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('google', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('google2', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ]);
     return cupsPrintersBrowserProxy.whenCalled('getCupsPrintersList')
         .then(() => {
@@ -972,21 +953,29 @@
           // There are 4 total printers but only 3 printers are visible and 1 is
           // hidden underneath the Show more section.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED)
           ]);
           // Assert that the Show more button is shown since printer list length
           // is > 3.
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
 
           // Add a new printer and expect it to be at the top of the list.
-          addNewSavedPrinter(createCupsPrinterInfo('newPrinter', '5', 'id5'));
+          addNewSavedPrinter(cups_printer_test_util.createCupsPrinterInfo(
+              'newPrinter', '5', 'id5'));
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('newPrinter', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED),
-            createPrinterListEntry('test1', '1', 'id1', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'newPrinter', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test1', '1', 'id1', PrinterType.SAVED)
           ]);
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
 
@@ -997,9 +986,12 @@
           // visible printers. In this case, we remove 'test1' and now only
           // have 3 visible printers and 1 hidden printer: 'test2'.
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('newPrinter', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('google2', '4', 'id4', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'newPrinter', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google2', '4', 'id4', PrinterType.SAVED)
           ]);
           assertTrue(!!savedPrintersElement.$$('#show-more-container'));
 
@@ -1007,9 +999,12 @@
           // printers but now 'test2' is our third visible printer.
           removeSavedPrinter('id4');
           verifyVisiblePrinters(printerEntryListTestElement, [
-            createPrinterListEntry('newPrinter', '5', 'id5', PrinterType.SAVED),
-            createPrinterListEntry('google', '3', 'id3', PrinterType.SAVED),
-            createPrinterListEntry('test2', '2', 'id2', PrinterType.SAVED)
+            cups_printer_test_util.createPrinterListEntry(
+                'newPrinter', '5', 'id5', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'google', '3', 'id3', PrinterType.SAVED),
+            cups_printer_test_util.createPrinterListEntry(
+                'test2', '2', 'id2', PrinterType.SAVED)
           ]);
           // Printer list length is <= 3, Show more button should be hidden.
           assertFalse(!!savedPrintersElement.$$('#show-more-container'));
@@ -1069,12 +1064,12 @@
 
   test('nearbyPrintersSuccessfullyPopulates', function() {
     const automaticPrinterList = [
-      createCupsPrinterInfo('test1', '1', 'id1'),
-      createCupsPrinterInfo('test2', '2', 'id2'),
+      cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+      cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
     ];
     const discoveredPrinterList = [
-      createCupsPrinterInfo('test3', '3', 'id3'),
-      createCupsPrinterInfo('test4', '4', 'id4'),
+      cups_printer_test_util.createCupsPrinterInfo('test3', '3', 'id3'),
+      cups_printer_test_util.createCupsPrinterInfo('test4', '4', 'id4'),
     ];
 
     return test_util.flushTasks().then(() => {
@@ -1082,7 +1077,8 @@
       assertTrue(!!nearbyPrintersElement);
 
       // Assert that no printers have been detected.
-      let nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+      let nearbyPrinterEntries =
+          cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
       assertEquals(0, nearbyPrinterEntries.length);
 
       // Simuluate finding nearby printers.
@@ -1092,7 +1088,8 @@
 
       Polymer.dom.flush();
 
-      nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+      nearbyPrinterEntries =
+          cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
 
       const expectedPrinterList =
           automaticPrinterList.concat(discoveredPrinterList);
@@ -1101,18 +1098,18 @@
   });
 
   test('nearbyPrintersSortOrderAutoFirstThenDiscovered', function() {
-    const discoveredPrinterA =
-        createCupsPrinterInfo('printerNameA', 'printerAddress1', 'printerId1');
-    const discoveredPrinterB =
-        createCupsPrinterInfo('printerNameB', 'printerAddress2', 'printerId2');
-    const discoveredPrinterC =
-        createCupsPrinterInfo('printerNameC', 'printerAddress3', 'printerId3');
-    const autoPrinterD =
-        createCupsPrinterInfo('printerNameD', 'printerAddress4', 'printerId4');
-    const autoPrinterE =
-        createCupsPrinterInfo('printerNameE', 'printerAddress5', 'printerId5');
-    const autoPrinterF =
-        createCupsPrinterInfo('printerNameF', 'printerAddress6', 'printerId6');
+    const discoveredPrinterA = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameA', 'printerAddress1', 'printerId1');
+    const discoveredPrinterB = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameB', 'printerAddress2', 'printerId2');
+    const discoveredPrinterC = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameC', 'printerAddress3', 'printerId3');
+    const autoPrinterD = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameD', 'printerAddress4', 'printerId4');
+    const autoPrinterE = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameE', 'printerAddress5', 'printerId5');
+    const autoPrinterF = cups_printer_test_util.createCupsPrinterInfo(
+        'printerNameF', 'printerAddress6', 'printerId6');
 
     // Add printers in a non-alphabetical order to test sorting.
     const automaticPrinterList = [autoPrinterF, autoPrinterD, autoPrinterE];
@@ -1137,14 +1134,16 @@
 
       Polymer.dom.flush();
 
-      nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+      nearbyPrinterEntries =
+          cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
 
       verifyPrintersList(nearbyPrinterEntries, expectedPrinterList);
     });
   });
 
   test('addingAutomaticPrinterIsSuccessful', function() {
-    const automaticPrinterList = [createCupsPrinterInfo('test1', '1', 'id1')];
+    const automaticPrinterList =
+        [cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1')];
     const discoveredPrinterList = [];
 
     return test_util.flushTasks()
@@ -1161,7 +1160,8 @@
 
           // Requery and assert that the newly detected printer automatic
           // printer has the correct button.
-          nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+          nearbyPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
           assertEquals(1, nearbyPrinterEntries.length);
           assertTrue(!!nearbyPrinterEntries[0].$$('#savePrinterButton'));
 
@@ -1182,7 +1182,8 @@
 
   test('addingDiscoveredPrinterIsSuccessful', function() {
     const automaticPrinterList = [];
-    const discoveredPrinterList = [createCupsPrinterInfo('test3', '3', 'id3')];
+    const discoveredPrinterList =
+        [cups_printer_test_util.createCupsPrinterInfo('test3', '3', 'id3')];
 
     let manufacturerDialog = null;
 
@@ -1200,7 +1201,8 @@
 
           // Requery and assert that a newly detected discovered printer has
           // the correct icon button.
-          nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+          nearbyPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
           assertEquals(1, nearbyPrinterEntries.length);
           assertTrue(!!nearbyPrinterEntries[0].$$('#setupPrinterButton'));
 
@@ -1286,12 +1288,12 @@
         })
         .then(() => {
           const automaticPrinterList = [
-            createCupsPrinterInfo('test1', '1', 'id1'),
-            createCupsPrinterInfo('test2', '2', 'id2'),
+            cups_printer_test_util.createCupsPrinterInfo('test1', '1', 'id1'),
+            cups_printer_test_util.createCupsPrinterInfo('test2', '2', 'id2'),
           ];
           const discoveredPrinterList = [
-            createCupsPrinterInfo('test3', '3', 'id3'),
-            createCupsPrinterInfo('test4', '4', 'id4'),
+            cups_printer_test_util.createCupsPrinterInfo('test3', '3', 'id3'),
+            cups_printer_test_util.createCupsPrinterInfo('test4', '4', 'id4'),
           ];
 
           // Simuluate finding nearby printers.
@@ -1308,7 +1310,8 @@
               nearbyPrintersElement.$$('#printerEntryList');
           assertTrue(!!printerEntryListTestElement);
 
-          nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
+          nearbyPrinterEntries =
+              cups_printer_test_util.getPrinterEntries(nearbyPrintersElement);
 
           const expectedPrinterList =
               automaticPrinterList.concat(discoveredPrinterList);
@@ -1318,9 +1321,12 @@
 
   test('NearbyPrintersSearchTermFiltersCorrectPrinters', function() {
     const discoveredPrinterList = [
-      createCupsPrinterInfo('test1', 'printerAddress1', 'printerId1'),
-      createCupsPrinterInfo('test2', 'printerAddress2', 'printerId2'),
-      createCupsPrinterInfo('google', 'printerAddress3', 'printerId3'),
+      cups_printer_test_util.createCupsPrinterInfo(
+          'test1', 'printerAddress1', 'printerId1'),
+      cups_printer_test_util.createCupsPrinterInfo(
+          'test2', 'printerAddress2', 'printerId2'),
+      cups_printer_test_util.createCupsPrinterInfo(
+          'google', 'printerAddress3', 'printerId3'),
     ];
 
     return test_util.flushTasks().then(() => {
@@ -1338,11 +1344,11 @@
       Polymer.dom.flush();
 
       verifyVisiblePrinters(printerEntryListTestElement, [
-        createPrinterListEntry(
+        cups_printer_test_util.createPrinterListEntry(
             'google', 'printerAddress3', 'printerId3', PrinterType.DISCOVERD),
-        createPrinterListEntry(
+        cups_printer_test_util.createPrinterListEntry(
             'test1', 'printerAddress1', 'printerId1', PrinterType.DISCOVERD),
-        createPrinterListEntry(
+        cups_printer_test_util.createPrinterListEntry(
             'test2', 'printerAddress2', 'printerId2', PrinterType.DISCOVERD)
       ]);
 
@@ -1353,7 +1359,7 @@
       // Filtering "google" should result in one visible entry and two hidden
       // entries.
       verifySearchQueryResults(
-          nearbyPrintersElement, [createPrinterListEntry(
+          nearbyPrintersElement, [cups_printer_test_util.createPrinterListEntry(
                                      'google', 'printerAddress3', 'printerId3',
                                      PrinterType.DISCOVERD)],
           searchTerm);
@@ -1367,19 +1373,19 @@
       verifySearchQueryResults(
           nearbyPrintersElement,
           [
-            createPrinterListEntry(
+            cups_printer_test_util.createPrinterListEntry(
                 'test1', 'printerAddress1', 'printerId1',
                 PrinterType.DISCOVERD),
-            createPrinterListEntry(
+            cups_printer_test_util.createPrinterListEntry(
                 'test2', 'printerAddress2', 'printerId2', PrinterType.DISCOVERD)
           ],
           searchTerm);
 
       // Add more printers and assert that they are correctly filtered.
-      discoveredPrinterList.push(
-          createCupsPrinterInfo('test3', 'printerAddress4', 'printerId4'));
-      discoveredPrinterList.push(
-          createCupsPrinterInfo('google2', 'printerAddress5', 'printerId5'));
+      discoveredPrinterList.push(cups_printer_test_util.createCupsPrinterInfo(
+          'test3', 'printerAddress4', 'printerId4'));
+      discoveredPrinterList.push(cups_printer_test_util.createCupsPrinterInfo(
+          'google2', 'printerAddress5', 'printerId5'));
 
       // Simuluate finding nearby printers.
       cr.webUIListenerCallback(
@@ -1390,13 +1396,13 @@
       verifySearchQueryResults(
           nearbyPrintersElement,
           [
-            createPrinterListEntry(
+            cups_printer_test_util.createPrinterListEntry(
                 'test1', 'printerAddress1', 'printerId1',
                 PrinterType.DISCOVERD),
-            createPrinterListEntry(
+            cups_printer_test_util.createPrinterListEntry(
                 'test2', 'printerAddress2', 'printerId2',
                 PrinterType.DISCOVERD),
-            createPrinterListEntry(
+            cups_printer_test_util.createPrinterListEntry(
                 'test3', 'printerAddress4', 'printerId4', PrinterType.DISCOVERD)
           ],
           searchTerm);
@@ -1405,8 +1411,10 @@
 
   test('NearbyPrintersNoSearchFound', function() {
     const discoveredPrinterList = [
-      createCupsPrinterInfo('test1', 'printerAddress1', 'printerId1'),
-      createCupsPrinterInfo('google', 'printerAddress2', 'printerId2')
+      cups_printer_test_util.createCupsPrinterInfo(
+          'test1', 'printerAddress1', 'printerId1'),
+      cups_printer_test_util.createCupsPrinterInfo(
+          'google', 'printerAddress2', 'printerId2')
     ];
 
     return test_util.flushTasks().then(() => {
@@ -1430,7 +1438,7 @@
       // Set the search term and filter out the printers. Filtering "google"
       // should result in one visible entry and one hidden entries.
       verifySearchQueryResults(
-          nearbyPrintersElement, [createPrinterListEntry(
+          nearbyPrintersElement, [cups_printer_test_util.createPrinterListEntry(
                                      'google', 'printerAddress2', 'printerId2',
                                      PrinterType.DISCOVERED)],
           searchTerm);
@@ -1449,7 +1457,7 @@
       Polymer.dom.flush();
 
       verifySearchQueryResults(
-          nearbyPrintersElement, [createPrinterListEntry(
+          nearbyPrintersElement, [cups_printer_test_util.createPrinterListEntry(
                                      'google', 'printerAddress2', 'printerId2',
                                      PrinterType.DISCOVERD)],
           searchTerm);
diff --git a/chrome/test/data/webui/settings/chromeos/cups_printer_test_utils.js b/chrome/test/data/webui/settings/chromeos/cups_printer_test_utils.js
new file mode 100644
index 0000000..140e9ae2
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/cups_printer_test_utils.js
@@ -0,0 +1,91 @@
+// Copyright 2020 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.
+
+cr.define('cups_printer_test_util', function() {
+  /**
+   * @param {string} printerName
+   * @param {string} printerAddress
+   * @param {string} printerId
+   * @return {!CupsPrinterInfo}
+   * @private
+   */
+  function createCupsPrinterInfo(printerName, printerAddress, printerId) {
+    const printer = {
+      ppdManufacturer: '',
+      ppdModel: '',
+      printerAddress: printerAddress,
+      printerDescription: '',
+      printerId: printerId,
+      printerManufacturer: '',
+      printerModel: '',
+      printerMakeAndModel: '',
+      printerName: printerName,
+      printerPPDPath: '',
+      printerPpdReference: {
+        userSuppliedPpdUrl: '',
+        effectiveMakeAndModel: '',
+        autoconf: false,
+      },
+      printerProtocol: 'ipp',
+      printerQueue: 'moreinfohere',
+      printerStatus: '',
+    };
+    return printer;
+  }
+
+  /**
+   * Helper function that creates a new PrinterListEntry.
+   * @param {string} printerName
+   * @param {string} printerAddress
+   * @param {string} printerId
+   * @param {string} printerType
+   * @return {!PrinterListEntry}
+   */
+  function createPrinterListEntry(
+      printerName, printerAddress, printerId, printerType) {
+    const entry = {
+      printerInfo: {
+        ppdManufacturer: '',
+        ppdModel: '',
+        printerAddress: printerAddress,
+        printerDescription: '',
+        printerId: printerId,
+        printerManufacturer: '',
+        printerModel: '',
+        printerMakeAndModel: '',
+        printerName: printerName,
+        printerPPDPath: '',
+        printerPpdReference: {
+          userSuppliedPpdUrl: '',
+          effectiveMakeAndModel: '',
+          autoconf: false,
+        },
+        printerProtocol: 'ipp',
+        printerQueue: 'moreinfohere',
+        printerStatus: '',
+      },
+      printerType: printerType,
+    };
+    return entry;
+  }
+
+  /**
+   * Helper method to pull an array of CupsPrinterEntry out of a
+   * |printersElement|.
+   * @param {!HTMLElement} printersElement
+   * @return {!Array<!HTMLElement>}
+   * @private
+   */
+  function getPrinterEntries(printersElement) {
+    const entryList = printersElement.$$('#printerEntryList');
+    return entryList.querySelectorAll(
+        'settings-cups-printers-entry:not([hidden])');
+  }
+
+  return {
+    createCupsPrinterInfo: createCupsPrinterInfo,
+    getPrinterEntries: getPrinterEntries,
+    createPrinterListEntry: createPrinterListEntry,
+  };
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index a9b3f4a..89b23c0c 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -957,6 +957,8 @@
   get extraLibraries() {
     return super.extraLibraries.concat([
       '//ui/webui/resources/js/promise_resolver.js',
+      '//ui/webui/resources/js/util.js',
+      BROWSER_SETTINGS_PATH + '../test_util.js',
       BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
       'plugin_vm_page_test.js',
     ]);
@@ -1008,6 +1010,7 @@
       BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
       BROWSER_SETTINGS_PATH + '../fake_chrome_event.js',
       BROWSER_SETTINGS_PATH + '../chromeos/fake_network_config_mojom.js',
+      'cups_printer_test_utils.js',
       'test_cups_printers_browser_proxy.js',
       'cups_printer_landing_page_tests.js',
     ]);
@@ -1033,6 +1036,7 @@
       BROWSER_SETTINGS_PATH + '../test_util.js',
       BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
       'test_cups_printers_browser_proxy.js',
+      'cups_printer_test_utils.js',
       'cups_printer_page_tests.js',
     ]);
   }
diff --git a/chrome/test/data/webui/settings/chromeos/plugin_vm_page_test.js b/chrome/test/data/webui/settings/chromeos/plugin_vm_page_test.js
index eb84cef..93166c0 100644
--- a/chrome/test/data/webui/settings/chromeos/plugin_vm_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/plugin_vm_page_test.js
@@ -126,20 +126,58 @@
 });
 
 suite('Remove', function() {
+  let page;
+  const getDialog = () => page.$$(
+      '#plugin-vm-remove settings-plugin-vm-remove-confirmation-dialog');
+  const hasDialog = () => !!getDialog();
+
   setup(function() {
     pluginVmBrowserProxy = new TestPluginVmBrowserProxy();
     settings.PluginVmBrowserProxyImpl.instance_ = pluginVmBrowserProxy;
     PolymerTest.clearBody();
-    this.page = document.createElement('settings-plugin-vm-subpage');
-    document.body.appendChild(this.page);
+    page = document.createElement('settings-plugin-vm-subpage');
+    document.body.appendChild(page);
   });
 
   teardown(function() {
-    this.page.remove();
+    page.remove();
   });
 
-  test('Remove', function() {
-    this.page.$$('#plugin-vm-remove cr-button').click();
+  test('Remove', async function() {
+    assertFalse(hasDialog());
+
+    page.$.pluginVmRemoveButton.click();
+    assertFalse(hasDialog());
+
+    Polymer.dom.flush();
+    assertTrue(hasDialog());
+
+    getDialog().$.continue.click();
     assertEquals(1, pluginVmBrowserProxy.getCallCount('removePluginVm'));
+
+    await test_util.eventToPromise('dom-change', page.$$('#plugin-vm-remove'));
+    assertFalse(hasDialog());
+
+    assertEquals(getDeepActiveElement(), page.$.pluginVmRemoveButton);
+  });
+
+  test('RemoveDialogCancelled', async function() {
+    assertFalse(hasDialog());
+
+    page.$.pluginVmRemoveButton.click();
+    assertFalse(hasDialog());
+
+    Polymer.dom.flush();
+    assertTrue(hasDialog());
+
+    getDialog().$.cancel.click();
+    assertTrue(hasDialog());
+    assertEquals(0, pluginVmBrowserProxy.getCallCount('removePluginVm'));
+
+    await test_util.eventToPromise('dom-change', page.$$('#plugin-vm-remove'));
+    assertFalse(hasDialog());
+    assertEquals(0, pluginVmBrowserProxy.getCallCount('removePluginVm'));
+
+    assertEquals(getDeepActiveElement(), page.$.pluginVmRemoveButton);
   });
 });
diff --git a/chrome/test/enterprise/e2e/policy/fullscreen_allowed/is_fullscreen_allowed.py b/chrome/test/enterprise/e2e/policy/fullscreen_allowed/is_fullscreen_allowed.py
index c4891c3..bb850a2f 100644
--- a/chrome/test/enterprise/e2e/policy/fullscreen_allowed/is_fullscreen_allowed.py
+++ b/chrome/test/enterprise/e2e/policy/fullscreen_allowed/is_fullscreen_allowed.py
@@ -21,10 +21,8 @@
     container = w.child_window(best_match="Infobar Container")
     container.child_window(best_match="Close").click_input()
 
-    print "Clicking on the Fullscreen button."
-    button = w.child_window(title_re="^Chrom(e|ium)$", control_type="Button")
-    button.click_input()
-    w.child_window(best_match="Full screen").click_input()
+    print "press F11 to enter full screen mode."
+    w.type_keys('{F11}')
 
     window_rect = w.rectangle()
     window_width = window_rect.width()
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 66ac32f..7bc18b97 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -1313,7 +1313,7 @@
   network::DnsLookupResult result2 =
       network::BlockingDnsLookup(network_context, kHostPortPair,
                                  std::move(params), net::NetworkIsolationKey());
-  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, result2.error);
+  EXPECT_EQ(net::ERR_DNS_CACHE_MISS, result2.error);
 }
 
 // HostResolver and HostResolverPrivate tests. The PPAPI code used by these
diff --git a/chromecast/base/cast_features.cc b/chromecast/base/cast_features.cc
index 3ebfa6a..bb485ea2 100644
--- a/chromecast/base/cast_features.cc
+++ b/chromecast/base/cast_features.cc
@@ -130,8 +130,8 @@
 // through getUserMedia API.
 const base::Feature kAllowUserMediaAccess{"allow_user_media_access",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
-// Enables the use of QUIC in Cast-specific URLRequestContextGetters. See
-// chromecast/browser/url_request_context_factory.cc for usage.
+// Enables the use of QUIC in Cast-specific NetworkContexts. See
+// chromecast/browser/cast_network_contexts.cc for usage.
 const base::Feature kEnableQuic{"enable_quic",
                                 base::FEATURE_DISABLED_BY_DEFAULT};
 // Enables triple-buffer 720p graphics (overriding default graphics buffer
@@ -143,7 +143,7 @@
 const base::Feature kSingleBuffer{"enable_single_buffer",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 // Disable idle sockets closing on memory pressure. See
-// chromecast/browser/url_request_context_factory.cc for usage.
+// chromecast/browser/cast_network_contexts.cc for usage.
 const base::Feature kDisableIdleSocketsCloseOnMemoryPressure{
     "disable_idle_sockets_close_on_memory_pressure",
     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 810d4e7..7a77bd63 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -84,10 +84,6 @@
     "cast_navigation_ui_data.h",
     "cast_network_contexts.cc",
     "cast_network_contexts.h",
-    "cast_network_delegate.cc",
-    "cast_network_delegate.h",
-    "cast_network_request_interceptor.cc",
-    "cast_network_request_interceptor.h",
     "cast_overlay_manifests.cc",
     "cast_overlay_manifests.h",
     "cast_permission_manager.cc",
@@ -139,15 +135,12 @@
     "tts/tts_platform.h",
     "tts/tts_platform_stub.cc",
     "tts/tts_platform_stub.h",
-    "url_request_context_factory.cc",
-    "url_request_context_factory.h",
   ]
 
   if (chromecast_branding == "public") {
     sources += [
       "cast_browser_main_parts_simple.cc",
       "cast_content_browser_client_simple.cc",
-      "cast_network_request_interceptor_simple.cc",
     ]
   }
 
diff --git a/chromecast/browser/cast_browser_context.cc b/chromecast/browser/cast_browser_context.cc
index 90dad23..66fd3a4 100644
--- a/chromecast/browser/cast_browser_context.cc
+++ b/chromecast/browser/cast_browser_context.cc
@@ -17,7 +17,6 @@
 #include "chromecast/base/cast_paths.h"
 #include "chromecast/browser/cast_download_manager_delegate.h"
 #include "chromecast/browser/cast_permission_manager.h"
-#include "chromecast/browser/url_request_context_factory.h"
 #include "components/keyed_service/core/simple_key_map.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chromecast/browser/cast_browser_main_parts.cc b/chromecast/browser/cast_browser_main_parts.cc
index 51dcf9b..e3eaf96 100644
--- a/chromecast/browser/cast_browser_main_parts.cc
+++ b/chromecast/browser/cast_browser_main_parts.cc
@@ -46,7 +46,6 @@
 #include "chromecast/browser/service_connector.h"
 #include "chromecast/browser/tts/tts_controller_impl.h"
 #include "chromecast/browser/tts/tts_platform_stub.h"
-#include "chromecast/browser/url_request_context_factory.h"
 #include "chromecast/chromecast_buildflags.h"
 #include "chromecast/graphics/cast_window_manager.h"
 #include "chromecast/media/base/key_systems_common.h"
@@ -70,6 +69,7 @@
 #include "gpu/command_buffer/service/gpu_switches.h"
 #include "media/base/media.h"
 #include "media/base/media_switches.h"
+#include "net/base/network_change_notifier.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/gl/gl_switches.h"
@@ -376,13 +376,12 @@
 
 CastBrowserMainParts::CastBrowserMainParts(
     const content::MainFunctionParams& parameters,
-    URLRequestContextFactory* url_request_context_factory,
     CastContentBrowserClient* cast_content_browser_client)
     : BrowserMainParts(),
       cast_browser_process_(new CastBrowserProcess()),
       parameters_(parameters),
       cast_content_browser_client_(cast_content_browser_client),
-      url_request_context_factory_(url_request_context_factory),
+
       media_caps_(new media::MediaCapsImpl()),
       cast_system_memory_pressure_evaluator_adjuster_(nullptr) {
   DCHECK(cast_content_browser_client);
@@ -525,8 +524,6 @@
                      base::Unretained(this)));
 #endif  // defined(OS_ANDROID)
 
-  url_request_context_factory_->InitializeOnUIThread(nullptr);
-
   cast_browser_process_->SetBrowserContext(
       std::make_unique<CastBrowserContext>());
 
@@ -640,7 +637,6 @@
   // initialized by cast service.
   cast_browser_process_->cast_browser_metrics()->Initialize();
   cast_content_browser_client_->InitializeURLLoaderThrottleDelegate();
-  url_request_context_factory_->InitializeNetworkDelegates();
 
   cast_content_browser_client_->CreateGeneralAudienceBrowsingService();
 
diff --git a/chromecast/browser/cast_browser_main_parts.h b/chromecast/browser/cast_browser_main_parts.h
index 4480a46e..c198261 100644
--- a/chromecast/browser/cast_browser_main_parts.h
+++ b/chromecast/browser/cast_browser_main_parts.h
@@ -55,7 +55,6 @@
 namespace shell {
 class CastBrowserProcess;
 class CastContentBrowserClient;
-class URLRequestContextFactory;
 
 class CastBrowserMainParts : public content::BrowserMainParts {
  public:
@@ -63,12 +62,10 @@
   // link in an implementation as needed.
   static std::unique_ptr<CastBrowserMainParts> Create(
       const content::MainFunctionParams& parameters,
-      URLRequestContextFactory* url_request_context_factory,
       CastContentBrowserClient* cast_content_browser_client);
 
   // This class does not take ownership of |url_request_content_factory|.
   CastBrowserMainParts(const content::MainFunctionParams& parameters,
-                       URLRequestContextFactory* url_request_context_factory,
                        CastContentBrowserClient* cast_content_browser_client);
   ~CastBrowserMainParts() override;
 
@@ -92,7 +89,6 @@
   const content::MainFunctionParams parameters_;  // For running browser tests.
   // Caches a pointer of the CastContentBrowserClient.
   CastContentBrowserClient* const cast_content_browser_client_ = nullptr;
-  URLRequestContextFactory* const url_request_context_factory_;
   std::unique_ptr<media::VideoPlaneController> video_plane_controller_;
   std::unique_ptr<media::MediaCapsImpl> media_caps_;
   std::unique_ptr<ServiceConnector> service_connector_;
diff --git a/chromecast/browser/cast_browser_main_parts_simple.cc b/chromecast/browser/cast_browser_main_parts_simple.cc
index 045fd058..9dd0ad15 100644
--- a/chromecast/browser/cast_browser_main_parts_simple.cc
+++ b/chromecast/browser/cast_browser_main_parts_simple.cc
@@ -10,10 +10,9 @@
 // static
 std::unique_ptr<CastBrowserMainParts> CastBrowserMainParts::Create(
     const content::MainFunctionParams& parameters,
-    URLRequestContextFactory* url_request_context_factory,
     CastContentBrowserClient* cast_content_browser_client) {
-  return std::make_unique<CastBrowserMainParts>(
-      parameters, url_request_context_factory, cast_content_browser_client);
+  return std::make_unique<CastBrowserMainParts>(parameters,
+                                                cast_content_browser_client);
 }
 
 }  // namespace shell
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index fa721ffc..fac6cf13 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -35,7 +35,6 @@
 #include "chromecast/browser/cast_http_user_agent_settings.h"
 #include "chromecast/browser/cast_navigation_ui_data.h"
 #include "chromecast/browser/cast_network_contexts.h"
-#include "chromecast/browser/cast_network_delegate.h"
 #include "chromecast/browser/cast_overlay_manifests.h"
 #include "chromecast/browser/cast_quota_permission_context.h"
 #include "chromecast/browser/cast_session_id_map.h"
@@ -48,7 +47,6 @@
 #include "chromecast/browser/service/cast_service_simple.h"
 #include "chromecast/browser/service_connector.h"
 #include "chromecast/browser/tts/tts_controller.h"
-#include "chromecast/browser/url_request_context_factory.h"
 #include "chromecast/common/cast_content_client.h"
 #include "chromecast/common/global_descriptors.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
@@ -151,7 +149,6 @@
       cast_browser_main_parts_(nullptr),
       cast_network_contexts_(
           std::make_unique<CastNetworkContexts>(GetCorsExemptHeadersList())),
-      url_request_context_factory_(new URLRequestContextFactory()),
       cast_feature_list_creator_(cast_feature_list_creator) {
   cast_feature_list_creator_->SetExtraEnableFeatures({
     ::media::kInternalMediaSession, features::kNetworkServiceInProcess,
@@ -171,8 +168,6 @@
   DCHECK(!media_resource_tracker_)
       << "ResetMediaResourceTracker was not called";
   cast_network_contexts_.reset();
-  base::DeleteSoon(FROM_HERE, {content::BrowserThread::IO},
-                   url_request_context_factory_.release());
 }
 
 std::unique_ptr<ServiceConnector>
@@ -325,8 +320,7 @@
     const content::MainFunctionParams& parameters) {
   DCHECK(!cast_browser_main_parts_);
 
-  auto main_parts = CastBrowserMainParts::Create(
-      parameters, url_request_context_factory_.get(), this);
+  auto main_parts = CastBrowserMainParts::Create(parameters, this);
 
   cast_browser_main_parts_ = main_parts.get();
   CastBrowserProcess::GetInstance()->SetCastContentBrowserClient(this);
@@ -584,7 +578,7 @@
   // we need to return (if permitted) is the Cast device cert, which we can
   // access directly through the ClientAuthSigner instance. However, we need to
   // be on the IO thread to determine whether the app is whitelisted to return
-  // it, because CastNetworkDelegate is bound to the IO thread.
+  // it.
   // Subsequently, the callback must then itself be performed back here
   // on the UI thread.
   //
@@ -606,6 +600,15 @@
   return base::OnceClosure();
 }
 
+bool CastContentBrowserClient::IsWhitelisted(
+    const GURL& /* gurl */,
+    const std::string& /* session_id */,
+    int /* render_process_id */,
+    int /* render_frame_id */,
+    bool /* for_device_auth */) {
+  return false;
+}
+
 void CastContentBrowserClient::SelectClientCertificateOnIOThread(
     GURL requesting_url,
     const std::string& session_id,
@@ -616,11 +619,8 @@
                             scoped_refptr<net::SSLPrivateKey>)>
         continue_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  CastNetworkDelegate* network_delegate =
-      url_request_context_factory_->app_network_delegate();
-  if (network_delegate->IsWhitelisted(requesting_url, session_id,
-                                      render_process_id, render_frame_id,
-                                      false)) {
+  if (IsWhitelisted(requesting_url, session_id, render_process_id,
+                    render_frame_id, false)) {
     original_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(continue_callback), DeviceCert(),
                                   DeviceKey()));
diff --git a/chromecast/browser/cast_content_browser_client.h b/chromecast/browser/cast_content_browser_client.h
index 6f41af87..36d167b 100644
--- a/chromecast/browser/cast_content_browser_client.h
+++ b/chromecast/browser/cast_content_browser_client.h
@@ -23,7 +23,6 @@
 #include "media/mojo/mojom/media_service.mojom.h"
 #include "media/mojo/mojom/renderer.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "net/url_request/url_request_context.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/mojom/interface_provider.mojom-forward.h"
 #include "services/service_manager/public/mojom/service.mojom-forward.h"
@@ -79,7 +78,6 @@
 namespace shell {
 class CastBrowserMainParts;
 class CastNetworkContexts;
-class URLRequestContextFactory;
 
 class CastContentBrowserClient
     : public content::ContentBrowserClient,
@@ -273,10 +271,6 @@
   explicit CastContentBrowserClient(
       CastFeatureListCreator* cast_feature_list_creator);
 
-  URLRequestContextFactory* url_request_context_factory() const {
-    return url_request_context_factory_.get();
-  }
-
   void BindMediaRenderer(
       mojo::PendingReceiver<::media::mojom::Renderer> receiver);
 
@@ -289,6 +283,12 @@
   virtual scoped_refptr<net::X509Certificate> DeviceCert();
   virtual scoped_refptr<net::SSLPrivateKey> DeviceKey();
 
+  virtual bool IsWhitelisted(const GURL& gurl,
+                             const std::string& session_id,
+                             int render_process_id,
+                             int render_frame_id,
+                             bool for_device_auth);
+
   void SelectClientCertificateOnIOThread(
       GURL requesting_url,
       const std::string& session_id,
@@ -340,7 +340,6 @@
   // Created by CastContentBrowserClient but owned by BrowserMainLoop.
   CastBrowserMainParts* cast_browser_main_parts_;
   std::unique_ptr<CastNetworkContexts> cast_network_contexts_;
-  std::unique_ptr<URLRequestContextFactory> url_request_context_factory_;
   std::unique_ptr<media::CmaBackendFactory> cma_backend_factory_;
   std::unique_ptr<GeneralAudienceBrowsingService>
       general_audience_browsing_service_;
diff --git a/chromecast/browser/cast_network_contexts.cc b/chromecast/browser/cast_network_contexts.cc
index 29a803f..299e766b 100644
--- a/chromecast/browser/cast_network_contexts.cc
+++ b/chromecast/browser/cast_network_contexts.cc
@@ -14,7 +14,6 @@
 #include "chromecast/browser/cast_browser_context.h"
 #include "chromecast/browser/cast_browser_process.h"
 #include "chromecast/browser/cast_http_user_agent_settings.h"
-#include "chromecast/browser/url_request_context_factory.h"
 #include "chromecast/common/cast_content_client.h"
 #include "components/proxy_config/pref_proxy_config_tracker_impl.h"
 #include "components/variations/net/variations_http_headers.h"
diff --git a/chromecast/browser/cast_network_delegate.cc b/chromecast/browser/cast_network_delegate.cc
deleted file mode 100644
index 8a66325dd..0000000
--- a/chromecast/browser/cast_network_delegate.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/cast_network_delegate.h"
-
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/files/file_path.h"
-#include "chromecast/base/chromecast_switches.h"
-#include "chromecast/browser/cast_navigation_ui_data.h"
-#include "chromecast/browser/cast_network_request_interceptor.h"
-#include "content/public/common/child_process_host.h"
-#include "net/base/net_errors.h"
-
-namespace chromecast {
-namespace shell {
-
-std::unique_ptr<CastNetworkDelegate> CastNetworkDelegate::Create() {
-  return std::make_unique<CastNetworkDelegate>(
-      CastNetworkRequestInterceptor::Create());
-}
-
-CastNetworkDelegate::CastNetworkDelegate(
-    std::unique_ptr<CastNetworkRequestInterceptor> network_request_interceptor)
-    : network_request_interceptor_(std::move(network_request_interceptor)) {
-  DCHECK(network_request_interceptor_);
-  DETACH_FROM_THREAD(thread_checker_);
-}
-
-CastNetworkDelegate::~CastNetworkDelegate() {
-}
-
-void CastNetworkDelegate::Initialize() {
-  network_request_interceptor_->Initialize();
-}
-
-bool CastNetworkDelegate::IsWhitelisted(const GURL& gurl,
-                                        const std::string& session_id,
-                                        int render_process_id,
-                                        int render_frame_id,
-                                        bool for_device_auth) const {
-  return network_request_interceptor_->IsWhiteListed(
-      gurl, session_id, render_process_id, render_frame_id, for_device_auth);
-}
-
-int CastNetworkDelegate::OnBeforeURLRequest(
-    net::URLRequest* request,
-    net::CompletionOnceCallback callback,
-    GURL* new_url) {
-  return net::OK;
-}
-
-int CastNetworkDelegate::OnBeforeStartTransaction(
-    net::URLRequest* request,
-    net::CompletionOnceCallback callback,
-    net::HttpRequestHeaders* headers) {
-  if (!network_request_interceptor_->IsInitialized())
-    return net::OK;
-  return network_request_interceptor_->OnBeforeStartTransaction(
-      request, std::move(callback), headers);
-}
-
-void CastNetworkDelegate::OnURLRequestDestroyed(net::URLRequest* request) {
-  if (network_request_interceptor_->IsInitialized())
-    network_request_interceptor_->OnURLRequestDestroyed(request);
-}
-
-}  // namespace shell
-}  // namespace chromecast
diff --git a/chromecast/browser/cast_network_delegate.h b/chromecast/browser/cast_network_delegate.h
deleted file mode 100644
index 094c685..0000000
--- a/chromecast/browser/cast_network_delegate.h
+++ /dev/null
@@ -1,55 +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.
-
-#ifndef CHROMECAST_BROWSER_CAST_NETWORK_DELEGATE_H_
-#define CHROMECAST_BROWSER_CAST_NETWORK_DELEGATE_H_
-
-#include <memory>
-#include <string>
-
-#include "base/macros.h"
-#include "net/base/network_delegate_impl.h"
-
-namespace chromecast {
-
-class CastNetworkRequestInterceptor;
-
-namespace shell {
-
-class CastNetworkDelegate : public net::NetworkDelegateImpl {
- public:
-  static std::unique_ptr<CastNetworkDelegate> Create();
-
-  explicit CastNetworkDelegate(std::unique_ptr<CastNetworkRequestInterceptor>
-                                   network_request_interceptor);
-  ~CastNetworkDelegate() override;
-
-  void Initialize();
-
-  bool IsWhitelisted(const GURL& gurl,
-                     const std::string& session_id,
-                     int render_process_id,
-                     int render_frame_id,
-                     bool for_device_auth) const;
-
- private:
-  // net::NetworkDelegate implementation:
-  int OnBeforeURLRequest(net::URLRequest* request,
-                         net::CompletionOnceCallback callback,
-                         GURL* new_url) override;
-  int OnBeforeStartTransaction(net::URLRequest* request,
-                               net::CompletionOnceCallback callback,
-                               net::HttpRequestHeaders* headers) override;
-  void OnURLRequestDestroyed(net::URLRequest* request) override;
-
-  const std::unique_ptr<CastNetworkRequestInterceptor>
-      network_request_interceptor_;
-
-  DISALLOW_COPY_AND_ASSIGN(CastNetworkDelegate);
-};
-
-}  // namespace shell
-}  // namespace chromecast
-
-#endif  // CHROMECAST_BROWSER_CAST_NETWORK_DELEGATE_H_
diff --git a/chromecast/browser/cast_network_request_interceptor.cc b/chromecast/browser/cast_network_request_interceptor.cc
deleted file mode 100644
index 2e65521f..0000000
--- a/chromecast/browser/cast_network_request_interceptor.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/cast_network_request_interceptor.h"
-
-#include "net/base/net_errors.h"
-
-namespace chromecast {
-
-CastNetworkRequestInterceptor::CastNetworkRequestInterceptor() = default;
-
-CastNetworkRequestInterceptor::~CastNetworkRequestInterceptor() = default;
-
-bool CastNetworkRequestInterceptor::IsWhiteListed(
-    const GURL& /* gurl */,
-    const std::string& /* session_id */,
-    int /* render_process_id */,
-    int /* render_frame_id */,
-    bool /* for_device_auth */) const {
-  return false;
-}
-
-void CastNetworkRequestInterceptor::Initialize() {}
-
-bool CastNetworkRequestInterceptor::IsInitialized() {
-  return true;
-}
-
-int CastNetworkRequestInterceptor::OnBeforeURLRequest(
-    net::URLRequest* /* request */,
-    const std::string& /* session_id */,
-    int /* render_process_id */,
-    int /* render_frame_id */,
-    net::CompletionOnceCallback /* callback */,
-    GURL* /* new_url */) {
-  return net::OK;
-}
-
-int CastNetworkRequestInterceptor::OnBeforeStartTransaction(
-    net::URLRequest* /* request */,
-    net::CompletionOnceCallback /* callback */,
-    net::HttpRequestHeaders* headers) {
-  return net::OK;
-}
-
-void CastNetworkRequestInterceptor::OnURLRequestDestroyed(
-    net::URLRequest* /* request */) {}
-
-}  // namespace chromecast
diff --git a/chromecast/browser/cast_network_request_interceptor.h b/chromecast/browser/cast_network_request_interceptor.h
deleted file mode 100644
index 6ec2528..0000000
--- a/chromecast/browser/cast_network_request_interceptor.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMECAST_BROWSER_CAST_NETWORK_REQUEST_INTERCEPTOR_H_
-#define CHROMECAST_BROWSER_CAST_NETWORK_REQUEST_INTERCEPTOR_H_
-
-#include <memory>
-#include <string>
-
-#include "base/macros.h"
-#include "base/sequence_checker.h"
-#include "net/base/completion_once_callback.h"
-
-class GURL;
-
-namespace net {
-class HttpRequestHeaders;
-class URLRequest;
-}  // namespace net
-
-namespace chromecast {
-
-// Used to intercept the network request and modify the headers.
-class CastNetworkRequestInterceptor {
- public:
-  static std::unique_ptr<CastNetworkRequestInterceptor> Create();
-
-  CastNetworkRequestInterceptor();
-  virtual ~CastNetworkRequestInterceptor();
-
-  // TODO(juke): Remove render_process_id.
-  virtual bool IsWhiteListed(const GURL& gurl,
-                             const std::string& session_id,
-                             int render_process_id,
-                             int render_frame_id,
-                             bool for_device_auth) const;
-  virtual void Initialize();
-  virtual bool IsInitialized();
-
-  virtual int OnBeforeURLRequest(net::URLRequest* request,
-                                 const std::string& session_id,
-                                 int render_process_id,
-                                 int render_frame_id,
-                                 net::CompletionOnceCallback callback,
-                                 GURL* new_url);
-
-  virtual int OnBeforeStartTransaction(net::URLRequest* request,
-                                       net::CompletionOnceCallback callback,
-                                       net::HttpRequestHeaders* headers);
-
-  virtual void OnURLRequestDestroyed(net::URLRequest* request);
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CastNetworkRequestInterceptor);
-};
-}  // namespace chromecast
-
-#endif  // CHROMECAST_BROWSER_CAST_NETWORK_REQUEST_INTERCEPTOR_H_
diff --git a/chromecast/browser/cast_network_request_interceptor_simple.cc b/chromecast/browser/cast_network_request_interceptor_simple.cc
deleted file mode 100644
index ed4d4abfb..0000000
--- a/chromecast/browser/cast_network_request_interceptor_simple.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/cast_network_request_interceptor.h"
-
-namespace chromecast {
-
-std::unique_ptr<CastNetworkRequestInterceptor>
-CastNetworkRequestInterceptor::Create() {
-  return std::make_unique<CastNetworkRequestInterceptor>();
-}
-
-}  // namespace chromecast
diff --git a/chromecast/browser/url_request_context_factory.cc b/chromecast/browser/url_request_context_factory.cc
deleted file mode 100644
index 0e33b64..0000000
--- a/chromecast/browser/url_request_context_factory.cc
+++ /dev/null
@@ -1,429 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/url_request_context_factory.h"
-
-#include <algorithm>
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/files/file_path.h"
-#include "base/macros.h"
-#include "base/single_thread_task_runner.h"
-#include "base/task/post_task.h"
-#include "chromecast/base/cast_features.h"
-#include "chromecast/base/chromecast_switches.h"
-#include "chromecast/browser/cast_browser_process.h"
-#include "chromecast/browser/cast_http_user_agent_settings.h"
-#include "chromecast/browser/cast_network_delegate.h"
-#include "chromecast/chromecast_buildflags.h"
-#include "components/network_session_configurator/common/network_switches.h"
-#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/cookie_store_factory.h"
-#include "content/public/common/url_constants.h"
-#include "net/base/network_change_notifier.h"
-#include "net/cert/cert_verifier.h"
-#include "net/cert/ct_policy_enforcer.h"
-#include "net/cert/ct_policy_status.h"
-#include "net/cert/multi_log_ct_verifier.h"
-#include "net/cookies/cookie_store.h"
-#include "net/dns/host_resolver.h"
-#include "net/dns/host_resolver_manager.h"
-#include "net/http/http_auth_handler_factory.h"
-#include "net/http/http_network_layer.h"
-#include "net/http/http_server_properties.h"
-#include "net/http/http_stream_factory.h"
-#include "net/proxy_resolution/proxy_resolution_service.h"
-#include "net/quic/quic_context.h"
-#include "net/ssl/ssl_config_service_defaults.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_builder.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "net/url_request/url_request_intercepting_job_factory.h"
-#include "net/url_request/url_request_job_factory_impl.h"
-
-namespace chromecast {
-namespace shell {
-
-namespace {
-
-const char kCookieStoreFile[] = "Cookies";
-
-bool IgnoreCertificateErrors() {
-  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
-  return cmd_line->HasSwitch(switches::kIgnoreCertificateErrors);
-}
-
-}  // namespace
-
-// Private classes to expose URLRequestContextGetter that call back to the
-// URLRequestContextFactory to create the URLRequestContext on demand.
-//
-// The URLRequestContextFactory::URLRequestContextGetter class is used for both
-// the system and media URLRequestCotnexts.
-class URLRequestContextFactory::URLRequestContextGetter
-    : public net::URLRequestContextGetter {
- public:
-  URLRequestContextGetter(URLRequestContextFactory* factory, bool is_media)
-      : is_media_(is_media),
-        factory_(factory) {
-  }
-
-  net::URLRequestContext* GetURLRequestContext() override {
-    if (!request_context_) {
-      if (is_media_) {
-        request_context_.reset(factory_->CreateMediaRequestContext());
-      } else {
-        request_context_.reset(factory_->CreateSystemRequestContext());
-      }
-    }
-    return request_context_.get();
-  }
-
-  scoped_refptr<base::SingleThreadTaskRunner>
-      GetNetworkTaskRunner() const override {
-    return base::CreateSingleThreadTaskRunner({content::BrowserThread::IO});
-  }
-
- private:
-  ~URLRequestContextGetter() override {}
-
-  const bool is_media_;
-  URLRequestContextFactory* const factory_;
-  std::unique_ptr<net::URLRequestContext> request_context_;
-
-  DISALLOW_COPY_AND_ASSIGN(URLRequestContextGetter);
-};
-
-// The URLRequestContextFactory::MainURLRequestContextGetter class is used for
-// the main URLRequestContext.
-class URLRequestContextFactory::MainURLRequestContextGetter
-    : public net::URLRequestContextGetter {
- public:
-  MainURLRequestContextGetter(
-      URLRequestContextFactory* factory,
-      content::BrowserContext* browser_context,
-      content::ProtocolHandlerMap* protocol_handlers,
-      content::URLRequestInterceptorScopedVector request_interceptors)
-      : factory_(factory),
-        cookie_path_(browser_context->GetPath().Append(kCookieStoreFile)),
-        request_interceptors_(std::move(request_interceptors)) {
-    std::swap(protocol_handlers_, *protocol_handlers);
-  }
-
-  net::URLRequestContext* GetURLRequestContext() override {
-    if (!request_context_) {
-      request_context_.reset(factory_->CreateMainRequestContext(
-          cookie_path_, &protocol_handlers_, std::move(request_interceptors_)));
-      protocol_handlers_.clear();
-    }
-    return request_context_.get();
-  }
-
-  scoped_refptr<base::SingleThreadTaskRunner>
-      GetNetworkTaskRunner() const override {
-    return base::CreateSingleThreadTaskRunner({content::BrowserThread::IO});
-  }
-
- private:
-  ~MainURLRequestContextGetter() override {}
-
-  URLRequestContextFactory* const factory_;
-  base::FilePath cookie_path_;
-  content::ProtocolHandlerMap protocol_handlers_;
-  content::URLRequestInterceptorScopedVector request_interceptors_;
-  std::unique_ptr<net::URLRequestContext> request_context_;
-
-  DISALLOW_COPY_AND_ASSIGN(MainURLRequestContextGetter);
-};
-
-URLRequestContextFactory::URLRequestContextFactory()
-    : app_network_delegate_(CastNetworkDelegate::Create()),
-      system_network_delegate_(CastNetworkDelegate::Create()),
-      system_dependencies_initialized_(false),
-      main_dependencies_initialized_(false),
-      media_dependencies_initialized_(false) {}
-
-URLRequestContextFactory::~URLRequestContextFactory() {
-  pref_proxy_config_tracker_impl_->DetachFromPrefService();
-}
-
-void URLRequestContextFactory::InitializeOnUIThread(net::NetLog* net_log) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  // Cast http user agent settings must be initialized in UI thread
-  // because it registers itself to pref notification observer which is not
-  // thread safe.
-  http_user_agent_settings_.reset(new CastHttpUserAgentSettings());
-
-  // Proxy config service should be initialized in UI thread, since
-  // ProxyConfigServiceDelegate on Android expects UI thread.
-  pref_proxy_config_tracker_impl_ =
-      std::make_unique<PrefProxyConfigTrackerImpl>(
-          CastBrowserProcess::GetInstance()->pref_service(),
-          base::CreateSingleThreadTaskRunner({content::BrowserThread::IO}));
-
-  proxy_config_service_ =
-      pref_proxy_config_tracker_impl_->CreateTrackingProxyConfigService(
-          nullptr);
-  DCHECK(proxy_config_service_.get());
-  net_log_ = net_log;
-}
-
-net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter(
-    content::BrowserContext* browser_context,
-    content::ProtocolHandlerMap* protocol_handlers,
-    content::URLRequestInterceptorScopedVector request_interceptors) {
-  DCHECK(!main_getter_.get())
-      << "Main URLRequestContextGetter already initialized";
-  main_getter_ =
-      new MainURLRequestContextGetter(this, browser_context, protocol_handlers,
-                                      std::move(request_interceptors));
-  return main_getter_.get();
-}
-
-net::URLRequestContextGetter* URLRequestContextFactory::GetMainGetter() {
-  CHECK(main_getter_.get());
-  return main_getter_.get();
-}
-
-net::URLRequestContextGetter* URLRequestContextFactory::GetSystemGetter() {
-  if (!system_getter_.get()) {
-    system_getter_ = new URLRequestContextGetter(this, false);
-  }
-  return system_getter_.get();
-}
-
-net::URLRequestContextGetter* URLRequestContextFactory::GetMediaGetter() {
-  if (!media_getter_.get()) {
-    media_getter_ = new URLRequestContextGetter(this, true);
-  }
-  return media_getter_.get();
-}
-
-void URLRequestContextFactory::InitializeSystemContextDependencies() {
-  if (system_dependencies_initialized_)
-    return;
-
-  host_resolver_manager_ = std::make_unique<net::HostResolverManager>(
-      net::HostResolver::ManagerOptions(),
-      net::NetworkChangeNotifier::GetSystemDnsConfigNotifier(),
-      /*net_log=*/nullptr);
-  cert_verifier_ =
-      net::CertVerifier::CreateDefault(/*cert_net_fetcher=*/nullptr);
-  ssl_config_service_.reset(new net::SSLConfigServiceDefaults);
-  transport_security_state_.reset(new net::TransportSecurityState());
-  cert_transparency_verifier_.reset(new net::MultiLogCTVerifier());
-  ct_policy_enforcer_.reset(new net::DefaultCTPolicyEnforcer());
-
-  http_auth_handler_factory_ = net::HttpAuthHandlerFactory::CreateDefault();
-
-  // Use in-memory HttpServerProperties. Disk-based can improve performance
-  // but benefit seems small (only helps 1st request to a server).
-  http_server_properties_ = std::make_unique<net::HttpServerProperties>();
-
-  DCHECK(proxy_config_service_);
-  proxy_resolution_service_ =
-      net::ProxyResolutionService::CreateUsingSystemProxyResolver(
-          std::move(proxy_config_service_), nullptr);
-
-  system_host_resolver_ =
-      net::HostResolver::CreateResolver(host_resolver_manager_.get());
-
-  quic_context_ = std::make_unique<net::QuicContext>();
-
-  system_dependencies_initialized_ = true;
-}
-
-void URLRequestContextFactory::InitializeMainContextDependencies(
-    content::ProtocolHandlerMap* protocol_handlers,
-    content::URLRequestInterceptorScopedVector request_interceptors) {
-  if (main_dependencies_initialized_)
-    return;
-
-  std::unique_ptr<net::URLRequestJobFactoryImpl> job_factory(
-      new net::URLRequestJobFactoryImpl());
-  // Keep ProtocolHandlers added in sync with
-  // CastContentBrowserClient::IsHandledURL().
-  for (content::ProtocolHandlerMap::iterator it = protocol_handlers->begin();
-       it != protocol_handlers->end();
-       ++it) {
-    bool set_protocol =
-        job_factory->SetProtocolHandler(it->first, std::move(it->second));
-    DCHECK(set_protocol);
-  }
-
-  // Set up interceptors in the reverse order.
-  std::unique_ptr<net::URLRequestJobFactory> top_job_factory =
-      std::move(job_factory);
-  for (auto i = request_interceptors.rbegin(); i != request_interceptors.rend();
-       ++i) {
-    top_job_factory.reset(new net::URLRequestInterceptingJobFactory(
-        std::move(top_job_factory), std::move(*i)));
-  }
-  request_interceptors.clear();
-
-  main_job_factory_ = std::move(top_job_factory);
-
-  main_host_resolver_ =
-      net::HostResolver::CreateResolver(host_resolver_manager_.get());
-
-  main_dependencies_initialized_ = true;
-}
-
-void URLRequestContextFactory::InitializeMediaContextDependencies() {
-  if (media_dependencies_initialized_)
-    return;
-
-  media_host_resolver_ =
-      net::HostResolver::CreateResolver(host_resolver_manager_.get());
-  media_dependencies_initialized_ = true;
-}
-
-void URLRequestContextFactory::PopulateNetworkSessionParams(
-    bool ignore_certificate_errors,
-    net::HttpNetworkSession::Params* session_params) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-
-  session_params->ignore_certificate_errors = ignore_certificate_errors;
-
-  // Enable QUIC if instructed by DCS. This remains constant for the lifetime of
-  // the process.
-  session_params->enable_quic = chromecast::IsFeatureEnabled(kEnableQuic);
-  LOG(INFO) << "Set HttpNetworkSessionParams.enable_quic = "
-            << session_params->enable_quic;
-
-  // Disable idle sockets close on memory pressure, if instructed by DCS. On
-  // memory constrained devices:
-  // 1. if idle sockets are closed when memory pressure happens, cast_shell will
-  // close and re-open lots of connections to server.
-  // 2. if idle sockets are kept alive when memory pressure happens, this may
-  // cause JS engine gc frequently, leading to JS suspending.
-  session_params->disable_idle_sockets_close_on_memory_pressure =
-      chromecast::IsFeatureEnabled(kDisableIdleSocketsCloseOnMemoryPressure);
-  LOG(INFO) << "Set HttpNetworkSessionParams."
-            << "disable_idle_sockets_close_on_memory_pressure = "
-            << session_params->disable_idle_sockets_close_on_memory_pressure;
-}
-
-std::unique_ptr<net::HttpNetworkSession>
-URLRequestContextFactory::CreateNetworkSession(
-    const net::URLRequestContext* context) {
-  net::HttpNetworkSession::Params session_params;
-  net::HttpNetworkSession::Context session_context;
-  PopulateNetworkSessionParams(IgnoreCertificateErrors(), &session_params);
-  net::URLRequestContextBuilder::SetHttpNetworkSessionComponents(
-      context, &session_context);
-  return std::make_unique<net::HttpNetworkSession>(session_params,
-                                                   session_context);
-}
-
-net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  InitializeSystemContextDependencies();
-  system_job_factory_.reset(new net::URLRequestJobFactoryImpl());
-  system_cookie_store_ =
-      content::CreateCookieStore(content::CookieStoreConfig(), net_log_);
-
-  net::URLRequestContext* system_context = new net::URLRequestContext();
-  ConfigureURLRequestContext(system_context, system_job_factory_,
-                             system_cookie_store_, system_network_delegate_,
-                             system_host_resolver_);
-
-  system_network_session_ = CreateNetworkSession(system_context);
-  system_transaction_factory_ =
-      std::make_unique<net::HttpNetworkLayer>(system_network_session_.get());
-  system_context->set_http_transaction_factory(
-      system_transaction_factory_.get());
-
-  return system_context;
-}
-
-net::URLRequestContext* URLRequestContextFactory::CreateMediaRequestContext() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  DCHECK(main_getter_.get())
-      << "Getting MediaRequestContext before MainRequestContext";
-
-  InitializeSystemContextDependencies();
-  InitializeMediaContextDependencies();
-
-  // Reuse main context dependencies except HostResolver and
-  // HttpTransactionFactory.
-  net::URLRequestContext* media_context = new net::URLRequestContext();
-  ConfigureURLRequestContext(media_context, main_job_factory_,
-                             main_cookie_store_, app_network_delegate_,
-                             media_host_resolver_);
-
-  media_network_session_ = CreateNetworkSession(media_context);
-  media_transaction_factory_ =
-      std::make_unique<net::HttpNetworkLayer>(media_network_session_.get());
-  media_context->set_http_transaction_factory(media_transaction_factory_.get());
-
-  return media_context;
-}
-
-net::URLRequestContext* URLRequestContextFactory::CreateMainRequestContext(
-    const base::FilePath& cookie_path,
-    content::ProtocolHandlerMap* protocol_handlers,
-    content::URLRequestInterceptorScopedVector request_interceptors) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  InitializeSystemContextDependencies();
-  InitializeMainContextDependencies(
-      protocol_handlers, std::move(request_interceptors));
-
-  content::CookieStoreConfig cookie_config(cookie_path, false, true, nullptr);
-  main_cookie_store_ = content::CreateCookieStore(cookie_config, net_log_);
-
-  net::URLRequestContext* main_context = new net::URLRequestContext();
-  ConfigureURLRequestContext(main_context, main_job_factory_,
-                             main_cookie_store_, app_network_delegate_,
-                             main_host_resolver_);
-
-  main_network_session_ = CreateNetworkSession(main_context);
-  main_transaction_factory_ =
-      std::make_unique<net::HttpNetworkLayer>(main_network_session_.get());
-  main_context->set_http_transaction_factory(main_transaction_factory_.get());
-
-  return main_context;
-}
-
-void URLRequestContextFactory::ConfigureURLRequestContext(
-    net::URLRequestContext* context,
-    const std::unique_ptr<net::URLRequestJobFactory>& job_factory,
-    const std::unique_ptr<net::CookieStore>& cookie_store,
-    const std::unique_ptr<CastNetworkDelegate>& network_delegate,
-    const std::unique_ptr<net::HostResolver>& host_resolver) {
-  // common settings
-  context->set_cert_verifier(cert_verifier_.get());
-  context->set_cert_transparency_verifier(cert_transparency_verifier_.get());
-  context->set_ct_policy_enforcer(ct_policy_enforcer_.get());
-  context->set_proxy_resolution_service(proxy_resolution_service_.get());
-  context->set_ssl_config_service(ssl_config_service_.get());
-  context->set_transport_security_state(transport_security_state_.get());
-  context->set_http_auth_handler_factory(http_auth_handler_factory_.get());
-  context->set_http_server_properties(http_server_properties_.get());
-  context->set_http_user_agent_settings(http_user_agent_settings_.get());
-  context->set_net_log(net_log_);
-  context->set_quic_context(quic_context_.get());
-
-  // settings from the caller
-  context->set_job_factory(job_factory.get());
-  context->set_cookie_store(cookie_store.get());
-  context->set_network_delegate(network_delegate.get());
-  context->set_host_resolver(host_resolver.get());
-
-  host_resolver->SetRequestContext(context);
-}
-
-void URLRequestContextFactory::InitializeNetworkDelegates() {
-  app_network_delegate_->Initialize();
-  LOG(INFO) << "Initialized app network delegate.";
-  system_network_delegate_->Initialize();
-  LOG(INFO) << "Initialized system network delegate.";
-}
-
-}  // namespace shell
-}  // namespace chromecast
diff --git a/chromecast/browser/url_request_context_factory.h b/chromecast/browser/url_request_context_factory.h
deleted file mode 100644
index f86e30f..0000000
--- a/chromecast/browser/url_request_context_factory.h
+++ /dev/null
@@ -1,156 +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.
-
-#ifndef CHROMECAST_BROWSER_URL_REQUEST_CONTEXT_FACTORY_H_
-#define CHROMECAST_BROWSER_URL_REQUEST_CONTEXT_FACTORY_H_
-
-#include <memory>
-
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/content_browser_client.h"
-#include "net/http/http_network_session.h"
-
-class PrefProxyConfigTracker;
-
-namespace base {
-class FilePath;
-}
-
-namespace net {
-class CookieStore;
-class HostResolver;
-class HostResolverManager;
-class HttpTransactionFactory;
-class HttpUserAgentSettings;
-class NetLog;
-class ProxyConfigService;
-class QuicContext;
-class URLRequestContextGetter;
-class URLRequestJobFactory;
-}  // namespace net
-
-namespace chromecast {
-namespace shell {
-class CastNetworkDelegate;
-
-class URLRequestContextFactory {
- public:
-  URLRequestContextFactory();
-  ~URLRequestContextFactory();
-
-  // Some members must be initialized on UI thread.
-  void InitializeOnUIThread(net::NetLog* net_log);
-
-  // Since main context requires a bunch of input params, if these get called
-  // multiple times, either multiple main contexts should be supported/managed
-  // or the input params need to be the same as before.  So to be safe,
-  // the CreateMainGetter function currently DCHECK to make sure it is not
-  // called more than once.
-  // The media and system getters however, do not need input, so it is actually
-  // safe to call these multiple times.  The impl create only 1 getter of each
-  // type and return the same instance each time the methods are called, thus
-  // the name difference.
-  net::URLRequestContextGetter* GetSystemGetter();
-  net::URLRequestContextGetter* CreateMainGetter(
-      content::BrowserContext* browser_context,
-      content::ProtocolHandlerMap* protocol_handlers,
-      content::URLRequestInterceptorScopedVector request_interceptors);
-  net::URLRequestContextGetter* GetMainGetter();
-  net::URLRequestContextGetter* GetMediaGetter();
-
-  CastNetworkDelegate* app_network_delegate() const {
-    return app_network_delegate_.get();
-  }
-
-  // Initialize the CastNetworkDelegate objects. This needs to be done
-  // after the CastService is created, but before any URL requests are made.
-  void InitializeNetworkDelegates();
-
- private:
-  class URLRequestContextGetter;
-  class MainURLRequestContextGetter;
-  friend class URLRequestContextGetter;
-  friend class MainURLRequestContextGetter;
-
-  void InitializeSystemContextDependencies();
-  void InitializeMainContextDependencies(
-      content::ProtocolHandlerMap* protocol_handlers,
-      content::URLRequestInterceptorScopedVector request_interceptors);
-  void InitializeMediaContextDependencies();
-
-  void PopulateNetworkSessionParams(
-      bool ignore_certificate_errors,
-      net::HttpNetworkSession::Params* session_params);
-  std::unique_ptr<net::HttpNetworkSession> CreateNetworkSession(
-      const net::URLRequestContext* context);
-
-  // These are called by the RequestContextGetters to create each
-  // RequestContext.
-  // They must be called on the IO thread.
-  net::URLRequestContext* CreateSystemRequestContext();
-  net::URLRequestContext* CreateMediaRequestContext();
-  net::URLRequestContext* CreateMainRequestContext(
-      const base::FilePath& cookie_path,
-      content::ProtocolHandlerMap* protocol_handlers,
-      content::URLRequestInterceptorScopedVector request_interceptors);
-
-  // Helper function for configuring the settings of URLRequestContext
-  void ConfigureURLRequestContext(
-      net::URLRequestContext* context,
-      const std::unique_ptr<net::URLRequestJobFactory>& job_factory,
-      const std::unique_ptr<net::CookieStore>& cookie_store,
-      const std::unique_ptr<CastNetworkDelegate>& network_delegate,
-      const std::unique_ptr<net::HostResolver>& host_resolver);
-
-  scoped_refptr<net::URLRequestContextGetter> system_getter_;
-  scoped_refptr<net::URLRequestContextGetter> media_getter_;
-  scoped_refptr<net::URLRequestContextGetter> main_getter_;
-  std::unique_ptr<CastNetworkDelegate> app_network_delegate_;
-  std::unique_ptr<CastNetworkDelegate> system_network_delegate_;
-
-  // Shared objects for all contexts.
-  // The URLRequestContextStorage class is not used as owner to these objects
-  // since they are shared between the different URLRequestContexts.
-  // The URLRequestContextStorage class manages dependent resources for a single
-  // instance of URLRequestContext only.
-  bool system_dependencies_initialized_;
-  std::unique_ptr<net::HostResolverManager> host_resolver_manager_;
-  std::unique_ptr<net::CertVerifier> cert_verifier_;
-  std::unique_ptr<net::SSLConfigService> ssl_config_service_;
-  std::unique_ptr<net::TransportSecurityState> transport_security_state_;
-  std::unique_ptr<net::CTVerifier> cert_transparency_verifier_;
-  std::unique_ptr<net::CTPolicyEnforcer> ct_policy_enforcer_;
-  std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
-  std::unique_ptr<net::ProxyResolutionService> proxy_resolution_service_;
-  std::unique_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory_;
-  std::unique_ptr<net::HttpServerProperties> http_server_properties_;
-  std::unique_ptr<net::HttpUserAgentSettings> http_user_agent_settings_;
-  std::unique_ptr<net::HttpNetworkSession> system_network_session_;
-  std::unique_ptr<net::HttpTransactionFactory> system_transaction_factory_;
-  std::unique_ptr<net::CookieStore> system_cookie_store_;
-  std::unique_ptr<net::URLRequestJobFactory> system_job_factory_;
-  std::unique_ptr<net::HostResolver> system_host_resolver_;
-  std::unique_ptr<net::QuicContext> quic_context_;
-
-  bool main_dependencies_initialized_;
-  std::unique_ptr<net::HttpNetworkSession> main_network_session_;
-  std::unique_ptr<net::HttpTransactionFactory> main_transaction_factory_;
-  std::unique_ptr<net::CookieStore> main_cookie_store_;
-  std::unique_ptr<net::URLRequestJobFactory> main_job_factory_;
-  std::unique_ptr<net::HostResolver> main_host_resolver_;
-
-  bool media_dependencies_initialized_;
-  std::unique_ptr<net::HttpNetworkSession> media_network_session_;
-  std::unique_ptr<net::HttpTransactionFactory> media_transaction_factory_;
-  std::unique_ptr<net::HostResolver> media_host_resolver_;
-
-  std::unique_ptr<PrefProxyConfigTracker> pref_proxy_config_tracker_impl_;
-
-  net::NetLog* net_log_;
-};
-
-}  // namespace shell
-}  // namespace chromecast
-
-#endif  // CHROMECAST_BROWSER_URL_REQUEST_CONTEXT_FACTORY_H_
diff --git a/chromeos/components/BUILD.gn b/chromeos/components/BUILD.gn
index cd6b4ac..aad3797 100644
--- a/chromeos/components/BUILD.gn
+++ b/chromeos/components/BUILD.gn
@@ -9,9 +9,7 @@
 # To add a unit test to this target, make a "unit_test" source_set in your
 # component and add a reference here.
 test("chromeos_components_unittests") {
-  sources = [
-    "run_all_unittests.cc",
-  ]
+  sources = [ "run_all_unittests.cc" ]
 
   deps = [
     "//base",
diff --git a/chromeos/components/help_app_ui/help_app_guest_ui.cc b/chromeos/components/help_app_ui/help_app_guest_ui.cc
index 2e503377..a180b0a8 100644
--- a/chromeos/components/help_app_ui/help_app_guest_ui.cc
+++ b/chromeos/components/help_app_ui/help_app_guest_ui.cc
@@ -20,7 +20,7 @@
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(kChromeUIHelpAppGuestHost);
   source->AddResourcePath("app.html", IDR_HELP_APP_APP_HTML);
-  source->AddResourcePath("bootstrap.js", IDR_HELP_APP_BOOTSTRAP_JS);
+  // source->AddResourcePath("app_bin.js", IDR_HELP_APP_APP_BIN_JS);
   source->AddResourcePath("load_time_data.js", IDR_WEBUI_JS_LOAD_TIME_DATA);
 
   // Add all resources from chromeos_media_app_bundle.pak.
diff --git a/chromeos/components/help_app_ui/resources/app.html b/chromeos/components/help_app_ui/resources/app.html
index 93259ad..a69c1bf 100644
--- a/chromeos/components/help_app_ui/resources/app.html
+++ b/chromeos/components/help_app_ui/resources/app.html
@@ -10,4 +10,4 @@
 </style>
 <script src="/load_time_data.js"></script>
 <script src="/strings.js"></script>
-<script src="/bootstrap.js"></script>
+<!--<script src="/app_bin.js"></script>-->
diff --git a/chromeos/components/help_app_ui/resources/mock/bootstrap.js b/chromeos/components/help_app_ui/resources/mock/app_bin.js
similarity index 100%
rename from chromeos/components/help_app_ui/resources/mock/bootstrap.js
rename to chromeos/components/help_app_ui/resources/mock/app_bin.js
diff --git a/chromeos/components/help_app_ui/resources/mock/help_app_bundle_mock_resources.grd b/chromeos/components/help_app_ui/resources/mock/help_app_bundle_mock_resources.grd
index b0e18c1..439b5cb 100644
--- a/chromeos/components/help_app_ui/resources/mock/help_app_bundle_mock_resources.grd
+++ b/chromeos/components/help_app_ui/resources/mock/help_app_bundle_mock_resources.grd
@@ -10,7 +10,7 @@
   </outputs>
   <release seq="1">
     <includes>
-      <include name="IDR_HELP_APP_BOOTSTRAP_JS" file="bootstrap.js" type="BINDATA" />
+      <include name="IDR_HELP_APP_APP_BIN_JS" file="app_bin.js" type="BINDATA" />
     </includes>
   </release>
 </grit>
diff --git a/chromeos/geolocation/BUILD.gn b/chromeos/geolocation/BUILD.gn
index acbb141f..f6a62e61 100644
--- a/chromeos/geolocation/BUILD.gn
+++ b/chromeos/geolocation/BUILD.gn
@@ -40,7 +40,5 @@
     "//services/network/public/cpp",
     "//testing/gtest",
   ]
-  sources = [
-    "simple_geolocation_unittest.cc",
-  ]
+  sources = [ "simple_geolocation_unittest.cc" ]
 }
diff --git a/chromeos/timezone/BUILD.gn b/chromeos/timezone/BUILD.gn
index 4a75140..0e91f53b 100644
--- a/chromeos/timezone/BUILD.gn
+++ b/chromeos/timezone/BUILD.gn
@@ -38,7 +38,5 @@
     "//services/network/public/cpp",
     "//testing/gtest",
   ]
-  sources = [
-    "timezone_unittest.cc",
-  ]
+  sources = [ "timezone_unittest.cc" ]
 }
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 011edbe..94b831131 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -519,7 +519,10 @@
       "autofill/content/renderer/password_form_conversion_utils_browsertest.cc",
       "dom_distiller/content/browser/distillable_page_utils_browsertest.cc",
       "dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc",
+      "dom_distiller/content/browser/test/distilled_page_js_browsertest.cc",
       "dom_distiller/content/browser/test/dom_distiller_js_browsertest.cc",
+      "dom_distiller/content/browser/test/test_util.cc",
+      "dom_distiller/content/browser/test/test_util.h",
       "offline_pages/content/renovations/test/page_renovator_browsertest.cc",
       "paint_preview/renderer/paint_preview_recorder_browsertest.cc",
       "security_state/content/content_utils_browsertest.cc",
diff --git a/components/account_id/mojom/BUILD.gn b/components/account_id/mojom/BUILD.gn
index 59aac05..2b76713 100644
--- a/components/account_id/mojom/BUILD.gn
+++ b/components/account_id/mojom/BUILD.gn
@@ -5,7 +5,5 @@
 import("//mojo/public/tools/bindings/mojom.gni")
 
 mojom("mojom") {
-  sources = [
-    "account_id.mojom",
-  ]
+  sources = [ "account_id.mojom" ]
 }
diff --git a/components/autofill/ios/form_util/resources/fill.js b/components/autofill/ios/form_util/resources/fill.js
index e604923..ebb7074c 100644
--- a/components/autofill/ios/form_util/resources/fill.js
+++ b/components/autofill/ios/form_util/resources/fill.js
@@ -280,7 +280,7 @@
 
   if (overrideProperty) {
     const newProperty = {
-      get: function() {
+      get() {
         if (setterCalled && oldPropertyDescriptor.get) {
           return oldPropertyDescriptor.get.call(input);
         }
diff --git a/components/cronet/stale_host_resolver.cc b/components/cronet/stale_host_resolver.cc
index 58fb0c8..5097042 100644
--- a/components/cronet/stale_host_resolver.cc
+++ b/components/cronet/stale_host_resolver.cc
@@ -216,10 +216,8 @@
   cache_parameters.source = net::HostResolverSource::LOCAL_ONLY;
   cache_request_ = resolver_->inner_resolver_->CreateRequest(
       host_, network_isolation_key_, net_log_, cache_parameters);
-  int error =
+  cache_error_ =
       cache_request_->Start(base::BindOnce([](int error) { NOTREACHED(); }));
-  DCHECK_NE(net::ERR_IO_PENDING, error);
-  cache_error_ = cache_request_->GetResolveErrorInfo().error;
   DCHECK_NE(net::ERR_IO_PENDING, cache_error_);
   // If it's a fresh cache hit (or literal), return it synchronously.
   if (cache_error_ != net::ERR_DNS_CACHE_MISS &&
diff --git a/components/dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc b/components/dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc
index 96c3126..8505931 100644
--- a/components/dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc
+++ b/components/dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
+#include "components/dom_distiller/content/browser/test/test_util.h"
 #include "components/dom_distiller/core/distiller_page.h"
 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
 #include "components/dom_distiller/core/proto/distilled_page.pb.h"
@@ -83,24 +84,6 @@
 
 const char* kSimpleArticlePath = "/simple_article.html";
 const char* kVideoArticlePath = "/video_article.html";
-const char* kDistilledPagePath = "/distilled_page.html";
-
-void ExecuteJsScript(content::WebContents* web_contents,
-                     const std::string& script) {
-  base::Value result;
-  base::RunLoop run_loop;
-  web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
-      base::UTF8ToUTF16(script),
-      base::BindOnce(
-          [](base::OnceClosure callback, base::Value* out, base::Value result) {
-            (*out) = std::move(result);
-            std::move(callback).Run();
-          },
-          run_loop.QuitClosure(), &result));
-  run_loop.Run();
-  ASSERT_EQ(base::Value::Type::BOOLEAN, result.type());
-  EXPECT_TRUE(result.GetBool());
-}
 
 class DistillerPageWebContentsTest : public ContentBrowserTest {
  public:
@@ -110,7 +93,7 @@
       SetDistillerJavaScriptWorldId(content::ISOLATED_WORLD_ID_CONTENT_END);
     }
     AddComponentsResources();
-    SetUpTestServer();
+    SetUpTestServer(embedded_test_server());
     ContentBrowserTest::SetUpOnMainThread();
   }
 
@@ -131,37 +114,6 @@
     std::move(quit_closure).Run();
   }
 
- private:
-  void AddComponentsResources() {
-    base::FilePath pak_file;
-    base::FilePath pak_dir;
-#if defined(OS_ANDROID)
-    CHECK(base::PathService::Get(base::DIR_ANDROID_APP_DATA, &pak_dir));
-    pak_dir = pak_dir.Append(FILE_PATH_LITERAL("paks"));
-#else
-    base::PathService::Get(base::DIR_MODULE, &pak_dir);
-#endif  // OS_ANDROID
-    pak_file =
-        pak_dir.Append(FILE_PATH_LITERAL("components_tests_resources.pak"));
-    ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
-        pak_file, ui::SCALE_FACTOR_NONE);
-  }
-
-  void SetUpTestServer() {
-    base::FilePath path;
-    base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
-
-    embedded_test_server()->ServeFilesFromDirectory(
-        path.AppendASCII("components/test/data/dom_distiller"));
-    embedded_test_server()->ServeFilesFromDirectory(
-        path.AppendASCII("components/dom_distiller/core/javascript"));
-
-    response_ = std::make_unique<net::test_server::ControllableHttpResponse>(
-        embedded_test_server(), kDistilledPagePath);
-
-    ASSERT_TRUE(embedded_test_server()->Start());
-  }
-
  protected:
   void RunUseCurrentWebContentsTest(const std::string& url,
                                     bool expect_new_web_contents,
@@ -169,8 +121,6 @@
 
   DistillerPageWebContents* distiller_page_;
   std::unique_ptr<proto::DomDistillerResult> distiller_result_;
-
-  std::unique_ptr<net::test_server::ControllableHttpResponse> response_;
 };
 
 // Use this class to be able to leak the WebContents, which is needed for when
@@ -534,36 +484,4 @@
   }
 }
 
-#if defined(OS_WIN)
-#define MAYBE_TestPinch DISABLED_TestPinch
-#else
-#define MAYBE_TestPinch TestPinch
-#endif
-IN_PROC_BROWSER_TEST_F(DistillerPageWebContentsTest, MAYBE_TestPinch) {
-  // Load the test file in content shell and wait until it has fully loaded.
-  content::WebContents* web_contents = shell()->web_contents();
-  base::RunLoop url_loaded_runner;
-  WebContentsMainFrameHelper main_frame_loaded(
-      web_contents, url_loaded_runner.QuitClosure(), true);
-  web_contents->GetController().LoadURL(
-      embedded_test_server()->GetURL(kDistilledPagePath), content::Referrer(),
-      ui::PAGE_TRANSITION_TYPED, std::string());
-
-  const std::string html_template = viewer::GetArticleTemplateHtml(
-      DistilledPagePrefs::THEME_LIGHT,
-      DistilledPagePrefs::FONT_FAMILY_SANS_SERIF);
-
-  const std::string scripts = R"(
-    <script src='dom_distiller_viewer.js'></script>
-    <script src='pinch_tester.js'></script>
-  )";
-
-  response_->WaitForRequest();
-  response_->Send(net::HTTP_OK, "text/html", html_template + scripts);
-  response_->Done();
-  url_loaded_runner.Run();
-
-  ExecuteJsScript(web_contents, "pinchtest.run()");
-}
-
 }  // namespace dom_distiller
diff --git a/components/dom_distiller/content/browser/test/distilled_page_js_browsertest.cc b/components/dom_distiller/content/browser/test/distilled_page_js_browsertest.cc
new file mode 100644
index 0000000..082b3db
--- /dev/null
+++ b/components/dom_distiller/content/browser/test/distilled_page_js_browsertest.cc
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/path_service.h"
+#include "base/strings/strcat.h"
+#include "build/build_config.h"
+#include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
+#include "components/dom_distiller/content/browser/test/test_util.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/shell/browser/shell.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/resource/scale_factor.h"
+
+namespace dom_distiller {
+namespace {
+
+base::Value ExecuteJsScript(content::WebContents* web_contents,
+                            const std::string& script) {
+  base::Value result;
+  base::RunLoop run_loop;
+  web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+      base::UTF8ToUTF16(script),
+      base::BindOnce(
+          [](base::Closure callback, base::Value* out, base::Value result) {
+            (*out) = std::move(result);
+            callback.Run();
+          },
+          run_loop.QuitClosure(), &result));
+  run_loop.Run();
+  return result;
+}
+
+class DistilledPageJsTest : public content::ContentBrowserTest {
+ protected:
+  explicit DistilledPageJsTest()
+      : content::ContentBrowserTest(), distilled_page_(nullptr) {}
+  ~DistilledPageJsTest() override = default;
+
+  void SetUpOnMainThread() override {
+    if (!DistillerJavaScriptWorldIdIsSet()) {
+      SetDistillerJavaScriptWorldId(content::ISOLATED_WORLD_ID_CONTENT_END);
+    }
+
+    AddComponentsResources();
+    distilled_page_ = SetUpTestServerWithDistilledPage(embedded_test_server());
+  }
+
+  void LoadAndExecuteTestScript(const std::string& file,
+                                const std::string& fixture_name) {
+    distilled_page_->AppendScriptFile(file);
+    distilled_page_->Load(embedded_test_server(), shell()->web_contents());
+    const base::Value result = ExecuteJsScript(
+        shell()->web_contents(), base::StrCat({fixture_name, ".run()"}));
+    ASSERT_EQ(base::Value::Type::BOOLEAN, result.type());
+    EXPECT_TRUE(result.GetBool());
+  }
+
+  std::unique_ptr<FakeDistilledPage> distilled_page_;
+};
+
+#if defined(OS_WIN)
+#define MAYBE_Pinch DISABLED_Pinch
+#else
+#define MAYBE_Pinch Pinch
+#endif
+IN_PROC_BROWSER_TEST_F(DistilledPageJsTest, MAYBE_Pinch) {
+  LoadAndExecuteTestScript("pinch_tester.js", "pinchtest");
+}
+
+}  // namespace
+}  // namespace dom_distiller
diff --git a/components/dom_distiller/content/browser/test/test_util.cc b/components/dom_distiller/content/browser/test/test_util.cc
new file mode 100644
index 0000000..236661f
--- /dev/null
+++ b/components/dom_distiller/content/browser/test/test_util.cc
@@ -0,0 +1,121 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/dom_distiller/content/browser/test/test_util.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/strcat.h"
+#include "build/build_config.h"
+#include "components/dom_distiller/core/viewer.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/resource/scale_factor.h"
+
+namespace dom_distiller {
+namespace {
+
+using base::FilePath;
+using base::PathService;
+using base::StrAppend;
+using content::JsReplace;
+using content::Referrer;
+using content::WaitForLoadStop;
+using content::WebContents;
+using net::test_server::ControllableHttpResponse;
+using net::test_server::EmbeddedTestServer;
+using viewer::GetArticleTemplateHtml;
+
+// The path of the distilled page URL relative to the EmbeddedTestServer's base
+// directory. This file's contents are generated at test runtime; it is not a
+// real file in the repository.
+const char* kDistilledPagePath = "/distilled_page.html";
+
+void SetUpTestServerWithoutStarting(EmbeddedTestServer* server) {
+  FilePath root_dir;
+  PathService::Get(base::DIR_SOURCE_ROOT, &root_dir);
+  server->ServeFilesFromDirectory(
+      root_dir.AppendASCII("components/test/data/dom_distiller"));
+  server->ServeFilesFromDirectory(
+      root_dir.AppendASCII("components/dom_distiller/core/javascript"));
+}
+
+}  // namespace
+
+FakeDistilledPage::FakeDistilledPage(EmbeddedTestServer* server)
+    : response_(
+          std::make_unique<ControllableHttpResponse>(server,
+                                                     kDistilledPagePath)) {
+  CHECK(!server->Started());
+
+  // The distilled page HTML does not contain a script element loading this
+  // file. On a real distilled page, this file is executed by
+  // DomDistillerRequestViewBase::SendCommonJavaScript(); however, this method
+  // is impractical to use in testing.
+  AppendScriptFile("dom_distiller_viewer.js");
+}
+
+FakeDistilledPage::~FakeDistilledPage() = default;
+
+void FakeDistilledPage::AppendScriptFile(const std::string& script_file) {
+  scripts_.push_back(script_file);
+}
+
+void FakeDistilledPage::Load(EmbeddedTestServer* server,
+                             WebContents* web_contents) {
+  web_contents->GetController().LoadURL(server->GetURL(kDistilledPagePath),
+                                        Referrer(), ui::PAGE_TRANSITION_TYPED,
+                                        std::string());
+  response_->WaitForRequest();
+  response_->Send(net::HTTP_OK, "text/html", GetPageHtmlWithScripts());
+  response_->Done();
+  ASSERT_TRUE(WaitForLoadStop(web_contents));
+}
+
+std::string FakeDistilledPage::GetPageHtmlWithScripts() {
+  std::string html =
+      GetArticleTemplateHtml(DistilledPagePrefs::THEME_LIGHT,
+                             DistilledPagePrefs::FONT_FAMILY_SANS_SERIF);
+  for (const std::string& file : scripts_) {
+    StrAppend(&html, {JsReplace("<script src=$1></script>", file)});
+  }
+  return html;
+}
+
+void SetUpTestServer(EmbeddedTestServer* server) {
+  SetUpTestServerWithoutStarting(server);
+  ASSERT_TRUE(server->Start());
+}
+
+std::unique_ptr<FakeDistilledPage> SetUpTestServerWithDistilledPage(
+    EmbeddedTestServer* server) {
+  SetUpTestServerWithoutStarting(server);
+  auto distilled_page = std::make_unique<FakeDistilledPage>(server);
+
+  // CHECKs for server start instead of ASSERTs because ASSERT/EXPECT macros
+  // only work in functions with a return type of void.
+  CHECK(server->Start());
+  return distilled_page;
+}
+
+void AddComponentsResources() {
+  FilePath pak_file;
+  FilePath pak_dir;
+#if defined(OS_ANDROID)
+  CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &pak_dir));
+  pak_dir = pak_dir.Append(FILE_PATH_LITERAL("paks"));
+#else
+  PathService::Get(base::DIR_MODULE, &pak_dir);
+#endif  // OS_ANDROID
+  pak_file =
+      pak_dir.Append(FILE_PATH_LITERAL("components_tests_resources.pak"));
+  ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
+      pak_file, ui::SCALE_FACTOR_NONE);
+}
+
+}  // namespace dom_distiller
diff --git a/components/dom_distiller/content/browser/test/test_util.h b/components/dom_distiller/content/browser/test/test_util.h
new file mode 100644
index 0000000..f2f2ebd
--- /dev/null
+++ b/components/dom_distiller/content/browser/test/test_util.h
@@ -0,0 +1,75 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_TEST_TEST_UTIL_H_
+#define COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_TEST_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace net {
+namespace test_server {
+class EmbeddedTestServer;
+class ControllableHttpResponse;
+}  // namespace test_server
+}  // namespace net
+
+namespace content {
+class WebContents;
+}
+
+namespace dom_distiller {
+
+// Wrapper for building and loading a fake distilled page for testing.
+//
+// This object MUST be constructed prior to starting |server|. This is enforced
+// via CHECK.
+class FakeDistilledPage {
+ public:
+  explicit FakeDistilledPage(net::test_server::EmbeddedTestServer* server);
+  ~FakeDistilledPage();
+
+  // Adds |script_file| to the list of scripts to load along with the page HTML.
+  //
+  // Scripts are loaded by appending script elements with src=|script_file| to
+  // the end of the HTML template. This function must be called before Load() to
+  // have an effect.
+  void AppendScriptFile(const std::string& script_file);
+
+  // Generates the distilled page HTML and loads it to |web_contents|.
+  void Load(net::test_server::EmbeddedTestServer* server,
+            content::WebContents* web_contents);
+
+  // Disallow copy and assign because it's not obvious how these operations
+  // should handle |response_|.
+  FakeDistilledPage(const FakeDistilledPage&) = delete;
+  FakeDistilledPage& operator=(const FakeDistilledPage&) = delete;
+
+ private:
+  // Generates the distilled page's HTML and appends script elements for scripts
+  // added via AppendScriptFile().
+  std::string GetPageHtmlWithScripts();
+
+  std::unique_ptr<net::test_server::ControllableHttpResponse> response_;
+  std::vector<const std::string> scripts_;
+};
+
+// Starts |server| after initializing it to load files from the following
+// directories:
+//   * components/test/data/dom_distiller
+//   * components/dom_distiller/core/javascript
+void SetUpTestServer(net::test_server::EmbeddedTestServer* server);
+
+// Same as SetUpTestServer(), but allows the server to load a distilled page
+// generated at runtime without going through the full distillation process.
+std::unique_ptr<FakeDistilledPage> SetUpTestServerWithDistilledPage(
+    net::test_server::EmbeddedTestServer* server);
+
+// Sets the path to the .pak file to use when loading resources.
+void AddComponentsResources();
+
+}  // namespace dom_distiller
+
+#endif  // COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_TEST_TEST_UTIL_H_
diff --git a/components/flags_ui/resources/flags.js b/components/flags_ui/resources/flags.js
index 52595c3..fcca6b3 100644
--- a/components/flags_ui/resources/flags.js
+++ b/components/flags_ui/resources/flags.js
@@ -428,7 +428,7 @@
    * Initialises the in page search. Adding searchbox listeners and
    * collates the text elements used for string matching.
    */
-  init: function() {
+  init() {
     this.experiments_.link = /** @type {!NodeList<!HTMLElement>} */ (
         document.querySelectorAll('#tab-content-available .permalink'));
     this.experiments_.title = /** @type {!NodeList<!HTMLElement>} */ (
@@ -473,7 +473,7 @@
   /**
    * Clears a search showing all experiments.
    */
-  clearSearch: function() {
+  clearSearch() {
     this.searchBox_.value = '';
     this.doSearch();
   },
@@ -483,7 +483,7 @@
    * @param {HTMLElement} el The element to remove all highlighted mark up on.
    * @param {string} text Text to reset the element's textContent to.
    */
-  resetHighlights: function(el, text) {
+  resetHighlights(el, text) {
     if (el.children) {
       el.textContent = text;
     }
@@ -495,7 +495,7 @@
    * @param {HTMLElement} el The node containing the text to match against.
    * @return {boolean} Whether there was a match.
    */
-  highlightMatchInElement: function(searchTerm, el) {
+  highlightMatchInElement(searchTerm, el) {
     // Experiment container.
     const parentEl = el.parentNode.parentNode.parentNode;
     const text = el.textContent;
@@ -543,7 +543,7 @@
    * @param {string} searchTerm
    * @return {number} The number of matches found.
    */
-  highlightAllMatches: function(searchContent, searchTerm) {
+  highlightAllMatches(searchContent, searchTerm) {
     let matches = 0;
     for (let i = 0, j = searchContent.link.length; i < j; i++) {
       if (this.highlightMatchInElement(searchTerm, searchContent.title[i])) {
@@ -580,7 +580,7 @@
   /**
    * Performs a search against the experiment title, description, permalink.
    */
-  doSearch: function() {
+  doSearch() {
     const searchTerm = this.searchBox_.value.trim().toLowerCase();
 
     if (searchTerm || searchTerm == '') {
@@ -600,7 +600,7 @@
     this.searchIntervalId_ = null;
   },
 
-  announceSearchResults: function() {
+  announceSearchResults() {
     const searchTerm = this.searchBox_.value.trim().toLowerCase();
     if (!searchTerm) {
       return;
@@ -628,7 +628,7 @@
    * Debounces the search to improve performance and prevent too many searches
    * from being initiated.
    */
-  debounceSearch: function() {
+  debounceSearch() {
     if (this.searchIntervalId_) {
       clearTimeout(this.searchIntervalId_);
     }
diff --git a/components/net_log/resources/net_export.js b/components/net_log/resources/net_export.js
index a4aa47b..19b7d43 100644
--- a/components/net_log/resources/net_export.js
+++ b/components/net_log/resources/net_export.js
@@ -56,7 +56,7 @@
     /**
      * Starts saving NetLog data to a file.
      */
-    onStartLogging_: function() {
+    onStartLogging_() {
       // Determine the capture mode to use.
       const logMode =
           document.querySelector('input[name="log-mode"]:checked').value;
@@ -77,14 +77,14 @@
     /**
      * Stops saving NetLog data to a file.
      */
-    onStopLogging_: function() {
+    onStopLogging_() {
       chrome.send('stopNetLog');
     },
 
     /**
      * Sends NetLog data via email from browser (mobile only).
      */
-    onSendEmail_: function() {
+    onSendEmail_() {
       chrome.send('sendNetLog');
     },
 
@@ -92,14 +92,14 @@
      * Reveals the log file in the shell (i.e. selects it in the Finder on
      * Mac).
      */
-    onShowFile_: function() {
+    onShowFile_() {
       chrome.send('showFile');
     },
 
     /**
      * Transitions back to the "Start logging to disk" state.
      */
-    onStartOver_: function() {
+    onStartOver_() {
       this.infoForLoggedFile_ = null;
       this.renderInitial_();
     },
@@ -140,7 +140,7 @@
      *        finalized. Once the state transitions to NOT_LOGGING then the log
      *        is complete, and can safely be copied/emailed.
      */
-    onExportNetLogInfoChanged: function(info) {
+    onExportNetLogInfoChanged(info) {
       switch (info.state) {
         case 'UNINITIALIZED':
         case 'INITIALIZING':
@@ -190,7 +190,7 @@
      * visible for a short period of time, or longer if initialization failed
      * (and didn't transition to a different state).
      */
-    renderUninitialized_: function() {
+    renderUninitialized_() {
       this.showStateDiv_(kIdStateDivUninitialized);
     },
 
@@ -199,7 +199,7 @@
      * logging has not been started yet, and there are controls to start
      * logging.
      */
-    renderInitial_: function() {
+    renderInitial_() {
       this.showStateDiv_(kIdStateDivInitial);
       $(kIdStartLoggingButton).onclick = this.onStartLogging_.bind(this);
     },
@@ -208,7 +208,7 @@
      * Updates the UI to display the "logging" state. This is the state while
      * capturing is in progress and being written to disk.
      */
-    renderLogging_: function(info) {
+    renderLogging_(info) {
       this.showStateDiv_(kIdStateDivLogging);
 
       $(kIdStopLoggingButton).onclick = this.onStopLogging_.bind(this);
@@ -219,7 +219,7 @@
     /*
      * Updates the UI to display the state when logging has stopped.
      */
-    renderStoppedLogging_: function(info) {
+    renderStoppedLogging_(info) {
       this.showStateDiv_(kIdStateDivStopped);
 
       // The email button is only available in the mobile UI.
@@ -250,7 +250,7 @@
     /**
      * Gets the textual label for a capture mode from the HTML.
      */
-    getCaptureModeText_: function(info) {
+    getCaptureModeText_(info) {
       // TODO(eroman): Should not hardcode "Unknown" (will not work properly if
       //               the HTML is internationalized).
       if (!info.logCaptureModeKnown) {
@@ -265,17 +265,17 @@
       return radioButton.parentElement.textContent;
     },
 
-    showPrivacyReadMore_: function(show) {
+    showPrivacyReadMore_(show) {
       $(kIdPrivacyReadMoreDiv).hidden = !show;
       $(kIdPrivacyReadMoreLink).hidden = show;
     },
 
-    showTooBigReadMore_: function(show) {
+    showTooBigReadMore_(show) {
       $(kIdTooBigReadMoreDiv).hidden = !show;
       $(kIdTooBigReadMoreLink).hidden = show;
     },
 
-    showStateDiv_: function(divId) {
+    showStateDiv_(divId) {
       const kAllDivIds = [
         kIdStateDivUninitialized,
         kIdStateDivInitial,
diff --git a/components/neterror/resources/offline.js b/components/neterror/resources/offline.js
index e546b21e..629c873 100644
--- a/components/neterror/resources/offline.js
+++ b/components/neterror/resources/offline.js
@@ -244,14 +244,14 @@
    * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.
    * @return {boolean}
    */
-  isDisabled: function() {
+  isDisabled() {
     return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');
   },
 
   /**
    * For disabled instances, set up a snackbar with the disabled message.
    */
-  setupDisabledRunner: function() {
+  setupDisabledRunner() {
     this.containerEl = document.createElement('div');
     this.containerEl.className = Runner.classes.SNACKBAR;
     this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');
@@ -271,7 +271,7 @@
    * @param {string} setting
    * @param {number|string} value
    */
-  updateConfigSetting: function(setting, value) {
+  updateConfigSetting(setting, value) {
     if (setting in this.config && value != undefined) {
       this.config[setting] = value;
 
@@ -295,7 +295,7 @@
    * Cache the appropriate image sprite from the page and get the sprite sheet
    * definition.
    */
-  loadImages: function() {
+  loadImages() {
     if (IS_HIDPI) {
       Runner.imageSprite = /** @type {HTMLImageElement} */
           (document.getElementById('offline-resources-2x'));
@@ -318,7 +318,7 @@
   /**
    * Load and decode base 64 encoded sounds.
    */
-  loadSounds: function() {
+  loadSounds() {
     if (!IS_IOS) {
       this.audioContext = new AudioContext();
 
@@ -343,7 +343,7 @@
    * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
    * @param {number=} opt_speed
    */
-  setSpeed: function(opt_speed) {
+  setSpeed(opt_speed) {
     const speed = opt_speed || this.currentSpeed;
 
     // Reduce the speed on smaller mobile screens.
@@ -359,7 +359,7 @@
   /**
    * Game initialiser.
    */
-  init: function() {
+  init() {
     // Hide the static icon.
     document.querySelector('.' + Runner.classes.ICON).style.visibility =
         'hidden';
@@ -411,7 +411,7 @@
   /**
    * Create the touch controller. A div that covers whole screen.
    */
-  createTouchController: function() {
+  createTouchController() {
     this.touchController = document.createElement('div');
     this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
     this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
@@ -422,7 +422,7 @@
   /**
    * Debounce the resize event.
    */
-  debounceResize: function() {
+  debounceResize() {
     if (!this.resizeTimerId_) {
       this.resizeTimerId_ =
           setInterval(this.adjustDimensions.bind(this), 250);
@@ -432,7 +432,7 @@
   /**
    * Adjust game space dimensions on resize.
    */
-  adjustDimensions: function() {
+  adjustDimensions() {
     clearInterval(this.resizeTimerId_);
     this.resizeTimerId_ = null;
 
@@ -482,7 +482,7 @@
    * Play the game intro.
    * Canvas container width expands out to the full width.
    */
-  playIntro: function() {
+  playIntro() {
     if (!this.activated && !this.crashed) {
       this.playingIntro = true;
       this.tRex.playingIntro = true;
@@ -511,7 +511,7 @@
   /**
    * Update the game status to started.
    */
-  startGame: function() {
+  startGame() {
     if (this.isArcadeMode()) {
       this.setArcadeMode();
     }
@@ -532,7 +532,7 @@
           this.onVisibilityChange.bind(this));
   },
 
-  clearCanvas: function() {
+  clearCanvas() {
     this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
         this.dimensions.HEIGHT);
   },
@@ -542,7 +542,7 @@
    * through the current scroll position.
    * @return boolean.
    */
-  isCanvasInView: function() {
+  isCanvasInView() {
     return this.containerEl.getBoundingClientRect().top >
         Runner.config.CANVAS_IN_VIEW_OFFSET;
   },
@@ -550,7 +550,7 @@
   /**
    * Update the game frame and schedules the next one.
    */
-  update: function() {
+  update() {
     this.updatePending = false;
 
     const now = getTimeStamp();
@@ -638,7 +638,7 @@
    * Event handler.
    * @param {Event} e
    */
-  handleEvent: function(e) {
+  handleEvent(e) {
     return (function(evtType, events) {
       switch (evtType) {
         case events.KEYDOWN:
@@ -661,7 +661,7 @@
   /**
    * Bind relevant key / mouse / touch listeners.
    */
-  startListening: function() {
+  startListening() {
     // Keys.
     document.addEventListener(Runner.events.KEYDOWN, this);
     document.addEventListener(Runner.events.KEYUP, this);
@@ -680,7 +680,7 @@
   /**
    * Remove all listeners.
    */
-  stopListening: function() {
+  stopListening() {
     document.removeEventListener(Runner.events.KEYDOWN, this);
     document.removeEventListener(Runner.events.KEYUP, this);
 
@@ -702,7 +702,7 @@
    * Process keydown.
    * @param {Event} e
    */
-  onKeyDown: function(e) {
+  onKeyDown(e) {
     // Prevent native page scrolling whilst tapping on mobile.
     if (IS_MOBILE && this.playing) {
       e.preventDefault();
@@ -753,7 +753,7 @@
    * Process key up.
    * @param {Event} e
    */
-  onKeyUp: function(e) {
+  onKeyUp(e) {
     const keyCode = String(e.keyCode);
     const isjumpKey = Runner.keycodes.JUMP[keyCode] ||
        e.type == Runner.events.TOUCHEND ||
@@ -785,7 +785,7 @@
    * Process gamepad connected event.
    * @param {Event} e
    */
-  onGamepadConnected: function(e) {
+  onGamepadConnected(e) {
     if (!this.pollingGamepads) {
       this.pollGamepadState();
     }
@@ -794,7 +794,7 @@
   /**
    * rAF loop for gamepad polling.
    */
-  pollGamepadState: function() {
+  pollGamepadState() {
     const gamepads = navigator.getGamepads();
     this.pollActiveGamepad(gamepads);
 
@@ -807,7 +807,7 @@
    * becomes the "active" gamepad and all others are ignored.
    * @param {!Array<Gamepad>} gamepads
    */
-  pollForActiveGamepad: function(gamepads) {
+  pollForActiveGamepad(gamepads) {
     for (let i = 0; i < gamepads.length; ++i) {
       if (gamepads[i] && gamepads[i].buttons.length > 0 &&
           gamepads[i].buttons[0].pressed) {
@@ -823,7 +823,7 @@
    * to integrate with the rest of the game logic.
    * @param {!Array<Gamepad>} gamepads
    */
-  pollActiveGamepad: function(gamepads) {
+  pollActiveGamepad(gamepads) {
     if (this.gamepadIndex === undefined) {
       this.pollForActiveGamepad(gamepads);
       return;
@@ -855,7 +855,7 @@
    * @param {number} buttonIndex
    * @param {number} keyCode
    */
-  pollGamepadButton: function(gamepad, buttonIndex, keyCode) {
+  pollGamepadButton(gamepad, buttonIndex, keyCode) {
     const state = gamepad.buttons[buttonIndex].pressed;
     let previousState = false;
     if (this.previousGamepad) {
@@ -875,7 +875,7 @@
    * A user is able to tap the high score twice to reset it.
    * @param {Event} e
    */
-  handleGameOverClicks: function(e) {
+  handleGameOverClicks(e) {
     e.preventDefault();
     if (this.distanceMeter.hasClickedOnHighScore(e) && this.highestScore) {
       if (this.distanceMeter.isHighScoreFlashing()) {
@@ -898,7 +898,7 @@
    * @param {Event} e
    * @return {boolean}
    */
-  isLeftClickOnCanvas: function(e) {
+  isLeftClickOnCanvas(e) {
     return e.button != null && e.button < 2 &&
         e.type == Runner.events.POINTERUP && e.target == this.canvas;
   },
@@ -906,7 +906,7 @@
   /**
    * RequestAnimationFrame wrapper.
    */
-  scheduleNextUpdate: function() {
+  scheduleNextUpdate() {
     if (!this.updatePending) {
       this.updatePending = true;
       this.raqId = requestAnimationFrame(this.update.bind(this));
@@ -917,7 +917,7 @@
    * Whether the game is running.
    * @return {boolean}
    */
-  isRunning: function() {
+  isRunning() {
     return !!this.raqId;
   },
 
@@ -925,7 +925,7 @@
    * Set the initial high score as stored in the user's profile.
    * @param {number} highScore
    */
-  initializeHighScore: function(highScore) {
+  initializeHighScore(highScore) {
     this.syncHighestScore = true;
     highScore = Math.ceil(highScore);
     if (highScore < this.highestScore) {
@@ -943,7 +943,7 @@
    * @param {number} distanceRan Total distance ran.
    * @param {boolean=} opt_resetScore Whether to reset the score.
    */
-  saveHighScore: function(distanceRan, opt_resetScore) {
+  saveHighScore(distanceRan, opt_resetScore) {
     this.highestScore = Math.ceil(distanceRan);
     this.distanceMeter.setHighScore(this.highestScore);
 
@@ -960,7 +960,7 @@
   /**
    * Game over state.
    */
-  gameOver: function() {
+  gameOver() {
     this.playSound(this.soundFx.HIT);
     vibrate(200);
 
@@ -990,14 +990,14 @@
     this.time = getTimeStamp();
   },
 
-  stop: function() {
+  stop() {
     this.setPlayStatus(false);
     this.paused = true;
     cancelAnimationFrame(this.raqId);
     this.raqId = 0;
   },
 
-  play: function() {
+  play() {
     if (!this.crashed) {
       this.setPlayStatus(true);
       this.paused = false;
@@ -1007,7 +1007,7 @@
     }
   },
 
-  restart: function() {
+  restart() {
     if (!this.raqId) {
       this.playCount++;
       this.runningTime = 0;
@@ -1029,7 +1029,7 @@
     }
   },
 
-  setPlayStatus: function(isPlaying) {
+  setPlayStatus(isPlaying) {
     if (this.touchController) {
       this.touchController.classList.toggle(HIDDEN_CLASS, !isPlaying);
     }
@@ -1040,14 +1040,14 @@
    * Whether the game should go into arcade mode.
    * @return {boolean}
    */
-  isArcadeMode: function() {
+  isArcadeMode() {
     return document.title == ARCADE_MODE_URL;
   },
 
   /**
    * Hides offline messaging for a fullscreen game only experience.
    */
-  setArcadeMode: function() {
+  setArcadeMode() {
     document.body.classList.add(Runner.classes.ARCADE_MODE);
     this.setArcadeModeContainerScale();
   },
@@ -1055,7 +1055,7 @@
   /**
    * Sets the scaling for arcade mode.
    */
-  setArcadeModeContainerScale: function() {
+  setArcadeModeContainerScale() {
     const windowHeight = window.innerHeight;
     const scaleHeight = windowHeight / this.dimensions.HEIGHT;
     const scaleWidth = window.innerWidth / this.dimensions.WIDTH;
@@ -1074,7 +1074,7 @@
   /**
    * Pause the game if the tab is not in focus.
    */
-  onVisibilityChange: function(e) {
+  onVisibilityChange(e) {
     if (document.hidden || document.webkitHidden || e.type == 'blur' ||
       document.visibilityState != 'visible') {
       this.stop();
@@ -1088,7 +1088,7 @@
    * Play a sound.
    * @param {AudioBuffer} soundBuffer
    */
-  playSound: function(soundBuffer) {
+  playSound(soundBuffer) {
     if (soundBuffer) {
       const sourceNode = this.audioContext.createBufferSource();
       sourceNode.buffer = soundBuffer;
@@ -1101,7 +1101,7 @@
    * Inverts the current page / canvas colors.
    * @param {boolean} reset Whether to reset colors.
    */
-  invert: function(reset) {
+  invert(reset) {
     const htmlEl = document.firstElementChild;
 
     if (reset) {
@@ -1276,7 +1276,7 @@
    * @param {number} width New canvas width.
    * @param {number} opt_height Optional new canvas height.
    */
-  updateDimensions: function(width, opt_height) {
+  updateDimensions(width, opt_height) {
     this.canvasDimensions.WIDTH = width;
     if (opt_height) {
       this.canvasDimensions.HEIGHT = opt_height;
@@ -1286,7 +1286,7 @@
   /**
    * Draw the panel.
    */
-  draw: function() {
+  draw() {
     const dimensions = GameOverPanel.dimensions;
 
     const centerX = this.canvasDimensions.WIDTH / 2;
@@ -1517,7 +1517,7 @@
  * Maximum obstacle grouping count.
  * @const
  */
-Obstacle.MAX_OBSTACLE_LENGTH = 3,
+Obstacle.MAX_OBSTACLE_LENGTH = 3;
 
 
 Obstacle.prototype = {
@@ -1525,7 +1525,7 @@
    * Initialise the DOM for the obstacle.
    * @param {number} speed
    */
-  init: function(speed) {
+  init(speed) {
     this.cloneCollisionBoxes();
 
     // Only allow sizing if we're at the right speed.
@@ -1536,9 +1536,9 @@
     this.width = this.typeConfig.width * this.size;
 
     // Check if obstacle can be positioned at various heights.
-    if (Array.isArray(this.typeConfig.yPos))  {
-      const yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile :
-          this.typeConfig.yPos;
+    if (Array.isArray(this.typeConfig.yPos)) {
+      const yPosConfig =
+          IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos;
       this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
     } else {
       this.yPos = this.typeConfig.yPos;
@@ -1563,7 +1563,7 @@
     // For obstacles that go at a different speed from the horizon.
     if (this.typeConfig.speedOffset) {
       this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
-          -this.typeConfig.speedOffset;
+                                               -this.typeConfig.speedOffset;
     }
 
     this.gap = this.getGap(this.gapCoefficient, speed);
@@ -1572,7 +1572,7 @@
   /**
    * Draw and crop based on size.
    */
-  draw: function() {
+  draw() {
     let sourceWidth = this.typeConfig.width;
     let sourceHeight = this.typeConfig.height;
 
@@ -1582,19 +1582,18 @@
     }
 
     // X position in sprite.
-    let sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
-        this.spritePos.x;
+    let sourceX =
+        (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x;
 
     // Animation frames.
     if (this.currentFrame > 0) {
       sourceX += sourceWidth * this.currentFrame;
     }
 
-    this.canvasCtx.drawImage(Runner.imageSprite,
-      sourceX, this.spritePos.y,
-      sourceWidth * this.size, sourceHeight,
-      this.xPos, this.yPos,
-      this.typeConfig.width * this.size, this.typeConfig.height);
+    this.canvasCtx.drawImage(
+        Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size,
+        sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size,
+        this.typeConfig.height);
   },
 
   /**
@@ -1602,7 +1601,7 @@
    * @param {number} deltaTime
    * @param {number} speed
    */
-  update: function(deltaTime, speed) {
+  update(deltaTime, speed) {
     if (!this.remove) {
       if (this.typeConfig.speedOffset) {
         speed += this.speedOffset;
@@ -1615,7 +1614,8 @@
         if (this.timer >= this.typeConfig.frameRate) {
           this.currentFrame =
               this.currentFrame == this.typeConfig.numFrames - 1 ?
-              0 : this.currentFrame + 1;
+              0 :
+              this.currentFrame + 1;
           this.timer = 0;
         }
       }
@@ -1634,9 +1634,9 @@
    * @param {number} speed
    * @return {number} The gap size.
    */
-  getGap: function(gapCoefficient, speed) {
-    const minGap = Math.round(this.width * speed +
-          this.typeConfig.minGap * gapCoefficient);
+  getGap(gapCoefficient, speed) {
+    const minGap = Math.round(
+        this.width * speed + this.typeConfig.minGap * gapCoefficient);
     const maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
     return getRandomNum(minGap, maxGap);
   },
@@ -1645,7 +1645,7 @@
    * Check if obstacle is visible.
    * @return {boolean} Whether the obstacle is in the game area.
    */
-  isVisible: function() {
+  isVisible() {
     return this.xPos + this.width > 0;
   },
 
@@ -1653,12 +1653,12 @@
    * Make a copy of the collision boxes, since these will change based on
    * obstacle type and size.
    */
-  cloneCollisionBoxes: function() {
+  cloneCollisionBoxes() {
     const collisionBoxes = this.typeConfig.collisionBoxes;
 
     for (let i = collisionBoxes.length - 1; i >= 0; i--) {
-      this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
-          collisionBoxes[i].y, collisionBoxes[i].width,
+      this.collisionBoxes[i] = new CollisionBox(
+          collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width,
           collisionBoxes[i].height);
     }
   }
@@ -1867,7 +1867,7 @@
    * T-rex player initaliser.
    * Sets the t-rex to blink at random intervals.
    */
-  init: function() {
+  init() {
     this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
         Runner.config.BOTTOM_PAD;
     this.yPos = this.groundYPos;
@@ -1882,7 +1882,7 @@
    * The approriate drop velocity is also set.
    * @param {number} setting
    */
-  setJumpVelocity: function(setting) {
+  setJumpVelocity(setting) {
     this.config.INIITAL_JUMP_VELOCITY = -setting;
     this.config.DROP_VELOCITY = -setting / 2;
   },
@@ -1892,7 +1892,7 @@
    * @param {!number} deltaTime
    * @param {Trex.status=} opt_status Optional status to switch to.
    */
-  update: function(deltaTime, opt_status) {
+  update(deltaTime, opt_status) {
     this.timer += deltaTime;
 
     // Update the status.
@@ -1940,7 +1940,7 @@
    * @param {number} x
    * @param {number} y
    */
-  draw: function(x, y) {
+  draw(x, y) {
     let sourceX = x;
     let sourceY = y;
     let sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
@@ -1982,7 +1982,7 @@
   /**
    * Sets a random time for the blink to happen.
    */
-  setBlinkDelay: function() {
+  setBlinkDelay() {
     this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
   },
 
@@ -1990,7 +1990,7 @@
    * Make t-rex blink at random intervals.
    * @param {number} time Current time in milliseconds.
    */
-  blink: function(time) {
+  blink(time) {
     const deltaTime = time - this.animStartTime;
 
     if (deltaTime >= this.blinkDelay) {
@@ -2009,7 +2009,7 @@
    * Initialise a jump.
    * @param {number} speed
    */
-  startJump: function(speed) {
+  startJump(speed) {
     if (!this.jumping) {
       this.update(0, Trex.status.JUMPING);
       // Tweak the jump velocity based on the speed.
@@ -2023,7 +2023,7 @@
   /**
    * Jump is complete, falling down.
    */
-  endJump: function() {
+  endJump() {
     if (this.reachedMinHeight &&
         this.jumpVelocity < this.config.DROP_VELOCITY) {
       this.jumpVelocity = this.config.DROP_VELOCITY;
@@ -2034,7 +2034,7 @@
    * Update frame for a jump.
    * @param {number} deltaTime
    */
-  updateJump: function(deltaTime) {
+  updateJump(deltaTime) {
     const msPerFrame = Trex.animFrames[this.status].msPerFrame;
     const framesElapsed = deltaTime / msPerFrame;
 
@@ -2068,7 +2068,7 @@
   /**
    * Set the speed drop. Immediately cancels the current jump.
    */
-  setSpeedDrop: function() {
+  setSpeedDrop() {
     this.speedDrop = true;
     this.jumpVelocity = 1;
   },
@@ -2076,7 +2076,7 @@
   /**
    * @param {boolean} isDucking
    */
-  setDuck: function(isDucking) {
+  setDuck(isDucking) {
     if (isDucking && this.status != Trex.status.DUCKING) {
       this.update(0, Trex.status.DUCKING);
       this.ducking = true;
@@ -2089,7 +2089,7 @@
   /**
    * Reset the t-rex to running at start of game.
    */
-  reset: function() {
+  reset() {
     this.xPos = this.xInitialPos;
     this.yPos = this.groundYPos;
     this.jumpVelocity = 0;
@@ -2190,7 +2190,7 @@
    * Initialise the distance meter to '00000'.
    * @param {number} width Canvas width in px.
    */
-  init: function(width) {
+  init(width) {
     let maxDistanceStr = '';
 
     this.calcXPos(width);
@@ -2208,7 +2208,7 @@
    * Calculate the xPos in the canvas.
    * @param {number} canvasWidth
    */
-  calcXPos: function(canvasWidth) {
+  calcXPos(canvasWidth) {
     this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
         (this.maxScoreUnits + 1));
   },
@@ -2219,7 +2219,7 @@
    * @param {number} value Digit value 0-9.
    * @param {boolean=} opt_highScore Whether drawing the high score.
    */
-  draw: function(digitPos, value, opt_highScore) {
+  draw(digitPos, value, opt_highScore) {
     let sourceWidth = DistanceMeter.dimensions.WIDTH;
     let sourceHeight = DistanceMeter.dimensions.HEIGHT;
     let sourceX = DistanceMeter.dimensions.WIDTH * value;
@@ -2265,7 +2265,7 @@
    * @param {number} distance Pixel distance ran.
    * @return {number} The 'real' distance ran.
    */
-  getActualDistance: function(distance) {
+  getActualDistance(distance) {
     return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
   },
 
@@ -2275,7 +2275,7 @@
    * @param {number} deltaTime
    * @return {boolean} Whether the acheivement sound fx should be played.
    */
-  update: function(deltaTime, distance) {
+  update(deltaTime, distance) {
     let paint = true;
     let playSound = false;
 
@@ -2339,7 +2339,7 @@
   /**
    * Draw the high score.
    */
-  drawHighScore: function() {
+  drawHighScore() {
     this.canvasCtx.save();
     this.canvasCtx.globalAlpha = .8;
     for (let i = this.highScore.length - 1; i >= 0; i--) {
@@ -2353,7 +2353,7 @@
    * Position of char in the sprite: H - 10, I - 11.
    * @param {number} distance Distance ran in pixels.
    */
-  setHighScore: function(distance) {
+  setHighScore(distance) {
     distance = this.getActualDistance(distance);
     const highScoreStr = (this.defaultString +
         distance).substr(-this.maxScoreUnits);
@@ -2367,7 +2367,7 @@
    * @param {Event} e Event object.
    * @return {boolean} Whether the click was in the high score bounds.
    */
-  hasClickedOnHighScore: function(e) {
+  hasClickedOnHighScore(e) {
     let x = 0;
     let y = 0;
 
@@ -2392,7 +2392,7 @@
    * Get the bounding box for the high score.
    * @return {Object} Object with x, y, width and height properties.
    */
-  getHighScoreBounds: function() {
+  getHighScoreBounds() {
     return {
       x: (this.x - (this.maxScoreUnits * 2) *
           DistanceMeter.dimensions.WIDTH) -
@@ -2409,7 +2409,7 @@
    * Animate flashing the high score to indicate ready for resetting.
    * The flashing stops following this.config.FLASH_ITERATIONS x 2 flashes.
    */
-  flashHighScore: function() {
+  flashHighScore() {
     const now = getTimeStamp();
     const deltaTime = now - (this.frameTimeStamp || now);
     let paint = true;
@@ -2443,7 +2443,7 @@
   /**
    * Draw empty rectangle over high score.
    */
-  clearHighScoreBounds: function() {
+  clearHighScoreBounds() {
     this.canvasCtx.save();
     this.canvasCtx.fillStyle = '#fff';
     this.canvasCtx.rect(this.highScoreBounds.x, this.highScoreBounds.y,
@@ -2471,7 +2471,7 @@
   /**
    * Stop flashing the high score.
    */
-  cancelHighScoreFlashing: function() {
+  cancelHighScoreFlashing() {
     if (this.flashingRafId) {
       cancelAnimationFrame(this.flashingRafId);
     }
@@ -2485,7 +2485,7 @@
   /**
    * Clear the high score.
    */
-  resetHighScore: function() {
+  resetHighScore() {
     this.setHighScore(0);
     this.cancelHighScoreFlashing();
   },
@@ -2493,7 +2493,7 @@
   /**
    * Reset the distance meter back to '00000'.
    */
-  reset: function() {
+  reset() {
     this.update(0, 0);
     this.achievement = false;
   }
@@ -2544,7 +2544,7 @@
   /**
    * Initialise the cloud. Sets the Cloud height.
    */
-  init: function() {
+  init() {
     this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
         Cloud.config.MIN_SKY_LEVEL);
     this.draw();
@@ -2553,7 +2553,7 @@
   /**
    * Draw the cloud.
    */
-  draw: function() {
+  draw() {
     this.canvasCtx.save();
     let sourceWidth = Cloud.config.WIDTH;
     let sourceHeight = Cloud.config.HEIGHT;
@@ -2577,7 +2577,7 @@
    * Update the cloud position.
    * @param {number} speed
    */
-  update: function(speed) {
+  update(speed) {
     if (!this.remove) {
       this.xPos -= Math.ceil(speed);
       this.draw();
@@ -2593,7 +2593,7 @@
    * Check if the cloud is visible on the stage.
    * @return {boolean}
    */
-  isVisible: function() {
+  isVisible() {
     return this.xPos + Cloud.config.WIDTH > 0;
   }
 };
@@ -2644,7 +2644,7 @@
    * Update moving moon, changing phases.
    * @param {boolean} activated Whether night mode is activated.
    */
-  update: function(activated) {
+  update(activated) {
     // Moon phase.
     if (activated && this.opacity == 0) {
       this.currentPhase++;
@@ -2667,10 +2667,10 @@
 
       // Update stars.
       if (this.drawStars) {
-         for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
-            this.stars[i].x = this.updateXPos(this.stars[i].x,
-                NightMode.config.STAR_SPEED);
-         }
+        for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
+          this.stars[i].x =
+              this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);
+        }
       }
       this.draw();
     } else {
@@ -2680,7 +2680,7 @@
     this.drawStars = true;
   },
 
-  updateXPos: function(currentPos, speed) {
+  updateXPos(currentPos, speed) {
     if (currentPos < -NightMode.config.WIDTH) {
       currentPos = this.containerWidth;
     } else {
@@ -2689,7 +2689,7 @@
     return currentPos;
   },
 
-  draw: function() {
+  draw() {
     let moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 :
          NightMode.config.WIDTH;
     let moonSourceHeight = NightMode.config.HEIGHT;
@@ -2731,7 +2731,7 @@
   },
 
   // Do star placement.
-  placeStars: function() {
+  placeStars() {
     const segmentSize = Math.round(this.containerWidth /
         NightMode.config.NUM_STARS);
 
@@ -2750,7 +2750,7 @@
     }
   },
 
-  reset: function() {
+  reset() {
     this.currentPhase = 0;
     this.opacity = 0;
     this.update(false);
@@ -2801,8 +2801,7 @@
   /**
    * Set the source dimensions of the horizon line.
    */
-  setSourceDimensions: function() {
-
+  setSourceDimensions() {
     for (const dimension in HorizonLine.dimensions) {
       if (IS_HIDPI) {
         if (dimension != 'YPOS') {
@@ -2823,14 +2822,14 @@
   /**
    * Return the crop x position of a type.
    */
-  getRandomType: function() {
+  getRandomType() {
     return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
   },
 
   /**
    * Draw the horizon line.
    */
-  draw: function() {
+  draw() {
     this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
         this.spritePos.y,
         this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
@@ -2849,7 +2848,7 @@
    * @param {number} pos Line position.
    * @param {number} increment
    */
-  updateXPos: function(pos, increment) {
+  updateXPos(pos, increment) {
     const line1 = pos;
     const line2 = pos == 0 ? 1 : 0;
 
@@ -2868,7 +2867,7 @@
    * @param {number} deltaTime
    * @param {number} speed
    */
-  update: function(deltaTime, speed) {
+  update(deltaTime, speed) {
     const increment = Math.floor(speed * (FPS / 1000) * deltaTime);
 
     if (this.xPos[0] <= 0) {
@@ -2882,7 +2881,7 @@
   /**
    * Reset horizon to the starting position.
    */
-  reset: function() {
+  reset() {
     this.xPos[0] = 0;
     this.xPos[1] = HorizonLine.dimensions.WIDTH;
   }
@@ -2940,7 +2939,7 @@
   /**
    * Initialise the horizon. Just add the line and a cloud. No obstacles.
    */
-  init: function() {
+  init() {
     this.addCloud();
     this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON);
     this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
@@ -2955,7 +2954,7 @@
    *     ease in section.
    * @param {boolean} showNightMode Night mode activated.
    */
-  update: function(deltaTime, currentSpeed, updateObstacles, showNightMode) {
+  update(deltaTime, currentSpeed, updateObstacles, showNightMode) {
     this.runningTime += deltaTime;
     this.horizonLine.update(deltaTime, currentSpeed);
     this.nightMode.update(showNightMode);
@@ -2971,7 +2970,7 @@
    * @param {number} deltaTime
    * @param {number} speed
    */
-  updateClouds: function(deltaTime, speed) {
+  updateClouds(deltaTime, speed) {
     const cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
     const numClouds = this.clouds.length;
 
@@ -3003,7 +3002,7 @@
    * @param {number} deltaTime
    * @param {number} currentSpeed
    */
-  updateObstacles: function(deltaTime, currentSpeed) {
+  updateObstacles(deltaTime, currentSpeed) {
     // Obstacles, move to Horizon layer.
     const updatedObstacles = this.obstacles.slice(0);
 
@@ -3034,7 +3033,7 @@
     }
   },
 
-  removeFirstObstacle: function() {
+  removeFirstObstacle() {
     this.obstacles.shift();
   },
 
@@ -3042,7 +3041,7 @@
    * Add a new obstacle.
    * @param {number} currentSpeed
    */
-  addNewObstacle: function(currentSpeed) {
+  addNewObstacle(currentSpeed) {
     const obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
     const obstacleType = Obstacle.types[obstacleTypeIndex];
 
@@ -3071,7 +3070,7 @@
    * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
    * @return {boolean}
    */
-  duplicateObstacleCheck: function(nextObstacleType) {
+  duplicateObstacleCheck(nextObstacleType) {
     let duplicateCount = 0;
 
     for (let i = 0; i < this.obstacleHistory.length; i++) {
@@ -3085,7 +3084,7 @@
    * Reset the horizon layer.
    * Remove existing obstacles and reposition the horizon line.
    */
-  reset: function() {
+  reset() {
     this.obstacles = [];
     this.horizonLine.reset();
     this.nightMode.reset();
@@ -3096,7 +3095,7 @@
    * @param {number} width Canvas width.
    * @param {number} height Canvas height.
    */
-  resize: function(width, height) {
+  resize(width, height) {
     this.canvas.width = width;
     this.canvas.height = height;
   },
@@ -3104,7 +3103,7 @@
   /**
    * Add a new cloud to the horizon.
    */
-  addCloud: function() {
+  addCloud() {
     this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
         this.dimensions.WIDTH));
   }
diff --git a/components/optimization_guide/optimization_guide_switches.cc b/components/optimization_guide/optimization_guide_switches.cc
index 11cb8e84..e323d11 100644
--- a/components/optimization_guide/optimization_guide_switches.cc
+++ b/components/optimization_guide/optimization_guide_switches.cc
@@ -39,7 +39,7 @@
 // Overrides the Optimization Guide Service URL that the HintsFetcher will
 // request remote hints from.
 const char kOptimizationGuideServiceGetHintsURL[] =
-    "optimization-guide-service-get-hosts-url";
+    "optimization-guide-service-get-hints-url";
 
 // Overrides the Optimization Guide Service URL that the PredictionModelFetcher
 // will request remote models and host features from.
diff --git a/components/ownership/BUILD.gn b/components/ownership/BUILD.gn
index 53b02fb..2cf3c64 100644
--- a/components/ownership/BUILD.gn
+++ b/components/ownership/BUILD.gn
@@ -30,17 +30,13 @@
     ]
 
     if (use_nss_certs) {
-      public_deps = [
-        "//crypto:platform",
-      ]
+      public_deps = [ "//crypto:platform" ]
     }
   }
 
   source_set("unit_tests") {
     testonly = true
-    sources = [
-      "owner_key_util_impl_unittest.cc",
-    ]
+    sources = [ "owner_key_util_impl_unittest.cc" ]
 
     configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
 
diff --git a/components/policy/resources/webui/OWNERS b/components/policy/resources/webui/OWNERS
new file mode 100644
index 0000000..bcfe259
--- /dev/null
+++ b/components/policy/resources/webui/OWNERS
@@ -0,0 +1 @@
+file://ui/webui/PLATFORM_OWNERS  # For mechanical refactors.
diff --git a/components/policy/resources/webui/policy_base.js b/components/policy/resources/webui/policy_base.js
index 6c305b2..ecb5e84 100644
--- a/components/policy/resources/webui/policy_base.js
+++ b/components/policy/resources/webui/policy_base.js
@@ -2,63 +2,61 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('policy');
-
-/**
- * @typedef {{
- *    [id: string]: {
- *      name: string,
- *      policyNames: !Array<string>,
- * }}
- */
-policy.PolicyNamesResponse;
-
-/**
- * @typedef {!Array<{
- *  name: string,
- *  id: ?String,
- *  policies: {[name: string]: policy.Policy}
- * }>}
- */
-policy.PolicyValuesResponse;
-
-/**
- * @typedef {{
- *    level: string,
- *    scope: string,
- *    source: string,
- *    value: any,
- * }}
- */
-policy.Conflict;
-
-/**
- * @typedef {{
- *    ignored?: boolean,
- *    name: string,
- *    level: string,
- *    link: ?string,
- *    scope: string,
- *    source: string,
- *    error: string,
- *    value: any,
- *    allSourcesMerged: ?boolean,
- *    conflicts: ?Array<!Conflict>,
- * }}
- */
-policy.Policy;
-
-/**
- * @typedef {{
- *     id: ?string,
- *     name: string,
- *     policies: !Array<!Policy>
- * }}
- */
-policy.PolicyTableModel;
-
 cr.define('policy', function() {
   /**
+   * @typedef {{
+   *    [id: string]: {
+   *      name: string,
+   *      policyNames: !Array<string>,
+   * }}
+   */
+  let PolicyNamesResponse;
+
+  /**
+   * @typedef {!Array<{
+   *  name: string,
+   *  id: ?String,
+   *  policies: {[name: string]: policy.Policy}
+   * }>}
+   */
+  let PolicyValuesResponse;
+
+  /**
+   * @typedef {{
+   *    level: string,
+   *    scope: string,
+   *    source: string,
+   *    value: any,
+   * }}
+   */
+  let Conflict;
+
+  /**
+   * @typedef {{
+   *    ignored?: boolean,
+   *    name: string,
+   *    level: string,
+   *    link: ?string,
+   *    scope: string,
+   *    source: string,
+   *    error: string,
+   *    value: any,
+   *    allSourcesMerged: ?boolean,
+   *    conflicts: ?Array<!Conflict>,
+   * }}
+   */
+  let Policy;
+
+  /**
+   * @typedef {{
+   *     id: ?string,
+   *     name: string,
+   *     policies: !Array<!Policy>
+   * }}
+   */
+  let PolicyTableModel;
+
+  /**
    * A box that shows the status of cloud policy for a device, machine or user.
    * @constructor
    * @extends {HTMLFieldSetElement}
@@ -76,7 +74,7 @@
     /**
      * Initialization function for the cr.ui framework.
      */
-    decorate: function() {},
+    decorate() {},
 
     /**
      * Sets the text of a particular named label element in the status box
@@ -87,7 +85,7 @@
      * @param {boolean=} needsToBeShown True if we want to show the label
      *     False otherwise.
      */
-    setLabelAndShow_: function(labelName, labelValue, needsToBeShown = true) {
+    setLabelAndShow_(labelName, labelValue, needsToBeShown = true) {
       const labelElement = this.querySelector(labelName);
       labelElement.textContent = labelValue || '';
       if (needsToBeShown) {
@@ -100,7 +98,7 @@
      *     "user".
      * @param {Object} status Dictionary with information about the status.
      */
-    initialize: function(scope, status) {
+    initialize(scope, status) {
       const notSpecifiedString = loadTimeData.getString('notSpecified');
       if (scope == 'device') {
         // For device policy, set the appropriate title and populate the topmost
@@ -188,7 +186,7 @@
     // Set up the prototype chain.
     __proto__: HTMLDivElement.prototype,
 
-    decorate: function() {},
+    decorate() {},
 
     /** @param {Conflict} conflict */
     initialize(conflict) {
@@ -204,24 +202,24 @@
   };
 
   /**
-   * A single policy's entry in the policy table.
+   * A single policy's row entry in the policy table.
    * @constructor
    * @extends {HTMLDivElement}
    */
-  const Policy = cr.ui.define(function() {
+  const PolicyRow = cr.ui.define(function() {
     const node = $('policy-template').cloneNode(true);
     node.removeAttribute('id');
     return node;
   });
 
-  Policy.prototype = {
+  PolicyRow.prototype = {
     // Set up the prototype chain.
     __proto__: HTMLDivElement.prototype,
 
     /**
      * Initialization function for the cr.ui framework.
      */
-    decorate: function() {
+    decorate() {
       const toggle = this.querySelector('.policy.row .toggle');
       toggle.addEventListener('click', this.toggleExpanded_.bind(this));
     },
@@ -325,7 +323,7 @@
      * Toggle the visibility of an additional row containing the complete text.
      * @private
      */
-    toggleExpanded_: function() {
+    toggleExpanded_() {
       const warningRowDisplay = this.querySelector('.warnings.row');
       const errorRowDisplay = this.querySelector('.errors.row');
       const valueRowDisplay = this.querySelector('.value.row');
@@ -368,7 +366,7 @@
     /**
      * Initialization function for the cr.ui framework.
      */
-    decorate: function() {
+    decorate() {
       this.policies_ = {};
       this.filterPattern_ = '';
     },
@@ -400,7 +398,7 @@
             return a.value !== undefined ? -1 : 1;
           })
           .forEach(policy => {
-            const policyRow = new Policy;
+            const policyRow = new PolicyRow;
             policyRow.initialize(policy);
             mainContent.appendChild(policyRow);
           });
@@ -413,7 +411,7 @@
      * disabled by setting |pattern| to an empty string.
      * @param {string} pattern The filter pattern.
      */
-    setFilterPattern: function(pattern) {
+    setFilterPattern(pattern) {
       this.filterPattern_ = pattern.toLowerCase();
       this.filter();
     },
@@ -423,7 +421,7 @@
      * shown in the table. Furthermore, policies whose value is not currently
      * set are only shown if the corresponding checkbox is checked.
      */
-    filter: function() {
+    filter() {
       const showUnset = $('show-unset').checked;
       const policies = this.querySelectorAll('.policy-data');
       for (let i = 0; i < policies.length; i++) {
@@ -451,7 +449,7 @@
     /**
      * Main initialization function. Called by the browser on page load.
      */
-    initialize: function() {
+    initialize() {
       cr.ui.FocusOutlineManager.forDocument(document);
 
       this.mainSection = $('main-section');
@@ -545,7 +543,7 @@
      * status.
      * @param {Object} status Dictionary containing the current policy status.
      */
-    setStatus: function(status) {
+    setStatus(status) {
       // Remove any existing status boxes.
       const container = $('status-box-container');
       while (container.firstChild) {
@@ -569,10 +567,19 @@
      * Re-enable the reload policies button when the previous request to reload
      * policies values has completed.
      */
-    reloadPoliciesDone: function() {
+    reloadPoliciesDone() {
       $('reload-policies').disabled = false;
     },
   };
 
-  return {Page: Page, PolicyTable: PolicyTable, Policy: Policy};
+  return {
+    Page,
+    PolicyTable,
+    Policy,
+    PolicyNamesResponse,
+    PolicyValuesResponse,
+    Conflict,
+    PolicyRow,
+    PolicyTableModel
+  };
 });
diff --git a/components/sqlite_proto/BUILD.gn b/components/sqlite_proto/BUILD.gn
new file mode 100644
index 0000000..fdd39ab
--- /dev/null
+++ b/components/sqlite_proto/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("sqlite_proto") {
+  sources = [
+    "loading_predictor_key_value_data.h",
+    "loading_predictor_key_value_table.cc",
+    "loading_predictor_key_value_table.h",
+    "predictor_table_base.cc",
+    "predictor_table_base.h",
+  ]
+  deps = [
+    "//base",
+    "//sql",
+    "//third_party/protobuf:protobuf_lite",
+  ]
+}
diff --git a/components/sqlite_proto/DEPS b/components/sqlite_proto/DEPS
new file mode 100644
index 0000000..262d6969
--- /dev/null
+++ b/components/sqlite_proto/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+sql",
+  "+third_party/protobuf/src/google/protobuf",
+]
diff --git a/components/sqlite_proto/OWNERS b/components/sqlite_proto/OWNERS
new file mode 100644
index 0000000..f8dc556
--- /dev/null
+++ b/components/sqlite_proto/OWNERS
@@ -0,0 +1,4 @@
+# COMPONENT: Internals>Storage
+pasko@chromium.org
+lizeb@chromium.org
+alexilin@chromium.org
diff --git a/components/sqlite_proto/README.md b/components/sqlite_proto/README.md
new file mode 100644
index 0000000..0d439948
--- /dev/null
+++ b/components/sqlite_proto/README.md
@@ -0,0 +1,3 @@
+`//components/sqlite_proto` contains a lightweight SQLite-backed key-value store. It
+features in-memory caching with a configurable timer for passing operations through
+to disk.
diff --git a/chrome/browser/predictors/loading_predictor_key_value_data.h b/components/sqlite_proto/loading_predictor_key_value_data.h
similarity index 84%
rename from chrome/browser/predictors/loading_predictor_key_value_data.h
rename to components/sqlite_proto/loading_predictor_key_value_data.h
index f4cbffe..ae5fb9e6 100644
--- a/chrome/browser/predictors/loading_predictor_key_value_data.h
+++ b/components/sqlite_proto/loading_predictor_key_value_data.h
@@ -2,23 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
-#define CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
+#ifndef COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
+#define COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
 
 #include <algorithm>
 #include <map>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/predictors/loading_predictor_key_value_table.h"
-#include "chrome/browser/predictors/resource_prefetch_predictor_tables.h"
-#include "content/public/browser/browser_thread.h"
+#include "components/sqlite_proto/loading_predictor_key_value_table.h"
+#include "components/sqlite_proto/predictor_table_base.h"
 
 class PredictorsHandler;
 
@@ -30,16 +31,14 @@
 // Compare function to decide which entry should be evicted.
 //
 // InitializeOnDBSequence() must be called on the DB sequence of the
-// ResourcePrefetchPredictorTables. All other methods must be called on UI
-// thread.
+// PredictorTableBase. All other methods must be called on UI thread.
 template <typename T, typename Compare>
 class LoadingPredictorKeyValueData {
  public:
-  LoadingPredictorKeyValueData(
-      scoped_refptr<ResourcePrefetchPredictorTables> tables,
-      LoadingPredictorKeyValueTable<T>* backend,
-      size_t max_size,
-      base::TimeDelta flush_delay);
+  LoadingPredictorKeyValueData(scoped_refptr<PredictorTableBase> tables,
+                               LoadingPredictorKeyValueTable<T>* backend,
+                               size_t max_size,
+                               base::TimeDelta flush_delay);
 
   // Must be called on the DB sequence of the ResourcePrefetchPredictorTables
   // before calling all other methods.
@@ -59,8 +58,9 @@
   // Deletes all entries from the database.
   void DeleteAllData();
 
+  std::map<std::string, T>* DataCacheForTesting() { return data_cache_.get(); }
+
  private:
-  friend class ResourcePrefetchPredictorTest;
   friend class ::PredictorsHandler;
 
   struct EntryCompare : private Compare {
@@ -74,7 +74,7 @@
 
   void FlushDataToDisk();
 
-  scoped_refptr<ResourcePrefetchPredictorTables> tables_;
+  scoped_refptr<PredictorTableBase> tables_;
   LoadingPredictorKeyValueTable<T>* backend_table_;
   std::unique_ptr<std::map<std::string, T>> data_cache_;
   std::unordered_map<std::string, DeferredOperation> deferred_updates_;
@@ -83,21 +83,21 @@
   const size_t max_size_;
   EntryCompare entry_compare_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   DISALLOW_COPY_AND_ASSIGN(LoadingPredictorKeyValueData);
 };
 
 template <typename T, typename Compare>
 LoadingPredictorKeyValueData<T, Compare>::LoadingPredictorKeyValueData(
-    scoped_refptr<ResourcePrefetchPredictorTables> tables,
+    scoped_refptr<PredictorTableBase> tables,
     LoadingPredictorKeyValueTable<T>* backend,
     size_t max_size,
     base::TimeDelta flush_delay)
     : tables_(tables),
       backend_table_(backend),
       flush_delay_(flush_delay),
-      max_size_(max_size) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-}
+      max_size_(max_size) {}
 
 template <typename T, typename Compare>
 void LoadingPredictorKeyValueData<T, Compare>::InitializeOnDBSequence() {
@@ -115,7 +115,7 @@
     keys_to_delete.emplace_back(entry_to_delete->first);
     data_map->erase(entry_to_delete);
   }
-  if (keys_to_delete.size() > 0) {
+  if (!keys_to_delete.empty()) {
     tables_->ExecuteDBTaskOnDBSequence(
         base::BindOnce(&LoadingPredictorKeyValueTable<T>::DeleteData,
                        base::Unretained(backend_table_),
@@ -129,7 +129,7 @@
 bool LoadingPredictorKeyValueData<T, Compare>::TryGetData(
     const std::string& key,
     T* data) const {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(data_cache_);
   auto it = data_cache_->find(key);
   if (it == data_cache_->end())
@@ -144,7 +144,7 @@
 void LoadingPredictorKeyValueData<T, Compare>::UpdateData(
     const std::string& key,
     const T& data) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(data_cache_);
   auto it = data_cache_->find(key);
   if (it == data_cache_->end()) {
@@ -172,7 +172,7 @@
 template <typename T, typename Compare>
 void LoadingPredictorKeyValueData<T, Compare>::DeleteData(
     const std::vector<std::string>& keys) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(data_cache_);
   for (const std::string& key : keys) {
     if (data_cache_->erase(key))
@@ -186,7 +186,7 @@
 
 template <typename T, typename Compare>
 void LoadingPredictorKeyValueData<T, Compare>::DeleteAllData() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(data_cache_);
   data_cache_->clear();
   deferred_updates_.clear();
@@ -200,7 +200,7 @@
 
 template <typename T, typename Compare>
 void LoadingPredictorKeyValueData<T, Compare>::FlushDataToDisk() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (deferred_updates_.empty())
     return;
 
@@ -236,4 +236,4 @@
 
 }  // namespace predictors
 
-#endif  // CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
+#endif  // COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_DATA_H_
diff --git a/chrome/browser/predictors/loading_predictor_key_value_table.cc b/components/sqlite_proto/loading_predictor_key_value_table.cc
similarity index 94%
rename from chrome/browser/predictors/loading_predictor_key_value_table.cc
rename to components/sqlite_proto/loading_predictor_key_value_table.cc
index ab7b5c9..284be6a 100644
--- a/chrome/browser/predictors/loading_predictor_key_value_table.cc
+++ b/components/sqlite_proto/loading_predictor_key_value_table.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/predictors/loading_predictor_key_value_table.h"
+#include "components/sqlite_proto/loading_predictor_key_value_table.h"
 
 #include "base/strings/stringprintf.h"
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
diff --git a/chrome/browser/predictors/loading_predictor_key_value_table.h b/components/sqlite_proto/loading_predictor_key_value_table.h
similarity index 92%
rename from chrome/browser/predictors/loading_predictor_key_value_table.h
rename to components/sqlite_proto/loading_predictor_key_value_table.h
index c5c5939f..8eff3e8 100644
--- a/chrome/browser/predictors/loading_predictor_key_value_table.h
+++ b/components/sqlite_proto/loading_predictor_key_value_table.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
-#define CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
+#ifndef COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
+#define COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
 
 #include <map>
 #include <string>
@@ -38,9 +38,9 @@
 // class doesn't manage the creation and the deletion of the table.
 //
 // All the functions except of the constructor must be called on a DB sequence
-// of the ResourcePrefetchPredictorTables.
+// of the PredictorTableBase.
 // The preferred way to call the methods of this class is passing the method to
-// ResourcePrefetchPredictorTables::ScheduleDBTask().
+// PredictorTableBase::ScheduleDBTask().
 //
 // Example:
 // tables_->ScheduleDBTask(
@@ -120,4 +120,4 @@
 
 }  // namespace predictors
 
-#endif  // CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
+#endif  // COMPONENTS_SQLITE_PROTO_LOADING_PREDICTOR_KEY_VALUE_TABLE_H_
diff --git a/chrome/browser/predictors/predictor_table_base.cc b/components/sqlite_proto/predictor_table_base.cc
similarity index 63%
rename from chrome/browser/predictors/predictor_table_base.cc
rename to components/sqlite_proto/predictor_table_base.cc
index 7c4ce52..5c60926 100644
--- a/chrome/browser/predictors/predictor_table_base.cc
+++ b/components/sqlite_proto/predictor_table_base.cc
@@ -1,30 +1,42 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/predictors/predictor_table_base.h"
+#include "components/sqlite_proto/predictor_table_base.h"
 
 #include <utility>
 
+#include "base/bind.h"
 #include "base/logging.h"
 #include "base/sequenced_task_runner.h"
-#include "content/public/browser/browser_thread.h"
 #include "sql/database.h"
 
-using content::BrowserThread;
-
 namespace predictors {
 
 base::SequencedTaskRunner* PredictorTableBase::GetTaskRunner() {
   return db_task_runner_.get();
 }
 
+void PredictorTableBase::ScheduleDBTask(const base::Location& from_here,
+                                        DBTask task) {
+  GetTaskRunner()->PostTask(
+      from_here, base::BindOnce(&PredictorTableBase::ExecuteDBTaskOnDBSequence,
+                                this, std::move(task)));
+}
+
+void PredictorTableBase::ExecuteDBTaskOnDBSequence(DBTask task) {
+  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
+  if (CantAccessDatabase())
+    return;
+
+  std::move(task).Run(DB());
+}
+
 PredictorTableBase::PredictorTableBase(
     scoped_refptr<base::SequencedTaskRunner> db_task_runner)
     : db_task_runner_(std::move(db_task_runner)), db_(nullptr) {}
 
-PredictorTableBase::~PredictorTableBase() {
-}
+PredictorTableBase::~PredictorTableBase() = default;
 
 void PredictorTableBase::Initialize(sql::Database* db) {
   DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
diff --git a/chrome/browser/predictors/predictor_table_base.h b/components/sqlite_proto/predictor_table_base.h
similarity index 75%
rename from chrome/browser/predictors/predictor_table_base.h
rename to components/sqlite_proto/predictor_table_base.h
index 0bd711c..fc931007 100644
--- a/chrome/browser/predictors/predictor_table_base.h
+++ b/components/sqlite_proto/predictor_table_base.h
@@ -1,17 +1,19 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_PREDICTORS_PREDICTOR_TABLE_BASE_H_
-#define CHROME_BROWSER_PREDICTORS_PREDICTOR_TABLE_BASE_H_
+#ifndef COMPONENTS_SQLITE_PROTO_PREDICTOR_TABLE_BASE_H_
+#define COMPONENTS_SQLITE_PROTO_PREDICTOR_TABLE_BASE_H_
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/atomic_flag.h"
 
 namespace base {
+class Location;
 class SequencedTaskRunner;
-}
+}  // namespace base
 
 namespace sql {
 class Database;
@@ -30,6 +32,12 @@
   // Returns a SequencedTaskRunner that is used to run tasks on the DB sequence.
   base::SequencedTaskRunner* GetTaskRunner();
 
+  typedef base::OnceCallback<void(sql::Database*)> DBTask;
+
+  virtual void ScheduleDBTask(const base::Location& from_here, DBTask task);
+
+  virtual void ExecuteDBTaskOnDBSequence(DBTask task);
+
  protected:
   explicit PredictorTableBase(
       scoped_refptr<base::SequencedTaskRunner> db_task_runner);
@@ -59,4 +67,4 @@
 
 }  // namespace predictors
 
-#endif  // CHROME_BROWSER_PREDICTORS_PREDICTOR_TABLE_BASE_H_
+#endif  // COMPONENTS_SQLITE_PROTO_PREDICTOR_TABLE_BASE_H_
diff --git a/components/sync/driver/data_type_manager_impl.cc b/components/sync/driver/data_type_manager_impl.cc
index 1823657..7684860 100644
--- a/components/sync/driver/data_type_manager_impl.cc
+++ b/components/sync/driver/data_type_manager_impl.cc
@@ -344,8 +344,6 @@
 void DataTypeManagerImpl::OnAllDataTypesReadyForConfigure() {
   DCHECK(!download_started_);
   download_started_ = true;
-  UMA_HISTOGRAM_LONG_TIMES("Sync.USSLoadModelsTime",
-                           base::Time::Now() - last_restart_time_);
   // TODO(pavely): By now some of datatypes in |download_types_queue_| could
   // have failed loading and should be excluded from configuration. I need to
   // adjust |download_types_queue_| for such types.
diff --git a/components/sync/driver/resources/sync_node_browser.js b/components/sync/driver/resources/sync_node_browser.js
index dc8e51e..14d0097 100644
--- a/components/sync/driver/resources/sync_node_browser.js
+++ b/components/sync/driver/resources/sync_node_browser.js
@@ -101,7 +101,7 @@
     /**
      * Finds the children of this node and appends them to the tree.
      */
-    handleExpand_: function(event) {
+    handleExpand_(event) {
       const treeItem = this;
 
       if (treeItem.expanded_) {
@@ -133,13 +133,13 @@
   SyncNodeTree.prototype = {
     __proto__: cr.ui.Tree.prototype,
 
-    decorate: function() {
+    decorate() {
       cr.ui.Tree.prototype.decorate.call(this);
       this.addEventListener('change', this.handleChange_.bind(this));
       this.allNodes = [];
     },
 
-    populate: function(nodes) {
+    populate(nodes) {
       const tree = this;
 
       // We store the full set of nodes in the SyncNodeTree object.
@@ -153,7 +153,7 @@
       });
     },
 
-    handleChange_: function(event) {
+    handleChange_(event) {
       if (this.selectedItem) {
         updateNodeDetailView(this.selectedItem);
       }
@@ -214,7 +214,7 @@
     customSplitter.prototype = {
       __proto__: Splitter.prototype,
 
-      handleSplitterDragEnd: function(e) {
+      handleSplitterDragEnd(e) {
         Splitter.prototype.handleSplitterDragEnd.apply(this, arguments);
         const treeElement = $('sync-node-tree-container');
         const newWidth = parseFloat(treeElement.style.width);
diff --git a/components/sync/engine_impl/sync_manager_impl.cc b/components/sync/engine_impl/sync_manager_impl.cc
index 2e44c0e..1ccf4ca7 100644
--- a/components/sync/engine_impl/sync_manager_impl.cc
+++ b/components/sync/engine_impl/sync_manager_impl.cc
@@ -593,6 +593,7 @@
 void SyncManagerImpl::InvalidateCredentials() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   connection_manager_->SetAccessToken(std::string());
+  scheduler_->OnCredentialsInvalidated();
 }
 
 void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) {
diff --git a/components/sync/engine_impl/sync_scheduler.h b/components/sync/engine_impl/sync_scheduler.h
index a510020..3fc75d7 100644
--- a/components/sync/engine_impl/sync_scheduler.h
+++ b/components/sync/engine_impl/sync_scheduler.h
@@ -125,6 +125,12 @@
   // Called when credentials are updated by the user.
   virtual void OnCredentialsUpdated() = 0;
 
+  // Called when credentials are cleared.
+  // TODO(crbug.com/1041871): this function used only for temporary UMA
+  // metrics. Clean it up once Nigori metrics descrepancy investigation
+  // completed.
+  virtual void OnCredentialsInvalidated() = 0;
+
   // Called when the network layer detects a connection status change.
   virtual void OnConnectionStatusChange(
       network::mojom::ConnectionType type) = 0;
diff --git a/components/sync/engine_impl/sync_scheduler_impl.cc b/components/sync/engine_impl/sync_scheduler_impl.cc
index 5b03fd2a..97b4e2a 100644
--- a/components/sync/engine_impl/sync_scheduler_impl.cc
+++ b/components/sync/engine_impl/sync_scheduler_impl.cc
@@ -158,6 +158,16 @@
   }
 }
 
+void SyncSchedulerImpl::OnCredentialsInvalidated() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!nigori_configuration_with_invalidated_credentials_recorded &&
+      IsNigoriOnlyConfiguration(pending_configure_params_.get())) {
+    UMA_HISTOGRAM_BOOLEAN("Sync.NigoriConfigurationWithInvalidatedCredentials",
+                          true);
+    nigori_configuration_with_invalidated_credentials_recorded = true;
+  }
+}
+
 void SyncSchedulerImpl::OnConnectionStatusChange(
     network::mojom::ConnectionType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/sync/engine_impl/sync_scheduler_impl.h b/components/sync/engine_impl/sync_scheduler_impl.h
index d0517de..37124f9 100644
--- a/components/sync/engine_impl/sync_scheduler_impl.h
+++ b/components/sync/engine_impl/sync_scheduler_impl.h
@@ -61,6 +61,7 @@
   void SetNotificationsEnabled(bool notifications_enabled) override;
 
   void OnCredentialsUpdated() override;
+  void OnCredentialsInvalidated() override;
   void OnConnectionStatusChange(network::mojom::ConnectionType type) override;
 
   // SyncCycle::Delegate implementation.
@@ -283,10 +284,12 @@
   // Used to prevent changing nudge delays by the server in integration tests.
   bool force_short_nudge_delay_for_test_ = false;
 
-  // Indicates whether HasInvalidAccessTokenWhenNigoriOnlyConfigurationFailed*
-  // histograms already recorded.
+  // Indicates whether corresponding histograms already recorded.
+  // TODO(crbug.com/1041871): remove these members once Nigori metrics
+  // descrepancy investigation completed.
   bool nigori_configuration_failed_recorded = false;
   bool nigori_configuration_failed_with_5s_backoff_recorded = false;
+  bool nigori_configuration_with_invalidated_credentials_recorded = false;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/components/sync/test/engine/fake_sync_scheduler.cc b/components/sync/test/engine/fake_sync_scheduler.cc
index fc5ca2a1..e867113 100644
--- a/components/sync/test/engine/fake_sync_scheduler.cc
+++ b/components/sync/test/engine/fake_sync_scheduler.cc
@@ -39,6 +39,8 @@
 
 void FakeSyncScheduler::OnCredentialsUpdated() {}
 
+void FakeSyncScheduler::OnCredentialsInvalidated() {}
+
 void FakeSyncScheduler::OnConnectionStatusChange(
     network::mojom::ConnectionType type) {}
 
diff --git a/components/sync/test/engine/fake_sync_scheduler.h b/components/sync/test/engine/fake_sync_scheduler.h
index 8b12047..3df94aeb 100644
--- a/components/sync/test/engine/fake_sync_scheduler.h
+++ b/components/sync/test/engine/fake_sync_scheduler.h
@@ -37,6 +37,7 @@
   void SetNotificationsEnabled(bool notifications_enabled) override;
 
   void OnCredentialsUpdated() override;
+  void OnCredentialsInvalidated() override;
   void OnConnectionStatusChange(network::mojom::ConnectionType type) override;
 
   // SyncCycle::Delegate implementation.
diff --git a/components/translate/core/browser/resources/translate.js b/components/translate/core/browser/resources/translate.js
index 3401c33..5dd9c5a 100644
--- a/components/translate/core/browser/resources/translate.js
+++ b/components/translate/core/browser/resources/translate.js
@@ -314,7 +314,7 @@
      * @return {boolean} False if the translate library was not ready, in which
      *                   case the translation is not started.  True otherwise.
      */
-    translate: function(originalLang, targetLang) {
+    translate(originalLang, targetLang) {
       finished = false;
       errorCode = ERROR['NONE'];
       if (!libReady) {
@@ -336,7 +336,7 @@
      * Reverts the page contents to its original value, effectively reverting
      * any performed translation.  Does nothing if the page was not translated.
      */
-    revert: function() {
+    revert() {
       lib.restore();
     },
 
@@ -344,7 +344,7 @@
      * Called when an error is caught while executing script fetched in
      * translate_script.cc.
      */
-    onTranslateElementError: function(error) {
+    onTranslateElementError(error) {
       errorCode = ERROR['UNEXPECTED_SCRIPT_ERROR'];
       invokeReadyCallback();
     },
@@ -353,7 +353,7 @@
      * Entry point called by the Translate Element once it has been injected in
      * the page.
      */
-    onTranslateElementLoad: function() {
+    onTranslateElementLoad() {
       loadedTime = performance.now();
       try {
         lib = google.translate.TranslateService({
@@ -384,7 +384,7 @@
      * external CSS resource into the page.
      * @param {string} url URL of an external CSS resource to load.
      */
-    onLoadCSS: function(url) {
+    onLoadCSS(url) {
       const element = document.createElement('link');
       element.type = 'text/css';
       element.rel = 'stylesheet';
@@ -398,7 +398,7 @@
      * an external JavaScript on the page.
      * @param {string} url URL of an external JavaScript to load.
      */
-    onLoadJavascript: function(url) {
+    onLoadJavascript(url) {
       // securityOrigin is predefined by translate_script.cc.
       if (!url.startsWith(securityOrigin)) {
         console.error('Translate: ' + url + ' is not allowed to load.');
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.cc b/content/browser/blob_storage/chrome_blob_storage_context.cc
index 5e7af339..33a732d 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.cc
+++ b/content/browser/blob_storage/chrome_blob_storage_context.cc
@@ -93,12 +93,12 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!context->GetUserData(kBlobStorageContextKeyName)) {
-    scoped_refptr<ChromeBlobStorageContext> blob =
+    scoped_refptr<ChromeBlobStorageContext> blob_storage_context =
         new ChromeBlobStorageContext();
     context->SetUserData(
         kBlobStorageContextKeyName,
         std::make_unique<UserDataAdapter<ChromeBlobStorageContext>>(
-            blob.get()));
+            blob_storage_context.get()));
 
     // Check first to avoid memory leak in unittests.
     bool io_thread_valid =
@@ -131,7 +131,8 @@
     if (io_thread_valid) {
       base::PostTask(
           FROM_HERE, {BrowserThread::IO},
-          base::BindOnce(&ChromeBlobStorageContext::InitializeOnIOThread, blob,
+          base::BindOnce(&ChromeBlobStorageContext::InitializeOnIOThread,
+                         blob_storage_context, context->GetPath(),
                          std::move(blob_storage_dir),
                          std::move(file_task_runner)));
     }
@@ -161,10 +162,11 @@
 }
 
 void ChromeBlobStorageContext::InitializeOnIOThread(
-    FilePath blob_storage_dir,
+    const FilePath& profile_dir,
+    const FilePath& blob_storage_dir,
     scoped_refptr<base::TaskRunner> file_task_runner) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  context_.reset(new BlobStorageContext(std::move(blob_storage_dir),
+  context_.reset(new BlobStorageContext(profile_dir, blob_storage_dir,
                                         std::move(file_task_runner)));
   // Signal the BlobMemoryController when it's appropriate to calculate its
   // storage limits.
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.h b/content/browser/blob_storage/chrome_blob_storage_context.h
index 8cefb251..8f4e434 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.h
+++ b/content/browser/blob_storage/chrome_blob_storage_context.h
@@ -57,7 +57,8 @@
   static mojo::PendingRemote<storage::mojom::BlobStorageContext> GetRemoteFor(
       BrowserContext* browser_context);
 
-  void InitializeOnIOThread(base::FilePath blob_storage_dir,
+  void InitializeOnIOThread(const base::FilePath& profile_dir,
+                            const base::FilePath& blob_storage_dir,
                             scoped_refptr<base::TaskRunner> file_task_runner);
 
   storage::BlobStorageContext* context() const;
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index e319ad4..aca5377 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -524,6 +524,7 @@
                        info.url(), user_agent, info.content_disposition,
                        info.mime_type, info.request_origin, info.total_bytes,
                        info.transient, web_contents)) {
+    DropDownload();
     return true;
   }
   content::devtools_instrumentation::WillBeginDownload(
diff --git a/content/browser/frame_host/mixed_content_navigation_throttle.cc b/content/browser/frame_host/mixed_content_navigation_throttle.cc
index bcf18a0..1dc5e7f 100644
--- a/content/browser/frame_host/mixed_content_navigation_throttle.cc
+++ b/content/browser/frame_host/mixed_content_navigation_throttle.cc
@@ -220,8 +220,6 @@
         const GURL& origin_url = mixed_content_node->current_origin().GetURL();
         frame_host_delegate->DidRunInsecureContent(origin_url,
                                                    request->GetURL());
-        GetContentClient()->browser()->RecordURLMetric(
-            "ContentSettings.MixedScript.RanMixedScript", origin_url);
         mixed_content_features_.insert(
             blink::mojom::WebFeature::kMixedContentBlockableAllowed);
       }
diff --git a/content/browser/frame_host/navigation_request_browsertest.cc b/content/browser/frame_host/navigation_request_browsertest.cc
index 38de5db..1ff7285 100644
--- a/content/browser/frame_host/navigation_request_browsertest.cc
+++ b/content/browser/frame_host/navigation_request_browsertest.cc
@@ -1643,7 +1643,7 @@
 
   EXPECT_TRUE(observer.has_committed());
   EXPECT_TRUE(observer.is_error());
-  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, observer.net_error_code());
+  EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
   EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.resolve_error_info().error);
 }
 
diff --git a/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
index 6cd3d42..5cc68d93 100644
--- a/content/browser/frame_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
@@ -65,12 +65,12 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/mojom/browser_interface_broker.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
-#include "third_party/blink/public/mojom/remote_objects/remote_objects.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
 #if defined(OS_ANDROID)
 #include "base/android/build_info.h"
+#include "third_party/blink/public/mojom/remote_objects/remote_objects.mojom.h"
 #endif  // defined(OS_ANDROID)
 
 namespace content {
diff --git a/content/browser/native_file_system/native_file_system_file_handle_impl_unittest.cc b/content/browser/native_file_system/native_file_system_file_handle_impl_unittest.cc
index 66dc04d..cafe75a8 100644
--- a/content/browser/native_file_system/native_file_system_file_handle_impl_unittest.cc
+++ b/content/browser/native_file_system/native_file_system_file_handle_impl_unittest.cc
@@ -55,7 +55,8 @@
                                               test_file_url_));
 
     chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
-    chrome_blob_context_->InitializeOnIOThread(base::FilePath(), nullptr);
+    chrome_blob_context_->InitializeOnIOThread(base::FilePath(),
+                                               base::FilePath(), nullptr);
 
     manager_ = base::MakeRefCounted<NativeFileSystemManagerImpl>(
         file_system_context_, chrome_blob_context_,
diff --git a/content/browser/native_file_system/native_file_system_file_writer_impl_unittest.cc b/content/browser/native_file_system/native_file_system_file_writer_impl_unittest.cc
index 9019049..44cc5121 100644
--- a/content/browser/native_file_system/native_file_system_file_writer_impl_unittest.cc
+++ b/content/browser/native_file_system/native_file_system_file_writer_impl_unittest.cc
@@ -89,7 +89,8 @@
                                               test_swap_url_));
 
     chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
-    chrome_blob_context_->InitializeOnIOThread(base::FilePath(), nullptr);
+    chrome_blob_context_->InitializeOnIOThread(base::FilePath(),
+                                               base::FilePath(), nullptr);
     blob_context_ = chrome_blob_context_->context();
 
     manager_ = base::MakeRefCounted<NativeFileSystemManagerImpl>(
diff --git a/content/browser/native_file_system/native_file_system_handle_base_unittest.cc b/content/browser/native_file_system/native_file_system_handle_base_unittest.cc
index db820ee..2012c2e 100644
--- a/content/browser/native_file_system/native_file_system_handle_base_unittest.cc
+++ b/content/browser/native_file_system/native_file_system_handle_base_unittest.cc
@@ -55,7 +55,8 @@
         /*quota_manager_proxy=*/nullptr, dir_.GetPath());
 
     chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
-    chrome_blob_context_->InitializeOnIOThread(base::FilePath(), nullptr);
+    chrome_blob_context_->InitializeOnIOThread(base::FilePath(),
+                                               base::FilePath(), nullptr);
 
     manager_ = base::MakeRefCounted<NativeFileSystemManagerImpl>(
         file_system_context_, chrome_blob_context_,
diff --git a/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc b/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
index 9b914b1..27dbd38 100644
--- a/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
+++ b/content/browser/native_file_system/native_file_system_manager_impl_unittest.cc
@@ -42,7 +42,8 @@
         /*quota_manager_proxy=*/nullptr, dir_.GetPath());
 
     chrome_blob_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
-    chrome_blob_context_->InitializeOnIOThread(base::FilePath(), nullptr);
+    chrome_blob_context_->InitializeOnIOThread(base::FilePath(),
+                                               base::FilePath(), nullptr);
 
     manager_ = base::MakeRefCounted<NativeFileSystemManagerImpl>(
         file_system_context_, chrome_blob_context_, &permission_context_,
diff --git a/content/browser/tracing/background_tracing_config_impl.cc b/content/browser/tracing/background_tracing_config_impl.cc
index c5ff08a7..3f97bd8 100644
--- a/content/browser/tracing/background_tracing_config_impl.cc
+++ b/content/browser/tracing/background_tracing_config_impl.cc
@@ -498,7 +498,7 @@
           "benchmark,toplevel,ipc,base,browser,navigation,omnibox,ui,shutdown,"
           "safe_browsing,Java,EarlyJava,loading,startup,mojom,renderer_host,"
           "disabled-by-default-system_stats,disabled-by-default-cpu_profiler,"
-          "dwrite,fonts,ServiceWorker,passwords",
+          "dwrite,fonts,ServiceWorker,passwords,disabled-by-default-file",
           record_mode);
       // Filter only browser process events.
       base::trace_event::TraceConfig::ProcessFilterConfig process_config(
@@ -510,7 +510,7 @@
       return TraceConfig(
           "benchmark,toplevel,ipc,base,ui,v8,renderer,blink,blink_gc,mojom,"
           "latency,latencyInfo,renderer_host,cc,memory,dwrite,fonts,browser,"
-          "ServiceWorker,disabled-by-default-v8.gc,disabled-by-default-file"
+          "ServiceWorker,disabled-by-default-v8.gc,disabled-by-default-file,"
           "disabled-by-default-blink_gc,disabled-by-default-lifecycles,"
           "disabled-by-default-renderer.scheduler,"
           "disabled-by-default-system_stats,disabled-by-default-cpu_profiler,"
diff --git a/content/browser/tracing/background_tracing_config_unittest.cc b/content/browser/tracing/background_tracing_config_unittest.cc
index e75fe0f..5aaaf47 100644
--- a/content/browser/tracing/background_tracing_config_unittest.cc
+++ b/content/browser/tracing/background_tracing_config_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
+#include "base/system/sys_info.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "content/browser/tracing/background_tracing_config_impl.h"
@@ -690,7 +691,11 @@
 
   notifier.set_type(net::NetworkChangeNotifier::CONNECTION_2G);
 #if defined(OS_ANDROID)
-  EXPECT_EQ(300u, config->GetTraceConfig().GetTraceBufferSizeInKb());
+  int64_t ram_mb = base::SysInfo::AmountOfPhysicalMemoryMB();
+  size_t expected_trace_buffer_size =
+      (ram_mb > 0 && ram_mb <= 1024) ? 800u : 300u;
+  EXPECT_EQ(expected_trace_buffer_size,
+            config->GetTraceConfig().GetTraceBufferSizeInKb());
   EXPECT_EQ(600u, config->GetTraceUploadLimitKb());
 #endif
 
diff --git a/content/browser/web_package/web_bundle_blob_data_source_unittest.cc b/content/browser/web_package/web_bundle_blob_data_source_unittest.cc
index ad258504..17a76527 100644
--- a/content/browser/web_package/web_bundle_blob_data_source_unittest.cc
+++ b/content/browser/web_package/web_bundle_blob_data_source_unittest.cc
@@ -31,7 +31,7 @@
   void SetUp() override {
     ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
     context_ = std::make_unique<storage::BlobStorageContext>(
-        data_dir_.GetPath(),
+        data_dir_.GetPath(), data_dir_.GetPath(),
         base::CreateTaskRunner({base::ThreadPool(), base::MayBlock()}));
     storage::BlobStorageLimits limits;
     limits.max_ipc_memory_size = kTestBlobStorageMaxBytesDataItemSize;
diff --git a/content/public/browser/download_manager_delegate.cc b/content/public/browser/download_manager_delegate.cc
index 26132b8..6b8c6913 100644
--- a/content/public/browser/download_manager_delegate.cc
+++ b/content/public/browser/download_manager_delegate.cc
@@ -60,6 +60,9 @@
     base::Optional<url::Origin> request_initiator,
     bool from_download_cross_origin_redirect,
     CheckDownloadAllowedCallback check_download_allowed_cb) {
+  // TODO: once hook up delegate callback, make sure sync run of it doesn't
+  // crash and test it
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(check_download_allowed_cb), true));
 }
diff --git a/content/public/test/test_host_resolver.cc b/content/public/test/test_host_resolver.cc
index 3d13d11..4c040d2 100644
--- a/content/public/test/test_host_resolver.cc
+++ b/content/public/test/test_host_resolver.cc
@@ -45,8 +45,6 @@
     // queries, rather than perform them.
     // If you really need to make an external DNS query, use
     // net::RuleBasedHostResolverProc and its AllowDirectLookup method.
-    // TODO(crbug.com/1040686): Simulate failure using ERR_NAME_NOT_RESOLVED
-    // rather than ERR_NOT_IMPLEMENTED.
     if (!local) {
       DVLOG(1) << "To avoid external dependencies, simulating failure for "
                   "external DNS lookup of "
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 2a7d1f73..a2480a20 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -196,9 +196,6 @@
 # Produces blank images on Intel HD 630 w/ Mesa 19.0.2
 crbug.com/976861 [ linux intel-0x5912 ] Pixel_OffscreenCanvasTransferToImageBitmap [ Skip ]
 
-# Flakes regularly on Mac.
-crbug.com/1040202 [ mac ] Pixel_CSSFilterEffects_NoOverlays [ Failure ]
-
 # Skip swap chain tests on non-Windows
 [ android ] Pixel_CanvasLowLatency2DSwapChain [ Skip ]
 [ android ] Pixel_CanvasLowLatencyWebGLSwapChain [ Skip ]
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index a2aa9dd2..d17aa0b 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision 88d715c9115a5ce65c0bf660209dfeee9131ccd0
+Current webgl revision 4f3976e9b368ccfe7b9dd02014351936296dc72c
diff --git a/docs/linux/build_instructions.md b/docs/linux/build_instructions.md
index a1b5767e..92568ad0 100644
--- a/docs/linux/build_instructions.md
+++ b/docs/linux/build_instructions.md
@@ -1,7 +1,7 @@
 # Checking out and building Chromium on Linux
 
 There are instructions for other platforms linked from the
-[get the code](get_the_code.md) page.
+[get the code](../get_the_code.md) page.
 
 ## Instructions for Google Employees
 
@@ -331,7 +331,7 @@
 
 ### More links
 
-*   Information about [building with Clang](clang.md).
+*   Information about [building with Clang](../clang.md).
 *   You may want to [use a chroot](using_a_chroot.md) to
     isolate yourself from versioning or packaging conflicts.
 *   Cross-compiling for ARM? See [LinuxChromiumArm](chromium_arm.md).
diff --git a/extensions/browser/api/bluetooth/OWNERS b/extensions/browser/api/bluetooth/OWNERS
index aa215c7..e338311 100644
--- a/extensions/browser/api/bluetooth/OWNERS
+++ b/extensions/browser/api/bluetooth/OWNERS
@@ -1 +1,2 @@
-stevenjb@chromium.org
+file://device/bluetooth/chromeos/OWNERS
+
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc
index 3149dfa..2afb58be 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc
+++ b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.cc
@@ -4,28 +4,8 @@
 
 #include "ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h"
 
-#pragma mark - InfobarBannerMainActionResponse
-
 OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerMainActionResponse);
 
-InfobarBannerMainActionResponse::InfobarBannerMainActionResponse() = default;
-
-InfobarBannerMainActionResponse::~InfobarBannerMainActionResponse() = default;
-
-#pragma mark - InfobarBannerShowModalResponse
-
 OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerShowModalResponse);
 
-InfobarBannerShowModalResponse::InfobarBannerShowModalResponse() = default;
-
-InfobarBannerShowModalResponse::~InfobarBannerShowModalResponse() = default;
-
-#pragma mark - InfobarBannerUserInitiatedDismissalResponse
-
 OVERLAY_USER_DATA_SETUP_IMPL(InfobarBannerUserInitiatedDismissalResponse);
-
-InfobarBannerUserInitiatedDismissalResponse::
-    InfobarBannerUserInitiatedDismissalResponse() = default;
-
-InfobarBannerUserInitiatedDismissalResponse::
-    ~InfobarBannerUserInitiatedDismissalResponse() = default;
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h
index f577245..6f8e30b 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h
+++ b/ios/chrome/browser/overlays/public/infobar_banner/infobar_banner_overlay_responses.h
@@ -9,39 +9,16 @@
 
 // Response info used to create dispatched OverlayResponses that trigger the
 // infobar's main action.
-class InfobarBannerMainActionResponse
-    : public OverlayResponseInfo<InfobarBannerMainActionResponse> {
- public:
-  ~InfobarBannerMainActionResponse() override;
-
- private:
-  OVERLAY_USER_DATA_SETUP(InfobarBannerMainActionResponse);
-  InfobarBannerMainActionResponse();
-};
+DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(InfobarBannerMainActionResponse);
 
 // Response info used to create dispatched OverlayResponses that trigger the
 // presentation of the infobar's modal.
-class InfobarBannerShowModalResponse
-    : public OverlayResponseInfo<InfobarBannerShowModalResponse> {
- public:
-  ~InfobarBannerShowModalResponse() override;
-
- private:
-  OVERLAY_USER_DATA_SETUP(InfobarBannerShowModalResponse);
-  InfobarBannerShowModalResponse();
-};
+DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(InfobarBannerShowModalResponse);
 
 // Response info used to create dispatched OverlayResponses that notify the
 // model layer that the upcoming dismissal is user-initiated (i.e. swipe up to
 // dismiss the banner on the refresh banner UI).
-class InfobarBannerUserInitiatedDismissalResponse
-    : public OverlayResponseInfo<InfobarBannerUserInitiatedDismissalResponse> {
- public:
-  ~InfobarBannerUserInitiatedDismissalResponse() override;
-
- private:
-  OVERLAY_USER_DATA_SETUP(InfobarBannerUserInitiatedDismissalResponse);
-  InfobarBannerUserInitiatedDismissalResponse();
-};
+DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(
+    InfobarBannerUserInitiatedDismissalResponse);
 
 #endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_BANNER_INFOBAR_BANNER_OVERLAY_RESPONSES_H_
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/BUILD.gn b/ios/chrome/browser/overlays/public/infobar_modal/BUILD.gn
index e5dc84e..350abaf 100644
--- a/ios/chrome/browser/overlays/public/infobar_modal/BUILD.gn
+++ b/ios/chrome/browser/overlays/public/infobar_modal/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("infobar_modal") {
   sources = [
+    "infobar_modal_overlay_responses.h",
+    "infobar_modal_overlay_responses.mm",
     "password_infobar_modal_overlay_request_config.h",
     "password_infobar_modal_overlay_request_config.mm",
   ]
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h b/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h
new file mode 100644
index 0000000..9376cda5
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h
@@ -0,0 +1,14 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
+#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
+
+#include "ios/chrome/browser/overlays/public/overlay_response_info.h"
+
+// Response info used to create dispatched OverlayResponses that trigger the
+// infobar's main action for the modal view.
+DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(InfobarModalMainActionResponse);
+
+#endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.mm b/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.mm
new file mode 100644
index 0000000..3d5335f9
--- /dev/null
+++ b/ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.mm
@@ -0,0 +1,11 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+OVERLAY_USER_DATA_SETUP_IMPL(InfobarModalMainActionResponse);
diff --git a/ios/chrome/browser/overlays/public/overlay_request_config.h b/ios/chrome/browser/overlays/public/overlay_request_config.h
index 0b865a9..d7eb321 100644
--- a/ios/chrome/browser/overlays/public/overlay_request_config.h
+++ b/ios/chrome/browser/overlays/public/overlay_request_config.h
@@ -21,4 +21,13 @@
   }
 };
 
+// Macro used to define an OverlayRequestConfig that holds no data.  Should be
+// declared in headers and accompanied by OVERLAY_USER_DATA_SETUP_IMPL() in the
+// implementation.
+#define DEFINE_STATELESS_OVERLAY_REQUEST_CONFIG(ConfigType)    \
+  class ConfigType : public OverlayRequestConfig<ConfigType> { \
+   private:                                                    \
+    OVERLAY_USER_DATA_SETUP(ConfigType);                       \
+  }
+
 #endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_OVERLAY_REQUEST_CONFIG_H_
diff --git a/ios/chrome/browser/overlays/public/overlay_response_info.h b/ios/chrome/browser/overlays/public/overlay_response_info.h
index 5965b7d..8460fbf5 100644
--- a/ios/chrome/browser/overlays/public/overlay_response_info.h
+++ b/ios/chrome/browser/overlays/public/overlay_response_info.h
@@ -21,4 +21,13 @@
   }
 };
 
+// Macro used to define an OverlayResponseInfo that holds no data.  Should be
+// declared in headers and accompanied by OVERLAY_USER_DATA_SETUP_IMPL() in the
+// implementation.
+#define DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(InfoType)  \
+  class InfoType : public OverlayResponseInfo<InfoType> { \
+   private:                                               \
+    OVERLAY_USER_DATA_SETUP(InfoType);                    \
+  }
+
 #endif  // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_OVERLAY_RESPONSE_INFO_H_
diff --git a/ios/chrome/browser/overlays/test/overlay_test_macros.h b/ios/chrome/browser/overlays/test/overlay_test_macros.h
index c5d8a0d..3e4f744a 100644
--- a/ios/chrome/browser/overlays/test/overlay_test_macros.h
+++ b/ios/chrome/browser/overlays/test/overlay_test_macros.h
@@ -10,20 +10,14 @@
 
 // Macro used to define an OverlayRequestConfig that holds no data.  Can be used
 // in tests for functionality specific to config types.
-#define DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ConfigType)         \
-  class ConfigType : public OverlayRequestConfig<ConfigType> { \
-   private:                                                    \
-    OVERLAY_USER_DATA_SETUP(ConfigType);                       \
-  };                                                           \
+#define DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ConfigType) \
+  DEFINE_STATELESS_OVERLAY_REQUEST_CONFIG(ConfigType); \
   OVERLAY_USER_DATA_SETUP_IMPL(ConfigType)
 
 // Macro used to define a response info that holds no data.  Can be used
 // in tests for functionality specific to info types.
-#define DEFINE_TEST_OVERLAY_RESPONSE_INFO(InfoType)       \
-  class InfoType : public OverlayResponseInfo<InfoType> { \
-   private:                                               \
-    OVERLAY_USER_DATA_SETUP(InfoType);                    \
-  };                                                      \
+#define DEFINE_TEST_OVERLAY_RESPONSE_INFO(InfoType) \
+  DEFINE_STATELESS_OVERLAY_RESPONSE_INFO(InfoType); \
   OVERLAY_USER_DATA_SETUP_IMPL(InfoType)
 
 #endif  // IOS_CHROME_BROWSER_OVERLAYS_TEST_OVERLAY_TEST_MACROS_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm
index f34c1f3b..6661314 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/infobar_banner_overlay_mediator.mm
@@ -71,6 +71,7 @@
 - (void)infobarBannerWasDismissed {
   // Only needed in legacy implementation.  Dismissal completion cleanup occurs
   // in InfobarBannerOverlayCoordinator.
+  // TODO(crbug.com/1041917): Remove once non-overlay implementation is deleted.
 }
 
 @end
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/BUILD.gn b/ios/chrome/browser/ui/overlays/infobar_modal/BUILD.gn
index 58fc540..e5b2968 100644
--- a/ios/chrome/browser/ui/overlays/infobar_modal/BUILD.gn
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/BUILD.gn
@@ -10,3 +10,73 @@
 
   deps = []
 }
+
+source_set("coordinators") {
+  sources = [
+    "infobar_modal_overlay_coordinator+modal_configuration.h",
+    "infobar_modal_overlay_coordinator.h",
+    "infobar_modal_overlay_coordinator.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    ":mediators",
+    "//base",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/common/infobars",
+    "//ios/chrome/browser/ui/infobars/modals",
+    "//ios/chrome/browser/ui/overlays:coordinators",
+  ]
+}
+
+source_set("mediators") {
+  sources = [
+    "infobar_modal_overlay_mediator.h",
+    "infobar_modal_overlay_mediator.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+    "//components/infobars/core",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/infobar_modal",
+    "//ios/chrome/browser/ui/infobars/modals",
+    "//ios/chrome/browser/ui/overlays:coordinators",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "infobar_modal_overlay_coordinator_unittest.mm",
+    "infobar_modal_overlay_mediator_unittest.mm",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    ":coordinators",
+    ":mediators",
+    "//base/test:test_support",
+    "//components/infobars/core",
+    "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/infobars/test",
+    "//ios/chrome/browser/main:test_support",
+    "//ios/chrome/browser/overlays",
+    "//ios/chrome/browser/overlays/public/infobar_modal",
+    "//ios/chrome/browser/overlays/test",
+    "//ios/chrome/browser/ui/overlays:coordinators",
+    "//ios/chrome/browser/ui/overlays/test",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/chrome/browser/web_state_list:test_support",
+    "//ios/chrome/test:test_support",
+    "//ios/web/public/test",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/ocmock",
+    "//ui/base",
+  ]
+}
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h
new file mode 100644
index 0000000..21e18c2
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h
@@ -0,0 +1,36 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
+
+@class InfobarModalOverlayMediator;
+
+// Category implemented by InfobarModalOverlayCoordinator subclasses to
+// configure the view to display for infobar modals.
+@interface InfobarModalOverlayCoordinator (ModalConfiguration)
+
+// The mediator used to configure the modal view controller.  Created in
+// |-configureModal|.
+@property(nonatomic, readonly) InfobarModalOverlayMediator* modalMediator;
+// The view controller to display for the infobar modal.  Created in
+// |-configureModal|.  This view controller is not the view controller returned
+// by the InfobarModalOverlayCoordinator.viewController property, but is added
+// as a child view controller to the top-level infobar modal container view.
+@property(nonatomic, readonly) UIViewController* modalViewController;
+
+// Creates a modal view controller and configures it with a new mediator.
+// Resets |modalViewController| and |modalMediator| to the new instances.
+- (void)configureModal;
+
+// Resets |modalMediator| and |modalViewController|.
+- (void)resetModal;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h
new file mode 100644
index 0000000..5ca2ab1b
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h
@@ -0,0 +1,14 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
+
+// A coordinator that displays infobar modal UI using OverlayPresenter.
+@interface InfobarModalOverlayCoordinator : OverlayRequestCoordinator
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.mm b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.mm
new file mode 100644
index 0000000..4ae6b350
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.mm
@@ -0,0 +1,102 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h"
+
+#include "base/logging.h"
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface InfobarModalOverlayCoordinator ()
+// The navigation controller used to display the modal view.
+@property(nonatomic) UINavigationController* modalNavController;
+@end
+
+@implementation InfobarModalOverlayCoordinator
+
+#pragma mark - OverlayRequestCoordinator
+
+- (void)startAnimated:(BOOL)animated {
+  if (self.started || !self.request)
+    return;
+  [self configureModal];
+  self.mediator = self.modalMediator;
+  self.modalNavController = [[UINavigationController alloc]
+      initWithRootViewController:self.modalViewController];
+  // TODO(crbug.com/1030357): Use custom presentation.
+  self.modalNavController.modalPresentationStyle =
+      UIModalPresentationOverCurrentContext;
+  self.modalNavController.modalTransitionStyle =
+      UIModalTransitionStyleCrossDissolve;
+  [self.baseViewController presentViewController:self.viewController
+                                        animated:animated
+                                      completion:^{
+                                        [self finishPresentation];
+                                      }];
+  self.started = YES;
+}
+
+- (void)stopAnimated:(BOOL)animated {
+  if (!self.started)
+    return;
+  [self.baseViewController dismissViewControllerAnimated:animated
+                                              completion:^{
+                                                [self finishDismissal];
+                                              }];
+  self.started = NO;
+}
+
+- (UIViewController*)viewController {
+  return self.modalNavController;
+}
+
+#pragma mark - Private
+
+// Called when the presentation of the modal UI is completed.
+- (void)finishPresentation {
+  // Notify the presentation context that the presentation has finished.  This
+  // is necessary to synchronize OverlayPresenter scheduling logic with the UI
+  // layer.
+  self.delegate->OverlayUIDidFinishPresentation(self.request);
+}
+
+// Called when the dismissal of the modal UI is finished.
+- (void)finishDismissal {
+  [self resetModal];
+  self.navigationController = nil;
+  // Notify the presentation context that the dismissal has finished.  This
+  // is necessary to synchronize OverlayPresenter scheduling logic with the UI
+  // layer.
+  self.delegate->OverlayUIDidFinishDismissal(self.request);
+}
+
+@end
+
+@implementation InfobarModalOverlayCoordinator (ModalConfiguration)
+
+- (OverlayRequestMediator*)modalMediator {
+  NOTREACHED() << "Subclasses implement.";
+  return nullptr;
+}
+
+- (UIViewController*)modalViewController {
+  NOTREACHED() << "Subclasses implement.";
+  return nil;
+}
+
+- (void)configureModal {
+  NOTREACHED() << "Subclasses implement.";
+}
+
+- (void)resetModal {
+  NOTREACHED() << "Subclasses implement.";
+}
+
+@end
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator_unittest.mm
new file mode 100644
index 0000000..5e0be07e
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator_unittest.mm
@@ -0,0 +1,154 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h"
+
+#import "base/test/ios/wait_util.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/main/test_browser.h"
+#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
+#import "ios/chrome/browser/ui/overlays/test/mock_overlay_coordinator_delegate.h"
+#import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/test/scoped_key_window.h"
+#include "ios/web/public/test/web_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using base::test::ios::WaitUntilConditionOrTimeout;
+using base::test::ios::kWaitForUIElementTimeout;
+
+namespace {
+// Request config type used for testing.
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ModalConfig);
+}
+
+// Mediator used by FakeInfobarModalOverlayCoordinators.
+@interface FakeModalMediator : InfobarModalOverlayMediator
+@end
+
+@implementation FakeModalMediator
++ (const OverlayRequestSupport*)requestSupport {
+  return ModalConfig::RequestSupport();
+}
+@end
+
+// Modal coordinator for tests.
+@interface FakeInfobarModalOverlayCoordinator : InfobarModalOverlayCoordinator {
+  FakeModalMediator* _mediator;
+  UIViewController* _viewController;
+}
+@end
+
+@implementation FakeInfobarModalOverlayCoordinator
+
++ (const OverlayRequestSupport*)requestSupport {
+  return ModalConfig::RequestSupport();
+}
+
+- (OverlayRequestMediator*)modalMediator {
+  return _mediator;
+}
+
+- (UIViewController*)modalViewController {
+  return _viewController;
+}
+
+- (void)configureModal {
+  _mediator = [[FakeModalMediator alloc] initWithRequest:self.request];
+  _viewController = [[UIViewController alloc] init];
+}
+
+- (void)resetModal {
+  _mediator = nil;
+  _viewController = nil;
+}
+
+@end
+
+// Test fixture for InfobarModalOverlayCoordinator.
+class InfobarModalOverlayCoordinatorTest : public PlatformTest {
+ public:
+  InfobarModalOverlayCoordinatorTest()
+      : browser_state_(browser_state_builder_.Build()),
+        web_state_list_(&web_state_list_delegate_),
+        browser_(browser_state_.get(), &web_state_list_),
+        request_(OverlayRequest::CreateWithConfig<ModalConfig>()),
+        root_view_controller_([[UIViewController alloc] init]),
+        coordinator_([[FakeInfobarModalOverlayCoordinator alloc]
+            initWithBaseViewController:root_view_controller_
+                               browser:&browser_
+                               request:request_.get()
+                              delegate:&delegate_]) {
+    scoped_window_.Get().rootViewController = root_view_controller_;
+  }
+
+ protected:
+  web::WebTaskEnvironment task_environment_;
+  TestChromeBrowserState::Builder browser_state_builder_;
+  std::unique_ptr<ios::ChromeBrowserState> browser_state_;
+  FakeWebStateListDelegate web_state_list_delegate_;
+  WebStateList web_state_list_;
+  TestBrowser browser_;
+  MockOverlayRequestCoordinatorDelegate delegate_;
+  std::unique_ptr<OverlayRequest> request_;
+  ScopedKeyWindow scoped_window_;
+  UIViewController* root_view_controller_ = nil;
+  FakeInfobarModalOverlayCoordinator* coordinator_ = nil;
+};
+
+// Tests the modal presentation flow for a FakeInfobarModalOverlayCoordinator.
+TEST_F(InfobarModalOverlayCoordinatorTest, ModalPresentation) {
+  // Start the coordinator, expecting OverlayUIDidFinishPresentation() to be
+  // executed.
+  EXPECT_CALL(delegate_, OverlayUIDidFinishPresentation(request_.get()));
+  [coordinator_ startAnimated:NO];
+
+  // Wait for presentation to finish.
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^BOOL {
+    UIViewController* presented_view_controller =
+        root_view_controller_.presentedViewController;
+    return presented_view_controller &&
+           !presented_view_controller.beingPresented;
+  }));
+
+  // Verify that the view hierarchy is set up as expected.  The coordinator
+  // should have added the configured modal view as the root view controller for
+  // a UINavigationController, then presented it on |root_view_controller_|.
+  UIViewController* presented_view_controller =
+      root_view_controller_.presentedViewController;
+  EXPECT_TRUE(presented_view_controller);
+  EXPECT_TRUE(
+      [presented_view_controller isKindOfClass:[UINavigationController class]]);
+  UINavigationController* navigation_controller =
+      static_cast<UINavigationController*>(presented_view_controller);
+  UIViewController* navigation_root_controller =
+      [navigation_controller.viewControllers firstObject];
+  EXPECT_TRUE(navigation_root_controller);
+  EXPECT_EQ(coordinator_.modalViewController, navigation_root_controller);
+
+  // Verify that the mediator has been set on the coordinator and that its
+  // delegate was set to the coordinator.
+  OverlayRequestMediator* mediator = coordinator_.mediator;
+  EXPECT_TRUE(mediator);
+  EXPECT_EQ(coordinator_.modalMediator, mediator);
+  EXPECT_EQ(coordinator_, mediator.delegate);
+
+  // Stop the coordinator, expecting OverlayUIDidFinishDismissal() to be
+  // executed.
+  EXPECT_CALL(delegate_, OverlayUIDidFinishDismissal(request_.get()));
+  [coordinator_ stopAnimated:NO];
+
+  // Wait for dismissal to finish.
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^BOOL {
+    return !root_view_controller_.presentedViewController;
+  }));
+}
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h
new file mode 100644
index 0000000..7c298fd3
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
+
+#import "ios/chrome/browser/ui/overlays/overlay_request_mediator.h"
+
+#import "ios/chrome/browser/ui/infobars/modals/infobar_modal_delegate.h"
+
+// Mediator superclass for configuring infobar modal views.
+@interface InfobarModalOverlayMediator
+    : OverlayRequestMediator <InfobarModalDelegate>
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.mm
new file mode 100644
index 0000000..4316a6c
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.mm
@@ -0,0 +1,36 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
+
+#import <UIKit/UIKit.h>
+
+#include "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
+#include "ios/chrome/browser/overlays/public/overlay_response.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_mediator+subclassing.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation InfobarModalOverlayMediator
+
+#pragma mark - InfobarModalDelegate
+
+- (void)dismissInfobarModal:(id)infobarModal {
+  [self.delegate stopOverlayForMediator:self];
+}
+
+- (void)modalInfobarButtonWasAccepted:(id)infobarModal {
+  [self dispatchResponseAndStopOverlay:OverlayResponse::CreateWithInfo<
+                                           InfobarModalMainActionResponse>()];
+}
+
+- (void)modalInfobarWasDismissed:(id)infobarModal {
+  // Only needed in legacy implementation.  Dismissal completion cleanup occurs
+  // in InfobarModalOverlayCoordinator.
+  // TODO(crbug.com/1041917): Remove once non-overlay implementation is deleted.
+}
+
+@end
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator_unittest.mm
new file mode 100644
index 0000000..2cff71b
--- /dev/null
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator_unittest.mm
@@ -0,0 +1,80 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
+
+#import "base/bind.h"
+#include "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
+#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
+#include "ios/chrome/browser/overlays/public/overlay_request.h"
+#include "ios/chrome/browser/overlays/public/overlay_response.h"
+#include "ios/chrome/browser/overlays/public/overlay_response_support.h"
+#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
+#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// Request ConfigType used for tests.
+DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ModalConfig);
+}
+
+// Mediator used in tests.
+@interface FakeInfobarModalOverlayMediator : InfobarModalOverlayMediator
+@end
+
+@implementation FakeInfobarModalOverlayMediator
++ (const OverlayRequestSupport*)requestSupport {
+  return ModalConfig::RequestSupport();
+}
+@end
+
+// Test fixture for InfobarModalOverlayMediator.
+class InfobarModalOverlayMediatorTest : public PlatformTest {
+ public:
+  InfobarModalOverlayMediatorTest()
+      : request_(OverlayRequest::CreateWithConfig<ModalConfig>()),
+        delegate_(
+            OCMStrictProtocolMock(@protocol(OverlayRequestMediatorDelegate))),
+        mediator_([[FakeInfobarModalOverlayMediator alloc]
+            initWithRequest:request_.get()]) {
+    mediator_.delegate = delegate_;
+  }
+  ~InfobarModalOverlayMediatorTest() override {
+    EXPECT_OCMOCK_VERIFY(delegate_);
+  }
+
+ protected:
+  std::unique_ptr<OverlayRequest> request_;
+  id<OverlayRequestMediatorDelegate> delegate_ = nil;
+  InfobarModalOverlayMediator* mediator_ = nil;
+};
+
+// Tests that |-dismissInfobarModal| triggers dismissal via the delegate.
+TEST_F(InfobarModalOverlayMediatorTest, DismissInfobarModal) {
+  OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
+  [mediator_ dismissInfobarModal:nil];
+}
+
+// Tests that |-modalInfobarButtonWasAccepted| dispatches a main action response
+// then dismisses the modal.
+TEST_F(InfobarModalOverlayMediatorTest, ModalInfobarButtonWasAccepted) {
+  // Add a dispatch callback that resets |main_action_callback_executed| to true
+  // upon receiving an OverlayResponse created with an
+  // InfobarModalMainActionResponse.
+  __block bool main_action_callback_executed = false;
+  request_->GetCallbackManager()->AddDispatchCallback(OverlayDispatchCallback(
+      base::BindRepeating(^(OverlayResponse* response) {
+        main_action_callback_executed = true;
+      }),
+      InfobarModalMainActionResponse::ResponseSupport()));
+  OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
+  [mediator_ modalInfobarButtonWasAccepted:nil];
+  EXPECT_TRUE(main_action_callback_executed);
+}
diff --git a/ios/chrome/browser/ui/overlays/overlay_request_coordinator.mm b/ios/chrome/browser/ui/overlays/overlay_request_coordinator.mm
index 657f3789..c2d2708 100644
--- a/ios/chrome/browser/ui/overlays/overlay_request_coordinator.mm
+++ b/ios/chrome/browser/ui/overlays/overlay_request_coordinator.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
+#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
 
 #include "base/logging.h"
 #include "ios/chrome/browser/overlays/public/overlay_request_support.h"
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 4aa2a5b..146b37d 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -38,9 +38,7 @@
     "testing_application_context.mm",
   ]
 
-  public_deps = [
-    ":block_cleanup_test",
-  ]
+  public_deps = [ ":block_cleanup_test" ]
 
   deps = [
     "//base",
@@ -119,9 +117,7 @@
 
 source_set("run_all_unittests") {
   testonly = true
-  sources = [
-    "run_all_unittests.cc",
-  ]
+  sources = [ "run_all_unittests.cc" ]
   deps = [
     ":test_support",
     "//base",
@@ -270,6 +266,7 @@
     "//ios/chrome/browser/ui/overlays/common/alerts:unit_tests",
     "//ios/chrome/browser/ui/overlays/infobar_banner:unit_tests",
     "//ios/chrome/browser/ui/overlays/infobar_banner/passwords:unit_tests",
+    "//ios/chrome/browser/ui/overlays/infobar_modal:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/app_launcher:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/http_auth_dialogs:unit_tests",
     "//ios/chrome/browser/ui/overlays/web_content_area/java_script_dialogs:unit_tests",
diff --git a/media/formats/mp4/box_definitions.cc b/media/formats/mp4/box_definitions.cc
index 625f332..8206c5a 100644
--- a/media/formats/mp4/box_definitions.cc
+++ b/media/formats/mp4/box_definitions.cc
@@ -71,6 +71,27 @@
 }
 #endif  // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
 
+// Read color coordinate value as defined in the MasteringDisplayColorVolume
+// ('mdcv') box.  Each coordinate is a float encoded in uint16_t, with upper
+// bound set to 50000.
+bool ReadMdcvColorCoordinate(BoxReader* reader,
+                             float* normalized_value_in_float) {
+  const float kColorCoordinateUpperBound = 50000.;
+
+  uint16_t value_in_uint16;
+  RCHECK(reader->Read2(&value_in_uint16));
+
+  float value_in_float = static_cast<float>(value_in_uint16);
+
+  if (value_in_float >= kColorCoordinateUpperBound) {
+    *normalized_value_in_float = 1.f;
+    return true;
+  }
+
+  *normalized_value_in_float = value_in_float / kColorCoordinateUpperBound;
+  return true;
+}
+
 }  // namespace
 
 FileType::FileType() = default;
@@ -783,11 +804,77 @@
 FourCC PixelAspectRatioBox::BoxType() const { return FOURCC_PASP; }
 
 bool PixelAspectRatioBox::Parse(BoxReader* reader) {
-  RCHECK(reader->Read4(&h_spacing) &&
-         reader->Read4(&v_spacing));
+  RCHECK(reader->Read4(&h_spacing) && reader->Read4(&v_spacing));
   return true;
 }
 
+ColorParameterInformation::ColorParameterInformation() = default;
+ColorParameterInformation::ColorParameterInformation(
+    const ColorParameterInformation& other) = default;
+ColorParameterInformation::~ColorParameterInformation() = default;
+FourCC ColorParameterInformation::BoxType() const {
+  return FOURCC_COLR;
+}
+
+bool ColorParameterInformation::Parse(BoxReader* reader) {
+  FourCC type;
+  RCHECK(reader->ReadFourCC(&type));
+  // TODO: Support 'nclc', 'rICC', and 'prof'.
+  RCHECK(type == FOURCC_NCLX);
+
+  uint8_t full_range_byte;
+  RCHECK(reader->Read2(&colour_primaries) &&
+         reader->Read2(&transfer_characteristics) &&
+         reader->Read2(&matrix_coefficients) &&
+         reader->Read1(&full_range_byte));
+  full_range = full_range_byte & 0x80;
+
+  return true;
+}
+
+MasteringDisplayColorVolume::MasteringDisplayColorVolume() = default;
+MasteringDisplayColorVolume::MasteringDisplayColorVolume(
+    const MasteringDisplayColorVolume& other) = default;
+MasteringDisplayColorVolume::~MasteringDisplayColorVolume() = default;
+
+FourCC MasteringDisplayColorVolume::BoxType() const {
+  return FOURCC_MDCV;
+}
+
+bool MasteringDisplayColorVolume::Parse(BoxReader* reader) {
+  // Technically the color coordinates may be in any order.  The spec recommends
+  // GBR and it is assumed that the color coordinates are in such order.
+  RCHECK(ReadMdcvColorCoordinate(reader, &display_primaries_gx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_gy) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_bx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_by) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_rx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_ry) &&
+         ReadMdcvColorCoordinate(reader, &white_point_x) &&
+         ReadMdcvColorCoordinate(reader, &white_point_y) &&
+         reader->Read4(&max_display_mastering_luminance) &&
+         reader->Read4(&min_display_mastering_luminance));
+
+  const uint32_t kUnitOfMasteringLuminance = 10000;
+  max_display_mastering_luminance /= kUnitOfMasteringLuminance;
+  min_display_mastering_luminance /= kUnitOfMasteringLuminance;
+
+  return true;
+}
+
+ContentLightLevelInformation::ContentLightLevelInformation() = default;
+ContentLightLevelInformation::ContentLightLevelInformation(
+    const ContentLightLevelInformation& other) = default;
+ContentLightLevelInformation::~ContentLightLevelInformation() = default;
+FourCC ContentLightLevelInformation::BoxType() const {
+  return FOURCC_CLLI;
+}
+
+bool ContentLightLevelInformation::Parse(BoxReader* reader) {
+  return reader->Read2(&max_content_light_level) &&
+         reader->Read2(&max_pic_average_light_level);
+}
+
 VideoSampleEntry::VideoSampleEntry()
     : format(FOURCC_NULL),
       data_reference_index(0),
@@ -937,6 +1024,24 @@
       frame_bitstream_converter = nullptr;
       video_codec = kCodecAV1;
       video_codec_profile = av1_config.profile;
+
+      ColorParameterInformation color_parameter_information;
+      if (reader->HasChild(&color_parameter_information)) {
+        RCHECK(reader->ReadChild(&color_parameter_information));
+        this->color_parameter_information = color_parameter_information;
+      }
+
+      MasteringDisplayColorVolume mastering_display_color_volume;
+      if (reader->HasChild(&mastering_display_color_volume)) {
+        RCHECK(reader->ReadChild(&mastering_display_color_volume));
+        this->mastering_display_color_volume = mastering_display_color_volume;
+      }
+
+      ContentLightLevelInformation content_light_level_information;
+      if (reader->HasChild(&content_light_level_information)) {
+        RCHECK(reader->ReadChild(&content_light_level_information));
+        this->content_light_level_information = content_light_level_information;
+      }
       break;
     }
 #endif
diff --git a/media/formats/mp4/box_definitions.h b/media/formats/mp4/box_definitions.h
index 35b6e63..93d7d255 100644
--- a/media/formats/mp4/box_definitions.h
+++ b/media/formats/mp4/box_definitions.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/optional.h"
 #include "media/base/decrypt_config.h"
 #include "media/base/media_export.h"
 #include "media/base/media_log.h"
@@ -265,6 +266,37 @@
   uint32_t v_spacing;
 };
 
+struct MEDIA_EXPORT ColorParameterInformation : Box {
+  DECLARE_BOX_METHODS(ColorParameterInformation);
+
+  uint16_t colour_primaries;
+  uint16_t transfer_characteristics;
+  uint16_t matrix_coefficients;
+  bool full_range;
+};
+
+struct MEDIA_EXPORT MasteringDisplayColorVolume : Box {
+  DECLARE_BOX_METHODS(MasteringDisplayColorVolume);
+
+  float display_primaries_gx;
+  float display_primaries_gy;
+  float display_primaries_bx;
+  float display_primaries_by;
+  float display_primaries_rx;
+  float display_primaries_ry;
+  float white_point_x;
+  float white_point_y;
+  uint32_t max_display_mastering_luminance;
+  uint32_t min_display_mastering_luminance;
+};
+
+struct MEDIA_EXPORT ContentLightLevelInformation : Box {
+  DECLARE_BOX_METHODS(ContentLightLevelInformation);
+
+  uint16_t max_content_light_level;
+  uint16_t max_pic_average_light_level;
+};
+
 struct MEDIA_EXPORT VideoSampleEntry : Box {
   DECLARE_BOX_METHODS(VideoSampleEntry);
 
@@ -280,6 +312,10 @@
   VideoCodecProfile video_codec_profile;
   VideoCodecLevel video_codec_level;
 
+  base::Optional<ColorParameterInformation> color_parameter_information;
+  base::Optional<MasteringDisplayColorVolume> mastering_display_color_volume;
+  base::Optional<ContentLightLevelInformation> content_light_level_information;
+
   bool IsFormatValid() const;
 
   scoped_refptr<BitstreamConverter> frame_bitstream_converter;
diff --git a/media/formats/mp4/fourccs.h b/media/formats/mp4/fourccs.h
index 19924d3..35ca1dd 100644
--- a/media/formats/mp4/fourccs.h
+++ b/media/formats/mp4/fourccs.h
@@ -28,7 +28,9 @@
   FOURCC_BLOC = 0x626C6F63,
   FOURCC_CBCS = 0x63626373,
   FOURCC_CENC = 0x63656e63,
+  FOURCC_CLLI = 0x636c6c69,
   FOURCC_CO64 = 0x636f3634,
+  FOURCC_COLR = 0x636f6c72,
   FOURCC_CTTS = 0x63747473,
   FOURCC_DFLA = 0x64664c61,  // "dfLa"
   FOURCC_DINF = 0x64696e66,
@@ -63,6 +65,7 @@
   FOURCC_ID32 = 0x49443332,
   FOURCC_IODS = 0x696f6473,
   FOURCC_MDAT = 0x6d646174,
+  FOURCC_MDCV = 0x6d646376,
   FOURCC_MDHD = 0x6d646864,
   FOURCC_MDIA = 0x6d646961,
   FOURCC_MECO = 0x6d65636f,
@@ -81,6 +84,7 @@
   FOURCC_MP4V = 0x6d703476,
   FOURCC_MVEX = 0x6d766578,
   FOURCC_MVHD = 0x6d766864,
+  FOURCC_NCLX = 0x6e636c78,
   FOURCC_OPUS = 0x4f707573,  // "Opus"
   FOURCC_PASP = 0x70617370,
   FOURCC_PDIN = 0x7064696e,
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc
index d274c41..19617fe 100644
--- a/media/formats/mp4/mp4_stream_parser.cc
+++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -59,6 +59,44 @@
   }
   return EncryptionScheme::kUnencrypted;
 }
+
+VideoColorSpace ConvertColorParameterInformationToColorSpace(
+    const ColorParameterInformation& info) {
+  auto primary_id =
+      static_cast<VideoColorSpace::PrimaryID>(info.colour_primaries);
+  auto transfer_id =
+      static_cast<VideoColorSpace::TransferID>(info.transfer_characteristics);
+  auto matrix_id =
+      static_cast<VideoColorSpace::MatrixID>(info.matrix_coefficients);
+
+  // Note that we don't check whether the embedded ids are valid.  We rely on
+  // the underlying video decoder to reject any ids that it doesn't support.
+  return VideoColorSpace(primary_id, transfer_id, matrix_id,
+                         info.full_range ? gfx::ColorSpace::RangeID::FULL
+                                         : gfx::ColorSpace::RangeID::LIMITED);
+}
+
+MasteringMetadata ConvertMdcvToMasteringMetadata(
+    const MasteringDisplayColorVolume& mdcv) {
+  MasteringMetadata mastering_metadata;
+
+  mastering_metadata.primary_r = MasteringMetadata::Chromaticity(
+      mdcv.display_primaries_rx, mdcv.display_primaries_ry);
+  mastering_metadata.primary_g = MasteringMetadata::Chromaticity(
+      mdcv.display_primaries_gx, mdcv.display_primaries_gy);
+  mastering_metadata.primary_b = MasteringMetadata::Chromaticity(
+      mdcv.display_primaries_bx, mdcv.display_primaries_by);
+  mastering_metadata.white_point =
+      MasteringMetadata::Chromaticity(mdcv.white_point_x, mdcv.white_point_y);
+
+  mastering_metadata.luminance_max =
+      static_cast<float>(mdcv.max_display_mastering_luminance);
+  mastering_metadata.luminance_min =
+      static_cast<float>(mdcv.min_display_mastering_luminance);
+
+  return mastering_metadata;
+}
+
 }  // namespace
 
 MP4StreamParser::MP4StreamParser(const std::set<int>& audio_object_types,
@@ -516,6 +554,30 @@
                               EmptyExtraData(), scheme);
       video_config.set_level(entry.video_codec_level);
 
+      if (entry.color_parameter_information) {
+        video_config.set_color_space_info(
+            ConvertColorParameterInformationToColorSpace(
+                *entry.color_parameter_information));
+
+        if (entry.mastering_display_color_volume ||
+            entry.content_light_level_information) {
+          HDRMetadata hdr_metadata;
+          if (entry.mastering_display_color_volume) {
+            hdr_metadata.mastering_metadata = ConvertMdcvToMasteringMetadata(
+                *entry.mastering_display_color_volume);
+          }
+
+          if (entry.content_light_level_information) {
+            hdr_metadata.max_content_light_level =
+                entry.content_light_level_information->max_content_light_level;
+            hdr_metadata.max_frame_average_light_level =
+                entry.content_light_level_information
+                    ->max_pic_average_light_level;
+          }
+          video_config.set_hdr_metadata(hdr_metadata);
+        }
+      }
+
       DVLOG(1) << "video_track_id=" << video_track_id
                << " config=" << video_config.AsHumanReadableString();
       if (!video_config.IsValidConfig()) {
diff --git a/net/BUILD.gn b/net/BUILD.gn
index d746ab1..863ebf4 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -5383,6 +5383,7 @@
     "test/run_all_unittests.cc",
     "test/tcp_socket_proxy_unittest.cc",
     "third_party/nist-pkits/pkits_testcases-inl.h",
+    "third_party/quiche/src/common/quiche_data_writer_test.cc",
     "third_party/quiche/src/common/simple_linked_hash_map_test.cc",
     "third_party/quiche/src/http2/decoder/decode_buffer_test.cc",
     "third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc",
@@ -5601,6 +5602,7 @@
     "third_party/quiche/src/quic/platform/api/quic_mem_slice_test.cc",
     "third_party/quiche/src/quic/platform/api/quic_reference_counted_test.cc",
     "third_party/quiche/src/quic/platform/api/quic_socket_address_test.cc",
+    "third_party/quiche/src/quic/platform/api/quic_string_utils_test.cc",
     "third_party/quiche/src/quic/quic_transport/quic_transport_client_session_test.cc",
     "third_party/quiche/src/quic/quic_transport/quic_transport_integration_test.cc",
     "third_party/quiche/src/quic/quic_transport/quic_transport_server_session_test.cc",
@@ -5638,6 +5640,7 @@
     "third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc",
     "third_party/quiche/src/spdy/core/spdy_framer_test.cc",
     "third_party/quiche/src/spdy/core/spdy_header_block_test.cc",
+    "third_party/quiche/src/spdy/core/spdy_header_storage_test.cc",
     "third_party/quiche/src/spdy/core/spdy_intrusive_list_test.cc",
     "third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc",
     "third_party/quiche/src/spdy/core/spdy_no_op_visitor.h",
diff --git a/net/base/net_errors.cc b/net/base/net_errors.cc
index 7e7ee37ac..ed6bc4514 100644
--- a/net/base/net_errors.cc
+++ b/net/base/net_errors.cc
@@ -63,8 +63,8 @@
 }
 
 bool IsDnsError(int error) {
-  DCHECK_NE(ERR_NAME_RESOLUTION_FAILED, error);
-  return error == ERR_NAME_NOT_RESOLVED;
+  return (error == ERR_NAME_NOT_RESOLVED ||
+          error == ERR_NAME_RESOLUTION_FAILED);
 }
 
 Error FileErrorToNetError(base::File::Error file_error) {
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc
index 8aee929..76b9bc6 100644
--- a/net/dns/host_resolver.cc
+++ b/net/dns/host_resolver.cc
@@ -242,10 +242,7 @@
 
 // static
 int HostResolver::SquashErrorCode(int error) {
-  // TODO(crbug.com/1040686): Once InProcessBrowserTests do not use
-  // ERR_NOT_IMPLEMENTED to simulate DNS failures, it should be ok to squash
-  // ERR_NOT_IMPLEMENTED.
-  if (error == OK || error == ERR_IO_PENDING || error == ERR_NOT_IMPLEMENTED ||
+  if (error == OK || error == ERR_IO_PENDING ||
       error == ERR_NAME_NOT_RESOLVED) {
     return error;
   } else {
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index 264ab583..092ac6f 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -66,9 +66,12 @@
     // On any other returned value, the request was handled synchronously and
     // |callback| will not be invoked.
     //
-    // Results in ERR_NAME_NOT_RESOLVED if the hostname is not resolved. More
-    // detail about the underlying error can be retrieved using
-    // GetResolveErrorInfo().
+    // Results in ERR_NAME_NOT_RESOLVED if the hostname is invalid, or if it is
+    // an incompatible IP literal (e.g. IPv6 is disabled and it is an IPv6
+    // literal).
+    //
+    // Results in ERR_DNS_CACHE_MISS if only fast local sources are to be
+    // queried and a cache lookup attempt fails.
     //
     // The parent HostResolver must still be alive when Start() is called,  but
     // if it is destroyed before an asynchronous result completes, the request
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 632f5b4..884d3ff 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -650,7 +650,7 @@
     LogFinishRequest(error);
 
     DCHECK(callback_);
-    std::move(callback_).Run(HostResolver::SquashErrorCode(error));
+    std::move(callback_).Run(error);
   }
 
   Job* job() const { return job_; }
@@ -3015,7 +3015,7 @@
                     effective_secure_dns_mode, base::TimeDelta());
     request->set_error_info(results.error(),
                             false /* is_secure_network_error */);
-    return HostResolver::SquashErrorCode(results.error());
+    return results.error();
   }
 
   CreateAndStartJob(effective_query_type, effective_host_resolver_flags,
diff --git a/net/quic/bidirectional_stream_quic_impl_unittest.cc b/net/quic/bidirectional_stream_quic_impl_unittest.cc
index 2c240aa..45f1a52 100644
--- a/net/quic/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/bidirectional_stream_quic_impl_unittest.cc
@@ -99,11 +99,6 @@
   quic::ParsedQuicVersionVector all_supported_versions =
       quic::AllSupportedVersions();
   for (const auto& version : all_supported_versions) {
-    // Test fails for soon to be deprecated Q099, so skip it.
-    if (version.transport_version == quic::QUIC_VERSION_99 &&
-        version.handshake_protocol == quic::PROTOCOL_QUIC_CRYPTO) {
-      continue;
-    }
     params.push_back(TestParams{version, false});
     params.push_back(TestParams{version, true});
   }
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index b0a0f99..e17a6a0 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -109,11 +109,6 @@
   quic::ParsedQuicVersionVector all_supported_versions =
       quic::AllSupportedVersions();
   for (const auto& version : all_supported_versions) {
-    // Test fails for soon to be deprecated Q099, so skip it.
-    // TODO(bnc): Re-enable T099 when PRIORITY_UPDATE is added.
-    if (version.transport_version == quic::QUIC_VERSION_99) {
-      continue;
-    }
     params.push_back(TestParams{version, false});
     params.push_back(TestParams{version, true});
   }
diff --git a/net/url_request/http_with_dns_over_https_unittest.cc b/net/url_request/http_with_dns_over_https_unittest.cc
index d4f1d9e..12fe95c0 100644
--- a/net/url_request/http_with_dns_over_https_unittest.cc
+++ b/net/url_request/http_with_dns_over_https_unittest.cc
@@ -313,7 +313,7 @@
   EXPECT_EQ(test_https_requests_served_, 0u);
 
   EXPECT_TRUE(d.response_completed());
-  EXPECT_EQ(d.request_status(), net::ERR_NAME_NOT_RESOLVED);
+  EXPECT_EQ(d.request_status(), net::ERR_DNS_MALFORMED_RESPONSE);
 
   const auto& resolve_error_info = req->response_info().resolve_error_info;
   EXPECT_TRUE(resolve_error_info.is_secure_network_error);
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 105facc..94e9588a 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -215,10 +215,6 @@
 #define SK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS
 #endif
 
-#ifndef SK_SUPPORT_LEGACY_CANVAS_MATRIX_VIRTUALS
-#define SK_SUPPORT_LEGACY_CANVAS_MATRIX_VIRTUALS
-#endif
-
 // Max. verb count for paths rendered by the edge-AA tessellating path renderer.
 #define GR_AA_TESSELLATOR_MAX_VERB_COUNT 100
 
diff --git a/skia/ext/benchmarking_canvas.cc b/skia/ext/benchmarking_canvas.cc
index 97caba5e..ac9a132 100644
--- a/skia/ext/benchmarking_canvas.cc
+++ b/skia/ext/benchmarking_canvas.cc
@@ -435,6 +435,13 @@
   INHERITED::willRestore();
 }
 
+void BenchmarkingCanvas::didConcat44(const SkScalar m[16]) {
+  AutoOp op(this, "Concat44");
+  op.addParam("column-major", AsListValue(m, 16));
+
+  INHERITED::didConcat44(m);
+}
+
 void BenchmarkingCanvas::didConcat(const SkMatrix& m) {
   AutoOp op(this, "Concat");
   op.addParam("matrix", AsValue(m));
@@ -442,6 +449,22 @@
   INHERITED::didConcat(m);
 }
 
+void BenchmarkingCanvas::didScale(SkScalar x, SkScalar y) {
+  AutoOp op(this, "Scale");
+  op.addParam("scale-x", AsValue(x));
+  op.addParam("scale-y", AsValue(y));
+
+  INHERITED::didScale(x, y);
+}
+
+void BenchmarkingCanvas::didTranslate(SkScalar x, SkScalar y) {
+  AutoOp op(this, "Translate");
+  op.addParam("translate-x", AsValue(x));
+  op.addParam("translate-y", AsValue(y));
+
+  INHERITED::didTranslate(x, y);
+}
+
 void BenchmarkingCanvas::didSetMatrix(const SkMatrix& m) {
   AutoOp op(this, "SetMatrix");
   op.addParam("matrix", AsValue(m));
diff --git a/skia/ext/benchmarking_canvas.h b/skia/ext/benchmarking_canvas.h
index 67bfa93..08d74e7 100644
--- a/skia/ext/benchmarking_canvas.h
+++ b/skia/ext/benchmarking_canvas.h
@@ -32,7 +32,10 @@
   SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
   void willRestore() override;
 
+  void didConcat44(const SkScalar[16]) override;
   void didConcat(const SkMatrix&) override;
+  void didScale(SkScalar, SkScalar) override;
+  void didTranslate(SkScalar, SkScalar) override;
   void didSetMatrix(const SkMatrix&) override;
 
   void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index f6429932..c8505eb 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -70,6 +70,8 @@
     "blob/shareable_file_reference.h",
     "blob/view_blob_internals_job.cc",
     "blob/view_blob_internals_job.h",
+    "blob/write_blob_to_file.cc",
+    "blob/write_blob_to_file.h",
     "database/database_quota_client.cc",
     "database/database_quota_client.h",
     "database/database_tracker.cc",
@@ -273,6 +275,7 @@
     "blob/blob_reader_unittest.cc",
     "blob/blob_registry_impl_unittest.cc",
     "blob/blob_slice_unittest.cc",
+    "blob/blob_storage_context_mojo_unittest.cc",
     "blob/blob_storage_context_unittest.cc",
     "blob/blob_storage_registry_unittest.cc",
     "blob/blob_transport_strategy_unittest.cc",
diff --git a/storage/browser/blob/blob_builder_from_stream_unittest.cc b/storage/browser/blob/blob_builder_from_stream_unittest.cc
index 60035fd..41ec973 100644
--- a/storage/browser/blob/blob_builder_from_stream_unittest.cc
+++ b/storage/browser/blob/blob_builder_from_stream_unittest.cc
@@ -52,7 +52,7 @@
   void SetUp() override {
     ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
     context_ = std::make_unique<BlobStorageContext>(
-        data_dir_.GetPath(),
+        data_dir_.GetPath(), data_dir_.GetPath(),
         base::CreateTaskRunner({base::ThreadPool(), base::MayBlock()}));
 
     limits_.max_ipc_memory_size = kTestBlobStorageMaxBytesDataItemSize;
diff --git a/storage/browser/blob/blob_flattener_unittest.cc b/storage/browser/blob/blob_flattener_unittest.cc
index 592a3b7..cb750a2 100644
--- a/storage/browser/blob/blob_flattener_unittest.cc
+++ b/storage/browser/blob/blob_flattener_unittest.cc
@@ -179,8 +179,8 @@
   // * full data blob,
   // * pending data,
 
-  context_ =
-      std::make_unique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_);
+  context_ = std::make_unique<BlobStorageContext>(
+      temp_dir_.GetPath(), temp_dir_.GetPath(), file_runner_);
   SetTestMemoryLimits();
 
   std::unique_ptr<BlobDataHandle> data_blob;
diff --git a/storage/browser/blob/blob_registry_impl_unittest.cc b/storage/browser/blob/blob_registry_impl_unittest.cc
index 8cbcc69..af309aa 100644
--- a/storage/browser/blob/blob_registry_impl_unittest.cc
+++ b/storage/browser/blob/blob_registry_impl_unittest.cc
@@ -62,7 +62,7 @@
   void SetUp() override {
     ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
     context_ = std::make_unique<BlobStorageContext>(
-        data_dir_.GetPath(),
+        data_dir_.GetPath(), data_dir_.GetPath(),
         base::CreateTaskRunner({base::ThreadPool(), base::MayBlock()}));
     auto storage_policy =
         base::MakeRefCounted<content::MockSpecialStoragePolicy>();
diff --git a/storage/browser/blob/blob_storage_context.cc b/storage/browser/blob/blob_storage_context.cc
index 17060f7..d3f50f3 100644
--- a/storage/browser/blob/blob_storage_context.cc
+++ b/storage/browser/blob/blob_storage_context.cc
@@ -32,6 +32,7 @@
 #include "storage/browser/blob/blob_data_snapshot.h"
 #include "storage/browser/blob/blob_impl.h"
 #include "storage/browser/blob/shareable_blob_data_item.h"
+#include "storage/browser/blob/write_blob_to_file.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
 #include "third_party/blink/public/mojom/blob/data_element.mojom.h"
 #include "url/gurl.h"
@@ -43,15 +44,18 @@
 }  // namespace
 
 BlobStorageContext::BlobStorageContext()
-    : memory_controller_(base::FilePath(), scoped_refptr<base::TaskRunner>()) {
+    : profile_directory_(base::FilePath()),
+      memory_controller_(base::FilePath(), scoped_refptr<base::TaskRunner>()) {
   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
       this, "BlobStorageContext", base::ThreadTaskRunnerHandle::Get());
 }
 
 BlobStorageContext::BlobStorageContext(
-    base::FilePath storage_directory,
+    const base::FilePath& profile_directory,
+    const base::FilePath& storage_directory,
     scoped_refptr<base::TaskRunner> file_runner)
-    : memory_controller_(std::move(storage_directory), std::move(file_runner)) {
+    : profile_directory_(profile_directory),
+      memory_controller_(storage_directory, std::move(file_runner)) {
   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
       this, "BlobStorageContext", base::ThreadTaskRunnerHandle::Get());
 }
@@ -734,4 +738,45 @@
   BlobImpl::Create(std::move(handle), std::move(blob));
 }
 
+void BlobStorageContext::WriteBlobToFile(
+    mojo::PendingRemote<::blink::mojom::Blob> pending_blob,
+    const base::FilePath& file_path,
+    bool flush_on_write,
+    base::Optional<base::Time> last_modified,
+    BlobStorageContext::WriteBlobToFileCallback callback) {
+  DCHECK(!last_modified || !last_modified.value().is_null());
+  if (profile_directory_.empty()) {
+    std::move(callback).Run(mojom::WriteBlobToFileResult::kBadPath);
+    return;
+  }
+  if (file_path.ReferencesParent()) {
+    std::move(callback).Run(mojom::WriteBlobToFileResult::kBadPath);
+    return;
+  }
+  if (!profile_directory_.IsParent(file_path)) {
+    std::move(callback).Run(mojom::WriteBlobToFileResult::kBadPath);
+    return;
+  }
+
+  GetBlobDataFromBlobRemote(
+      std::move(pending_blob),
+      base::BindOnce(
+          [](base::WeakPtr<BlobStorageContext> blob_context,
+             const base::FilePath& file_path, bool flush_on_write,
+             base::Optional<base::Time> last_modified,
+             BlobStorageContext::WriteBlobToFileCallback callback,
+             std::unique_ptr<BlobDataHandle> handle) {
+            if (!handle || !blob_context) {
+              std::move(callback).Run(
+                  mojom::WriteBlobToFileResult::kInvalidBlob);
+              return;
+            }
+            ::storage::WriteBlobToFile(std::move(handle), file_path,
+                                       flush_on_write, last_modified,
+                                       std::move(callback));
+          },
+          AsWeakPtr(), file_path, flush_on_write, last_modified,
+          std::move(callback)));
+}
+
 }  // namespace storage
diff --git a/storage/browser/blob/blob_storage_context.h b/storage/browser/blob/blob_storage_context.h
index e2f71ba..3b93639 100644
--- a/storage/browser/blob/blob_storage_context.h
+++ b/storage/browser/blob/blob_storage_context.h
@@ -15,6 +15,7 @@
 
 #include "base/callback_forward.h"
 #include "base/component_export.h"
+#include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -58,7 +59,8 @@
   // Initializes the context without disk support.
   BlobStorageContext();
   // Disk support is enabled if |file_runner| isn't null.
-  BlobStorageContext(base::FilePath storage_directory,
+  BlobStorageContext(const base::FilePath& profile_directory,
+                     const base::FilePath& blob_storage_directory,
                      scoped_refptr<base::TaskRunner> file_runner);
   ~BlobStorageContext() override;
 
@@ -249,7 +251,13 @@
   void RegisterFromMemory(mojo::PendingReceiver<::blink::mojom::Blob> blob,
                           const std::string& uuid,
                           mojo_base::BigBuffer data) override;
+  void WriteBlobToFile(mojo::PendingRemote<::blink::mojom::Blob> blob,
+                       const base::FilePath& path,
+                       bool flush_on_write,
+                       base::Optional<base::Time> last_modified,
+                       WriteBlobToFileCallback callback) override;
 
+  base::FilePath profile_directory_;
   BlobStorageRegistry registry_;
   BlobMemoryController memory_controller_;
   mojo::ReceiverSet<mojom::BlobStorageContext> receivers_;
diff --git a/storage/browser/blob/blob_storage_context_mojo_unittest.cc b/storage/browser/blob/blob_storage_context_mojo_unittest.cc
new file mode 100644
index 0000000..41e1354
--- /dev/null
+++ b/storage/browser/blob/blob_storage_context_mojo_unittest.cc
@@ -0,0 +1,590 @@
+// 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 "storage/browser/blob/blob_storage_context.h"
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/span.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_restrictions.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/data_pipe_drainer.h"
+#include "net/base/net_errors.h"
+#include "storage/browser/blob/blob_data_builder.h"
+#include "storage/browser/blob/blob_impl.h"
+#include "storage/browser/blob/mojom/blob_storage_context.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/blob/blob.mojom.h"
+
+namespace storage {
+namespace {
+
+class DataPipeReader : public mojo::DataPipeDrainer::Client {
+ public:
+  DataPipeReader(std::string* data_out, base::OnceClosure done_callback)
+      : data_out_(data_out), done_callback_(std::move(done_callback)) {}
+
+  void OnDataAvailable(const void* data, size_t num_bytes) override {
+    data_out_->append(static_cast<const char*>(data), num_bytes);
+  }
+
+  void OnDataComplete() override { std::move(done_callback_).Run(); }
+
+ private:
+  std::string* data_out_;
+  base::OnceClosure done_callback_;
+};
+
+std::string ReadDataPipe(mojo::ScopedDataPipeConsumerHandle pipe) {
+  base::RunLoop loop;
+  std::string data;
+  DataPipeReader reader(&data, loop.QuitClosure());
+  mojo::DataPipeDrainer drainer(&reader, std::move(pipe));
+  loop.Run();
+  return data;
+}
+
+class BlobStorageContextMojoTest : public testing::Test {
+ protected:
+  BlobStorageContextMojoTest() = default;
+  ~BlobStorageContextMojoTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    base::ThreadRestrictions::SetIOAllowed(false);
+  }
+
+  void TearDown() override {
+    task_environment_.RunUntilIdle();
+    base::ThreadRestrictions::SetIOAllowed(true);
+    ASSERT_TRUE(!temp_dir_.IsValid() || temp_dir_.Delete());
+  }
+
+  void SetUpOnDiskContext() {
+    if (!file_runner_) {
+      file_runner_ = base::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::ThreadPool()});
+    }
+    context_ = std::make_unique<BlobStorageContext>(
+        temp_dir_.GetPath(), temp_dir_.GetPath(), file_runner_);
+  }
+
+  void SetUpInMemoryContext() {
+    context_ = std::make_unique<BlobStorageContext>();
+  }
+
+  mojo::Remote<mojom::BlobStorageContext> CreateContextConnection() {
+    mojo::Remote<mojom::BlobStorageContext> remote_context;
+    context_->Bind(remote_context.BindNewPipeAndPassReceiver());
+    return remote_context;
+  }
+
+  std::string UUIDFromBlob(blink::mojom::Blob* blob) {
+    base::RunLoop loop;
+    std::string received_uuid;
+    blob->GetInternalUUID(base::BindOnce(
+        [](base::OnceClosure quit_closure, std::string* uuid_out,
+           const std::string& uuid) {
+          *uuid_out = uuid;
+          std::move(quit_closure).Run();
+        },
+        loop.QuitClosure(), &received_uuid));
+    loop.Run();
+    return received_uuid;
+  }
+
+  // This is used because Mac saves file modification timestamps in second
+  // granularity.
+  base::Time TruncateToSeconds(base::Time time) {
+    base::Time::Exploded exploded;
+    time.UTCExplode(&exploded);
+    exploded.millisecond = 0;
+    EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time));
+    return time;
+  }
+
+  base::ScopedTempDir temp_dir_;
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::IO};
+  scoped_refptr<base::SequencedTaskRunner> file_runner_;
+  std::unique_ptr<BlobStorageContext> context_;
+};
+
+TEST_F(BlobStorageContextMojoTest, BasicBlobCreation) {
+  SetUpInMemoryContext();
+  const std::string kData = "Hello There!";
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  EXPECT_EQ(std::string("1234"), UUIDFromBlob(blob.get()));
+
+  mojo::ScopedDataPipeProducerHandle data_pipe_producer;
+  mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
+  ASSERT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(nullptr, &data_pipe_producer,
+                                                 &data_pipe_consumer));
+  blob->ReadAll(std::move(data_pipe_producer), mojo::NullRemote());
+  std::string received = ReadDataPipe(std::move(data_pipe_consumer));
+  EXPECT_EQ(std::string(kData), received);
+}
+
+TEST_F(BlobStorageContextMojoTest, SaveBlobToFile) {
+  SetUpOnDiskContext();
+  const std::string kData = "Hello There!";
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  // Create a 'last modified' that is different from now.
+  base::Time last_modified =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+
+  base::RunLoop loop;
+  base::FilePath file_path = temp_dir_.GetPath().AppendASCII("TestFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, last_modified,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, kData);
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+
+  // Because Mac rounds file modification time to the nearest second, make sure
+  // the difference is within that range.
+  base::TimeDelta difference = file_info.last_modified - last_modified;
+  EXPECT_LT(difference.magnitude(), base::TimeDelta::FromSeconds(1));
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, SaveBlobToFileNoDate) {
+  SetUpOnDiskContext();
+  const std::string kData = "Hello There!";
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  base::RunLoop loop;
+  base::FilePath file_path = temp_dir_.GetPath().AppendASCII("TestFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, base::nullopt,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, kData);
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, SaveEmptyBlobToFile) {
+  SetUpOnDiskContext();
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(blob.BindNewPipeAndPassReceiver(), "1234",
+                              mojo_base::BigBuffer());
+
+  // Create a 'last modified' that is different from now.
+  base::Time last_modified =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+
+  base::RunLoop loop;
+  base::FilePath file_path = temp_dir_.GetPath().AppendASCII("TestFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, last_modified,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, std::string(""));
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+
+  // Because Mac rounds file modification time to the nearest second, make sure
+  // the difference is within that range.
+  base::TimeDelta difference = file_info.last_modified - last_modified;
+  EXPECT_LT(difference.magnitude(), base::TimeDelta::FromSeconds(1));
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, FileCopyOptimization) {
+  SetUpOnDiskContext();
+  const std::string kData = "Hello There!";
+
+  base::FilePath copy_from_file =
+      temp_dir_.GetPath().AppendASCII("SourceFile.txt");
+
+  // Create a 'modification_time' that is different from now.
+  base::Time modification_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int size = base::WriteFile(copy_from_file, kData.data(), kData.size());
+    ASSERT_GT(size, 0);
+    EXPECT_EQ(size, static_cast<int>(kData.size()));
+    ASSERT_TRUE(
+        base::TouchFile(copy_from_file, modification_time, modification_time));
+  }
+
+  std::unique_ptr<BlobDataBuilder> builder =
+      std::make_unique<BlobDataBuilder>("1234");
+  builder->AppendFile(copy_from_file, 0, kData.size(), modification_time);
+  std::unique_ptr<BlobDataHandle> blob_handle =
+      context_->AddFinishedBlob(std::move(builder));
+
+  mojo::PendingRemote<blink::mojom::Blob> blob;
+  BlobImpl::Create(std::move(blob_handle),
+                   blob.InitWithNewPipeAndPassReceiver());
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("DestinationFile.txt");
+  context->WriteBlobToFile(
+      std::move(blob), file_path, true, modification_time,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, kData);
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+
+  // Because Mac rounds file modification time to the nearest second, make sure
+  // the difference is within that range.
+  base::TimeDelta difference = file_info.last_modified - modification_time;
+  EXPECT_LT(difference.magnitude(), base::TimeDelta::FromSeconds(1));
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, FileCopyOptimizationOffsetSize) {
+  SetUpOnDiskContext();
+  static const std::string kData = "Hello There!";
+  static const int64_t kOffset = 1;
+  static const int64_t kSize = kData.size() - 2;
+
+  base::FilePath copy_from_file =
+      temp_dir_.GetPath().AppendASCII("SourceFile.txt");
+
+  // Create a 'modification_time' that is different from now.
+  base::Time modification_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int size = base::WriteFile(copy_from_file, kData.data(), kData.size());
+    ASSERT_GT(size, 0);
+    EXPECT_EQ(size, static_cast<int>(kData.size()));
+    ASSERT_TRUE(
+        base::TouchFile(copy_from_file, modification_time, modification_time));
+  }
+
+  std::unique_ptr<BlobDataBuilder> builder =
+      std::make_unique<BlobDataBuilder>("1234");
+  builder->AppendFile(copy_from_file, kOffset, kSize, modification_time);
+  std::unique_ptr<BlobDataHandle> blob_handle =
+      context_->AddFinishedBlob(std::move(builder));
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  BlobImpl::Create(std::move(blob_handle), blob.BindNewPipeAndPassReceiver());
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("DestinationFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, modification_time,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, kData.substr(kOffset, kSize));
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+
+  // Because Mac rounds file modification time to the nearest second, make sure
+  // the difference is within that range.
+  base::TimeDelta difference = file_info.last_modified - modification_time;
+  EXPECT_LT(difference.magnitude(), base::TimeDelta::FromSeconds(1));
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, FileCopyEmptyFile) {
+  SetUpOnDiskContext();
+  static const std::string kData = "";
+
+  base::FilePath copy_from_file =
+      temp_dir_.GetPath().AppendASCII("SourceFile.txt");
+
+  // Create a 'modification_time' that is different from now.
+  base::Time modification_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int size = base::WriteFile(copy_from_file, kData.data(), kData.size());
+    ASSERT_EQ(size, 0);
+    ASSERT_TRUE(
+        base::TouchFile(copy_from_file, modification_time, modification_time));
+  }
+
+  std::unique_ptr<BlobDataBuilder> builder =
+      std::make_unique<BlobDataBuilder>("1234");
+  builder->AppendFile(copy_from_file, 0ll, 0ll, modification_time);
+  std::unique_ptr<BlobDataHandle> blob_handle =
+      context_->AddFinishedBlob(std::move(builder));
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  BlobImpl::Create(std::move(blob_handle), blob.BindNewPipeAndPassReceiver());
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("DestinationFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, modification_time,
+      base::BindLambdaForTesting([&loop](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kSuccess);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(file_path, &file_contents));
+  EXPECT_EQ(file_contents, std::string(""));
+
+  base::File::Info file_info;
+  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+  EXPECT_EQ(file_info.size, 0);
+
+  // Because Mac rounds file modification time to the nearest second, make sure
+  // the difference is within that range.
+  base::TimeDelta difference = file_info.last_modified - modification_time;
+  EXPECT_LT(difference.magnitude(), base::TimeDelta::FromSeconds(1));
+
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, InvalidInputFileSize) {
+  SetUpOnDiskContext();
+  static const std::string kData = "ABCDE";
+
+  base::FilePath copy_from_file =
+      temp_dir_.GetPath().AppendASCII("SourceFile.txt");
+
+  // Create a 'modification_time' that is different from now.
+  base::Time modification_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int size = base::WriteFile(copy_from_file, kData.data(), kData.size());
+    ASSERT_EQ(size, static_cast<int>(kData.size()));
+    ASSERT_TRUE(
+        base::TouchFile(copy_from_file, modification_time, modification_time));
+  }
+
+  std::unique_ptr<BlobDataBuilder> builder =
+      std::make_unique<BlobDataBuilder>("1234");
+  builder->AppendFile(copy_from_file, 0ll, kData.size() * 2, modification_time);
+  std::unique_ptr<BlobDataHandle> blob_handle =
+      context_->AddFinishedBlob(std::move(builder));
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  BlobImpl::Create(std::move(blob_handle), blob.BindNewPipeAndPassReceiver());
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("DestinationFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, modification_time,
+      base::BindLambdaForTesting([&loop](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kInvalidBlob);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, InvalidInputFileTimeModified) {
+  SetUpOnDiskContext();
+  static const std::string kData = "ABCDE";
+
+  base::FilePath copy_from_file =
+      temp_dir_.GetPath().AppendASCII("SourceFile.txt");
+
+  // Create a 'modification_time' that is different from now.
+  base::Time bad_modified_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(2));
+  base::Time file_modified_time =
+      TruncateToSeconds(base::Time::Now() - base::TimeDelta::FromDays(1));
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int size = base::WriteFile(copy_from_file, kData.data(), kData.size());
+    ASSERT_EQ(size, static_cast<int>(kData.size()));
+    ASSERT_TRUE(base::TouchFile(copy_from_file, file_modified_time,
+                                file_modified_time));
+  }
+
+  std::unique_ptr<BlobDataBuilder> builder =
+      std::make_unique<BlobDataBuilder>("1234");
+  builder->AppendFile(copy_from_file, 0ll, kData.size(), bad_modified_time);
+  std::unique_ptr<BlobDataHandle> blob_handle =
+      context_->AddFinishedBlob(std::move(builder));
+
+  mojo::Remote<blink::mojom::Blob> blob;
+  BlobImpl::Create(std::move(blob_handle), blob.BindNewPipeAndPassReceiver());
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("DestinationFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, base::nullopt,
+      base::BindLambdaForTesting([&loop](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kInvalidBlob);
+        loop.Quit();
+      }));
+  loop.Run();
+
+  base::ThreadRestrictions::SetIOAllowed(true);
+  base::DeleteFile(file_path, false);
+  ASSERT_TRUE(temp_dir_.Delete());
+}
+
+TEST_F(BlobStorageContextMojoTest, NoProfileDirectory) {
+  SetUpInMemoryContext();
+  static const std::string kData = "ABCDE";
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  base::RunLoop loop;
+  base::FilePath file_path = temp_dir_.GetPath().AppendASCII("TestFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, base::nullopt,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kBadPath);
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(BlobStorageContextMojoTest, PathWithReferences) {
+  SetUpOnDiskContext();
+  static const std::string kData = "ABCDE";
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  base::RunLoop loop;
+  base::FilePath file_path =
+      temp_dir_.GetPath().AppendASCII("..").AppendASCII("UnaccessibleFile.txt");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, base::nullopt,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kBadPath);
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(BlobStorageContextMojoTest, InvalidPath) {
+  SetUpOnDiskContext();
+  static const std::string kData = "ABCDE";
+
+  mojo::Remote<mojom::BlobStorageContext> context = CreateContextConnection();
+  mojo::Remote<blink::mojom::Blob> blob;
+  context->RegisterFromMemory(
+      blob.BindNewPipeAndPassReceiver(), "1234",
+      mojo_base::BigBuffer(base::as_bytes(base::make_span(kData))));
+
+  base::RunLoop loop;
+  base::FilePath file_path = base::FilePath::FromUTF8Unsafe("/etc/passwd");
+  context->WriteBlobToFile(
+      blob.Unbind(), file_path, true, base::nullopt,
+      base::BindLambdaForTesting([&](mojom::WriteBlobToFileResult result) {
+        EXPECT_EQ(result, mojom::WriteBlobToFileResult::kBadPath);
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+}  // namespace
+}  // namespace storage
diff --git a/storage/browser/blob/blob_storage_context_unittest.cc b/storage/browser/blob/blob_storage_context_unittest.cc
index dc5fc4c..770bf64 100644
--- a/storage/browser/blob/blob_storage_context_unittest.cc
+++ b/storage/browser/blob/blob_storage_context_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "net/base/io_buffer.h"
@@ -76,12 +77,14 @@
 
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    base::ThreadRestrictions::SetIOAllowed(false);
     context_ = std::make_unique<BlobStorageContext>();
   }
 
   void TearDown() override {
     base::RunLoop().RunUntilIdle();
-    file_runner_->RunPendingTasks();
+    RunFileTasks();
+    base::ThreadRestrictions::SetIOAllowed(true);
     ASSERT_TRUE(temp_dir_.Delete());
   }
 
@@ -126,6 +129,11 @@
     return received_uuid;
   }
 
+  void RunFileTasks() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    file_runner_->RunPendingTasks();
+  }
+
   std::vector<FileCreationInfo> files_;
   base::ScopedTempDir temp_dir_;
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
@@ -500,8 +508,8 @@
 
 TEST_F(BlobStorageContextTest, BuildFutureFileOnlyBlob) {
   const std::string kId1("id1");
-  context_ =
-      std::make_unique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_);
+  context_ = std::make_unique<BlobStorageContext>(
+      temp_dir_.GetPath(), temp_dir_.GetPath(), file_runner_);
   SetTestMemoryLimits();
 
   auto builder = std::make_unique<BlobDataBuilder>(kId1);
@@ -521,7 +529,7 @@
   EXPECT_EQ(0u, blobs_finished);
 
   EXPECT_TRUE(file_runner_->HasPendingTask());
-  file_runner_->RunPendingTasks();
+  RunFileTasks();
   EXPECT_EQ(0u, blobs_finished);
   EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status);
   EXPECT_EQ(BlobStatus::PENDING_QUOTA, handle->GetBlobStatus());
@@ -546,7 +554,7 @@
   base::RunLoop().RunUntilIdle();
   // We should have file cleanup tasks.
   EXPECT_TRUE(file_runner_->HasPendingTask());
-  file_runner_->RunPendingTasks();
+  RunFileTasks();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0lu, context_->memory_controller().memory_usage());
   EXPECT_EQ(0lu, context_->memory_controller().disk_usage());
@@ -770,8 +778,8 @@
 TEST_F(BlobStorageContextTest, BuildBlobCombinations) {
   const std::string kId("id");
 
-  context_ =
-      std::make_unique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_);
+  context_ = std::make_unique<BlobStorageContext>(
+      temp_dir_.GetPath(), temp_dir_.GetPath(), file_runner_);
 
   SetTestMemoryLimits();
   auto data_handle = base::MakeRefCounted<storage::FakeBlobDataHandle>(
@@ -837,7 +845,7 @@
   // We should be needing to send a page or two to disk.
   EXPECT_TRUE(file_runner_->HasPendingTask());
   do {
-    file_runner_->RunPendingTasks();
+    RunFileTasks();
     base::RunLoop().RunUntilIdle();
     // Continue populating data for items that can fit.
     for (size_t i = 0; i < kTotalRawBlobs; i++) {
@@ -871,7 +879,7 @@
   files_.clear();
   // We should have file cleanup tasks.
   EXPECT_TRUE(file_runner_->HasPendingTask());
-  file_runner_->RunPendingTasks();
+  RunFileTasks();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0lu, context_->memory_controller().memory_usage());
   EXPECT_EQ(0lu, context_->memory_controller().disk_usage());
diff --git a/storage/browser/blob/mojom/blob_storage_context.mojom b/storage/browser/blob/mojom/blob_storage_context.mojom
index 45d4698..f3706c7 100644
--- a/storage/browser/blob/mojom/blob_storage_context.mojom
+++ b/storage/browser/blob/mojom/blob_storage_context.mojom
@@ -5,6 +5,8 @@
 module storage.mojom;
 
 import "mojo/public/mojom/base/big_buffer.mojom";
+import "mojo/public/mojom/base/file_path.mojom";
+import "mojo/public/mojom/base/time.mojom";
 import "third_party/blink/public/mojom/blob/blob.mojom";
 
 // A reader for the data and side data in a cache storage entry.
@@ -44,10 +46,23 @@
   pending_remote<BlobDataItemReader> reader;
 };
 
+// The result of writing a blob to disk.
+enum WriteBlobToFileResult {
+  kError,             // There was an error writing the blob to a file.
+  kBadPath,           // The path given is not accessible or has references.
+  kInvalidBlob,       // The blob is invalid and cannot be read.
+  kIOError,           // Error writing bytes on disk.
+  kTimestampError,    // Error writing the last modified timestamp.
+  kSuccess,
+};
+
 // This interface is the primary access point to the browser's blob system
-// for chrome internals.  This is a simplified version of the
-// blink.mojom.BlobRegistry interface.  To avoid giving the renderer
-// different capabilities, this is a separate interface.
+// for chrome internals. This interface lives in the browser process. This is a
+// simplified version of the blink.mojom.BlobRegistry interface.
+//
+// This interface has enhanced capabilities that should NOT be accessible to a
+// renderer, which is why it is a separate interface. For example,
+// WriteBlobToFile writes a blob to an arbitrary file path.
 interface BlobStorageContext {
   // Create a blob with a particular uuid and consisting of a single
   // BlobDataItem::DataHandle constructed from |item|.
@@ -57,4 +72,15 @@
   // in |data|.
   RegisterFromMemory(pending_receiver<blink.mojom.Blob> blob, string uuid,
                      mojo_base.mojom.BigBuffer data);
+
+  // Writes the given blob to the given file path. If the given |path| is not
+  // under the blob storage context's profile directory or if it has references
+  // (like "..") then the implementation returns kBadPath. If a file already
+  // exists at |path| then it is overwritten. If |flush_on_write| is true, then
+  // Flush will be called on the new file before it is closed.
+  WriteBlobToFile(pending_remote<blink.mojom.Blob> blob,
+                  mojo_base.mojom.FilePath path,
+                  bool flush_on_write,
+                  mojo_base.mojom.Time? last_modified)
+      => (WriteBlobToFileResult result);
 };
diff --git a/storage/browser/blob/write_blob_to_file.cc b/storage/browser/blob/write_blob_to_file.cc
new file mode 100644
index 0000000..3611778
--- /dev/null
+++ b/storage/browser/blob/write_blob_to_file.cc
@@ -0,0 +1,341 @@
+// 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 "storage/browser/blob/write_blob_to_file.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <limits>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/numerics/checked_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_data_item.h"
+#include "storage/browser/blob/blob_data_snapshot.h"
+#include "storage/browser/blob/shareable_blob_data_item.h"
+#include "storage/browser/file_system/file_stream_reader.h"
+#include "storage/browser/file_system/file_stream_writer.h"
+#include "storage/browser/file_system/file_writer_delegate.h"
+#include "storage/common/file_system/file_system_mount_option.h"
+#include "third_party/blink/public/common/blob/blob_utils.h"
+
+namespace storage {
+namespace {
+using DelegateNoProgressWriteCallback = base::OnceCallback<void(
+    base::File::Error result,
+    int64_t bytes,
+    FileWriterDelegate::WriteProgressStatus write_status)>;
+
+struct WriteState {
+  std::unique_ptr<FileWriterDelegate> delegate;
+  DelegateNoProgressWriteCallback callback;
+  base::CheckedNumeric<int64_t> total_bytes;
+};
+
+// Utility function to ignore all progress events returned when running
+// FileWriterDelegate::Start. This means that there is either a single call for
+// a success or failure. This wrapper also handles owning the lifetime of the
+// FileWriterDelegate, which it destructs after receiving a success or error.
+FileWriterDelegate::DelegateWriteCallback IgnoreProgressWrapper(
+    std::unique_ptr<FileWriterDelegate> delegate,
+    DelegateNoProgressWriteCallback callback) {
+  return base::BindRepeating(
+      [](WriteState* write_state, base::File::Error result, int64_t bytes,
+         FileWriterDelegate::WriteProgressStatus write_status) {
+        DCHECK_GE(bytes, 0);
+        DCHECK(!write_state->callback.is_null());
+        DCHECK(write_state->delegate);
+        if (result == base::File::FILE_OK) {
+          DCHECK(write_status == FileWriterDelegate::SUCCESS_COMPLETED ||
+                 write_status == FileWriterDelegate::SUCCESS_IO_PENDING);
+        } else {
+          DCHECK(write_status == FileWriterDelegate::ERROR_WRITE_STARTED ||
+                 write_status == FileWriterDelegate::ERROR_WRITE_NOT_STARTED);
+        }
+
+        write_state->total_bytes += bytes;
+        if (write_status == FileWriterDelegate::SUCCESS_IO_PENDING)
+          return;
+        std::move(write_state->callback)
+            .Run(result, write_state->total_bytes.ValueOrDie(), write_status);
+        // This is necessary because FileWriterDelegate owns this repeating
+        // callback, so there is a cyclic dependency.
+        write_state->delegate.reset();
+      },
+      base::Owned(new WriteState{std::move(delegate), std::move(callback), 0}));
+}
+
+// Copied from file_util_posix.cc, with the addition of |offset| and |max_size|
+// parameters. The parameters apply to the |infile|. The |bytes_copied|
+// parameter keeps track of the number of bytes written to |outfile|.
+// Note - this function can still succeed if the size |infile| is less than
+// |max_size|. The caller should use |bytes_copied| to know exactly how many
+// bytes were copied.
+bool CopyFileContentsWithOffsetAndSize(base::File* infile,
+                                       base::File* outfile,
+                                       int64_t* bytes_copied,
+                                       int64_t offset,
+                                       int64_t max_size) {
+  static constexpr size_t kBufferSize = 32768;
+  DCHECK_GE(max_size, 0);
+  *bytes_copied = 0;
+  base::CheckedNumeric<int64_t> checked_max_size = max_size;
+
+  std::vector<char> buffer(
+      std::min(kBufferSize, base::checked_cast<size_t>(max_size)));
+  infile->Seek(base::File::FROM_BEGIN, offset);
+
+  for (;;) {
+    size_t bytes_to_read =
+        std::min(buffer.size(), base::saturated_cast<size_t>(max_size));
+    int bytes_read = infile->ReadAtCurrentPos(buffer.data(), bytes_to_read);
+    if (bytes_read < 0)
+      return false;
+    if (bytes_read == 0)
+      return true;
+    checked_max_size -= bytes_read;
+    if (!checked_max_size.IsValid())
+      return false;
+
+    // Allow for partial writes
+    int bytes_written_per_read = 0;
+    do {
+      int bytes_written_partial = outfile->WriteAtCurrentPos(
+          &buffer[bytes_written_per_read], bytes_read - bytes_written_per_read);
+      if (bytes_written_partial < 0)
+        return false;
+
+      bytes_written_per_read += bytes_written_partial;
+      *bytes_copied += bytes_written_partial;
+    } while (bytes_written_per_read < bytes_read);
+    if (checked_max_size.ValueOrDie() == 0)
+      return true;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
+// Copies the contents of |copy_from| to |copy_to|, with the given |offset| and
+// |size| applied to the |copy_from| file. The
+// |expected_last_modified_copy_from| must match, within a second, the last
+// modified time of the |copy_from| file. Afterwards, the |last_modified| date
+// is optionally saved as the last modified & last accessed time of |copy_to|.
+// If |flush_on_close| is true, then Flush is called on the |copy_to| file
+// before it is closed.
+mojom::WriteBlobToFileResult CopyFileAndMaybeWriteTimeModified(
+    const base::FilePath& copy_from,
+    base::Time expected_last_modified_copy_from,
+    const base::FilePath& copy_to,
+    int64_t offset,
+    int64_t size,
+    base::Optional<base::Time> last_modified,
+    bool flush_on_close) {
+  // Do a full file copy if the sizes match and there is no offset.
+  if (offset == 0) {
+    base::File::Info info;
+    base::GetFileInfo(copy_from, &info);
+    if (!storage::FileStreamReader::VerifySnapshotTime(
+            expected_last_modified_copy_from, info)) {
+      return mojom::WriteBlobToFileResult::kInvalidBlob;
+    }
+    if (info.size == size) {
+      bool success = base::CopyFile(copy_from, copy_to);
+      if (!success)
+        return mojom::WriteBlobToFileResult::kIOError;
+      if (!base::TouchFile(copy_to, last_modified.value(),
+                           last_modified.value())) {
+        return mojom::WriteBlobToFileResult::kTimestampError;
+      }
+    }
+  }
+
+  // Do a manual file-to-file copy. This will overwrite the file if there
+  // already is one.
+  base::File infile =
+      base::File(copy_from, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  base::File outfile(copy_to,
+                     base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
+  if (!outfile.IsValid())
+    return mojom::WriteBlobToFileResult::kIOError;
+
+  base::File::Info info;
+  infile.GetInfo(&info);
+  if (!storage::FileStreamReader::VerifySnapshotTime(
+          expected_last_modified_copy_from, info)) {
+    return mojom::WriteBlobToFileResult::kInvalidBlob;
+  }
+
+  int64_t bytes_copied = 0;
+  if (!CopyFileContentsWithOffsetAndSize(&infile, &outfile, &bytes_copied,
+                                         offset, size)) {
+    return mojom::WriteBlobToFileResult::kIOError;
+  }
+  if (bytes_copied != size)
+    return mojom::WriteBlobToFileResult::kInvalidBlob;
+
+  if (!outfile.SetTimes(last_modified.value(), last_modified.value())) {
+    // If the file modification time isn't set correctly, then reading will
+    // fail.
+    return mojom::WriteBlobToFileResult::kTimestampError;
+  }
+  if (flush_on_close)
+    outfile.Flush();
+  outfile.Close();
+  return mojom::WriteBlobToFileResult::kSuccess;
+}
+
+mojom::WriteBlobToFileResult CreateEmptyFileAndMaybeSetModifiedTime(
+    base::FilePath file_path,
+    base::Optional<base::Time> last_modified) {
+  base::File file(file_path,
+                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+  bool file_success = file.created();
+  if (!file_success)
+    return mojom::WriteBlobToFileResult::kIOError;
+  if (last_modified &&
+      !file.SetTimes(last_modified.value(), last_modified.value())) {
+    // If the file modification time isn't set correctly, then reading
+    // the blob later will fail. Thus, failing to save it is an error.
+    file.Close();
+    return mojom::WriteBlobToFileResult::kTimestampError;
+  }
+  file.Close();
+  return mojom::WriteBlobToFileResult::kSuccess;
+}
+
+void HandleModifiedTimeOnBlobFileWriteComplete(
+    base::FilePath file_path,
+    base::Optional<base::Time> last_modified,
+    mojom::BlobStorageContext::WriteBlobToFileCallback callback,
+    base::File::Error rv,
+    int64_t bytes_written,
+    storage::FileWriterDelegate::WriteProgressStatus write_status) {
+  bool success = write_status == storage::FileWriterDelegate::SUCCESS_COMPLETED;
+  if (success && !bytes_written) {
+    // Special Case 1: Success but no bytes were written, so just create
+    // an empty file (LocalFileStreamWriter only creates a file
+    // if data is actually written).
+    base::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+         base::ThreadPool()},
+        base::BindOnce(CreateEmptyFileAndMaybeSetModifiedTime,
+                       std::move(file_path), last_modified),
+        std::move(callback));
+    return;
+  } else if (success && last_modified) {
+    // Special Case 2: Success and |last_modified| needs to be set. Set
+    // that before reporting write completion.
+    base::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+         base::ThreadPool()},
+        base::BindOnce(
+            [](int64_t bytes_written, base::FilePath file_path,
+               base::Optional<base::Time> last_modified) {
+              if (!base::TouchFile(file_path, last_modified.value(),
+                                   last_modified.value())) {
+                // If the file modification time isn't set correctly, then
+                // reading the blob later will fail. Thus, failing to save it is
+                // an error.
+                return mojom::WriteBlobToFileResult::kTimestampError;
+              }
+              return mojom::WriteBlobToFileResult::kSuccess;
+            },
+            bytes_written, std::move(file_path), last_modified),
+        std::move(callback));
+    return;
+  }
+  std::move(callback).Run(mojom::WriteBlobToFileResult::kSuccess);
+}
+
+void WriteConstructedBlobToFile(
+    std::unique_ptr<BlobDataHandle> blob_handle,
+    const base::FilePath& file_path,
+    bool flush_on_write,
+    base::Optional<base::Time> last_modified,
+    mojom::BlobStorageContext::WriteBlobToFileCallback callback,
+    BlobStatus status) {
+  DCHECK(!last_modified || !last_modified.value().is_null());
+  if (status != BlobStatus::DONE) {
+    DCHECK(BlobStatusIsError(status));
+    std::move(callback).Run(mojom::WriteBlobToFileResult::kInvalidBlob);
+    return;
+  }
+  // Check if we can do a copy optimization.
+  // TODO(dmurph): Optimize the case of IDB blobs, which have a type
+  // kReadableDataHandle.
+  std::unique_ptr<BlobDataSnapshot> snapshot = blob_handle->CreateSnapshot();
+  const auto& items = snapshot->items();
+  if (items.size() == 1) {
+    const BlobDataItem& item = *items[0];
+    if (item.type() == BlobDataItem::Type::kFile) {
+      // The File API cannot handle uint64_t.
+      if (item.length() > std::numeric_limits<int64_t>::max()) {
+        std::move(callback).Run(mojom::WriteBlobToFileResult::kError);
+        return;
+      }
+      if (item.offset() > std::numeric_limits<int64_t>::max()) {
+        std::move(callback).Run(mojom::WriteBlobToFileResult::kError);
+        return;
+      }
+
+      base::PostTaskAndReplyWithResult(
+          FROM_HERE,
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+           base::ThreadPool()},
+          base::BindOnce(CopyFileAndMaybeWriteTimeModified, item.path(),
+                         item.expected_modification_time(), file_path,
+                         item.offset(), item.length(), last_modified,
+                         flush_on_write),
+          std::move(callback));
+      return;
+    }
+  }
+
+  // If not, copy the BlobReader and FileStreamWriter.
+  std::unique_ptr<storage::FileStreamWriter> writer =
+      storage::FileStreamWriter::CreateForLocalFile(
+          base::CreateTaskRunner({base::MayBlock(), base::ThreadPool(),
+                                  base::TaskPriority::USER_VISIBLE})
+              .get(),
+          file_path, /*initial_offset=*/0,
+          storage::FileStreamWriter::CREATE_NEW_FILE_ALWAYS);
+
+  storage::FlushPolicy policy =
+      flush_on_write || last_modified
+          ? storage::FlushPolicy::FLUSH_ON_COMPLETION
+          : storage::FlushPolicy::NO_FLUSH_ON_COMPLETION;
+  std::unique_ptr<storage::FileWriterDelegate> delegate(
+      std::make_unique<storage::FileWriterDelegate>(std::move(writer), policy));
+
+  auto* raw_delegate = delegate.get();
+  raw_delegate->Start(
+      blob_handle->CreateReader(),
+      IgnoreProgressWrapper(
+          std::move(delegate),
+          base::BindOnce(HandleModifiedTimeOnBlobFileWriteComplete, file_path,
+                         std::move(last_modified), std::move(callback))));
+}
+
+}  // namespace
+
+void WriteBlobToFile(
+    std::unique_ptr<BlobDataHandle> blob_handle,
+    const base::FilePath& file_path,
+    bool flush_on_write,
+    base::Optional<base::Time> last_modified,
+    mojom::BlobStorageContext::WriteBlobToFileCallback callback) {
+  auto* blob_handle_ptr = blob_handle.get();
+  blob_handle_ptr->RunOnConstructionComplete(base::BindOnce(
+      &WriteConstructedBlobToFile, std::move(blob_handle), file_path,
+      flush_on_write, last_modified, std::move(callback)));
+}
+
+}  // namespace storage
diff --git a/storage/browser/blob/write_blob_to_file.h b/storage/browser/blob/write_blob_to_file.h
new file mode 100644
index 0000000..0f0968b
--- /dev/null
+++ b/storage/browser/blob/write_blob_to_file.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 STORAGE_BROWSER_BLOB_WRITE_BLOB_TO_FILE_H_
+#define STORAGE_BROWSER_BLOB_WRITE_BLOB_TO_FILE_H_
+
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_entry.h"
+#include "storage/browser/blob/mojom/blob_storage_context.mojom.h"
+
+namespace storage {
+
+// Writes the blob at |blob_handle| to the file at |file_path|. If a file
+// already exists, then it is overwritten. If |flush_on_write| is true, then the
+// Flush will be called on the file before it is closed. If |last_modified| is
+// populated, then the file's last modified & last accessed time will be set to
+// |last_modified|.
+// If successful, |callback| is called with the resulting file size. If not,
+// then a net error code is used ( < 0).
+void WriteBlobToFile(
+    std::unique_ptr<BlobDataHandle> blob_handle,
+    const base::FilePath& file_path,
+    bool flush_on_write,
+    base::Optional<base::Time> last_modified,
+    mojom::BlobStorageContext::WriteBlobToFileCallback callback);
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_BLOB_WRITE_BLOB_TO_FILE_H_
diff --git a/storage/browser/file_system/file_stream_writer.h b/storage/browser/file_system/file_stream_writer.h
index 4a9c79e..2ddbecc6 100644
--- a/storage/browser/file_system/file_stream_writer.h
+++ b/storage/browser/file_system/file_stream_writer.h
@@ -31,7 +31,11 @@
 // A generic interface for writing to a file-like object.
 class FileStreamWriter {
  public:
-  enum OpenOrCreate { OPEN_EXISTING_FILE, CREATE_NEW_FILE };
+  enum OpenOrCreate {
+    OPEN_EXISTING_FILE,
+    CREATE_NEW_FILE,
+    CREATE_NEW_FILE_ALWAYS
+  };
 
   // Creates a writer for the existing file in the path |file_path| starting
   // from |initial_offset|. Uses |task_runner| for async file operations.
@@ -44,6 +48,8 @@
 
   // Creates a writer for the existing memory file in the path |file_path|
   // starting from |initial_offset|.
+  // TODO(mek): Remove or use |open_or_create| field here, as it is not
+  // currently used. https://crbug.com/1041048
   COMPONENT_EXPORT(STORAGE_BROWSER)
   static std::unique_ptr<FileStreamWriter> CreateForMemoryFile(
       base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
diff --git a/storage/browser/file_system/local_file_stream_writer.cc b/storage/browser/file_system/local_file_stream_writer.cc
index b2ddacfe..342f1e8 100644
--- a/storage/browser/file_system/local_file_stream_writer.cc
+++ b/storage/browser/file_system/local_file_stream_writer.cc
@@ -20,6 +20,9 @@
     base::File::FLAG_OPEN | base::File::FLAG_WRITE | base::File::FLAG_ASYNC;
 const int kCreateFlagsForWrite =
     base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_ASYNC;
+const int kCreateFlagsForWriteAlways = base::File::FLAG_CREATE_ALWAYS |
+                                       base::File::FLAG_WRITE |
+                                       base::File::FLAG_ASYNC;
 
 }  // namespace
 
@@ -110,6 +113,9 @@
     case CREATE_NEW_FILE:
       open_flags = kCreateFlagsForWrite;
       break;
+    case CREATE_NEW_FILE_ALWAYS:
+      open_flags = kCreateFlagsForWriteAlways;
+      break;
   }
 
   return stream_impl_->Open(
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 74a12a6..e2eb7c5 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -647,6 +647,26 @@
             ]
         }
     ],
+    "AssumeOverlapAfterFixedOrStickyPosition": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AssumeOverlapAfterFixedOrStickyPosition"
+                    ]
+                }
+            ]
+        }
+    ],
     "AsyncDns": [
         {
             "platforms": [
@@ -3157,8 +3177,8 @@
                 {
                     "name": "WideRange",
                     "params": {
-                        "RatioLowerBound": "0.1",
-                        "RatioUpperBound": "0.2"
+                        "IncognitoQuotaRatioLowerBound": "0.1",
+                        "IncognitoQuotaRatioUpperBound": "0.2"
                     },
                     "enable_features": [
                         "IncognitoDynamicQuota"
@@ -5213,7 +5233,7 @@
                 {
                     "name": "Enabled",
                     "params": {
-                        "connection_options": "NSTP",
+                        "connection_options": "5RTO,ACKD",
                         "enable_quic": "true"
                     },
                     "enable_features": [
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 15059dc..88cae84 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -237,11 +237,14 @@
     "mediasession/media_session.mojom",
     "payments/payment_handler_host.mojom",
     "payments/payment_request.mojom",
-    "remote_objects/remote_objects.mojom",
     "webauthn/authenticator.mojom",
     "webauthn/internal_authenticator.mojom",
     "webshare/webshare.mojom",
   ]
+
+  if (is_android) {
+    sources += [ "remote_objects/remote_objects.mojom" ]
+  }
   public_deps = [
     "//components/payments/mojom",
     "//mojo/public/mojom/base",
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h
index e4748e4..91ff86c 100644
--- a/third_party/blink/renderer/core/css/selector_checker.h
+++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -48,7 +48,10 @@
   STACK_ALLOCATED();
 
  public:
-  enum VisitedMatchType { kVisitedMatchDisabled, kVisitedMatchEnabled };
+  enum VisitedMatchType : uint8_t {
+    kVisitedMatchDisabled,
+    kVisitedMatchEnabled
+  };
 
   enum Mode {
     // Used when matching selectors inside style recalc. This mode will set
@@ -89,12 +92,15 @@
   };
 
   explicit SelectorChecker(const Init& init)
-      : mode_(init.mode),
-        is_ua_rule_(init.is_ua_rule),
-        element_style_(init.element_style),
+      : element_style_(init.element_style),
         scrollbar_(init.scrollbar),
+        part_names_(init.part_names),
         scrollbar_part_(init.scrollbar_part),
-        part_names_(init.part_names) {}
+        mode_(init.mode) {
+#if DCHECK_IS_ON()
+    is_ua_rule_ = init.is_ua_rule;
+#endif
+  }
 
   // Wraps the current element and a CSSSelector and stores some other state of
   // the selector matching process.
@@ -105,31 +111,20 @@
     // Initial selector constructor
     SelectorCheckingContext(Element* element,
                             VisitedMatchType visited_match_type)
-        : selector(nullptr),
-          element(element),
-          previous_element(nullptr),
-          scope(nullptr),
-          visited_match_type(visited_match_type),
-          pseudo_id(kPseudoIdNone),
-          is_sub_selector(false),
-          in_rightmost_compound(true),
-          has_scrollbar_pseudo(false),
-          has_selection_pseudo(false),
-          treat_shadow_host_as_normal_scope(false),
-          is_from_vtt(false) {}
+        : element(element), visited_match_type(visited_match_type) {}
 
-    const CSSSelector* selector;
-    Member<Element> element;
-    Member<Element> previous_element;
-    Member<const ContainerNode> scope;
+    const CSSSelector* selector = nullptr;
+    Member<Element> element = nullptr;
+    Member<Element> previous_element = nullptr;
+    Member<const ContainerNode> scope = nullptr;
     VisitedMatchType visited_match_type;
-    PseudoId pseudo_id;
-    bool is_sub_selector;
-    bool in_rightmost_compound;
-    bool has_scrollbar_pseudo;
-    bool has_selection_pseudo;
-    bool treat_shadow_host_as_normal_scope;
-    bool is_from_vtt;
+    PseudoId pseudo_id = kPseudoIdNone;
+    bool is_sub_selector = false;
+    bool in_rightmost_compound = true;
+    bool has_scrollbar_pseudo = false;
+    bool has_selection_pseudo = false;
+    bool treat_shadow_host_as_normal_scope = false;
+    bool is_from_vtt = false;
   };
 
   struct MatchResult {
@@ -215,12 +210,16 @@
   bool CheckPseudoNot(const SelectorCheckingContext&, MatchResult&) const;
   bool CheckPseudoNotForVTT(const SelectorCheckingContext&, MatchResult&) const;
 
-  Mode mode_;
-  bool is_ua_rule_;
   ComputedStyle* element_style_;
   Member<CustomScrollbar> scrollbar_;
-  ScrollbarPart scrollbar_part_;
   PartNames* part_names_;
+  ScrollbarPart scrollbar_part_;
+  Mode mode_;
+#if DCHECK_IS_ON()
+  bool is_ua_rule_;
+#else
+  static constexpr bool is_ua_rule_ = true;
+#endif
   DISALLOW_COPY_AND_ASSIGN(SelectorChecker);
 };
 
diff --git a/third_party/blink/renderer/core/style/computed_style_constants.h b/third_party/blink/renderer/core/style/computed_style_constants.h
index ab01fcdd..cc396e7 100644
--- a/third_party/blink/renderer/core/style/computed_style_constants.h
+++ b/third_party/blink/renderer/core/style/computed_style_constants.h
@@ -29,6 +29,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_COMPUTED_STYLE_CONSTANTS_H_
 
 #include <cstddef>
+#include <cstdint>
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
 
 namespace blink {
@@ -50,7 +51,7 @@
 enum class BoxSide : unsigned { kTop, kRight, kBottom, kLeft };
 
 // Static pseudo styles. Dynamic ones are produced on the fly.
-enum PseudoId {
+enum PseudoId : uint8_t {
   // The order must be NOP ID, public IDs, and then internal IDs.
   // If you add or remove a public ID, you must update the field_size of
   // "PseudoBits" in computed_style_extra_fields.json5.
@@ -76,10 +77,6 @@
   kAfterLastInternalPseudoId,
   kFirstPublicPseudoId = kPseudoIdFirstLine,
   kFirstInternalPseudoId = kPseudoIdFirstLineInherited,
-  kElementPseudoIdMask = (1 << (kPseudoIdBefore - kFirstPublicPseudoId)) |
-                         (1 << (kPseudoIdAfter - kFirstPublicPseudoId)) |
-                         (1 << (kPseudoIdMarker - kFirstPublicPseudoId)) |
-                         (1 << (kPseudoIdBackdrop - kFirstPublicPseudoId))
 };
 
 enum class OutlineIsAuto : bool { kOff = false, kOn = true };
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 12ccba1e..524741e 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -129,7 +129,6 @@
     "//third_party/blink/renderer/modules/push_messaging",
     "//third_party/blink/renderer/modules/quota",
     "//third_party/blink/renderer/modules/remoteplayback",
-    "//third_party/blink/renderer/modules/remote_objects",
     "//third_party/blink/renderer/modules/scheduler",
     "//third_party/blink/renderer/modules/screen_enumeration",
     "//third_party/blink/renderer/modules/screen_orientation",
@@ -157,6 +156,10 @@
     "//third_party/blink/renderer/modules/xr",
   ]
 
+  if (is_android) {
+    sub_modules += [ "//third_party/blink/renderer/modules/remote_objects" ]
+  }
+
   deps = [
     ":make_modules_generated",
     ":module_names",
diff --git a/third_party/blink/renderer/modules/modules_initializer.cc b/third_party/blink/renderer/modules/modules_initializer.cc
index da30a92..5d2a4ed 100644
--- a/third_party/blink/renderer/modules/modules_initializer.cc
+++ b/third_party/blink/renderer/modules/modules_initializer.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/ptr_util.h"
+#include "build/build_config.h"
 #include "mojo/public/cpp/bindings/binder_map.h"
 #include "third_party/blink/public/mojom/dom_storage/session_storage_namespace.mojom-blink.h"
 #include "third_party/blink/public/platform/interface_registry.h"
@@ -68,7 +69,6 @@
 #include "third_party/blink/renderer/modules/presentation/presentation_controller.h"
 #include "third_party/blink/renderer/modules/presentation/presentation_receiver.h"
 #include "third_party/blink/renderer/modules/push_messaging/push_messaging_client.h"
-#include "third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h"
 #include "third_party/blink/renderer/modules/remoteplayback/html_media_element_remote_playback.h"
 #include "third_party/blink/renderer/modules/remoteplayback/remote_playback.h"
 #include "third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller_impl.h"
@@ -100,6 +100,10 @@
 #include "third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context.h"
 #endif
 
+#if defined(OS_ANDROID)
+#include "third_party/blink/renderer/modules/remote_objects/remote_object_gateway_impl.h"
+#endif
+
 namespace blink {
 
 void ModulesInitializer::Initialize() {
@@ -171,8 +175,10 @@
       &AppBannerController::BindMojoRequest, WrapWeakPersistent(&frame)));
   frame.GetInterfaceRegistry()->AddInterface(WTF::BindRepeating(
       &TextSuggestionBackendImpl::Create, WrapWeakPersistent(&frame)));
+#if defined(OS_ANDROID)
   frame.GetInterfaceRegistry()->AddInterface(WTF::BindRepeating(
       &RemoteObjectGatewayFactoryImpl::Create, WrapWeakPersistent(&frame)));
+#endif  // OS_ANDROID
 }
 
 void ModulesInitializer::InstallSupplements(LocalFrame& frame) const {
@@ -254,10 +260,12 @@
     PresentationReceiver::From(document);
   }
 
+#if defined(OS_ANDROID)
   LocalFrame* frame = document.GetFrame();
   DCHECK(frame);
   if (auto* gateway = RemoteObjectGatewayImpl::From(*frame))
     gateway->OnClearWindowObjectInMainWorld();
+#endif  // OS_ANDROID
 }
 
 std::unique_ptr<WebMediaPlayer> ModulesInitializer::CreateWebMediaPlayer(
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 3b731cf5..2f8e452 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -447,29 +447,19 @@
   paint_chunk_indices.push_back(chunk_index);
 }
 
-void PaintArtifactCompositor::PendingLayer::Merge(
-    const PendingLayer& guest,
-    const PropertyTreeState& merged_state) {
-  DCHECK(compositing_type != kRequiresOwnLayer &&
-         guest.compositing_type != kRequiresOwnLayer);
-  DCHECK_EQ(&property_tree_state.Effect(), &merged_state.Effect());
+bool PaintArtifactCompositor::PendingLayer::Merge(const PendingLayer& guest) {
+  if (!CanMerge(guest, guest.property_tree_state, &property_tree_state,
+                &bounds))
+    return false;
 
   paint_chunk_indices.AppendVector(guest.paint_chunk_indices);
-  if (merged_state != property_tree_state) {
-    FloatClipRect new_home_bounds(bounds);
-    GeometryMapper::LocalToAncestorVisualRect(property_tree_state, merged_state,
-                                              new_home_bounds);
-    bounds = new_home_bounds.Rect();
-    property_tree_state = merged_state;
-  }
-  FloatClipRect guest_bounds_in_home(guest.bounds);
-  GeometryMapper::LocalToAncestorVisualRect(
-      guest.property_tree_state, property_tree_state, guest_bounds_in_home);
-  bounds.Unite(guest_bounds_in_home.Rect());
+
   // TODO(crbug.com/701991): Upgrade GeometryMapper.
   // If we knew the new bounds is enclosed by the mapped opaque region of
   // the guest layer, we can deduce the merged layer being opaque too, and
   // update rect_known_to_be_opaque accordingly.
+
+  return true;
 }
 
 void PaintArtifactCompositor::PendingLayer::Upcast(
@@ -570,19 +560,55 @@
   return PropertyTreeState(*upcast_transform, clip_lca, home.Effect());
 }
 
-base::Optional<PropertyTreeState>
-PaintArtifactCompositor::PendingLayer::CanMerge(
+// We will only allow merging if the merged-area:home-area+guest-area doesn't
+// exceed the ratio |kMergingSparsityTolerance|:1.
+static constexpr float kMergeSparsityTolerance = 6;
+
+bool PaintArtifactCompositor::PendingLayer::CanMerge(
     const PendingLayer& guest,
-    const PropertyTreeState& guest_state) const {
+    const PropertyTreeState& guest_state,
+    PropertyTreeState* out_merged_state,
+    FloatRect* out_merged_bounds) const {
   if (compositing_type == kRequiresOwnLayer ||
       guest.compositing_type == kRequiresOwnLayer) {
-    return base::nullopt;
+    return false;
   }
   if (&property_tree_state.Effect().Unalias() !=
       &guest_state.Effect().Unalias()) {
-    return base::nullopt;
+    return false;
   }
-  return CanUpcastWith(guest_state, property_tree_state);
+
+  const base::Optional<PropertyTreeState>& merged_state =
+      CanUpcastWith(guest_state, property_tree_state);
+  if (!merged_state)
+    return false;
+
+  FloatClipRect new_home_bounds(bounds);
+  GeometryMapper::LocalToAncestorVisualRect(property_tree_state, *merged_state,
+                                            new_home_bounds);
+  FloatClipRect new_guest_bounds(guest.bounds);
+  GeometryMapper::LocalToAncestorVisualRect(guest_state, *merged_state,
+                                            new_guest_bounds);
+
+  FloatRect merged_bounds =
+      UnionRect(new_home_bounds.Rect(), new_guest_bounds.Rect());
+  // Don't check for sparcity if we may further decomposite the effect, so that
+  // the merged layer may be merged to other layers with the decomposited
+  // effect, which is often better than not merging even if the merged layer is
+  // sparse because we may create less composited effects and render surfaces.
+  if (guest_state.Effect().IsRoot() ||
+      guest_state.Effect().HasDirectCompositingReasons()) {
+    float sum_area = new_home_bounds.Rect().Size().Area() +
+                     new_guest_bounds.Rect().Size().Area();
+    if (merged_bounds.Size().Area() > kMergeSparsityTolerance * sum_area)
+      return false;
+  }
+
+  if (out_merged_state)
+    *out_merged_state = *merged_state;
+  if (out_merged_bounds)
+    *out_merged_bounds = merged_bounds;
+  return true;
 }
 
 // Returns nullptr if 'ancestor' is not a strict ancestor of 'node'.
@@ -742,10 +768,10 @@
   // Every paint chunk will be visited by the main loop below for exactly
   // once, except for chunks that enter or exit groups (case B & C below). For
   // normal chunk visit (case A), the only cost is determining squash, which
-  // costs O(qd), where d came from "canUpcastTo" and geometry mapping.
+  // costs O(qd), where d came from |CanUpcastWith| and geometry mapping.
   // Subtotal: O(pqd)
   // For group entering and exiting, it could cost O(d) for each group, for
-  // searching the shallowest subgroup (strictChildOfAlongPath), thus O(d^2)
+  // searching the shallowest subgroup (StrictChildOfAlongPath), thus O(d^2)
   // in total.
   // Also when exiting group, the group may be decomposited and squashed to a
   // previous layer. Again finding the host costs O(qd). Merging would cost
@@ -806,10 +832,7 @@
     for (wtf_size_t candidate_index = pending_layers_.size() - 1;
          candidate_index-- > first_layer_in_current_group;) {
       PendingLayer& candidate_layer = pending_layers_[candidate_index];
-      if (const base::Optional<PropertyTreeState>& merged_state =
-              candidate_layer.CanMerge(new_layer,
-                                       new_layer.property_tree_state)) {
-        candidate_layer.Merge(new_layer, *merged_state);
+      if (candidate_layer.Merge(new_layer)) {
         pending_layers_.pop_back();
         break;
       }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index fddee901..e11cc3d7 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -205,19 +205,21 @@
                  wtf_size_t first_chunk_index,
                  bool requires_own_layer);
 
-    // Merge another pending layer into this one, appending all its paint
-    // chunks after chunks in this layer, with appropriate space conversion
-    // applied to both this layer and the guest layer from their original
-    // property tree state to |merged_state|.
-    void Merge(const PendingLayer& guest,
-               const PropertyTreeState& merged_state);
-    // If the guest layer can be merged into this layer, returns the property
-    // tree state of the merged layer. |guest_state| is for cases that we want
-    // to check if we can merge |guest| if it has |guest_state| in the future
-    // (which may be different from its current state).
-    base::Optional<PropertyTreeState> CanMerge(
-        const PendingLayer& guest,
-        const PropertyTreeState& guest_state) const;
+    // Merges |guest| into |this| if it can, by appending chunks of |guest|
+    // after chunks of |this|, with appropriate space conversion applied to
+    // both layers from their original property tree states to |merged_state|.
+    // Returns whether the merge is successful.
+    bool Merge(const PendingLayer& guest);
+
+    // Returns true if |guest| can be merged into |this|, and sets the output
+    // paramsters with the property tree state and bounds of the merged layer.
+    // |guest_state| is for cases that we want to check if we can merge |guest|
+    // if it has |guest_state| in the future (which may be different from its
+    // current state).
+    bool CanMerge(const PendingLayer& guest,
+                  const PropertyTreeState& guest_state,
+                  PropertyTreeState* merged_state = nullptr,
+                  FloatRect* merged_bounds = nullptr) const;
 
     // Mutate this layer's property tree state to a more general (shallower)
     // state, thus the name "upcast". The concrete effect of this is to
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index d798f75..e8d3facd0 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -1927,8 +1927,7 @@
   chunk2.properties = chunk1.properties;
   chunk2.known_to_be_opaque = true;
   chunk2.bounds = IntRect(10, 20, 30, 40);
-  pending_layer.Merge(PendingLayer(chunk2, 1, false),
-                      pending_layer.property_tree_state);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
 
   // Bounds not equal to one PaintChunk.
   EXPECT_EQ(FloatRect(0, 0, 40, 60), pending_layer.bounds);
@@ -1939,35 +1938,150 @@
   chunk3.properties = chunk1.properties;
   chunk3.known_to_be_opaque = true;
   chunk3.bounds = IntRect(-5, -25, 20, 20);
-  pending_layer.Merge(PendingLayer(chunk3, 2, false),
-                      pending_layer.property_tree_state);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk3, 2, false)));
 
   EXPECT_EQ(FloatRect(-5, -25, 45, 85), pending_layer.bounds);
   EXPECT_EQ((Vector<wtf_size_t>{0, 1, 2}), pending_layer.paint_chunk_indices);
   EXPECT_NE(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
 }
 
-TEST_P(PaintArtifactCompositorTest, PendingLayerWithGeometry) {
-  auto transform =
-      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
-                      FloatPoint3D(100, 100, 0));
+TEST_P(PaintArtifactCompositorTest, PendingLayerMergeWithGuestTransform) {
+  auto transform = Create2DTranslation(t0(), 20, 25);
 
   PaintChunk chunk1 = DefaultChunk();
   chunk1.properties = PropertyTreeState::Root();
   chunk1.bounds = IntRect(0, 0, 30, 40);
 
-  PendingLayer pending_layer(chunk1, 0, false);
-
-  EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
-
   PaintChunk chunk2 = DefaultChunk();
   chunk2.properties = chunk1.properties;
   SetTransform(chunk2, *transform);
   chunk2.bounds = IntRect(0, 0, 50, 60);
-  pending_layer.Merge(PendingLayer(chunk2, 1, false),
-                      pending_layer.property_tree_state);
 
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
   EXPECT_EQ(FloatRect(0, 0, 70, 85), pending_layer.bounds);
+  EXPECT_EQ(PropertyTreeState::Root(), pending_layer.property_tree_state);
+}
+
+TEST_P(PaintArtifactCompositorTest, PendingLayerMergeWithHomeTransform) {
+  auto transform = Create2DTranslation(t0(), 20, 25);
+
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState::Root();
+  SetTransform(chunk1, *transform);
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = PropertyTreeState::Root();
+  chunk2.bounds = IntRect(0, 0, 50, 60);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(0, 0, 50, 65), pending_layer.bounds);
+  EXPECT_EQ(PropertyTreeState::Root(), pending_layer.property_tree_state);
+}
+
+TEST_P(PaintArtifactCompositorTest, PendingLayerMergeWithBothTransforms) {
+  auto t1 = Create2DTranslation(t0(), 20, 25);
+  auto t2 = Create2DTranslation(t0(), -20, -25);
+
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState::Root();
+  SetTransform(chunk1, *t1);
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = PropertyTreeState::Root();
+  SetTransform(chunk2, *t2);
+  chunk2.bounds = IntRect(0, 0, 50, 60);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(-20, -25, 70, 90), pending_layer.bounds);
+  EXPECT_EQ(PropertyTreeState::Root(), pending_layer.property_tree_state);
+}
+
+TEST_P(PaintArtifactCompositorTest, PendingLayerDontMergeSparse) {
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState::Root();  // (t0(), c0(), *e1);
+  chunk1.known_to_be_opaque = true;
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = chunk1.properties;
+  chunk2.known_to_be_opaque = true;
+  chunk2.bounds = IntRect(200, 200, 30, 40);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_FALSE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
+  EXPECT_EQ(chunk1.properties, pending_layer.property_tree_state);
+  EXPECT_EQ(Vector<wtf_size_t>{0}, pending_layer.paint_chunk_indices);
+}
+
+TEST_P(PaintArtifactCompositorTest, PendingLayerMergeSparseWithTransforms) {
+  auto t1 = Create2DTranslation(t0(), 20, 25);
+  auto t2 = Create2DTranslation(t0(), 1000, 1000);
+
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState::Root();
+  SetTransform(chunk1, *t1);
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = PropertyTreeState::Root();
+  SetTransform(chunk2, *t2);
+  chunk2.bounds = IntRect(0, 0, 50, 60);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_FALSE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
+  EXPECT_EQ(chunk1.properties, pending_layer.property_tree_state);
+  EXPECT_EQ(Vector<wtf_size_t>{0}, pending_layer.paint_chunk_indices);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       PendingLayerDontMergeSparseInCompositedEffect) {
+  auto t1 = Create2DTranslation(t0(), 20, 25);
+  auto t2 = Create2DTranslation(t0(), 1000, 1000);
+  auto e1 =
+      CreateOpacityEffect(e0(), 1.0f, CompositingReason::kWillChangeOpacity);
+
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState(*t1, c0(), *e1);
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = PropertyTreeState(*t2, c0(), *e1);
+  chunk2.bounds = IntRect(0, 0, 50, 60);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_FALSE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(0, 0, 30, 40), pending_layer.bounds);
+  EXPECT_EQ(chunk1.properties, pending_layer.property_tree_state);
+  EXPECT_EQ(Vector<wtf_size_t>{0}, pending_layer.paint_chunk_indices);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       PendingLayerMergeSparseInNonCompositedEffect) {
+  auto t1 = Create2DTranslation(t0(), 20, 25);
+  auto t2 = Create2DTranslation(t0(), 1000, 1000);
+  auto e1 = CreateOpacityEffect(e0(), 1.0f, CompositingReason::kNone);
+
+  PaintChunk chunk1 = DefaultChunk();
+  chunk1.properties = PropertyTreeState(*t1, c0(), *e1);
+  chunk1.bounds = IntRect(0, 0, 30, 40);
+
+  PaintChunk chunk2 = DefaultChunk();
+  chunk2.properties = PropertyTreeState(*t2, c0(), *e1);
+  chunk2.bounds = IntRect(0, 0, 50, 60);
+
+  PendingLayer pending_layer(chunk1, 0, false);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
+  EXPECT_EQ(FloatRect(20, 25, 1030, 1035), pending_layer.bounds);
+  EXPECT_EQ(PropertyTreeState(t0(), c0(), *e1),
+            pending_layer.property_tree_state);
+  EXPECT_EQ((Vector<wtf_size_t>{0, 1}), pending_layer.paint_chunk_indices);
 }
 
 // TODO(crbug.com/701991):
@@ -1985,8 +2099,7 @@
   chunk2.properties = chunk1.properties;
   chunk2.bounds = IntRect(0, 0, 25, 35);
   chunk2.known_to_be_opaque = true;
-  pending_layer.Merge(PendingLayer(chunk2, 1, false),
-                      pending_layer.property_tree_state);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk2, 1, false)));
 
   // Chunk 2 doesn't cover the entire layer, so not opaque.
   EXPECT_EQ(FloatRect(chunk2.bounds), pending_layer.rect_known_to_be_opaque);
@@ -1996,8 +2109,7 @@
   chunk3.properties = chunk1.properties;
   chunk3.bounds = IntRect(0, 0, 50, 60);
   chunk3.known_to_be_opaque = true;
-  pending_layer.Merge(PendingLayer(chunk3, 2, false),
-                      pending_layer.property_tree_state);
+  ASSERT_TRUE(pending_layer.Merge(PendingLayer(chunk3, 2, false)));
 
   // Chunk 3 covers the entire layer, so now it's opaque.
   EXPECT_EQ(FloatRect(chunk3.bounds), pending_layer.bounds);
diff --git a/third_party/blink/renderer/platform/graphics/intercepting_canvas.h b/third_party/blink/renderer/platform/graphics/intercepting_canvas.h
index bb7e499..d90c750 100644
--- a/third_party/blink/renderer/platform/graphics/intercepting_canvas.h
+++ b/third_party/blink/renderer/platform/graphics/intercepting_canvas.h
@@ -127,7 +127,10 @@
                      const SkMatrix*,
                      const SkPaint*) override = 0;
   void didSetMatrix(const SkMatrix&) override = 0;
+  void didConcat44(const SkScalar[16]) override = 0;
   void didConcat(const SkMatrix&) override = 0;
+  void didScale(SkScalar, SkScalar) override = 0;
+  void didTranslate(SkScalar, SkScalar) override = 0;
   void willSave() override = 0;
   SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override = 0;
   void willRestore() override = 0;
@@ -285,12 +288,22 @@
 
   void didSetMatrix(const SkMatrix& matrix) override {
     Interceptor interceptor(this);
-    this->SkCanvas::didSetMatrix(matrix);
+  }
+
+  void didConcat44(const SkScalar m[16]) override {
+    Interceptor interceptor(this);
   }
 
   void didConcat(const SkMatrix& matrix) override {
     Interceptor interceptor(this);
-    this->SkCanvas::didConcat(matrix);
+  }
+
+  void didScale(SkScalar x, SkScalar y) override {
+    Interceptor interceptor(this);
+  }
+
+  void didTranslate(SkScalar x, SkScalar y) override {
+    Interceptor interceptor(this);
   }
 
   void willSave() override {
diff --git a/third_party/blink/renderer/platform/graphics/logging_canvas.cc b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
index b22ea1d..5c3de9a 100644
--- a/third_party/blink/renderer/platform/graphics/logging_canvas.cc
+++ b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
@@ -315,6 +315,14 @@
   return matrix_array;
 }
 
+std::unique_ptr<JSONArray> ArrayForSkScalars(size_t count,
+                                             const SkScalar array[]) {
+  auto points_array_item = std::make_unique<JSONArray>();
+  for (size_t i = 0; i < count; ++i)
+    points_array_item->PushDouble(array[i]);
+  return points_array_item;
+}
+
 std::unique_ptr<JSONObject> ObjectForSkShader(const SkShader& shader) {
   return std::make_unique<JSONObject>();
 }
@@ -677,7 +685,12 @@
   AutoLogger logger(this);
   JSONObject* params = logger.LogItemWithParams("setMatrix");
   params->SetArray("matrix", ArrayForSkMatrix(matrix));
-  this->SkCanvas::didSetMatrix(matrix);
+}
+
+void LoggingCanvas::didConcat44(const SkScalar m[16]) {
+  AutoLogger logger(this);
+  JSONObject* params = logger.LogItemWithParams("concat44");
+  params->SetArray("matrix44", ArrayForSkScalars(16, m));
 }
 
 void LoggingCanvas::didConcat(const SkMatrix& matrix) {
@@ -701,7 +714,20 @@
       params = logger.LogItemWithParams("concat");
       params->SetArray("matrix", ArrayForSkMatrix(matrix));
   }
-  this->SkCanvas::didConcat(matrix);
+}
+
+void LoggingCanvas::didScale(SkScalar x, SkScalar y) {
+  AutoLogger logger(this);
+  JSONObject* params = logger.LogItemWithParams("scale");
+  params->SetDouble("scaleX", x);
+  params->SetDouble("scaleY", y);
+}
+
+void LoggingCanvas::didTranslate(SkScalar x, SkScalar y) {
+  AutoLogger logger(this);
+  JSONObject* params = logger.LogItemWithParams("translate");
+  params->SetDouble("dx", x);
+  params->SetDouble("dy", y);
 }
 
 void LoggingCanvas::willSave() {
diff --git a/third_party/blink/renderer/platform/graphics/logging_canvas.h b/third_party/blink/renderer/platform/graphics/logging_canvas.h
index 3951bbf5..06826768 100644
--- a/third_party/blink/renderer/platform/graphics/logging_canvas.h
+++ b/third_party/blink/renderer/platform/graphics/logging_canvas.h
@@ -91,7 +91,10 @@
                      const SkMatrix*,
                      const SkPaint*) override;
   void didSetMatrix(const SkMatrix&) override;
+  void didConcat44(const SkScalar[16]) override;
   void didConcat(const SkMatrix&) override;
+  void didScale(SkScalar, SkScalar) override;
+  void didTranslate(SkScalar, SkScalar) override;
   void willSave() override;
   SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
   void willRestore() override;
diff --git a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner.py b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner.py
index 921224e..6418a8b9 100644
--- a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner.py
+++ b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner.py
@@ -105,6 +105,7 @@
             test_inputs,
             int(self._options.child_processes),
             self._options.fully_parallel,
+            self._options.virtual_parallel,
             batch_size == 1)
 
         self._reorder_tests_by_args(locked_shards)
@@ -376,7 +377,7 @@
         self._split = test_split_fn
         self._max_locked_shards = max_locked_shards
 
-    def shard_tests(self, test_inputs, num_workers, fully_parallel, run_singly):
+    def shard_tests(self, test_inputs, num_workers, fully_parallel, parallel_includes_virtual, run_singly):
         """Groups tests into batches.
         This helps ensure that tests that depend on each other (aka bad tests!)
         continue to run together as most cross-tests dependencies tend to
@@ -392,7 +393,7 @@
         if num_workers == 1:
             return self._shard_in_two(test_inputs)
         elif fully_parallel:
-            return self._shard_every_file(test_inputs, run_singly)
+            return self._shard_every_file(test_inputs, run_singly, parallel_includes_virtual)
         return self._shard_by_directory(test_inputs)
 
     def _shard_in_two(self, test_inputs):
@@ -417,7 +418,7 @@
 
         return locked_shards, unlocked_shards
 
-    def _shard_every_file(self, test_inputs, run_singly):
+    def _shard_every_file(self, test_inputs, run_singly, virtual_is_unlocked):
         """Returns two lists of shards, each shard containing a single test file.
 
         This mode gets maximal parallelism at the cost of much higher flakiness.
@@ -432,7 +433,7 @@
             # which would be really redundant.
             if test_input.requires_lock:
                 locked_shards.append(TestShard('.', [test_input]))
-            elif test_input.test_name.startswith('virtual') and not run_singly:
+            elif test_input.test_name.startswith('virtual') and not run_singly and not virtual_is_unlocked:
                 # This violates the spirit of sharding every file, but in practice, since the
                 # virtual test suites require a different commandline flag and thus a restart
                 # of content_shell, it's too slow to shard them fully.
diff --git a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner_unittest.py b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner_unittest.py
index e83e182d..6d2c46d 100644
--- a/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_runner_unittest.py
@@ -185,7 +185,7 @@
         self.sharder = Sharder(port.split_test, max_locked_shards)
         test_list = test_list or self.test_list
         return self.sharder.shard_tests([self.get_test_input(test) for test in test_list],
-                                        num_workers, fully_parallel, run_singly)
+                                        num_workers, fully_parallel, False, run_singly)
 
     def assert_shards(self, actual_shards, expected_shard_names):
         self.assertEqual(len(actual_shards), len(expected_shard_names))
diff --git a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
index f341257..e670c5d2 100644
--- a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
+++ b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
@@ -481,6 +481,11 @@
                 action='store_true',
                 help='run all tests in parallel'),
             optparse.make_option(
+                '--virtual-parallel',
+                action='store_true',
+                help='When running in parallel, include virtual tests. Useful for running a single '
+                     'virtual test suite, but will be slower in other cases.'),
+            optparse.make_option(
                 '-i', '--ignore-tests',
                 action='append',
                 default=[],
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index 381c06b..8ecd54b 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -75,14 +75,12 @@
 crbug.com/667946 compositing/overflow/scrolls-with-respect-to.html [ Failure ]
 compositing/squashing/do-not-squash-scroll-child-with-composited-descendants.html [ Failure ]
 compositing/squashing/squashed-repaints.html [ Failure ]
-compositing/squashing/squashing-sparsity-heuristic.html [ Failure ]
 compositing/visibility/layer-visible-content.html [ Failure ]
 external/wpt/css/css-transforms/transform3d-backface-visibility-006.html [ Failure ]
 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-overflow.html [ Crash ]
 external/wpt/largest-contentful-paint/invisible-images.html [ Failure ]
 external/wpt/portals/portals-rendering.html [ Failure ]
 fast/css/outline-offset-large.html [ Failure ]
-fast/forms/validation-bubble-device-emulation-change.html [ Failure ]
 fast/webgl/pixelated.html [ Failure ]
 fast/sub-pixel/transformed-iframe-copy-on-scroll.html [ Failure ]
 fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index cd58b7d2..5c974689 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1759,9 +1759,6 @@
 # in-progress rebaselines, tracked at crbug.com/1035582.
 # ======
 
-# -
-# -
-# -
 # Group 1 Leftovers:
 crbug.com/1035582 fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-mouse-events.html [ Failure Pass ]
 crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-datetimelocal.html [ Failure Pass ]
@@ -1782,35 +1779,7 @@
 crbug.com/1035582 fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-clearbutton-change-and-input-events.html [ Failure Pass ]
 crbug.com/1035582 fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-spinbutton-change-and-input-events.html [ Failure Pass ]
 crbug.com/1035582 fast/forms/datetimelocal-multiple-fields/datetimelocal-multiple-fields-wheel-event.html [ Failure Pass ]
-# Focus ring:
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-ar.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-coarse.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-minimum-date.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-required-ar.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-required.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-ru.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-step.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-zoom125.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance-zoom200.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/calendar-picker-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/month-picker-appearance-step.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/month-picker-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/week-picker-appearance-step.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/calendar-picker/week-picker-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/color/color-suggestion-picker-appearance-zoom125.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/color/color-suggestion-picker-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/color/color-suggestion-picker-one-row-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/color/color-suggestion-picker-two-row-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/color/color-suggestion-picker-with-scrollbar-appearance.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/date/date-appearance-basic.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/datetimelocal/datetimelocal-appearance-basic.html [ Failure Pass ]
 
-
-# -
-# -
-# -
-# -
-# -
 # Group 2 Leftovers:
 crbug.com/1035582 fast/forms/date-multiple-fields/* [ Skip ]
 crbug.com/1035582 fast/forms/datetimelocal-multiple-fields/* [ Skip ]
@@ -1822,71 +1791,13 @@
 # Uncomment the one on 5715:
 crbug.com/1035582 fast/forms/suggested-value.html [ Failure Pass ]
 crbug.com/1035582 virtual/form-controls-refresh-disabled/fast/forms/select/menulist-update-text-popup.html [ Failure Pass ]
-# Focus ring:
-crbug.com/1035582 fast/forms/month/month-appearance-basic.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/number/number-appearance-datalist.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/number/number-appearance-spinbutton-disabled-readonly.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/range/input-appearance-range.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/select/listbox-appearance-basic.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/text/text-appearance-datalist.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/time/time-appearance-basic.html [ Failure Pass ]
-crbug.com/1035582 fast/forms/week/week-appearance-basic.html [ Failure Pass ]
 
-
-# -
-# -
-# -
-# -
-# -
-# Group 5 Leftovers (all focus rings)
-crbug.com/1035582  compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ Failure Pass ]
-crbug.com/1035582  compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers.html [ Failure Pass ]
-crbug.com/1035582  editing/caret/caret-color-001.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-002.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-003.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-004.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-005.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-007.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-010.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-color-012.html [ Failure Pass ]
-crbug.com/1035582 editing/caret/caret-position.html [ Failure Pass ]
-crbug.com/1035582 editing/input/caret-at-the-edge-of-input.html [ Failure Pass ]
-crbug.com/1035582 editing/input/reveal-caret-of-multiline-input.html [ Failure Pass ]
-crbug.com/1035582 editing/inserting/4960120-1.html [ Failure Pass ]
-crbug.com/1035582 editing/pasteboard/4806874.html [ Failure Pass ]
-# -
-# -
-# -
 # Group 7 leftovers:
 crbug.com/1035582 virtual/gpu-rasterization/images/12-55.html [ Skip ]
 crbug.com/1035582 virtual/gpu-rasterization/images/182.html [ Skip ]
-# Focus rings:
-crbug.com/1035582 fast/multicol/multicol-with-child-renderLayer-for-input.html [ Failure Pass ]
-crbug.com/1035582 fast/overflow/scroll-nested-positioned-layer-in-overflow.html [ Failure Pass ]
-crbug.com/1035582 fast/overflow/scrollRevealButton.html [ Failure Pass ]
-crbug.com/1035582 fast/spatial-navigation/snav-multiple-select-focusring.html [ Failure Pass ]
-crbug.com/1035582 http/tests/webfont/popup-menu-load-webfont-after-open.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/renderer-destruction-by-invalidateSelection-crash.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/forms/radio-focus-by-mouse-then-keydown.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/forms/textarea-caret.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/scroll/caret-invalidation-in-overflow-scroll.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/scroll/caret-with-composited-scroll.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/selection/selection-in-composited-scrolling-container.html [ Failure Pass ]
-crbug.com/1035582 paint/invalidation/selection/selection-in-non-composited-scrolling-container.html [ Failure Pass ]
-crbug.com/1035582 paint/selection/text-selection-with-composition.html [ Failure Pass ]
-crbug.com/1035582 transforms/transformed-focused-text-input.html [ Failure Pass ]
-crbug.com/1035582 virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input.html [ Failure Pass ]
 # Dark mode, flagged by schenney, looks wrong:
 crbug.com/1035582 virtual/dark-mode-native-theme-on/text-input-elements.html [ Failure Pass ]
 
-# -
-# -
-# -
-# -
-# -
-# -
 # Problems rebaselining:
 crbug.com/1035582 virtual/cascade/fast/forms/date/date-appearance-basic.html [ Skip ]
 crbug.com/1035582 virtual/cascade/fast/forms/month/month-appearance-basic.html [ Skip ]
@@ -1922,6 +1833,18 @@
 crbug.com/1035582 fast/forms/text/input-appearance-autocomplete-very-long-value.html [ Skip ]
 crbug.com/1035582 virtual/text-antialias/selection/mixed-directionality-selection.html [ Skip ]
 
+# Failed during rebaseline 3-1
+crbug.com/1035582 [ Fuchsia ] paint/invalidation/selection/selection-in-composited-scrolling-container.html [ Skip ]
+crbug.com/1035582 [ Linux ] paint/invalidation/scroll/caret-with-composited-scroll.html [ Skip ]
+crbug.com/1035582 [ Win ] paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown.html [ Skip ]
+crbug.com/1035582 [ Win ] paint/invalidation/forms/radio-focus-by-mouse-then-keydown.html [ Skip ]
+crbug.com/1035582 [ Win ] paint/invalidation/scroll/caret-with-composited-scroll.html [ Skip ]
+crbug.com/1035582 [ Win7 ] virtual/gpu-rasterization/images/feature-policy-oversized-images-responsive-image.html [ Skip ]
+crbug.com/1035582 [ Win10 ] editing/caret/caret-position.html [ Skip ]
+crbug.com/1035582 [ Win10 ] editing/pasteboard/4806874.html [ Skip ]
+crbug.com/1035582 [ Win10 ] fast/forms/color/color-suggestion-picker-appearance-zoom125.html [ Skip ]
+crbug.com/1035582 [ Win10 ] fast/multicol/multicol-with-child-renderLayer-for-input.html [ Skip ]
+
 # ======
 # ====== End of rebaselines for crbug.com/1035582 ======
 # ======
@@ -6499,4 +6422,6 @@
 
 crbug.com/836300 fast/css3-text/css3-text-decoration/text-decoration-skip-ink-links.html [ Pass Failure ]
 
-
+# Sheriff 2020-01-14
+crbug.com/1041973 external/wpt/html/semantics/forms/constraints/form-validation-reportValidity.html [ Pass Failure ]
+crbug.com/1041973 virtual/web-components-v0-disabled/external/wpt/html/semantics/forms/constraints/form-validation-reportValidity.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.html b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.html
new file mode 100644
index 0000000..35264ba8
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.html
@@ -0,0 +1,38 @@
+<style>
+  img {
+    position: absolute;
+    left: 10px;
+    top: 10px;
+    background-color: black;
+    width: 50px;
+    height: 50px;
+  }
+  
+  .stuff {
+    height: 20px;
+  }
+
+  .outer {
+    position: absolute;
+    left: 500px;
+    top: 100px;
+  }
+
+  .inner {
+    width: 100px;
+    height: 100px;
+    background-color: blue;
+  }
+</style>
+<img>
+<div class="outer">
+  <div class="stuff"></div>
+  <div class="inner"></div>
+</div>
+<div style="position: absolute; left: 0; top: 250px;">
+  <img>
+  <div class="outer">
+    <div class="stuff"></div>
+    <div class="inner"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
similarity index 97%
rename from third_party/blink/web_tests/platform/win/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
rename to third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
index e63eb38..a581645 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
+++ b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
@@ -18,7 +18,7 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV class='composited inner box'",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='composited inner'",
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backgroundColor": "#0000FF",
diff --git a/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant.html b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant.html
index 63186ea..625572b 100644
--- a/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant.html
+++ b/third_party/blink/web_tests/compositing/geometry/bounds-ignores-hidden-composited-descendant.html
@@ -4,55 +4,58 @@
   }
   
   img {
+    position: absolute;
+    left: 10px;
+    top: 10px;
+    z-index: 0;
     background-color: black;
     width: 50px;
     height: 50px;
   }
-  
-  .box {
-      width: 100px;
-      height: 100px;
-      background-color: blue;
+
+  .stuff {
+    height: 20px;
   }
-  
+
+  .outer {
+    position: absolute;
+    left: 500px;
+    top: 100px;
+    z-index: 1;
+    visibility: hidden;
+  }
+
   .inner {
-      position: relative;
-      visibility: visible;
-  }
-  
-  #layers {
-      opacity: 0; /* hide from pixel result */
+    width: 100px;
+    height: 100px;
+    background-color: blue;
+    position: relative;
+    visibility: visible;
   }
 </style>
 <script>
-    if (window.testRunner)
-        testRunner.dumpAsText();
-
     function dumpLayers()
     {
-        if (window.testRunner) {
-            document.getElementById('layers').innerText = internals.layerTreeAsText(document);
-        }
+        if (window.testRunner)
+            testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
     }
     window.addEventListener('load', dumpLayers, false);
 </script>
 <body>
     <div style="position: absolute; left: 0px; top: 0px; z-index: 1; " class="composited">
-        <img style="position: absolute; left: 10px; top: 10px; z-index: 0;">
-        <div style="position: absolute; left: 500px; top: 100px; z-index: 1; visibility: hidden;">
-            stuff
-            <div class="inner box"></div>
+        <img>
+        <div class="outer">
+            <div class="stuff">stuff</div>
+            <div class="inner"></div>
         </div>
     </div>
 
     <div style="position: absolute; left: 0px; top: 250px; z-index: 1; " class="composited">
-        <img style="position: absolute; left: 10px; top: 10px; z-index: 0;">
-        <div style="position: absolute; left: 500px; top: 100px; z-index: 1; visibility: hidden;">
-            stuff
+        <img>
+        <div class="outer">
+            <div class="stuff">stuff</div>
             <!-- Ideally this layer wouldn't affect the bounds of its composited ancestor. -->
-            <div class="composited inner box"></div>
+            <div class="composited inner"></div>
         </div>
     </div>
-
-<pre id="layers">Layer tree goes here in DRT</pre>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-expected.txt
deleted file mode 100644
index 29efcb5..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-expected.txt
+++ /dev/null
@@ -1,189 +0,0 @@
-This is a testharness.js-based test.
-FAIL .grid 1 assert_equals: 
-<div class="grid alignContentSpaceBetween" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 260 but got 40
-FAIL .grid 2 assert_equals: 
-<div class="grid alignContentSpaceAround" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="55" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="55" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="205" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="205" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 55 but got 0
-FAIL .grid 3 assert_equals: 
-<div class="grid alignContentSpaceEvenly" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="73" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="73" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="187" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="187" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 73 but got 0
-PASS .grid 4
-FAIL .grid 5 assert_equals: 
-<div class="grid alignContentSpaceBetween" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 130 but got 40
-FAIL .grid 6 assert_equals: 
-<div class="grid alignContentSpaceAround" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="30" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="30" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="230" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="230" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 30 but got 0
-FAIL .grid 7 assert_equals: 
-<div class="grid alignContentSpaceEvenly" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="45" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="45" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="215" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="215" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 45 but got 0
-PASS .grid 8
-FAIL .grid 9 assert_equals: 
-<div class="grid alignContentSpaceBetween" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="87" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="87" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="173" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="173" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="0" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="20" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 87 but got 40
-FAIL .grid 10 assert_equals: 
-<div class="grid alignContentSpaceAround" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="18" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="18" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="93" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="93" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="168" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="168" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="0" data-offset-y="243" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="20" data-offset-y="243" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 18 but got 0
-FAIL .grid 11 assert_equals: 
-<div class="grid alignContentSpaceEvenly" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="28" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="20" data-offset-y="28" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="96" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="20" data-offset-y="96" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="164" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="20" data-offset-y="164" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="0" data-offset-y="232" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="20" data-offset-y="232" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 28 but got 0
-PASS .grid 12
-FAIL .grid 13 assert_equals: 
-<div class="grid alignContentSpaceBetween directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 260 but got 40
-FAIL .grid 14 assert_equals: 
-<div class="grid alignContentSpaceAround directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="55" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="55" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="205" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="205" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 55 but got 0
-FAIL .grid 15 assert_equals: 
-<div class="grid alignContentSpaceEvenly directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="73" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="73" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="187" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="187" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 73 but got 0
-PASS .grid 16
-FAIL .grid 17 assert_equals: 
-<div class="grid alignContentSpaceBetween directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 130 but got 40
-FAIL .grid 18 assert_equals: 
-<div class="grid alignContentSpaceAround directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="30" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="30" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="230" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="230" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 30 but got 0
-FAIL .grid 19 assert_equals: 
-<div class="grid alignContentSpaceEvenly directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="45" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="45" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="130" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="215" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="215" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 45 but got 0
-PASS .grid 20
-FAIL .grid 21 assert_equals: 
-<div class="grid alignContentSpaceBetween directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="0" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="87" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="87" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="173" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="173" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="180" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="160" data-offset-y="260" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 87 but got 40
-FAIL .grid 22 assert_equals: 
-<div class="grid alignContentSpaceAround directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="18" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="18" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="93" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="93" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="168" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="168" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="180" data-offset-y="243" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="160" data-offset-y="243" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 18 but got 0
-FAIL .grid 23 assert_equals: 
-<div class="grid alignContentSpaceEvenly directionRTL" data-expected-width="200" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="180" data-offset-y="28" data-expected-width="20" data-expected-height="40"></div>
-        <div class="firstRowSecondColumn" data-offset-x="160" data-offset-y="28" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="96" data-expected-width="20" data-expected-height="40"></div>
-        <div class="secondRowSecondColumn" data-offset-x="160" data-offset-y="96" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="180" data-offset-y="164" data-expected-width="20" data-expected-height="40"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="160" data-offset-y="164" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="180" data-offset-y="232" data-expected-width="20" data-expected-height="40"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="160" data-offset-y="232" data-expected-width="20" data-expected-height="40"></div>
-    </div>
-offsetTop expected 28 but got 0
-PASS .grid 24
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr-expected.txt
deleted file mode 100644
index 82b19bbb..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr-expected.txt
+++ /dev/null
@@ -1,207 +0,0 @@
-This is a testharness.js-based test.
-FAIL .grid 1 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 360 but got 40
-FAIL .grid 2 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="80" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="80" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="280" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="280" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 80 but got 0
-FAIL .grid 3 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="107" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="107" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="253" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="253" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 107 but got 0
-PASS .grid 4
-FAIL .grid 5 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 180 but got 40
-FAIL .grid 6 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="47" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="47" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="313" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="313" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 47 but got 0
-FAIL .grid 7 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="70" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="70" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="290" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="290" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 70 but got 0
-FAIL .grid 8 assert_equals: 
-<div class="grid stretchedGrid alignContentStretch verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="267" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="267" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-    </div>
-width expected 133 but got 134
-FAIL .grid 9 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="120" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="120" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="240" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="240" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 120 but got 40
-FAIL .grid 10 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="30" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="30" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="130" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="130" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="230" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="230" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="330" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="330" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 30 but got 0
-FAIL .grid 11 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="48" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="48" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="136" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="136" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="224" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="224" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="312" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="312" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 48 but got 0
-PASS .grid 12
-FAIL .grid 13 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 360 but got 40
-FAIL .grid 14 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="80" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="80" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="280" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="280" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 80 but got 0
-FAIL .grid 15 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="107" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="107" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="253" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="253" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 107 but got 0
-PASS .grid 16
-FAIL .grid 17 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 180 but got 40
-FAIL .grid 18 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="47" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="47" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="313" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="313" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 47 but got 0
-FAIL .grid 19 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="70" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="70" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="290" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="290" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 70 but got 0
-FAIL .grid 20 assert_equals: 
-<div class="grid stretchedGrid alignContentStretch verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="267" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="267" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-    </div>
-width expected 133 but got 134
-FAIL .grid 21 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="120" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="120" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="240" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="240" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 120 but got 40
-FAIL .grid 22 assert_equals: 
-<div class="grid alignContentSpaceAround verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="30" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="30" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="130" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="130" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="230" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="230" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="330" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="330" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 30 but got 0
-FAIL .grid 23 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="48" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="48" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="136" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="136" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="224" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="224" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="312" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="312" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 48 but got 0
-PASS .grid 24
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr.html
index 8217f4d..cecab3e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-lr.html
@@ -6,6 +6,7 @@
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <link rel="stylesheet" href="/css/support/grid.css">
+<link rel="stylesheet" href="/css/support/alignment.css">
 
 <style>
 
@@ -21,6 +22,11 @@
     grid-auto-rows: auto;
 }
 
+.width300height400 {
+    width: 300px;
+    height: 400px;
+}
+
 .thirdRowFirstColumn {
   background-color: green;
   grid-column: 1;
@@ -145,13 +151,13 @@
 
 <div style="position: relative">
     <p>direction: LTR | align-content: 'stretch'</p>
-    <div class="grid stretchedGrid alignContentStretch verticalLR" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="267" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="267" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
+    <div class="grid stretchedGrid width300height400 alignContentStretch verticalLR" data-expected-width="300" data-expected-height="400">
+        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowFirstColumn" data-offset-x="100" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowSecondColumn" data-offset-x="100" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowFirstColumn" data-offset-x="200" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowSecondColumn" data-offset-x="200" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
     </div>
 </div>
 
@@ -290,13 +296,13 @@
 
 <div style="position: relative">
     <p>direction: RTL | align-content: 'stretch'</p>
-    <div class="grid stretchedGrid alignContentStretch verticalLR directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="267" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="267" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
+    <div class="grid stretchedGrid width300height400 alignContentStretch verticalLR directionRTL" data-expected-width="300" data-expected-height="400">
+        <div class="firstRowFirstColumn" data-offset-x="0" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="firstRowSecondColumn" data-offset-x="0" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowFirstColumn" data-offset-x="100" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowSecondColumn" data-offset-x="100" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowFirstColumn" data-offset-x="200" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowSecondColumn" data-offset-x="200" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
     </div>
 </div>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl-expected.txt
deleted file mode 100644
index 732395093..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl-expected.txt
+++ /dev/null
@@ -1,207 +0,0 @@
-This is a testharness.js-based test.
-FAIL .grid 1 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 0 but got 320
-FAIL .grid 2 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="280" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="280" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="80" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="80" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 280 but got 360
-FAIL .grid 3 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="253" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="253" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="107" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="107" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 253 but got 360
-PASS .grid 4
-FAIL .grid 5 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 180 but got 320
-FAIL .grid 6 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="313" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="313" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="47" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="47" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 313 but got 360
-FAIL .grid 7 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="290" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="290" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="70" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="70" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 290 but got 360
-FAIL .grid 8 assert_equals: 
-<div class="grid stretchedGrid alignContentStretch verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="267" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="267" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-    </div>
-width expected 133 but got 134
-FAIL .grid 9 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="240" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="240" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="120" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="120" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 240 but got 320
-FAIL .grid 10 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="330" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="330" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="230" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="230" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="130" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="130" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="30" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="30" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 330 but got 360
-FAIL .grid 11 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="312" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="312" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="224" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="224" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="136" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="136" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="48" data-offset-y="0" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="48" data-offset-y="20" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 312 but got 360
-PASS .grid 12
-FAIL .grid 13 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 0 but got 320
-FAIL .grid 14 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="280" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="280" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="80" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="80" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 280 but got 360
-FAIL .grid 15 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="253" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="253" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="107" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="107" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 253 but got 360
-PASS .grid 16
-FAIL .grid 17 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 180 but got 320
-FAIL .grid 18 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="313" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="313" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="47" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="47" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 313 but got 360
-FAIL .grid 19 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="290" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="290" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="180" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="180" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="70" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="70" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 290 but got 360
-FAIL .grid 20 assert_equals: 
-<div class="grid stretchedGrid alignContentStretch verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="267" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="267" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-    </div>
-width expected 133 but got 134
-FAIL .grid 21 assert_equals: 
-<div class="grid alignContentSpaceBetween verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="360" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="360" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="240" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="240" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="120" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="120" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 240 but got 320
-FAIL .grid 22 assert_equals: 
-<div class="grid alignContentSpaceAround verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="330" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="330" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="230" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="230" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="130" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="130" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="30" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="30" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 330 but got 360
-FAIL .grid 23 assert_equals: 
-<div class="grid alignContentSpaceEvenly verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="312" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="312" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="224" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="224" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="136" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="136" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowFirstColumn" data-offset-x="48" data-offset-y="280" data-expected-width="40" data-expected-height="20"></div>
-        <div class="fourthRowSecondColumn" data-offset-x="48" data-offset-y="260" data-expected-width="40" data-expected-height="20"></div>
-    </div>
-offsetLeft expected 312 but got 360
-PASS .grid 24
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl.html
index 096e3fd6..40ee221 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution-vertical-rl.html
@@ -6,6 +6,7 @@
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <link rel="stylesheet" href="/css/support/grid.css">
+<link rel="stylesheet" href="/css/support/alignment.css">
 
 <style>
 
@@ -21,6 +22,11 @@
     grid-auto-rows: auto;
 }
 
+.width300height400 {
+    width: 300px;
+    height: 400px;
+}
+
 .thirdRowFirstColumn {
   background-color: green;
   grid-column: 1;
@@ -145,13 +151,13 @@
 
 <div style="position: relative">
     <p>direction: LTR | align-content: 'stretch'</p>
-    <div class="grid stretchedGrid alignContentStretch verticalRL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="267" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="267" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="133" data-expected-height="20"></div>
+    <div class="grid stretchedGrid width300height400 alignContentStretch verticalRL" data-expected-width="300" data-expected-height="400">
+        <div class="firstRowFirstColumn" data-offset-x="200" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="firstRowSecondColumn" data-offset-x="200" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowFirstColumn" data-offset-x="100" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowSecondColumn" data-offset-x="100" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="0" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="20" data-expected-width="100" data-expected-height="20"></div>
     </div>
 </div>
 
@@ -290,13 +296,13 @@
 
 <div style="position: relative">
     <p>direction: RTL | align-content: 'stretch'</p>
-    <div class="grid stretchedGrid alignContentStretch verticalRL directionRTL" data-expected-width="400" data-expected-height="300">
-        <div class="firstRowFirstColumn" data-offset-x="267" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="firstRowSecondColumn" data-offset-x="267" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowFirstColumn" data-offset-x="133" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="secondRowSecondColumn" data-offset-x="133" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="280" data-expected-width="133" data-expected-height="20"></div>
-        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="260" data-expected-width="133" data-expected-height="20"></div>
+    <div class="grid stretchedGrid width300height400 alignContentStretch verticalRL directionRTL" data-expected-width="300" data-expected-height="400">
+        <div class="firstRowFirstColumn" data-offset-x="200" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="firstRowSecondColumn" data-offset-x="200" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowFirstColumn" data-offset-x="100" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="secondRowSecondColumn" data-offset-x="100" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowFirstColumn" data-offset-x="0" data-offset-y="380" data-expected-width="100" data-expected-height="20"></div>
+        <div class="thirdRowSecondColumn" data-offset-x="0" data-offset-y="360" data-expected-width="100" data-expected-height="20"></div>
     </div>
 </div>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution.html
index 7b66ddb..488fc05a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-align-content-distribution.html
@@ -6,6 +6,7 @@
 <meta name="flags" content="ahem">
 <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <link rel="stylesheet" href="/css/support/grid.css">
+<link rel="stylesheet" href="/css/support/alignment.css">
 
 <style>
 
diff --git a/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html
new file mode 100644
index 0000000..2cd36a1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_isSessionSupported_immersive-ar.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src="../resources/webxr_util.js"></script>
+  <script src="../resources/webxr_test_constants.js"></script>
+  <script>
+    xr_promise_test(
+      "isSessionSupported resolves to true for immersive-ar on a supported device",
+      (t) => {
+        return navigator.xr.test.simulateDeviceConnection(IMMERSIVE_AR_DEVICE)
+          .then( (controller) => {
+            return navigator.xr.isSessionSupported('immersive-ar').then((supported) => {
+              t.step(() => {
+                assert_true(supported);
+              });
+            });
+          });
+      });
+
+    xr_promise_test(
+      "isSessionSupported resolves to false for immersive-ar on an unsupported device",
+      (t) => {
+        return navigator.xr.test.simulateDeviceConnection(TRACKED_IMMERSIVE_DEVICE)
+          .then( (controller) => {
+            return navigator.xr.isSessionSupported('immersive-ar').then((supported) => {
+              t.step(() => {
+                assert_false(supported);
+              });
+            });
+          });
+      });
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html
new file mode 100644
index 0000000..c3fdeb34
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrDevice_requestSession_immersive-ar.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src="../resources/webxr_util.js"></script>
+  <script src="../resources/webxr_test_constants.js"></script>
+  <canvas></canvas>
+  <script>
+    xr_session_promise_test(
+      "Tests requestSession accepts immersive-ar mode",
+      (session) => {
+        assert_not_equals(session, null);
+      }, IMMERSIVE_AR_DEVICE, 'immersive-ar', {});
+
+    xr_promise_test(
+      "Tests requestSession rejects immersive-ar mode when unsupported",
+      (t) => {
+        return navigator.xr.test.simulateDeviceConnection(TRACKED_IMMERSIVE_DEVICE)
+          .then((controller) => new Promise((resolve) => {
+            navigator.xr.test.simulateUserActivation(() => {
+              resolve(promise_rejects(
+                t, "NotSupportedError",
+                navigator.xr.requestSession('immersive-ar', {})));
+            });
+          }));
+      });
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrSession_environmentBlendMode.https.html b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrSession_environmentBlendMode.https.html
new file mode 100644
index 0000000..da2ddc28
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/ar-module/xrSession_environmentBlendMode.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src="../resources/webxr_util.js"></script>
+  <script src="../resources/webxr_test_constants.js"></script>
+  <canvas></canvas>
+  <script>
+    xr_session_promise_test(
+      "Tests environmentBlendMode for an AR device",
+      (session) => {
+        assert_not_equals(session.environmentBlendMode, "opaque");
+        assert_in_array(session.environmentBlendMode, ["alpha-blend", "additive"]);
+      }, IMMERSIVE_AR_DEVICE, 'immersive-ar', {});
+
+
+    xr_session_promise_test(
+      "Tests environmentBlendMode for a VR device",
+      (session) => {
+        assert_not_equals(session.environmentBlendMode, "alpha-blend");
+        assert_in_array(session.environmentBlendMode, ["opaque", "additive"]);
+      }, TRACKED_IMMERSIVE_DEVICE, 'immersive-vr', {});
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_test_constants.js b/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_test_constants.js
index d56a4925f..553a8127 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_test_constants.js
+++ b/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_test_constants.js
@@ -136,6 +136,14 @@
     supportedFeatures: ALL_FEATURES
 };
 
+const IMMERSIVE_AR_DEVICE = {
+  supportsImmersive: true,
+  supportedModes: [ "inline", "immersive-ar"],
+  views: VALID_VIEWS,
+  viewerOrigin: IDENTITY_TRANSFORM,
+  supportedFeatures: ALL_FEATURES
+};
+
 const VALID_NON_IMMERSIVE_DEVICE = {
     supportsImmersive: false,
     supportedModes: ["inline"],
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
index dd8a8e7..9b2a375 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
@@ -9,10 +9,18 @@
     {
       "name": "LayoutImage (positioned) IMG",
       "position": [10, 10],
-      "bounds": [590, 210],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
       "backgroundColor": "#000000"
     },
     {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='outer'",
+      "position": [500, 120],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    },
+    {
       "name": "LayoutImage (positioned) IMG",
       "position": [10, 10],
       "bounds": [50, 50],
@@ -21,7 +29,7 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV class='composited inner box'",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='composited inner'",
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backgroundColor": "#0000FF",
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-dynamic-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-dynamic-expected.txt
index 49d3d06..e488edd1 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-dynamic-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/bounds-ignores-hidden-dynamic-expected.txt
@@ -9,7 +9,15 @@
     {
       "name": "LayoutImage (positioned) IMG",
       "position": [10, 10],
-      "bounds": [540, 240],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#000000"
+    },
+    {
+      "name": "LayoutImage (positioned) IMG class='to-visible'",
+      "position": [500, 200],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
       "backgroundColor": "#000000"
     },
     {
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/squashing-sparsity-heuristic-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/squashing-sparsity-heuristic-expected.txt
new file mode 100644
index 0000000..3a115aac
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/squashing-sparsity-heuristic-expected.txt
@@ -0,0 +1,42 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='composited'",
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='A' class='overlap1'",
+      "position": [140, 140],
+      "bounds": [10, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='B' class='overlap2'",
+      "position": [220, 220],
+      "bounds": [25, 90],
+      "backgroundColor": "#00FF00"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [60, 60, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-expected.png
deleted file mode 100644
index 0b11c6c1..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-xhtml-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-xhtml-expected.png
deleted file mode 100644
index 0b11c6c1..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/001-xhtml-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-declarative-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-declarative-expected.png
deleted file mode 100644
index 751ad636..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-declarative-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-expected.png
deleted file mode 100644
index 751ad636..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-xhtml-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-xhtml-expected.png
deleted file mode 100644
index 751ad636..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/body-propagation/overflow/005-xhtml-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
index 457d5b9..b4425b70 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/background/background-resize-height-expected.txt
@@ -97,103 +97,103 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV class='test image repeat-round'",
-      "position": [8, 8],
-      "bounds": [560, 144],
+      "position": [508, 8],
+      "bounds": [60, 44],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
       "paintInvalidations": [
         {
           "object": "LayoutNGBlockFlow (positioned) DIV class='test image repeat-round'",
-          "rect": [500, 0, 60, 44],
+          "rect": [0, 0, 60, 44],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated'",
+      "position": [8, 108],
+      "bounds": [110, 44],
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated size-cover'",
+          "rect": [50, 0, 60, 44],
           "reason": "background"
         },
         {
           "object": "LayoutNGBlockFlow (positioned) DIV class='test generated'",
-          "rect": [0, 100, 60, 44],
-          "reason": "background"
-        }
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated size-cover'",
-      "position": [58, 108],
-      "bounds": [110, 44],
-      "backgroundColor": "#000000",
-      "paintInvalidations": [
-        {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated size-contain'",
-          "rect": [50, 0, 60, 44],
-          "reason": "background"
-        },
-        {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated size-cover'",
           "rect": [0, 0, 60, 44],
           "reason": "background"
         }
       ]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated percent-height'",
-      "position": [208, 108],
-      "bounds": [110, 44],
+      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated size-contain'",
+      "position": [108, 108],
+      "bounds": [160, 44],
       "backgroundColor": "#000000",
       "paintInvalidations": [
         {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated top'",
-          "rect": [50, 0, 60, 44],
-          "reason": "background"
-        },
-        {
           "object": "LayoutNGBlockFlow (positioned) DIV class='test generated percent-height'",
+          "rect": [100, 0, 60, 44],
+          "reason": "background"
+        },
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated size-contain'",
           "rect": [0, 0, 60, 44],
           "reason": "background"
         }
       ]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated bottom'",
-      "position": [308, 108],
+      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated top'",
+      "position": [258, 108],
       "bounds": [110, 44],
       "backgroundColor": "#000000",
       "paintInvalidations": [
         {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated center'",
-          "rect": [50, 0, 60, 44],
-          "reason": "background"
-        },
-        {
           "object": "LayoutNGBlockFlow (positioned) DIV class='test generated bottom'",
-          "rect": [0, 0, 60, 44],
-          "reason": "background"
-        }
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated no-repeat'",
-      "position": [408, 108],
-      "bounds": [110, 44],
-      "backgroundColor": "#000000",
-      "paintInvalidations": [
-        {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated repeat-space'",
           "rect": [50, 0, 60, 44],
           "reason": "background"
         },
         {
-          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated no-repeat'",
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated top'",
           "rect": [0, 0, 60, 44],
           "reason": "background"
         }
       ]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated repeat-round'",
-      "position": [508, 108],
-      "bounds": [60, 44],
-      "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
+      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated center'",
+      "position": [358, 108],
+      "bounds": [110, 44],
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated no-repeat'",
+          "rect": [50, 0, 60, 44],
+          "reason": "background"
+        },
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated center'",
+          "rect": [0, 0, 60, 44],
+          "reason": "background"
+        }
+      ]
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='test generated repeat-space'",
+      "position": [458, 108],
+      "bounds": [110, 44],
       "backgroundColor": "#000000",
       "paintInvalidations": [
         {
           "object": "LayoutNGBlockFlow (positioned) DIV class='test generated repeat-round'",
+          "rect": [50, 0, 60, 44],
+          "reason": "background"
+        },
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV class='test generated repeat-space'",
           "rect": [0, 0, 60, 44],
           "reason": "background"
         }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/chunk-reorder-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/chunk-reorder-expected.txt
index 68de53f..73eda3e 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/chunk-reorder-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/chunk-reorder-expected.txt
@@ -8,21 +8,31 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV",
-      "bounds": [550, 550],
+      "bounds": [1, 1],
+      "contentsOpaque": true,
       "backgroundColor": "#FF0000",
       "paintInvalidations": [
         {
-          "object": "LayoutNGBlockFlow (positioned) DIV id='chunk3'",
-          "rect": [400, 400, 100, 100],
-          "reason": "chunk reordered"
-        },
-        {
           "object": "LayoutNGBlockFlow (positioned) DIV id='chunk2'",
           "rect": [0, 0, 1, 1],
           "reason": "chunk reordered"
         }
       ],
       "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='chunk5'",
+      "position": [400, 400],
+      "bounds": [150, 150],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutNGBlockFlow (positioned) DIV id='chunk5'",
+          "rect": [0, 0, 150, 150],
+          "reason": "full layer"
+        }
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/forms/range-focus-by-mouse-then-keydown-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/forms/range-focus-by-mouse-then-keydown-expected.txt
index 7aef69f4..7d8a007 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/forms/range-focus-by-mouse-then-keydown-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/forms/range-focus-by-mouse-then-keydown-expected.txt
@@ -8,12 +8,12 @@
       "paintInvalidations": [
         {
           "object": "LayoutSlider INPUT",
-          "rect": [9, 9, 131, 23],
+          "rect": [9, 9, 131, 18],
           "reason": "subtree"
         },
         {
           "object": "LayoutBlockFlow DIV id='thumb'",
-          "rect": [69, 10, 11, 21],
+          "rect": [66, 10, 17, 16],
           "reason": "subtree"
         }
       ]
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
index 0202d97..3d33847 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
@@ -18,7 +18,7 @@
       "transform": 1
     },
     {
-      "name": "LayoutBlockFlow (relative positioned) DIV class='composited inner box'",
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited inner'",
       "bounds": [100, 100],
       "contentsOpaque": true,
       "backgroundColor": "#0000FF",
diff --git a/third_party/blink/web_tests/platform/linux/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png b/third_party/blink/web_tests/platform/linux/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
index 5682c73..f8c749d 100644
--- a/third_party/blink/web_tests/platform/linux/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
+++ b/third_party/blink/web_tests/platform/linux/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png b/third_party/blink/web_tests/platform/linux/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
index 206c123..b17d1832 100644
--- a/third_party/blink/web_tests/platform/linux/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
+++ b/third_party/blink/web_tests/platform/linux/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-001-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-001-expected.png
index ed158f2..7c5a5c6 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-001-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-001-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-002-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-002-expected.png
index ed158f2..7c5a5c6 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-002-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-002-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-003-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-003-expected.png
index 0014f94..14313b8 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-003-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-003-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-004-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-004-expected.png
index a045a74..2929dbcc 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-004-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-004-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-005-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-005-expected.png
index 640ac00c..ceeab4b 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-005-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-005-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-007-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-007-expected.png
index 3dfc2ed2..547a7938 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-007-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-007-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-010-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-010-expected.png
index 3da5415..cbef3f4 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-010-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-010-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-012-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-012-expected.png
index ed158f2..7c5a5c6 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-012-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-color-012-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/caret/caret-position-expected.png b/third_party/blink/web_tests/platform/linux/editing/caret/caret-position-expected.png
index bf9a4d9a..9901224 100644
--- a/third_party/blink/web_tests/platform/linux/editing/caret/caret-position-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/caret/caret-position-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/input/caret-at-the-edge-of-input-expected.png b/third_party/blink/web_tests/platform/linux/editing/input/caret-at-the-edge-of-input-expected.png
index 962aaca..335360c 100644
--- a/third_party/blink/web_tests/platform/linux/editing/input/caret-at-the-edge-of-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/input/caret-at-the-edge-of-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/input/reveal-caret-of-multiline-input-expected.png b/third_party/blink/web_tests/platform/linux/editing/input/reveal-caret-of-multiline-input-expected.png
index eb79e1c6..98971d2 100644
--- a/third_party/blink/web_tests/platform/linux/editing/input/reveal-caret-of-multiline-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/input/reveal-caret-of-multiline-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/inserting/4960120-1-expected.png b/third_party/blink/web_tests/platform/linux/editing/inserting/4960120-1-expected.png
index 7a3018b..99eee064 100644
--- a/third_party/blink/web_tests/platform/linux/editing/inserting/4960120-1-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/inserting/4960120-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/editing/pasteboard/4806874-expected.png b/third_party/blink/web_tests/platform/linux/editing/pasteboard/4806874-expected.png
index 71b652c..0354320 100644
--- a/third_party/blink/web_tests/platform/linux/editing/pasteboard/4806874-expected.png
+++ b/third_party/blink/web_tests/platform/linux/editing/pasteboard/4806874-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
index df60748a..89bac7f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
index ec83ea4..89fd793 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
index ec83ea4..89fd793 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
index 27ca78cd..0ac8ae98 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
index a2f6f2c7..57f9c179 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
index 21dea2f..0c2b90b 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
index 6bd0482..038dd9f1 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
index 45bfe648..b504608 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
index ccfe425..e0dbed46 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
index 3aca173..f9dc015 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-expected.png
index 3d5fd63f..9d4dd77 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
index 1871117..a16b0ee 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-expected.png
index 6407c55..8464759 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
index 946fa0a1..ea4e07d 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-expected.png
index 0502a01b..5a3651e 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png
index 785d6154..6bd0ee8 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
index df4db332..a0d2fca 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
index 20c9c4d..88a20bf1 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
index ae8c53a8..651f5c70 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/date/date-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/date/date-appearance-basic-expected.png
index 5855a36..f8aff96 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/date/date-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/date/date-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
index 64fe9ab..8c6d644f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/month/month-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/month/month-appearance-basic-expected.png
index a4304e8..a27552fb 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/month/month-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/month/month-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-datalist-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-datalist-expected.png
index 2edff533..999fd4f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-datalist-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-datalist-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
index 64b0ea5b..3f865a7 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/select/listbox-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/select/listbox-appearance-basic-expected.png
index 5393534..36305b93 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/select/listbox-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/select/listbox-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/text-appearance-datalist-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/text-appearance-datalist-expected.png
index c508d1d..8eb8343 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/text/text-appearance-datalist-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/text-appearance-datalist-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/time/time-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/time/time-appearance-basic-expected.png
index 80098f9..4c2811bf 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/time/time-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/time/time-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/week/week-appearance-basic-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/week/week-appearance-basic-expected.png
index de840705..9ef46723 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/week/week-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/week/week-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png b/third_party/blink/web_tests/platform/linux/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
index b0d5fb47..50d556b 100644
--- a/third_party/blink/web_tests/platform/linux/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png b/third_party/blink/web_tests/platform/linux/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
index 28c9363..2d011bf 100644
--- a/third_party/blink/web_tests/platform/linux/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/overflow/scrollRevealButton-expected.png b/third_party/blink/web_tests/platform/linux/fast/overflow/scrollRevealButton-expected.png
index fc366c3..e9561e5f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/overflow/scrollRevealButton-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/overflow/scrollRevealButton-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/spatial-navigation/snav-multiple-select-focusring-expected.png b/third_party/blink/web_tests/platform/linux/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
index ca5d951..0bb43fa6 100644
--- a/third_party/blink/web_tests/platform/linux/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png b/third_party/blink/web_tests/platform/linux/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
index 81f18931f..59ff0c25 100644
--- a/third_party/blink/web_tests/platform/linux/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
+++ b/third_party/blink/web_tests/platform/linux/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png
index 61082dd2..89dbb86 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.txt
index 8ed0fd8..54cc259 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutButton BUTTON",
-          "rect": [7, 7, 52, 24],
+          "rect": [6, 6, 54, 26],
           "reason": "subtree"
         },
         {
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png
new file mode 100644
index 0000000..0154d78
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.txt
new file mode 100644
index 0000000..d1e1c5d4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.txt
@@ -0,0 +1,18 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow INPUT",
+          "rect": [11, 9, 17, 17],
+          "reason": "subtree"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/textarea-caret-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/textarea-caret-expected.png
index 1b8a8550..c1171acc 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/textarea-caret-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/forms/textarea-caret-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
index 32a0884..fc50a38 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
index e03b89f2..ae21b61 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-with-composited-scroll-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-with-composited-scroll-expected.png
index a3f6a3a..e73b096 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-with-composited-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/caret-with-composited-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
index 490f5840..08988b1 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index aa289e8d..0f0ff84b 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutTextControl INPUT id='target'",
-          "rect": [7, 7, 63, 24],
+          "rect": [6, 6, 65, 26],
           "reason": "subtree"
         },
         {
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
index fb8f70cf..9da92df 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index db4f2a5..b76087d 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutTextControl INPUT id='target'",
-          "rect": [7, 7, 63, 24],
+          "rect": [6, 6, 65, 26],
           "reason": "subtree"
         },
         {
diff --git a/third_party/blink/web_tests/platform/linux/paint/selection/text-selection-with-composition-expected.png b/third_party/blink/web_tests/platform/linux/paint/selection/text-selection-with-composition-expected.png
index add7e7f..ac8e8d36 100644
--- a/third_party/blink/web_tests/platform/linux/paint/selection/text-selection-with-composition-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/selection/text-selection-with-composition-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
index 365b2c5..9dfe526e 100644
--- a/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
index b0d5fb47..50d556b 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
deleted file mode 100644
index 0f80b52..0000000
--- a/third_party/blink/web_tests/platform/mac/compositing/geometry/bounds-ignores-hidden-composited-descendant-expected.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling Contents Layer",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF"
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='composited'",
-      "position": [10, 10],
-      "bounds": [590, 208]
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV class='composited'",
-      "position": [10, 10],
-      "bounds": [590, 208],
-      "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV class='composited inner box'",
-      "bounds": [100, 100],
-      "contentsOpaque": true,
-      "backgroundColor": "#0000FF",
-      "transform": 2
-    }
-  ],
-  "transforms": [
-    {
-      "id": 1,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [0, 250, 0, 1]
-      ]
-    },
-    {
-      "id": 2,
-      "parent": 1,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [500, 118, 0, 1]
-      ]
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
index c8ddef9..d08bbbb 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
index fb11c48..8c20c94 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/update-widget-positions-on-nested-frames-and-scrollers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-001-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-001-expected.png
index 2cca4148..7b90e09 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-001-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-001-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-002-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-002-expected.png
index 2cca4148..7b90e09 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-002-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-002-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-003-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-003-expected.png
index c08e282e..d2d038f 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-003-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-003-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-004-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-004-expected.png
index 17feb758..af42441 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-004-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-004-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-005-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-005-expected.png
index 1c9d7fd..1e5aaf6 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-005-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-005-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-007-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-007-expected.png
index f4334c5e..fdc22d85 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-007-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-007-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-010-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-010-expected.png
index bf0d432..14fc2532 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-010-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-010-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-012-expected.png b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-012-expected.png
index 2cca4148..7b90e09 100644
--- a/third_party/blink/web_tests/platform/win/editing/caret/caret-color-012-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/caret/caret-color-012-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/input/caret-at-the-edge-of-input-expected.png b/third_party/blink/web_tests/platform/win/editing/input/caret-at-the-edge-of-input-expected.png
index 0a8b8d3..63cf218 100644
--- a/third_party/blink/web_tests/platform/win/editing/input/caret-at-the-edge-of-input-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/input/caret-at-the-edge-of-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/input/reveal-caret-of-multiline-input-expected.png b/third_party/blink/web_tests/platform/win/editing/input/reveal-caret-of-multiline-input-expected.png
index 88587f1..f9a69b87 100644
--- a/third_party/blink/web_tests/platform/win/editing/input/reveal-caret-of-multiline-input-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/input/reveal-caret-of-multiline-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/editing/inserting/4960120-1-expected.png b/third_party/blink/web_tests/platform/win/editing/inserting/4960120-1-expected.png
index 42cdcf0c..5bbdab57 100644
--- a/third_party/blink/web_tests/platform/win/editing/inserting/4960120-1-expected.png
+++ b/third_party/blink/web_tests/platform/win/editing/inserting/4960120-1-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
index 589c8a3..8a9bcb8 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
index 6dfaa5a..470ca9c 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
index 6dfaa5a..470ca9c 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
index 1401ad42..b6dd5ee 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
index 25fa22d..7b6081a 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
index 8f46bc3..b2d4d69 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
index 54aa571..e333ecf 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-ru-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
index 942da54..46252bb 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
index caa729f..76a85da 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
index 05fa2a5..c320a446 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-expected.png
index 08107e5..669f9a7 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
index 75bd2c73..eb84d37 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-expected.png
index f016bf0..a590b31 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
index 62f2cb9..b957665 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-appearance-expected.png
index 93536ba2..e1ddbce 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
index 82fab5a..f2ad7199 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-one-row-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
index 819e269f..d20faf3 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-two-row-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
index dcebf18..5643173 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color/color-suggestion-picker-with-scrollbar-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/date/date-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/date/date-appearance-basic-expected.png
index 4e5957c..212531b 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/date/date-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/date/date-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
index 4f3f951..b2fe11c 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/month/month-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/month/month-appearance-basic-expected.png
index 7f735f4..79c1259 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/month/month-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/month/month-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-datalist-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-datalist-expected.png
index 209b5e9b..d924565 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-datalist-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-datalist-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
index 30e0f1f..cc19ff062 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/number/number-appearance-spinbutton-disabled-readonly-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/range/input-appearance-range-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/range/input-appearance-range-expected.png
index 626258ca..87f8474 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/range/input-appearance-range-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/range/input-appearance-range-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/select/listbox-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/select/listbox-appearance-basic-expected.png
index 634c21cd..6b75d3b 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/select/listbox-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/select/listbox-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/text-appearance-datalist-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/text-appearance-datalist-expected.png
index 8f4603a..dc58ef3c 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/text/text-appearance-datalist-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/text-appearance-datalist-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/time/time-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/time/time-appearance-basic-expected.png
index 1c1c3fd8..c6148d9 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/time/time-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/time/time-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/week/week-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/week/week-appearance-basic-expected.png
index 7e8690c..790d975d 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/week/week-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/week/week-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png b/third_party/blink/web_tests/platform/win/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
index 5cc3969..ba1993b 100644
--- a/third_party/blink/web_tests/platform/win/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/overflow/scroll-nested-positioned-layer-in-overflow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/overflow/scrollRevealButton-expected.png b/third_party/blink/web_tests/platform/win/fast/overflow/scrollRevealButton-expected.png
index 797a0a31..87607b58 100644
--- a/third_party/blink/web_tests/platform/win/fast/overflow/scrollRevealButton-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/overflow/scrollRevealButton-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/spatial-navigation/snav-multiple-select-focusring-expected.png b/third_party/blink/web_tests/platform/win/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
index 6e0879f..8c50459 100644
--- a/third_party/blink/web_tests/platform/win/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/spatial-navigation/snav-multiple-select-focusring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png b/third_party/blink/web_tests/platform/win/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
index 30d2f36f..4b72106 100644
--- a/third_party/blink/web_tests/platform/win/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
+++ b/third_party/blink/web_tests/platform/win/http/tests/webfont/popup-menu-load-webfont-after-open-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.png
index 182ad485..61be623 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.txt
index c210431..f8539337 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/checkbox-focus-by-mouse-then-keydown-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutBlockFlow INPUT",
-          "rect": [11, 10, 15, 15],
+          "rect": [10, 9, 17, 17],
           "reason": "subtree"
         }
       ]
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.png
index 29bbc8c1..5907532 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.txt
index d1cc9cfb..6a86bdd 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/forms/textarea-caret-expected.txt
@@ -8,12 +8,12 @@
       "paintInvalidations": [
         {
           "object": "LayoutTextControl TEXTAREA id='editor'",
-          "rect": [7, 7, 183, 40],
+          "rect": [6, 6, 185, 42],
           "reason": "chunk appeared"
         },
         {
           "object": "LayoutTextControl TEXTAREA id='editor'",
-          "rect": [7, 7, 183, 40],
+          "rect": [6, 6, 185, 42],
           "reason": "chunk appeared"
         },
         {
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
index 0f04f67..4eddd520 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/renderer-destruction-by-invalidateSelection-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
index a0d55c17..4a3155e 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/scroll/caret-invalidation-in-overflow-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
index c2a5851..fd4d900 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
index 820e52a..c49d49b 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-composited-scrolling-container-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutTextControl INPUT id='target'",
-          "rect": [7, 7, 70, 24],
+          "rect": [6, 6, 72, 26],
           "reason": "subtree"
         },
         {
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
index a8fec20e..0fed273 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
index f5e7944..88fd23c 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/selection/selection-in-non-composited-scrolling-container-expected.txt
@@ -8,7 +8,7 @@
       "paintInvalidations": [
         {
           "object": "LayoutTextControl INPUT id='target'",
-          "rect": [7, 7, 70, 24],
+          "rect": [6, 6, 72, 26],
           "reason": "subtree"
         },
         {
diff --git a/third_party/blink/web_tests/platform/win/paint/selection/text-selection-with-composition-expected.png b/third_party/blink/web_tests/platform/win/paint/selection/text-selection-with-composition-expected.png
index 5a5ce00..69d6d9e 100644
--- a/third_party/blink/web_tests/platform/win/paint/selection/text-selection-with-composition-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/selection/text-selection-with-composition-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
index 5717bdc2..c193244 100644
--- a/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
+++ b/third_party/blink/web_tests/platform/win/transforms/transformed-focused-text-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
new file mode 100644
index 0000000..85fc149f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_block_frag/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/editing/caret/caret-position-expected.png b/third_party/blink/web_tests/platform/win7/editing/caret/caret-position-expected.png
new file mode 100644
index 0000000..e77841c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/editing/caret/caret-position-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/editing/pasteboard/4806874-expected.png b/third_party/blink/web_tests/platform/win7/editing/pasteboard/4806874-expected.png
new file mode 100644
index 0000000..6031bf2
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/editing/pasteboard/4806874-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
index a2c4ab71..c503f7e 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
index 9043cdb..4f3eead 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-coarse-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
index 9043cdb..4f3eead 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
index fc2b5bb..d4a512fc 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-minimum-date-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
index 25fb19b2..e519b14 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-ar-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
index 87efeebb8..df9e1e5 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-required-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
index c869120..1c3d4465a 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
index 869fd52..02a2b4e 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
index 126baed9..7844bf6 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/calendar-picker-appearance-zoom200-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-expected.png
index bf5394e..b4f256e 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
index d89d19a..480f2833 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/month-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-expected.png
index 3f73a65..cfeb791 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-step-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
index d8ea212..3cc74eb 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/calendar-picker/week-picker-appearance-step-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png
new file mode 100644
index 0000000..02abfae
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/color/color-suggestion-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/month/month-appearance-basic-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/month/month-appearance-basic-expected.png
index b835a870b..e4e79fff 100644
--- a/third_party/blink/web_tests/platform/win7/fast/forms/month/month-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/month/month-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png b/third_party/blink/web_tests/platform/win7/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
new file mode 100644
index 0000000..85fc149f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/fast/multicol/multicol-with-child-renderLayer-for-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png b/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png
new file mode 100644
index 0000000..d693c00
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/button-reset-focus-by-mouse-then-keydown-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png b/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png
new file mode 100644
index 0000000..0154d78
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/paint/invalidation/forms/radio-focus-by-mouse-then-keydown-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/paint/invalidation/scroll/caret-with-composited-scroll-expected.png b/third_party/blink/web_tests/platform/win7/paint/invalidation/scroll/caret-with-composited-scroll-expected.png
new file mode 100644
index 0000000..ff6a349
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/paint/invalidation/scroll/caret-with-composited-scroll-expected.png
Binary files differ
diff --git a/third_party/libxml/OWNERS b/third_party/libxml/OWNERS
index ae56b432..494e9ef 100644
--- a/third_party/libxml/OWNERS
+++ b/third_party/libxml/OWNERS
@@ -1,6 +1,7 @@
 # There's no real owners here. If you're familiar with the code please send
 # a CL to add yourself here.
 dcheng@chromium.org
+schenney@chromium.org
 palmer@chromium.org
 
 # COMPONENT: Blink>XML
diff --git a/third_party/libxslt/OWNERS b/third_party/libxslt/OWNERS
index dba40d5f..4fae765 100644
--- a/third_party/libxslt/OWNERS
+++ b/third_party/libxslt/OWNERS
@@ -1,3 +1,2 @@
-dcheng@chromium.org
-palmer@chromium.org
+file://third_party/libxml/OWNERS
 # COMPONENT: Blink>XML
diff --git a/third_party/sqlite/PRESUBMIT.py b/third_party/sqlite/PRESUBMIT.py
index c80b754b..7be2bbc 100644
--- a/third_party/sqlite/PRESUBMIT.py
+++ b/third_party/sqlite/PRESUBMIT.py
@@ -13,6 +13,6 @@
   results += input_api.RunTests(
       input_api.canned_checks.GetUnitTests(input_api, output_api, [
           'scripts/extract_sqlite_api_unittest.py'
-      ]))
+      ], env=None, run_on_python2=False, run_on_python3=True))
 
   return results
diff --git a/third_party/sqlite/scripts/extract_sqlite_api.py b/third_party/sqlite/scripts/extract_sqlite_api.py
index f82f502..a301b2f6 100755
--- a/third_party/sqlite/scripts/extract_sqlite_api.py
+++ b/third_party/sqlite/scripts/extract_sqlite_api.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright 2018 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
@@ -18,6 +18,10 @@
 import re
 import sys
 
+class ExtractError(ValueError):
+  def __init__(self, message):
+    self.message = message
+
 def ExtractLineTuples(string):
   '''Returns a list of lines, with start/end whitespace stripped.
 
@@ -197,7 +201,7 @@
 def ExtractApiExport(macro_names, api_export_macro, statement):
   '''Extracts the symbol name from a statement exporting a function.
 
-  Returns None if the statement does not export a symbol. Throws ValueError if
+  Returns None if the statement does not export a symbol. Throws ExtractError if
   the parser cannot understand the statement.
   '''
   # See http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf, section 6.7
@@ -237,7 +241,7 @@
   seen_simple_type = False
   for word in words:
     if word in UNSUPPORTED_KEYWORDS:
-      raise ValueError("Unsupported keyword %s" % word)
+      raise ExtractError("Unsupported keyword %s" % word)
 
     if word in QUALIFIER_KEYWORDS:
       continue
@@ -249,7 +253,7 @@
 
     if word in COMPOSITE_TYPE_SPECIFIERS:
       if seen_simple_type:
-        raise ValueError('Mixed simple (struct_name) and composite (int) types')
+        raise ExtractError('Mixed simple (struct_name) and composite (int) types')
       seen_composite_type = True
       continue
 
@@ -260,16 +264,16 @@
     if not seen_composite_type and not seen_simple_type:
       seen_simple_type = True
       if IDENTIFIER_RE.match(word) is None:
-        raise ValueError(
+        raise ExtractError(
             "%s parsed as type name, which doesn't make sense" % word)
       continue
 
     if IDENTIFIER_RE.match(word) is None:
-      raise ValueError(
+      raise ExtractError(
           "%s parsed as symbol name, which doesn't make sense" % word)
     return word
 
-  raise ValueError('Failed to find symbol name')
+  raise ExtractError('Failed to find symbol name')
 
 
 def ExportedSymbolLine(symbol_prefix, symbol, statement_tuple):
@@ -311,7 +315,7 @@
       if symbol_name:
         output_lines.append(
             ExportedSymbolLine(symbol_prefix, symbol_name, statement_tuple))
-    except ValueError as exception:
+    except ExtractError as exception:
       output_lines.append(ExportedExceptionLine(exception, statement_tuple))
 
   output_lines.sort()
diff --git a/third_party/sqlite/scripts/extract_sqlite_api_unittest.py b/third_party/sqlite/scripts/extract_sqlite_api_unittest.py
index e612b20..f821f27 100755
--- a/third_party/sqlite/scripts/extract_sqlite_api_unittest.py
+++ b/third_party/sqlite/scripts/extract_sqlite_api_unittest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2018 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
@@ -8,7 +8,7 @@
 These tests should be getting picked up by the PRESUBMIT.py in this directory.
 """
 
-import imp
+from importlib.machinery import SourceFileLoader
 import os
 import shutil
 import sys
@@ -20,7 +20,7 @@
     self.test_root = tempfile.mkdtemp()
     source_path = os.path.join(
         os.path.dirname(os.path.realpath(__file__)), 'extract_sqlite_api.py')
-    self.extractor = imp.load_source('extract_api', source_path)
+    self.extractor = SourceFileLoader('extract_api', source_path).load_module()
 
   def tearDown(self):
     if self.test_root:
@@ -198,17 +198,21 @@
             set(['SQLITE_DEPRECATED']), 'SQLITE_API',
             'NOT_SQLITE_API struct sqlite_type sqlite3_sleep(int ms)'))
 
-    with self.assertRaisesRegexp(ValueError, 'Mixed simple .* and composite'):
+    with self.assertRaisesRegex(self.extractor.ExtractError,
+                                'Mixed simple .* and composite'):
       self.extractor.ExtractApiExport(
           set(), 'SQLITE_API', 'SQLITE_API void int sqlite3_sleep(int ms)')
-    with self.assertRaisesRegexp(ValueError, 'Unsupported keyword struct'):
+    with self.assertRaisesRegex(self.extractor.ExtractError,
+                                'Unsupported keyword struct'):
       self.extractor.ExtractApiExport(
           set(), 'SQLITE_API',
           'SQLITE_API struct sqlite_type sqlite3_sleep(int ms)')
-    with self.assertRaisesRegexp(ValueError, 'int\+\+ parsed as type name'):
+    with self.assertRaisesRegex(self.extractor.ExtractError,
+                                'int\+\+ parsed as type name'):
       self.extractor.ExtractApiExport(
           set(), 'SQLITE_API', 'SQLITE_API int++ sqlite3_sleep(int ms)')
-    with self.assertRaisesRegexp(ValueError, 'sqlite3\+sleep parsed as symbol'):
+    with self.assertRaisesRegex(self.extractor.ExtractError,
+                                'sqlite3\+sleep parsed as symbol'):
       self.extractor.ExtractApiExport(
           set(), 'SQLITE_API', 'SQLITE_API int sqlite3+sleep(int ms)')
 
@@ -228,7 +232,7 @@
     self.assertEqual(
         '// TODO: Lines 42-44 -- Something went wrong',
         self.extractor.ExportedExceptionLine(
-            ValueError('Something went wrong'),
+            self.extractor.ExtractError('Something went wrong'),
             (42, 44, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))
 
   def testProcessSource(self):
diff --git a/tools/mb/__init__.py b/tools/mb/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/mb/__init__.py
diff --git a/tools/mb/lib/__init__.py b/tools/mb/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/mb/lib/__init__.py
diff --git a/tools/mb/lib/validation.py b/tools/mb/lib/validation.py
new file mode 100644
index 0000000..137537fd
--- /dev/null
+++ b/tools/mb/lib/validation.py
@@ -0,0 +1,167 @@
+# Copyright 2020 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.
+"""Validation functions for the Meta-Build config file"""
+
+import collections
+
+
+def GetAllConfigsMaster(masters):
+  """Build a list of all of the configs referenced by builders.
+
+  Deprecated in favor or GetAllConfigsBucket
+  """
+  all_configs = {}
+  for master in masters:
+    for config in masters[master].values():
+      if isinstance(config, dict):
+        for c in config.values():
+          all_configs[c] = master
+      else:
+        all_configs[config] = master
+  return all_configs
+
+
+def GetAllConfigsBucket(buckets):
+  """Build a list of all of the configs referenced by builders."""
+  all_configs = {}
+  for bucket in buckets:
+    for config in buckets[bucket].values():
+      if isinstance(config, dict):
+        for c in config.values():
+          all_configs[c] = bucket
+      else:
+        all_configs[config] = bucket
+  return all_configs
+
+
+def CheckAllConfigsAndMixinsReferenced(errs, all_configs, configs, mixins):
+  """Check that every actual config is actually referenced."""
+  for config in configs:
+    if not config in all_configs:
+      errs.append('Unused config "%s".' % config)
+
+  # Figure out the whole list of mixins, and check that every mixin
+  # listed by a config or another mixin actually exists.
+  referenced_mixins = set()
+  for config, mixin_names in configs.items():
+    for mixin in mixin_names:
+      if not mixin in mixins:
+        errs.append(
+            'Unknown mixin "%s" referenced by config "%s".' % (mixin, config))
+      referenced_mixins.add(mixin)
+
+  for mixin in mixins:
+    for sub_mixin in mixins[mixin].get('mixins', []):
+      if not sub_mixin in mixins:
+        errs.append(
+            'Unknown mixin "%s" referenced by mixin "%s".' % (sub_mixin, mixin))
+      referenced_mixins.add(sub_mixin)
+
+  # Check that every mixin defined is actually referenced somewhere.
+  for mixin in mixins:
+    if not mixin in referenced_mixins:
+      errs.append('Unreferenced mixin "%s".' % mixin)
+
+  return errs
+
+
+def EnsureNoProprietaryMixinsBucket(errs, default_config, config_file,
+                                    public_artifact_builders, buckets, configs,
+                                    mixins):
+  """Check that the 'chromium' bots which build public artifacts
+  do not include the chrome_with_codecs mixin.
+  """
+  if config_file != default_config:
+    return
+
+  if public_artifact_builders is None:
+    errs.append('Missing "public_artifact_builders" config entry. '
+                'Please update this proprietary codecs check with the '
+                'name of the builders responsible for public build artifacts.')
+    return
+
+  # crbug/1033585
+  for bucket, builders in public_artifact_builders.items():
+    for builder in builders:
+      config = buckets[bucket][builder]
+
+      def RecurseMixins(builder, current_mixin):
+        if current_mixin == 'chrome_with_codecs':
+          errs.append('Public artifact builder "%s" can not contain the '
+                      '"chrome_with_codecs" mixin.' % builder)
+          return
+        if not 'mixins' in mixins[current_mixin]:
+          return
+        for mixin in mixins[current_mixin]['mixins']:
+          RecurseMixins(builder, mixin)
+
+      for mixin in configs[config]:
+        RecurseMixins(builder, mixin)
+
+  return errs
+
+
+def EnsureNoProprietaryMixinsMaster(errs, default_config, config_file, masters,
+                                    configs, mixins):
+  """If we're checking the Chromium config, check that the 'chromium' bots
+  which build public artifacts do not include the chrome_with_codecs mixin.
+
+  Deprecated in favor of BlacklistMixinsBucket
+  """
+  if config_file == default_config:
+    if 'chromium' in masters:
+      for builder in masters['chromium']:
+        config = masters['chromium'][builder]
+
+        def RecurseMixins(current_mixin):
+          if current_mixin == 'chrome_with_codecs':
+            errs.append('Public artifact builder "%s" can not contain the '
+                        '"chrome_with_codecs" mixin.' % builder)
+            return
+          if not 'mixins' in mixins[current_mixin]:
+            return
+          for mixin in mixins[current_mixin]['mixins']:
+            RecurseMixins(mixin)
+
+        for mixin in configs[config]:
+          RecurseMixins(mixin)
+    else:
+      errs.append('Missing "chromium" master. Please update this '
+                  'proprietary codecs check with the name of the master '
+                  'responsible for public build artifacts.')
+
+
+def CheckDuplicateConfigs(errs, config_pool, mixin_pool, grouping,
+                          flatten_config):
+  """Check for duplicate configs.
+
+  Evaluate all configs, and see if, when
+  evaluated, differently named configs are the same.
+  """
+  evaled_to_source = collections.defaultdict(set)
+  for group, builders in grouping.items():
+    for builder in builders:
+      config = grouping[group][builder]
+      if not config:
+        continue
+
+      if isinstance(config, dict):
+        # Ignore for now
+        continue
+      elif config.startswith('//'):
+        args = config
+      else:
+        args = flatten_config(config_pool, mixin_pool, config)['gn_args']
+        if 'error' in args:
+          continue
+
+      evaled_to_source[args].add(config)
+
+  for v in evaled_to_source.values():
+    if len(v) != 1:
+      errs.append(
+          'Duplicate configs detected. When evaluated fully, the '
+          'following configs are all equivalent: %s. Please '
+          'consolidate these configs into only one unique name per '
+          'configuration value.' % (', '.join(sorted('%r' % val for val in v))))
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 83344f8..a66d0e218 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2020 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.
 
@@ -9,6 +9,7 @@
 for sets of canned configurations and analyze them.
 """
 
+from __future__ import absolute_import
 from __future__ import print_function
 
 import argparse
@@ -34,8 +35,21 @@
 CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
     os.path.abspath(__file__))))
 sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.abspath(__file__)), '..'))
 
 import gn_helpers
+from mb.lib import validation
+
+
+def DefaultVals():
+  """Default mixin values"""
+  return {
+      'args_file': '',
+      'cros_passthrough': False,
+      'gn_args': '',
+  }
+
 
 def PruneVirtualEnv():
   # Set by VirtualEnv, no need to keep it.
@@ -68,16 +82,21 @@
 class MetaBuildWrapper(object):
   def __init__(self):
     self.chromium_src_dir = CHROMIUM_SRC_DIR
-    self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
-                                       'mb_config.pyl')
+    self.default_config_master = os.path.join(self.chromium_src_dir, 'tools',
+                                              'mb', 'mb_config.pyl')
+    self.default_config_bucket = os.path.join(self.chromium_src_dir, 'tools',
+                                              'mb', 'mb_config_buckets.pyl')
     self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
                                             'buildbot', 'gn_isolate_map.pyl')
+    self.group_by_bucket = False
     self.executable = sys.executable
     self.platform = sys.platform
     self.sep = os.sep
     self.args = argparse.Namespace()
     self.configs = {}
+    self.public_artifact_builders = None
     self.masters = {}
+    self.buckets = {}
     self.mixins = {}
 
   def Main(self, args):
@@ -99,20 +118,24 @@
 
   def ParseArgs(self, argv):
     def AddCommonOptions(subp):
+      group = subp.add_mutually_exclusive_group()
+      group.add_argument(
+          '-m', '--master', help='master name to look up config from')
+      group.add_argument('-u', '--bucket', help='bucket to look up config from')
       subp.add_argument('-b', '--builder',
                         help='builder name to look up config from')
-      subp.add_argument('-m', '--master',
-                        help='master name to look up config from')
       subp.add_argument('-c', '--config',
                         help='configuration to analyze')
       subp.add_argument('--phase',
                         help='optional phase name (used when builders '
                              'do multiple compiles with different '
                              'arguments in a single build)')
-      subp.add_argument('-f', '--config-file', metavar='PATH',
-                        default=self.default_config,
-                        help='path to config file '
-                             '(default is %(default)s)')
+      subp.add_argument(
+          '-f',
+          '--config-file',
+          metavar='PATH',
+          help=('path to config file '
+                '(default is mb_config[_bucket].pyl'))
       subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
                         help='path to isolate map file '
                              '(default is %(default)s)',
@@ -159,9 +182,12 @@
     subp = subps.add_parser('export',
                             description='Print out the expanded configuration '
                             'for each builder as a JSON object.')
-    subp.add_argument('-f', '--config-file', metavar='PATH',
-                      default=self.default_config,
-                      help='path to config file (default is %(default)s)')
+    subp.add_argument(
+        '-f',
+        '--config-file',
+        metavar='PATH',
+        help=('path to config file '
+              '(default is mb_config[_bucket].pyl'))
     subp.add_argument('-g', '--goma-dir',
                       help='path to goma directory')
     subp.set_defaults(func=self.CmdExport)
@@ -275,7 +301,6 @@
     subp = subps.add_parser('validate',
                             description='Validate the config file.')
     subp.add_argument('-f', '--config-file', metavar='PATH',
-                      default=self.default_config,
                       help='path to config file (default is %(default)s)')
     subp.set_defaults(func=self.CmdValidate)
 
@@ -304,6 +329,20 @@
 
     self.args = parser.parse_args(argv)
 
+    self.group_by_bucket = getattr(self.args, 'master', None) is None
+
+    # Use the correct default config file
+    # Not using hasattr here because it would still require a None check
+    if (self.args.func != self.CmdValidate
+        and getattr(self.args, 'config_file', None) is None):
+      # The default bucket config should be the same in all except replacing
+      # master with bucket and handling proprietary chrome mixins
+      if self.group_by_bucket:
+        self.args.config_file = self.default_config_bucket
+      else:
+        self.args.config_file = self.default_config_master
+
+
   def DumpInputFiles(self):
 
     def DumpContentsOfFilePassedTo(arg_name, path):
@@ -325,7 +364,42 @@
     vals = self.Lookup()
     return self.RunGNAnalyze(vals)
 
+  def CmdExportBucket(self):
+    self.ReadConfigFile()
+    obj = {}
+    for bucket, builders in self.buckets.items():
+      obj[bucket] = {}
+      for builder in builders:
+        config = self.buckets[bucket][builder]
+        if not config:
+          continue
+
+        if isinstance(config, dict):
+          args = {
+              k: FlattenConfig(self.configs, self.mixins, v)['gn_args']
+              for k, v in config.items()
+          }
+        elif config.startswith('//'):
+          args = config
+        else:
+          args = FlattenConfig(self.configs, self.mixins, config)['gn_args']
+          if 'error' in args:
+            continue
+
+        obj[bucket][builder] = args
+
+    # Dump object and trim trailing whitespace.
+    s = '\n'.join(
+        l.rstrip()
+        for l in json.dumps(obj, sort_keys=True, indent=2).splitlines())
+    self.Print(s)
+    return 0
+
   def CmdExport(self):
+    ''' Deprecated in favor of CmdExportBucket '''
+    if self.group_by_bucket:
+      return self.CmdExportBucket()
+
     self.ReadConfigFile()
     obj = {}
     for master, builders in self.masters.items():
@@ -336,12 +410,14 @@
           continue
 
         if isinstance(config, dict):
-          args = {k: self.FlattenConfig(v)['gn_args']
-                  for k, v in config.items()}
+          args = {
+              k: FlattenConfig(self.configs, self.mixins, v)['gn_args']
+              for k, v in config.items()
+          }
         elif config.startswith('//'):
           args = config
         else:
-          args = self.FlattenConfig(config)['gn_args']
+          args = FlattenConfig(self.configs, self.mixins, config)['gn_args']
           if 'error' in args:
             continue
 
@@ -632,21 +708,60 @@
             ('cpu', 'x86-64'),
             os_dim]
 
+  def CmdValidateBucket(self, print_ok=True):
+    errs = []
+
+    # Build a list of all of the configs referenced by builders.
+    all_configs = validation.GetAllConfigsBucket(self.buckets)
+
+    # Check that every referenced args file or config actually exists.
+    for config, loc in all_configs.items():
+      if config.startswith('//'):
+        if not self.Exists(self.ToAbsPath(config)):
+          errs.append(
+              'Unknown args file "%s" referenced from "%s".' % (config, loc))
+      elif not config in self.configs:
+        errs.append('Unknown config "%s" referenced from "%s".' % (config, loc))
+
+    # Check that every config and mixin is referenced.
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  self.configs, self.mixins)
+
+    validation.EnsureNoProprietaryMixinsBucket(
+        errs, self.default_config_bucket, self.args.config_file,
+        self.public_artifact_builders, self.buckets, self.configs, self.mixins)
+
+    validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
+                                     self.buckets, FlattenConfig)
+
+    if errs:
+      raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
+                  '\n  ' + '\n  '.join(errs))
+
+    if print_ok:
+      self.Print('mb config file %s looks ok.' % self.args.config_file)
+    return 0
+
   def CmdValidate(self, print_ok=True):
     errs = []
 
-    # Read the file to make sure it parses.
-    self.ReadConfigFile()
+    # Validate both bucket and master configs if
+    # a specific one isn't specified
+    if getattr(self.args, 'config_file', None) is None:
+      # Read the file to make sure it parses.
+      self.args.config_file = self.default_config_bucket
+      self.ReadConfigFile()
+      self.CmdValidateBucket()
+
+      self.args.config_file = self.default_config_master
+      self.ReadConfigFile()
+    else:
+      self.ReadConfigFile()
+      if self.group_by_bucket:
+        return self.CmdValidateBucket()
 
     # Build a list of all of the configs referenced by builders.
-    all_configs = {}
-    for master in self.masters:
-      for config in self.masters[master].values():
-        if isinstance(config, dict):
-          for c in config.values():
-            all_configs[c] = master
-        else:
-          all_configs[config] = master
+    all_configs = validation.GetAllConfigsMaster(self.masters)
 
     # Check that every referenced args file or config actually exists.
     for config, loc in all_configs.items():
@@ -658,84 +773,17 @@
         errs.append('Unknown config "%s" referenced from "%s".' %
                     (config, loc))
 
-    # Check that every actual config is actually referenced.
-    for config in self.configs:
-      if not config in all_configs:
-        errs.append('Unused config "%s".' % config)
+    # Check that every config and mixin is referenced.
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  self.configs, self.mixins)
 
-    # Figure out the whole list of mixins, and check that every mixin
-    # listed by a config or another mixin actually exists.
-    referenced_mixins = set()
-    for config, mixins in self.configs.items():
-      for mixin in mixins:
-        if not mixin in self.mixins:
-          errs.append('Unknown mixin "%s" referenced by config "%s".' %
-                      (mixin, config))
-        referenced_mixins.add(mixin)
+    validation.EnsureNoProprietaryMixinsMaster(
+        errs, self.default_config_master, self.args.config_file, self.masters,
+        self.configs, self.mixins)
 
-    for mixin in self.mixins:
-      for sub_mixin in self.mixins[mixin].get('mixins', []):
-        if not sub_mixin in self.mixins:
-          errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
-                      (sub_mixin, mixin))
-        referenced_mixins.add(sub_mixin)
+    validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
+                                     self.masters, FlattenConfig)
 
-    # Check that every mixin defined is actually referenced somewhere.
-    for mixin in self.mixins:
-      if not mixin in referenced_mixins:
-        errs.append('Unreferenced mixin "%s".' % mixin)
-
-    # If we're checking the Chromium config, check that the 'chromium' bots
-    # which build public artifacts do not include the chrome_with_codecs mixin.
-    if self.args.config_file == self.default_config:
-      if 'chromium' in self.masters:
-        for builder in self.masters['chromium']:
-          config = self.masters['chromium'][builder]
-          def RecurseMixins(builder, current_mixin):
-            if current_mixin == 'chrome_with_codecs':
-              errs.append('Public artifact builder "%s" can not contain the '
-                          '"chrome_with_codecs" mixin.' % builder)
-              return
-            if not 'mixins' in self.mixins[current_mixin]:
-              return
-            for mixin in self.mixins[current_mixin]['mixins']:
-              RecurseMixins(builder, mixin)
-
-          for mixin in self.configs[config]:
-            RecurseMixins(builder, mixin)
-      else:
-        errs.append('Missing "chromium" master. Please update this '
-                    'proprietary codecs check with the name of the master '
-                    'responsible for public build artifacts.')
-
-    # Check for duplicate configs. Evaluate all configs, and see if, when
-    # evaluated, differently named configs are the same.
-    evaled_to_source = collections.defaultdict(set)
-    for master, builders in self.masters.items():
-      for builder in builders:
-        config = self.masters[master][builder]
-        if not config:
-          continue
-
-        if isinstance(config, dict):
-          # Ignore for now
-          continue
-        elif config.startswith('//'):
-          args = config
-        else:
-          args = self.FlattenConfig(config)['gn_args']
-          if 'error' in args:
-            continue
-
-        evaled_to_source[args].add(config)
-
-    for v in evaled_to_source.values():
-      if len(v) != 1:
-        errs.append('Duplicate configs detected. When evaluated fully, the '
-                    'following configs are all equivalent: %s. Please '
-                    'consolidate these configs into only one unique name per '
-                    'configuration value.' % (
-                      ', '.join(sorted('%r' % val for val in v))))
     if errs:
       raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
                     '\n  ' + '\n  '.join(errs))
@@ -747,7 +795,7 @@
   def GetConfig(self):
     build_dir = self.args.path
 
-    vals = self.DefaultVals()
+    vals = DefaultVals()
     if self.args.builder or self.args.master or self.args.config:
       vals = self.Lookup()
       # Re-run gn gen in order to ensure the config is consistent with the
@@ -784,17 +832,20 @@
     vals = self.ReadIOSBotConfig()
     if not vals:
       self.ReadConfigFile()
-      config = self.ConfigFromArgs()
+      if self.group_by_bucket:
+        config = self.ConfigFromArgsBucket()
+      else:
+        config = self.ConfigFromArgs()
       if config.startswith('//'):
         if not self.Exists(self.ToAbsPath(config)):
           raise MBErr('args file "%s" not found' % config)
-        vals = self.DefaultVals()
+        vals = DefaultVals()
         vals['args_file'] = config
       else:
         if not config in self.configs:
           raise MBErr('Config "%s" not found in %s' %
                       (config, self.args.config_file))
-        vals = self.FlattenConfig(config)
+        vals = FlattenConfig(self.configs, self.mixins, config)
     return vals
 
   def ReadIOSBotConfig(self):
@@ -808,7 +859,7 @@
     contents = json.loads(self.ReadFile(path))
     gn_args = ' '.join(contents.get('gn_args', []))
 
-    vals = self.DefaultVals()
+    vals = DefaultVals()
     vals['gn_args'] = gn_args
     return vals
 
@@ -823,8 +874,12 @@
                  (self.args.config_file, e))
 
     self.configs = contents['configs']
-    self.masters = contents['masters']
     self.mixins = contents['mixins']
+    self.masters = contents.get('masters')
+    self.buckets = contents.get('buckets')
+    self.public_artifact_builders = contents.get('public_artifact_builders')
+
+    self.group_by_bucket = bool(self.buckets)
 
   def ReadIsolateMap(self):
     if not self.args.isolate_map_files:
@@ -848,7 +903,44 @@
             'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
     return isolate_maps
 
+  def ConfigFromArgsBucket(self):
+    if self.args.config:
+      if self.args.bucket or self.args.builder:
+        raise MBErr('Can not specify both -c/--config and -u/--bucket or '
+                    '-b/--builder')
+
+      return self.args.config
+
+    if not self.args.bucket or not self.args.builder:
+      raise MBErr('Must specify either -c/--config or '
+                  '(-u/--bucket and -b/--builder)')
+
+    if not self.args.bucket in self.buckets:
+      raise MBErr('Bucket name "%s" not found in "%s"' %
+                  (self.args.bucket, self.args.config_file))
+
+    if not self.args.builder in self.buckets[self.args.bucket]:
+      raise MBErr('Builder name "%s"  not found under buckets[%s] in "%s"' %
+                  (self.args.builder, self.args.bucket, self.args.config_file))
+
+    config = self.buckets[self.args.bucket][self.args.builder]
+    if isinstance(config, dict):
+      if self.args.phase is None:
+        raise MBErr('Must specify a build --phase for %s on %s' %
+                    (self.args.builder, self.args.bucket))
+      phase = str(self.args.phase)
+      if phase not in config:
+        raise MBErr('Phase %s doesn\'t exist for %s on %s' %
+                    (phase, self.args.builder, self.args.bucket))
+      return config[phase]
+
+    if self.args.phase is not None:
+      raise MBErr('Must not specify a build --phase for %s on %s' %
+                  (self.args.builder, self.args.bucket))
+    return config
+
   def ConfigFromArgs(self):
+    ''' Deprecated in favor ConfigFromArgsBucket '''
     if self.args.config:
       if self.args.master or self.args.builder:
         raise MBErr('Can not specific both -c/--config and -m/--master or '
@@ -884,47 +976,6 @@
                   (self.args.builder, self.args.master))
     return config
 
-  def FlattenConfig(self, config):
-    mixins = self.configs[config]
-    vals = self.DefaultVals()
-
-    visited = []
-    self.FlattenMixins(mixins, vals, visited)
-    return vals
-
-  def DefaultVals(self):
-    return {
-      'args_file': '',
-      'cros_passthrough': False,
-      'gn_args': '',
-    }
-
-  def FlattenMixins(self, mixins, vals, visited):
-    for m in mixins:
-      if m not in self.mixins:
-        raise MBErr('Unknown mixin "%s"' % m)
-
-      visited.append(m)
-
-      mixin_vals = self.mixins[m]
-
-      if 'cros_passthrough' in mixin_vals:
-        vals['cros_passthrough'] = mixin_vals['cros_passthrough']
-      if 'args_file' in mixin_vals:
-        if vals['args_file']:
-          raise MBErr('args_file specified multiple times in mixins '
-                      'for mixin %s' % m)
-        vals['args_file'] = mixin_vals['args_file']
-      if 'gn_args' in mixin_vals:
-        if vals['gn_args']:
-          vals['gn_args'] += ' ' + mixin_vals['gn_args']
-        else:
-          vals['gn_args'] = mixin_vals['gn_args']
-
-      if 'mixins' in mixin_vals:
-        self.FlattenMixins(mixin_vals['mixins'], vals, visited)
-    return vals
-
   def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
     build_dir = self.args.path
 
@@ -1705,26 +1756,6 @@
       raise MBErr('Error %s writing to the output path "%s"' %
                  (e, path))
 
-  def CheckCompile(self, master, builder):
-    url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
-    url = urllib2.quote(url_template.format(master=master, builder=builder),
-                        safe=':/()?=')
-    try:
-      builds = json.loads(self.Fetch(url))
-    except Exception as e:
-      return str(e)
-    successes = sorted(
-        [int(x) for x in builds.keys() if "text" in builds[x] and
-          cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
-        reverse=True)
-    if not successes:
-      return "no successful builds"
-    build = builds[str(successes[0])]
-    step_names = set([step["name"] for step in build["steps"]])
-    compile_indicators = set(["compile", "compile (with patch)", "analyze"])
-    if compile_indicators & step_names:
-      return "compiles"
-    return "does not compile"
 
   def PrintCmd(self, cmd, env):
     if self.platform == 'win32':
@@ -1886,6 +1917,42 @@
         self._run_cmd(self._result, cmd), self._run_cmd)
 
 
+def FlattenConfig(config_pool, mixin_pool, config):
+  mixins = config_pool[config]
+  vals = DefaultVals()
+
+  visited = []
+  FlattenMixins(mixin_pool, mixins, vals, visited)
+  return vals
+
+
+def FlattenMixins(mixin_pool, mixins_to_flatten, vals, visited):
+  for m in mixins_to_flatten:
+    if m not in mixin_pool:
+      raise MBErr('Unknown mixin "%s"' % m)
+
+    visited.append(m)
+
+    mixin_vals = mixin_pool[m]
+
+    if 'cros_passthrough' in mixin_vals:
+      vals['cros_passthrough'] = mixin_vals['cros_passthrough']
+    if 'args_file' in mixin_vals:
+      if vals['args_file']:
+        raise MBErr('args_file specified multiple times in mixins '
+                    'for mixin %s' % m)
+      vals['args_file'] = mixin_vals['args_file']
+    if 'gn_args' in mixin_vals:
+      if vals['gn_args']:
+        vals['gn_args'] += ' ' + mixin_vals['gn_args']
+      else:
+        vals['gn_args'] = mixin_vals['gn_args']
+
+    if 'mixins' in mixin_vals:
+      FlattenMixins(mixin_pool, mixin_vals['mixins'], vals, visited)
+  return vals
+
+
 
 class MBErr(Exception):
   pass
diff --git a/tools/mb/mb_config_buckets.pyl b/tools/mb/mb_config_buckets.pyl
new file mode 100644
index 0000000..77af754
--- /dev/null
+++ b/tools/mb/mb_config_buckets.pyl
@@ -0,0 +1,3392 @@
+
+# Copyright 2020 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 is a .pyl, or "Python Literal", file. You can treat it just like a
+# .json file, with the following exceptions:
+# * all keys must be quoted (use single quotes, please);
+# * comments are allowed, using '#' syntax; and
+# * trailing commas are allowed.
+
+{
+  'buckets': {
+    'ci': {
+      'ASAN Debug': 'asan_lsan_debug_bot',
+      'ASAN Release': 'asan_lsan_fuzzer_v8_heap_release_bot',
+      'ASAN Release Media': 'asan_lsan_fuzzer_v8_heap_chromeos_codecs_release_bot',
+      'ASan Debug (32-bit x86 with V8-ARM)': 'asan_v8_heap_debug_bot_hybrid',
+      'ASan Release (32-bit x86 with V8-ARM)': 'asan_fuzzer_v8_heap_release_bot_hybrid',
+      'ASan Release Media (32-bit x86 with V8-ARM)': 'asan_fuzzer_v8_heap_chromeos_codecs_release_bot_hybrid',
+      'Afl Upload Linux ASan': 'afl_asan_shared_release_bot',
+      'Android ASAN (dbg)': 'android_clang_asan_debug_bot',
+      'Android CFI': 'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'Android FYI 32 Vk Release (Pixel 2)': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'Android FYI 32 dEQP Vk Release (Pixel 2)': 'deqp_android_vulkan_ndk_release_trybot',
+      'Android FYI 64 Vk Release (Pixel 2)': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'Android FYI 64 dEQP Vk Release (Pixel 2)': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'Android FYI Release (NVIDIA Shield TV)': 'gpu_tests_android_release_trybot_arm64',
+      'Android FYI Release (Nexus 5)': 'gpu_tests_android_release_trybot',
+      'Android FYI Release (Nexus 5X)': 'gpu_tests_android_release_trybot_arm64',
+      'Android FYI Release (Nexus 6)': 'gpu_tests_android_release_trybot',
+      'Android FYI Release (Nexus 6P)': 'gpu_tests_android_release_trybot_arm64',
+      'Android FYI Release (Nexus 9)': 'gpu_tests_android_release_trybot_arm64',
+      'Android FYI Release (Pixel 2)': 'gpu_tests_android_release_trybot',
+      'Android FYI SkiaRenderer GL (Nexus 5X)': 'gpu_tests_android_release_trybot_arm64',
+      'Android FYI SkiaRenderer Vulkan (Pixel 2)': 'gpu_tests_android_release_trybot',
+      'Android FYI dEQP Release (Nexus 5X)': 'deqp_android_release_trybot_arm64',
+      'Android Release (Nexus 5X)': 'gpu_tests_android_release_trybot_arm64_fastbuild',
+      'Android WebView P FYI (rel)': 'android_release_bot_minimal_symbols_arm64_webview_google',
+      'Android WebView P OOR-CORS FYI (rel)': 'android_release_bot_minimal_symbols_arm64_webview_google',
+      'Android arm Builder (dbg)': 'android_webview_google_debug_static_bot',
+      'Android arm64 Builder (dbg)': 'android_webview_google_debug_static_bot_arm64',
+      'Android x64 Builder (dbg)': 'android_debug_static_bot_x64',
+      'Android x86 Builder (dbg)': 'android_debug_static_bot_x86',
+      'CFI Linux CF': 'cfi_full_cfi_icall_cfi_diag_recover_release_static',
+      'CFI Linux ToT': 'clang_tot_cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on',
+      'Cast Android (dbg)': 'android_cast_debug_static_bot',
+      'Cast Audio Linux': 'cast_audio_release_bot',
+      'Cast Linux': 'cast_release_bot',
+      'ChromiumOS ASAN Release': 'chromeos_asan_lsan_fuzzer_v8_heap_release_bot',
+      'CrWinAsan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release_tot',
+      'CrWinAsan(dll)': 'asan_clang_shared_v8_heap_minimal_symbols_release_tot',
+      'Dawn Linux x64 Builder': 'dawn_tests_release_trybot',
+      'Dawn Linux x64 DEPS Builder': 'dawn_tests_release_trybot',
+      'Dawn Mac x64 Builder': 'dawn_tests_release_trybot',
+      'Dawn Mac x64 DEPS Builder': 'dawn_tests_release_trybot',
+      'Dawn Win10 x64 Builder': 'dawn_tests_release_trybot',
+      'Dawn Win10 x64 DEPS Builder': 'dawn_tests_release_trybot',
+      'Dawn Win10 x86 Builder': 'dawn_tests_release_trybot_x86',
+      'Dawn Win10 x86 DEPS Builder': 'dawn_tests_release_trybot_x86',
+      'Deterministic Android': 'android_without_codecs_release_bot_minimal_symbols',
+      'Deterministic Android (dbg)': 'android_debug_bot',
+      'Deterministic Fuchsia (dbg)': 'debug_bot_fuchsia',
+      'Deterministic Linux': 'release_bot_minimal_symbols',
+      'Deterministic Linux (dbg)': {
+        'local': 'debug_bot_local_build',
+        'goma': 'debug_bot'
+      },
+      'Fuchsia ARM64': 'release_bot_fuchsia_arm64',
+      'Fuchsia x64': 'release_bot_fuchsia',
+      'GPU FYI Linux Builder': 'gpu_fyi_tests_release_trybot',
+      'GPU FYI Linux Builder (dbg)': 'gpu_fyi_tests_debug_trybot',
+      'GPU FYI Linux Ozone Builder': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
+      'GPU FYI Linux dEQP Builder': 'deqp_release_trybot',
+      'GPU FYI Mac Builder': 'gpu_fyi_tests_release_trybot',
+      'GPU FYI Mac Builder (dbg)': 'gpu_fyi_tests_debug_trybot',
+      'GPU FYI Mac dEQP Builder': 'deqp_release_trybot',
+      'GPU FYI Perf Android 64 Builder': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'GPU FYI Win Builder': 'gpu_fyi_tests_release_trybot_x86',
+      'GPU FYI Win Builder (dbg)': 'gpu_fyi_tests_debug_trybot_x86',
+      'GPU FYI Win dEQP Builder': 'deqp_release_trybot_x86',
+      'GPU FYI Win x64 Builder': 'gpu_fyi_tests_release_trybot',
+      'GPU FYI Win x64 Builder (dbg)': 'gpu_fyi_tests_debug_trybot',
+      'GPU FYI Win x64 DX12 Vulkan Builder': 'gpu_fyi_tests_dx12vk_release_trybot',
+      'GPU FYI Win x64 DX12 Vulkan Builder (dbg)': 'gpu_fyi_tests_dx12vk_debug_trybot',
+      'GPU FYI Win x64 dEQP Builder': 'deqp_release_trybot',
+      'GPU FYI XR Win x64 Builder': 'gpu_fyi_tests_release_trybot',
+      'GPU Linux Builder': 'gpu_tests_release_trybot',
+      'GPU Linux Builder (dbg)': 'gpu_tests_debug_bot',
+      'GPU Mac Builder': 'gpu_tests_release_trybot_deterministic_mac',
+      'GPU Mac Builder (dbg)': 'gpu_tests_debug_bot',
+      'GPU Win x64 Builder': 'gpu_tests_release_trybot_resource_whitelisting',
+      'GPU Win x64 Builder (dbg)': 'gpu_tests_debug_bot',
+      'Leak Detection Linux': 'release_bot',
+      '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',
+      'Libfuzzer Upload Linux MSan': 'libfuzzer_msan_release_bot',
+      'Libfuzzer Upload Linux UBSan': 'libfuzzer_ubsan_release_bot',
+      'Libfuzzer Upload Linux V8-ARM64 ASan': 'libfuzzer_asan_release_bot_v8_arm64',
+      'Libfuzzer Upload Linux V8-ARM64 ASan Debug': 'libfuzzer_asan_debug_bot_v8_arm64',
+      'Libfuzzer Upload Linux32 ASan': 'libfuzzer_asan_release_bot_x86',
+      'Libfuzzer Upload Linux32 ASan Debug': 'libfuzzer_asan_debug_bot_x86',
+      'Libfuzzer Upload Linux32 V8-ARM ASan': 'libfuzzer_asan_release_bot_x86_v8_arm',
+      'Libfuzzer Upload Linux32 V8-ARM ASan Debug': 'libfuzzer_asan_debug_bot_x86_v8_arm',
+      'Libfuzzer Upload Mac ASan': 'libfuzzer_mac_asan_shared_release_bot',
+      'Libfuzzer Upload Windows ASan': 'libfuzzer_windows_asan_release_bot',
+      'Linux ASan LSan Builder': 'asan_lsan_release_trybot',
+      'Linux Builder': 'gpu_tests_release_bot',
+      'Linux Builder (dbg)': 'gpu_tests_debug_bot',
+      'Linux Builder (dbg)(32)': 'gpu_tests_debug_bot_x86',
+      'Linux CFI': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'Linux Chromium OS ASan LSan Builder': 'asan_lsan_chromeos_release_trybot',
+      'Linux ChromiumOS Full': 'chromeos_with_codecs_release_bot',
+      'Linux ChromiumOS MSan Builder': 'chromeos_msan_release_bot',
+      'Linux FYI GPU TSAN Release': 'gpu_fyi_tests_release_trybot_tsan',
+      'Linux MSan Builder': 'msan_release_bot',
+      'Linux TSan Builder': 'tsan_disable_nacl_release_bot',
+      'Linux Viz': 'release_trybot',
+      'Linux remote_run Builder': 'release_bot',
+      'Linux remote_run Tester': 'release_bot',
+      'MSAN Release (chained origins)': 'msan_release_bot',
+      'MSAN Release (no origins)': 'msan_no_origins_release_bot',
+      'Mac ASAN Release': 'asan_disable_nacl_fuzzer_v8_heap_release_bot',
+      'Mac ASAN Release Media': 'asan_disable_nacl_fuzzer_v8_heap_chrome_with_codecs_release_bot',
+      'Mac ASan 64 Builder': 'asan_minimal_symbols_disable_nacl_release_bot_dcheck_always_on',
+      'Mac Builder': 'gpu_tests_release_bot_minimal_symbols',
+      'Mac Builder (dbg)': 'gpu_tests_debug_bot',
+      'Mac Builder Next': 'gpu_tests_release_bot_minimal_symbols',
+      'Mac FYI GPU ASAN Release': 'gpu_fyi_tests_release_trybot_asan',
+      'Mac deterministic': 'release_bot_mac_strip_minimal_symbols_deterministic',
+      'Mac deterministic (dbg)': 'debug_bot_deterministic',
+      'Mojo Android': 'android_release_bot_minimal_symbols_arm64',
+      'Mojo ChromiumOS': 'chromeos_with_codecs_release_trybot',
+      'Mojo Linux': 'release_trybot',
+      'Mojo Windows': 'release_bot_x86_minimal_symbols',
+      'Site Isolation Android': 'android_release_bot_minimal_symbols_arm64',
+      'TSAN Debug': 'tsan_disable_nacl_debug_bot',
+      'TSAN Release': 'tsan_disable_nacl_release_bot',
+      'ToTAndroid': 'android_clang_tot_release_minimal_symbols',
+      'ToTAndroid (dbg)': 'android_clang_tot_dbg',
+      'ToTAndroid x64': 'android_clang_tot_x64',
+      'ToTAndroid64': 'android_clang_tot_release_arm64',
+      'ToTAndroidASan': 'android_clang_tot_asan',
+      'ToTAndroidCFI': 'android_clang_tot_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on',
+      'ToTAndroidOfficial': 'android_clang_tot_release_minimal_symbols_official_optimize',
+      'ToTLinux': 'clang_tot_linux_full_symbols_shared_release',
+      'ToTLinux (dbg)': 'clang_tot_shared_debug',
+      'ToTLinuxASan': 'clang_tot_asan_lsan_static_release',
+      'ToTLinuxASanLibfuzzer': 'libfuzzer_asan_clang_tot_release',
+      'ToTLinuxCoverage': 'clang_tot_coverage_minimal_symbols_release',
+      'ToTLinuxMSan': 'clang_tot_msan_release',
+      'ToTLinuxTSan': 'clang_tot_tsan_release',
+      'ToTLinuxThinLTO': 'clang_tot_release_minimal_symbols_thin_lto_static',
+      'ToTLinuxUBSanVptr': 'clang_tot_ubsan_no_recover_hack_static_release',
+      'ToTMac': 'clang_tot_minimal_symbols_shared_release',
+      'ToTMac (dbg)': 'clang_tot_shared_debug',
+      'ToTMacASan': 'asan_disable_nacl_clang_tot_minimal_symbols_static_release',
+      'ToTMacCoverage': 'clang_tot_coverage_minimal_symbols_release',
+      'ToTWin(dbg)': 'clang_tot_shared_debug_x86',
+      'ToTWin(dll)': 'clang_tot_minimal_symbols_shared_release_x86_dcheck',
+      'ToTWin64(dbg)': 'clang_tot_shared_debug',
+      'ToTWin64(dll)': 'clang_tot_shared_release_dcheck',
+      'ToTWinASanLibfuzzer': 'libfuzzer_windows_asan_clang_tot_release_bot',
+      'ToTWinCFI': 'clang_tot_win_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_x86',
+      'ToTWinCFI64': 'clang_tot_win_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on',
+      'ToTWinLibcxx64': 'clang_tot_official_optimize_minimal_symbols_static_release_libcxx',
+      'ToTiOS': 'ios_error',
+      'UBSan Release': 'ubsan_release_bot',
+      'UBSan vptr Release': 'ubsan_vptr_release_bot',
+      'UBSanVptr Linux': 'ubsan_vptr_release_bot',
+      'VR Linux': 'vr_release_bot',
+      'WebKit Linux ASAN': 'asan_lsan_release_bot',
+      'WebKit Linux Leak': 'release_bot',
+      'WebKit Linux MSAN': 'msan_release_bot',
+      'WebKit Mac10.13 (retina)': 'release_bot',
+      'Win 10 Fast Ring': 'release_trybot',
+      'Win ASan Release': 'asan_fuzzer_v8_heap_release_bot',
+      'Win ASan Release Media': 'asan_fuzzer_v8_heap_chrome_with_codecs_release_bot',
+      'Win Builder': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder (dbg)': 'gpu_tests_debug_bot_x86',
+      'Win x64 Builder': 'gpu_tests_release_bot_minimal_symbols',
+      'Win x64 Builder (dbg)': 'gpu_tests_debug_bot',
+      'Windows deterministic': 'release_bot_x86_minimal_symbols',
+      'android-archive-dbg': 'android_without_codecs_debug_bot',
+      'android-archive-rel': 'android_without_codecs_release_bot_minimal_symbols',
+      'android-arm64-proguard-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
+      'android-asan': 'android_clang_asan_release_trybot',
+      'android-code-coverage': 'gpu_tests_android_release_bot_minimal_symbols_arm64_fastbuild_java_coverage',
+      'android-code-coverage-native': 'gpu_tests_android_release_bot_minimal_symbols_arm64_fastbuild_native_coverage',
+      'android-cronet-arm-dbg': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android-cronet-arm-rel': 'android_cronet_release_bot_minimal_symbols_arm_no_neon',
+      'android-cronet-arm64-dbg': 'android_cronet_debug_static_bot_arm64',
+      'android-cronet-arm64-rel': 'android_cronet_release_bot_minimal_symbols_arm64',
+      'android-cronet-asan-arm-rel': 'android_cronet_release_bot_minimal_symbols_arm_no_neon_clang_asan',
+      'android-cronet-marshmallow-arm64-perf-rel': 'android_cronet_release_bot_minimal_symbols_arm64',
+      '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-kitkat-arm-rel': 'android_release_trybot_fastbuild',
+      'android-marshmallow-arm64-rel': 'gpu_tests_android_release_trybot_arm64_resource_whitelisting_fastbuild_java_coverage',
+      'android-marshmallow-x86-fyi-rel': 'android_release_trybot_x86_resource_whitelisting',
+      'android-mojo-webview-rel': 'android_release_bot_minimal_symbols_arm64',
+      'android-pie-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google',
+      'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
+      'chromeos-amd64-generic-cfi-thin-lto-rel': 'cros_chrome_sdk_cfi_thin_lto',
+      'chromeos-amd64-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-amd64-generic-rel': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-vm-tests': 'cros_chrome_sdk_dcheck_always_on',
+      'chromeos-arm-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-arm-generic-rel': 'cros_chrome_sdk_dcheck_always_on',
+      'chromeos-kevin-rel': 'cros_chrome_sdk',
+      'chromeos-kevin-rel-hw-tests': 'cros_chrome_sdk',
+      'fuchsia-arm64-cast': 'release_trybot_fuchsia_arm64_cast',
+      'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
+      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
+      'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
+      'fuchsia-x64-cast': 'release_trybot_fuchsia_cast',
+      'fuchsia-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'ios-device': 'ios_error',
+      'ios-device-xcode-clang': 'ios_error',
+      'ios-simulator': 'ios_error',
+      'ios-simulator-code-coverage': 'clang_code_coverage_ios',
+      'ios-simulator-cr-recipe': 'ios_simulator_debug_static_bot',
+      'ios-simulator-cronet': 'ios_error',
+      'ios-simulator-full-configs': 'ios_error',
+      'ios-simulator-xcode-clang': 'ios_error',
+      'linux-annotator-rel': 'release_trybot',
+      'linux-archive-dbg': 'debug_bot',
+      'linux-archive-rel': 'release_bot',
+      'linux-bfcache-debug': 'debug_bot',
+      'linux-blink-animation-use-time-delta': 'debug_bot_enable_blink_animation_use_time_delta',
+      'linux-blink-heap-concurrent-marking-tsan-rel': 'release_trybot_tsan',
+      'linux-blink-heap-verification': 'release_bot_enable_blink_heap_verification_dcheck_always_on',
+      'linux-chromeos-code-coverage': 'chromeos_with_codecs_release_bot_coverage',
+      'linux-chromeos-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-rel': 'chromeos_with_codecs_release_trybot_code_coverage',
+      'linux-chromium-tests-staging-builder': 'release_bot',
+      'linux-code-coverage': 'clang_code_coverage',
+      'linux-fieldtrial-rel': 'release_bot_minimal_symbols',
+      'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
+      'linux-oor-cors-rel': 'release_bot_minimal_symbols',
+      'linux-ozone-rel': 'ozone_linux_release_trybot',
+      'linux-swangle-tot-angle-x64': 'deqp_release_trybot',
+      'linux-swangle-tot-angle-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-tot-swiftshader-x64': 'deqp_release_trybot',
+      'linux-swangle-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-x64': 'deqp_release_trybot',
+      'linux-swangle-x86': 'deqp_release_trybot_x86',
+      'linux-trusty-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange',
+      'linux-win_cross-rel': 'clang_tot_win_release_cross',
+      'linux-wpt-fyi-rel': 'release_trybot',
+      'mac-archive-dbg': 'debug_bot',
+      'mac-archive-rel': 'release_bot_mac_strip_minimal_symbols',
+      'mac-code-coverage': 'clang_code_coverage',
+      'mac-hermetic-upgrade-rel': 'release_bot',
+      'mac-mojo-rel': 'release_trybot',
+      'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'win-annotator-rel': 'release_trybot',
+      'win-archive-dbg': 'debug_bot',
+      'win-archive-rel': 'release_bot_minimal_symbols_enable_archive_compression',
+      'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release',
+      'win-celab-builder-rel': 'release_bot_minimal_symbols',
+      'win-pixel-builder-rel': 'release_bot',
+      'win-swangle-tot-angle-x64': 'deqp_release_trybot',
+      'win-swangle-tot-angle-x86': 'deqp_release_trybot_x86',
+      'win-swangle-tot-swiftshader-x64': 'deqp_release_trybot',
+      'win-swangle-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'win-swangle-x64': 'deqp_release_trybot',
+      'win-swangle-x86': 'deqp_release_trybot_x86',
+      'win10-code-coverage': 'clang_code_coverage',
+      'win32-archive-dbg': 'debug_bot_x86',
+      'win32-archive-rel': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'win32-arm64-rel': 'win32_arm64_release_bot',
+      'Chromium Android ARM 32-bit Goma RBE Prod (clobber)': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg) (clobber)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Staging': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT (ATS)': 'android_release_bot_minimal_symbols',
+      'Chromium Linux Goma RBE Prod': 'release_bot',
+      'Chromium Linux Goma RBE Prod (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Prod (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Prod (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging': 'release_bot',
+      'Chromium Linux Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE ToT': 'release_bot',
+      'Chromium Linux Goma RBE ToT (ATS)': 'release_bot',
+      'Chromium Linux Goma Staging': 'release_bot',
+      'Chromium Mac Goma RBE Prod': 'release_bot',
+      'Chromium Mac Goma RBE Staging': 'release_bot',
+      'Chromium Mac Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Mac Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Mac Goma RBE ToT': 'release_bot',
+      'Chromium Mac Goma Staging': 'release_bot',
+      'Chromium Win Goma RBE Prod': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (dbg)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Prod (dbg) (clobber)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Staging': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Staging (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE ToT': 'release_bot_x86_minimal_symbols',
+      'CrWinGomaStaging': 'release_bot_x86_minimal_symbols',
+      'Linux Builder Goma Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma Latest Client': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Latest Client': 'gpu_tests_release_bot',
+      'Mac Builder (dbg) Goma Canary': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder Goma Canary': 'gpu_tests_release_bot_minimal_symbols',
+      'Mac Builder Goma Latest Client': 'gpu_tests_release_bot_minimal_symbols',
+      'Win Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma RBE Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma RBE Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win cl.exe Goma Canary LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win cl.exe Goma Latest Client LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win7 Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win7 Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'WinMSVC64 Goma Canary': 'win_msvc_release_bot',
+      'WinMSVC64 Goma Latest Client': 'win_msvc_release_bot',
+      'android-archive-dbg-goma-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-latest': 'android_without_codecs_debug_bot',
+      'chromeos-amd64-generic-rel (Goma RBE FYI)': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-latest': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-latest': 'cros_chrome_sdk',
+      'fuchsia-fyi-arm64-rel (Goma RBE FYI)': 'release_bot_fuchsia_arm64',
+      'fuchsia-fyi-x64-rel (Goma RBE FYI)': 'release_bot_fuchsia',
+      'ios-device-goma-canary-clobber': 'ios_error',
+      'ios-device-goma-latest-clobber': 'ios_error',
+      'ios-device-goma-rbe-canary-clobber': 'ios_error',
+      'ios-device-goma-rbe-latest-clobber': 'ios_error',
+      'linux-archive-rel-goma-canary': 'release_bot',
+      'linux-archive-rel-goma-canary-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-latest': 'release_bot',
+      'linux-archive-rel-goma-latest-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-latest': 'release_bot',
+      'linux-archive-rel-goma-rbe-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-latest': 'release_bot',
+      'mac-archive-rel-goma-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-canary-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-latest': 'release_bot_mac_strip_minimal_symbols',
+      'win32-archive-rel-goma-canary-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'win32-archive-rel-goma-latest-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'android-bfcache-debug': 'android_debug_static_bot',
+      'android-binary-size': 'android_binary_size',
+      'android-deterministic-dbg': 'android_debug_bot',
+      'android-deterministic-rel': 'android_without_codecs_release_trybot',
+      'android-opus-kitkat-arm-rel': 'android_release_trybot',
+      'android-oreo-arm64-cts-networkservice-dbg': 'android_debug_trybot_arm64',
+      'android-oreo-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-webview-marshmallow-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-nougat-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-oreo-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-fyi-rel': 'android_release_trybot_arm64_webview_google',
+      'android_angle_deqp_rel_ng': 'deqp_android_release_trybot_arm64',
+      'android_angle_rel_ng': 'gpu_tests_android_release_trybot_arm64',
+      'android_angle_vk32_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot',
+      'android_angle_vk32_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'android_angle_vk64_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'android_angle_vk64_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'android_archive_rel_ng': 'android_release_trybot',
+      'android_arm64_dbg_recipe': 'android_debug_trybot_compile_only_arm64_fastbuild',
+      'android_blink_rel': 'android_release_trybot',
+      'android_cfi_rel_ng': 'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'android_clang_dbg_recipe': 'android_clang_asan_debug_trybot_compile_only_fastbuild',
+      'android_compile_dbg': 'android_debug_trybot_compile_only',
+      'android_compile_x64_dbg': 'android_debug_trybot_compile_only_x64',
+      'android_compile_x86_dbg': 'android_debug_trybot_compile_only_x86',
+      'android_cronet': 'android_cronet_release_trybot_arm_no_neon',
+      'android_cronet_tester': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android_mojo': 'android_release_trybot_arm64',
+      'android_n5x_swarming_dbg': 'android_debug_trybot_arm64',
+      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot_arm64',
+      'android_unswarmed_pixel_aosp': 'android_debug_trybot_arm64',
+      'cast_shell_android': 'android_cast_debug_static_bot_compile_only',
+      'cast_shell_audio_linux': 'cast_audio_release_trybot',
+      'cast_shell_linux': 'cast_release_trybot',
+      'chromeos-kevin-compile-rel': 'cros_chrome_sdk',
+      'chromium_presubmit': 'presubmit',
+      'closure_compilation': 'closure_compilation',
+      'dawn-linux-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-mac-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x86-deps-rel': 'dawn_tests_release_trybot_x86',
+      'fuchsia-angle-rel': 'gpu_fyi_tests_release_trybot_fuchsia',
+      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'fuchsia_arm64': 'release_trybot_fuchsia_arm64',
+      'fuchsia_x64': 'release_trybot_fuchsia',
+      'gpu-fyi-try-android-l-nexus-5-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-l-nexus-6-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-deqp-64': 'deqp_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-skgl-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-6p-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-9-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-n-nvidia-shield-tv-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-p-pixel-2-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-p-pixel-2-skv-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-32': 'deqp_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-64': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-android-q-pixel-2-vk-32': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-vk-64': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-linux-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-linux-nvidia-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-nvidia-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-tsn': 'gpu_fyi_tests_release_trybot_tsan',
+      'gpu-fyi-try-mac-amd-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-amd-pro-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-amd-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-asan': 'gpu_fyi_tests_release_trybot_asan',
+      'gpu-fyi-try-mac-intel-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win-xr-builder-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-intel-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dbg-64': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-dbg-64': 'gpu_fyi_tests_dx12vk_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-rel-64': 'gpu_fyi_tests_dx12vk_release_trybot',
+      'gpu-fyi-try-win10-nvidia-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-rel-32': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'gpu-fyi-try-win10-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-skgl-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win7-amd-dbg-32': 'gpu_fyi_tests_debug_trybot_x86',
+      'gpu-fyi-try-win7-amd-dqp-32': 'deqp_release_trybot_x86',
+      'gpu-fyi-try-win7-amd-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win7-nvidia-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-try-linux-nvidia-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-linux-nvidia-rel': 'gpu_tests_release_trybot',
+      'gpu-try-mac-amd-retina-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-mac-intel-dbg': 'gpu_tests_debug_bot',
+      'layout_test_leak_detection': 'release_trybot',
+      'leak_detection_linux': 'release_trybot',
+      'linux-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'linux-blink-heap-verification-try': 'release_trybot_enable_blink_heap_verification',
+      'linux-blink-rel': 'release_bot_minimal_symbols',
+      'linux-chromeos-compile-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-clang-tidy-dbg': 'debug_bot',
+      'linux-clang-tidy-rel': 'release_trybot',
+      'linux-dawn-rel': 'dawn_tests_release_trybot',
+      'linux-dcheck-off-rel': 'release_trybot_dcheck_off',
+      'linux-layout-tests-fragment-item': 'release_trybot',
+      'linux-layout-tests-fragment-paint': 'release_trybot',
+      'linux-libfuzzer-asan-rel': 'libfuzzer_asan_release_trybot',
+      'linux-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage',
+      'linux-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-x64': 'deqp_release_trybot',
+      'linux-swangle-try-x86': 'deqp_release_trybot_x86',
+      'linux-viz-rel': 'release_trybot',
+      'linux-webkit-msan-rel': 'msan_release_bot',
+      'linux_android_dbg_ng': 'android_debug_bot',
+      'linux_angle_deqp_rel_ng': 'deqp_release_trybot',
+      'linux_angle_ozone_rel_ng': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
+      'linux_arm': 'release_trybot_arm',
+      'linux_chromium_archive_rel_ng': 'release_bot',
+      'linux_chromium_asan_rel_ng': 'asan_lsan_release_trybot',
+      'linux_chromium_cfi_rel_ng': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'linux_chromium_chromeos_asan_rel_ng': 'asan_lsan_chromeos_release_trybot',
+      'linux_chromium_chromeos_msan_rel_ng': 'chromeos_msan_release_bot',
+      'linux_chromium_clobber_deterministic': 'release_trybot',
+      'linux_chromium_clobber_rel_ng': 'release_trybot',
+      'linux_chromium_compile_dbg_32_ng': 'debug_bot_x86',
+      'linux_chromium_compile_dbg_ng': 'debug_bot',
+      'linux_chromium_compile_rel_ng': 'release_trybot',
+      'linux_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'linux_chromium_msan_rel_ng': 'msan_release_bot',
+      'linux_chromium_tsan_rel_ng': 'tsan_disable_nacl_release_trybot',
+      'linux_chromium_ubsan_rel_ng': 'ubsan_vptr_release_bot',
+      'linux_layout_tests_composite_after_paint': 'release_trybot',
+      'linux_layout_tests_layout_ng_disabled': 'release_trybot',
+      'linux_mojo': 'release_trybot',
+      'linux_mojo_chromeos': 'chromeos_with_codecs_release_trybot',
+      'linux_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'linux_upload_clang': 'release_bot',
+      'linux_vr': 'vr_release_trybot',
+      'mac-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'mac-dawn-rel': 'dawn_tests_release_trybot',
+      'mac-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac10.10-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.11-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.12-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13_retina-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.14-blink-rel': 'release_bot_minimal_symbols',
+      'mac_chromium_10.10': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.12_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.13_rel_ng': 'release_trybot',
+      'mac_chromium_10.14_rel_ng': 'release_trybot',
+      'mac_chromium_archive_rel_ng': 'release_bot_mac_strip_minimal_symbols',
+      'mac_chromium_asan_rel_ng': 'asan_dcheck_disable_nacl_release_bot',
+      'mac_chromium_compile_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_chromium_compile_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'mac_upload_clang': 'release_bot',
+      'try-nougat-phone-tester': 'android_debug_trybot_arm64',
+      'win-angle-deqp-rel-32': 'deqp_release_trybot_x86',
+      'win-angle-deqp-rel-64': 'deqp_release_trybot',
+      'win-angle-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'win-angle-rel-64': 'gpu_fyi_tests_release_trybot',
+      'win-celab-try-rel': 'release_bot_minimal_symbols',
+      'win-dawn-rel': 'dawn_tests_release_trybot',
+      'win-libfuzzer-asan-rel': 'libfuzzer_windows_asan_release_trybot',
+      'win-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-x64': 'deqp_release_trybot',
+      'win-swangle-try-x86': 'deqp_release_trybot_x86',
+      'win10-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win10_chromium_x64_dbg_ng': 'gpu_tests_debug_bot',
+      'win10_chromium_x64_rel_ng': 'gpu_tests_release_trybot_resource_whitelisting',
+      'win10_chromium_x64_rel_ng_exp': 'release_trybot',
+      'win7-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_archive': 'release_trybot_x86',
+      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86_compile_only',
+      'win_chromium_compile_rel_ng': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_chromium_dbg_ng': 'gpu_tests_debug_bot_x86',
+      'win_chromium_x64_rel_ng': 'gpu_tests_release_trybot',
+      'win_mojo': 'release_trybot_x86',
+      'win_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'win_upload_clang': 'release_bot',
+      'win_x64_archive': 'release_trybot',
+      'WebRTC Chromium Android Builder': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+    'findit': {
+      'Chromium Android ARM 32-bit Goma RBE Prod (clobber)': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg) (clobber)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Staging': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT (ATS)': 'android_release_bot_minimal_symbols',
+      'Chromium Linux Goma RBE Prod': 'release_bot',
+      'Chromium Linux Goma RBE Prod (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Prod (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Prod (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging': 'release_bot',
+      'Chromium Linux Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE ToT': 'release_bot',
+      'Chromium Linux Goma RBE ToT (ATS)': 'release_bot',
+      'Chromium Linux Goma Staging': 'release_bot',
+      'Chromium Mac Goma RBE Prod': 'release_bot',
+      'Chromium Mac Goma RBE Staging': 'release_bot',
+      'Chromium Mac Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Mac Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Mac Goma RBE ToT': 'release_bot',
+      'Chromium Mac Goma Staging': 'release_bot',
+      'Chromium Win Goma RBE Prod': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (dbg)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Prod (dbg) (clobber)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Staging': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Staging (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE ToT': 'release_bot_x86_minimal_symbols',
+      'CrWinGomaStaging': 'release_bot_x86_minimal_symbols',
+      'Linux Builder Goma Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma Latest Client': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Latest Client': 'gpu_tests_release_bot',
+      'Mac Builder (dbg) Goma Canary': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder Goma Canary': 'gpu_tests_release_bot_minimal_symbols',
+      'Mac Builder Goma Latest Client': 'gpu_tests_release_bot_minimal_symbols',
+      'Win Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma RBE Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma RBE Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win cl.exe Goma Canary LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win cl.exe Goma Latest Client LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win7 Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win7 Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'WinMSVC64 Goma Canary': 'win_msvc_release_bot',
+      'WinMSVC64 Goma Latest Client': 'win_msvc_release_bot',
+      'android-archive-dbg-goma-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-latest': 'android_without_codecs_debug_bot',
+      'chromeos-amd64-generic-rel (Goma RBE FYI)': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-latest': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-latest': 'cros_chrome_sdk',
+      'fuchsia-fyi-arm64-rel (Goma RBE FYI)': 'release_bot_fuchsia_arm64',
+      'fuchsia-fyi-x64-rel (Goma RBE FYI)': 'release_bot_fuchsia',
+      'ios-device-goma-canary-clobber': 'ios_error',
+      'ios-device-goma-latest-clobber': 'ios_error',
+      'ios-device-goma-rbe-canary-clobber': 'ios_error',
+      'ios-device-goma-rbe-latest-clobber': 'ios_error',
+      'linux-archive-rel-goma-canary': 'release_bot',
+      'linux-archive-rel-goma-canary-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-latest': 'release_bot',
+      'linux-archive-rel-goma-latest-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-latest': 'release_bot',
+      'linux-archive-rel-goma-rbe-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-latest': 'release_bot',
+      'mac-archive-rel-goma-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-canary-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-latest': 'release_bot_mac_strip_minimal_symbols',
+      'win32-archive-rel-goma-canary-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'win32-archive-rel-goma-latest-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'android-asan': 'android_clang_asan_release_trybot',
+      'android-bfcache-debug': 'android_debug_static_bot',
+      'android-binary-size': 'android_binary_size',
+      'android-cronet-arm-dbg': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android-deterministic-dbg': 'android_debug_bot',
+      'android-deterministic-rel': 'android_without_codecs_release_trybot',
+      'android-kitkat-arm-rel': 'android_release_trybot_fastbuild',
+      'android-marshmallow-arm64-rel': 'gpu_tests_android_release_trybot_arm64_resource_whitelisting_fastbuild_java_coverage',
+      'android-marshmallow-x86-fyi-rel': 'android_release_trybot_x86_resource_whitelisting',
+      'android-opus-kitkat-arm-rel': 'android_release_trybot',
+      'android-oreo-arm64-cts-networkservice-dbg': 'android_debug_trybot_arm64',
+      'android-oreo-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google',
+      'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
+      'android-webview-marshmallow-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-nougat-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-oreo-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-fyi-rel': 'android_release_trybot_arm64_webview_google',
+      'android_angle_deqp_rel_ng': 'deqp_android_release_trybot_arm64',
+      'android_angle_rel_ng': 'gpu_tests_android_release_trybot_arm64',
+      'android_angle_vk32_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot',
+      'android_angle_vk32_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'android_angle_vk64_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'android_angle_vk64_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'android_archive_rel_ng': 'android_release_trybot',
+      'android_arm64_dbg_recipe': 'android_debug_trybot_compile_only_arm64_fastbuild',
+      'android_blink_rel': 'android_release_trybot',
+      'android_cfi_rel_ng': 'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'android_clang_dbg_recipe': 'android_clang_asan_debug_trybot_compile_only_fastbuild',
+      'android_compile_dbg': 'android_debug_trybot_compile_only',
+      'android_compile_x64_dbg': 'android_debug_trybot_compile_only_x64',
+      'android_compile_x86_dbg': 'android_debug_trybot_compile_only_x86',
+      'android_cronet': 'android_cronet_release_trybot_arm_no_neon',
+      'android_cronet_tester': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android_mojo': 'android_release_trybot_arm64',
+      'android_n5x_swarming_dbg': 'android_debug_trybot_arm64',
+      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot_arm64',
+      'android_unswarmed_pixel_aosp': 'android_debug_trybot_arm64',
+      'cast_shell_android': 'android_cast_debug_static_bot_compile_only',
+      'cast_shell_audio_linux': 'cast_audio_release_trybot',
+      'cast_shell_linux': 'cast_release_trybot',
+      'chromeos-amd64-generic-cfi-thin-lto-rel': 'cros_chrome_sdk_cfi_thin_lto',
+      'chromeos-amd64-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-amd64-generic-rel': 'cros_chrome_sdk',
+      'chromeos-arm-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-arm-generic-rel': 'cros_chrome_sdk_dcheck_always_on',
+      'chromeos-kevin-compile-rel': 'cros_chrome_sdk',
+      'chromeos-kevin-rel': 'cros_chrome_sdk',
+      'chromium_presubmit': 'presubmit',
+      'closure_compilation': 'closure_compilation',
+      'dawn-linux-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-mac-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x86-deps-rel': 'dawn_tests_release_trybot_x86',
+      'fuchsia-angle-rel': 'gpu_fyi_tests_release_trybot_fuchsia',
+      'fuchsia-arm64-cast': 'release_trybot_fuchsia_arm64_cast',
+      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
+      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
+      'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
+      'fuchsia-x64-cast': 'release_trybot_fuchsia_cast',
+      'fuchsia_arm64': 'release_trybot_fuchsia_arm64',
+      'fuchsia_x64': 'release_trybot_fuchsia',
+      'gpu-fyi-try-android-l-nexus-5-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-l-nexus-6-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-deqp-64': 'deqp_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-skgl-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-6p-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-9-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-n-nvidia-shield-tv-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-p-pixel-2-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-p-pixel-2-skv-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-32': 'deqp_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-64': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-android-q-pixel-2-vk-32': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-vk-64': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-linux-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-linux-nvidia-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-nvidia-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-tsn': 'gpu_fyi_tests_release_trybot_tsan',
+      'gpu-fyi-try-mac-amd-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-amd-pro-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-amd-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-asan': 'gpu_fyi_tests_release_trybot_asan',
+      'gpu-fyi-try-mac-intel-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win-xr-builder-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-intel-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dbg-64': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-dbg-64': 'gpu_fyi_tests_dx12vk_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-rel-64': 'gpu_fyi_tests_dx12vk_release_trybot',
+      'gpu-fyi-try-win10-nvidia-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-rel-32': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'gpu-fyi-try-win10-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-skgl-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win7-amd-dbg-32': 'gpu_fyi_tests_debug_trybot_x86',
+      'gpu-fyi-try-win7-amd-dqp-32': 'deqp_release_trybot_x86',
+      'gpu-fyi-try-win7-amd-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win7-nvidia-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-try-linux-nvidia-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-linux-nvidia-rel': 'gpu_tests_release_trybot',
+      'gpu-try-mac-amd-retina-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-mac-intel-dbg': 'gpu_tests_debug_bot',
+      'ios-device': 'ios_error',
+      'ios-device-xcode-clang': 'ios_error',
+      'ios-simulator': 'ios_error',
+      'ios-simulator-cr-recipe': 'ios_simulator_debug_static_bot',
+      'ios-simulator-cronet': 'ios_error',
+      'ios-simulator-full-configs': 'ios_error',
+      'ios-simulator-xcode-clang': 'ios_error',
+      'layout_test_leak_detection': 'release_trybot',
+      'leak_detection_linux': 'release_trybot',
+      'linux-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'linux-annotator-rel': 'release_trybot',
+      'linux-bfcache-debug': 'debug_bot',
+      'linux-blink-heap-concurrent-marking-tsan-rel': 'release_trybot_tsan',
+      'linux-blink-heap-verification-try': 'release_trybot_enable_blink_heap_verification',
+      'linux-blink-rel': 'release_bot_minimal_symbols',
+      'linux-chromeos-compile-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-rel': 'chromeos_with_codecs_release_trybot_code_coverage',
+      'linux-clang-tidy-dbg': 'debug_bot',
+      'linux-clang-tidy-rel': 'release_trybot',
+      'linux-dawn-rel': 'dawn_tests_release_trybot',
+      'linux-dcheck-off-rel': 'release_trybot_dcheck_off',
+      'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
+      'linux-layout-tests-fragment-item': 'release_trybot',
+      'linux-layout-tests-fragment-paint': 'release_trybot',
+      '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',
+      'linux-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-x64': 'deqp_release_trybot',
+      'linux-swangle-try-x86': 'deqp_release_trybot_x86',
+      'linux-trusty-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange',
+      'linux-viz-rel': 'release_trybot',
+      'linux-webkit-msan-rel': 'msan_release_bot',
+      'linux-wpt-fyi-rel': 'release_trybot',
+      'linux_android_dbg_ng': 'android_debug_bot',
+      'linux_angle_deqp_rel_ng': 'deqp_release_trybot',
+      'linux_angle_ozone_rel_ng': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
+      'linux_arm': 'release_trybot_arm',
+      'linux_chromium_archive_rel_ng': 'release_bot',
+      'linux_chromium_asan_rel_ng': 'asan_lsan_release_trybot',
+      'linux_chromium_cfi_rel_ng': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'linux_chromium_chromeos_asan_rel_ng': 'asan_lsan_chromeos_release_trybot',
+      'linux_chromium_chromeos_msan_rel_ng': 'chromeos_msan_release_bot',
+      'linux_chromium_clobber_deterministic': 'release_trybot',
+      'linux_chromium_clobber_rel_ng': 'release_trybot',
+      'linux_chromium_compile_dbg_32_ng': 'debug_bot_x86',
+      'linux_chromium_compile_dbg_ng': 'debug_bot',
+      'linux_chromium_compile_rel_ng': 'release_trybot',
+      'linux_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'linux_chromium_msan_rel_ng': 'msan_release_bot',
+      'linux_chromium_tsan_rel_ng': 'tsan_disable_nacl_release_trybot',
+      'linux_chromium_ubsan_rel_ng': 'ubsan_vptr_release_bot',
+      'linux_layout_tests_composite_after_paint': 'release_trybot',
+      'linux_layout_tests_layout_ng_disabled': 'release_trybot',
+      'linux_mojo': 'release_trybot',
+      'linux_mojo_chromeos': 'chromeos_with_codecs_release_trybot',
+      'linux_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'linux_upload_clang': 'release_bot',
+      'linux_vr': 'vr_release_trybot',
+      'mac-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'mac-dawn-rel': 'dawn_tests_release_trybot',
+      'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac10.10-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.11-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.12-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13_retina-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.14-blink-rel': 'release_bot_minimal_symbols',
+      'mac_chromium_10.10': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.12_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.13_rel_ng': 'release_trybot',
+      'mac_chromium_10.14_rel_ng': 'release_trybot',
+      'mac_chromium_archive_rel_ng': 'release_bot_mac_strip_minimal_symbols',
+      'mac_chromium_asan_rel_ng': 'asan_dcheck_disable_nacl_release_bot',
+      'mac_chromium_compile_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_chromium_compile_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'mac_upload_clang': 'release_bot',
+      'try-nougat-phone-tester': 'android_debug_trybot_arm64',
+      'win-angle-deqp-rel-32': 'deqp_release_trybot_x86',
+      'win-angle-deqp-rel-64': 'deqp_release_trybot',
+      'win-angle-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'win-angle-rel-64': 'gpu_fyi_tests_release_trybot',
+      'win-annotator-rel': 'release_trybot',
+      'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release',
+      'win-celab-try-rel': 'release_bot_minimal_symbols',
+      'win-dawn-rel': 'dawn_tests_release_trybot',
+      'win-libfuzzer-asan-rel': 'libfuzzer_windows_asan_release_trybot',
+      'win-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-x64': 'deqp_release_trybot',
+      'win-swangle-try-x86': 'deqp_release_trybot_x86',
+      'win10-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win10_chromium_x64_dbg_ng': 'gpu_tests_debug_bot',
+      'win10_chromium_x64_rel_ng': 'gpu_tests_release_trybot_resource_whitelisting',
+      'win10_chromium_x64_rel_ng_exp': 'release_trybot',
+      'win7-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_archive': 'release_trybot_x86',
+      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86_compile_only',
+      'win_chromium_compile_rel_ng': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_chromium_dbg_ng': 'gpu_tests_debug_bot_x86',
+      'win_chromium_x64_rel_ng': 'gpu_tests_release_trybot',
+      'win_mojo': 'release_trybot_x86',
+      'win_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'win_upload_clang': 'release_bot',
+      'win_x64_archive': 'release_trybot',
+      'WebRTC Chromium Android Builder': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+    'goma': {
+      'Chromium Android ARM 32-bit Goma RBE Prod': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE Prod (clobber)': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Prod (dbg) (clobber)': 'android_debug_static_bot',
+      'Chromium Android ARM 32-bit Goma RBE Staging': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT': 'android_release_bot_minimal_symbols',
+      'Chromium Android ARM 32-bit Goma RBE ToT (ATS)': 'android_release_bot_minimal_symbols',
+      'Chromium Linux Goma RBE Prod': 'release_bot',
+      'Chromium Linux Goma RBE Prod (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Prod (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Prod (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging': 'release_bot',
+      'Chromium Linux Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Linux Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Linux Goma RBE Staging (dbg) (clobber)': 'debug_bot',
+      'Chromium Linux Goma RBE ToT': 'release_bot',
+      'Chromium Linux Goma RBE ToT (ATS)': 'release_bot',
+      'Chromium Linux Goma Staging': 'release_bot',
+      'Chromium Mac Goma RBE Prod': 'release_bot',
+      'Chromium Mac Goma RBE Staging': 'release_bot',
+      'Chromium Mac Goma RBE Staging (clobber)': 'release_bot',
+      'Chromium Mac Goma RBE Staging (dbg)': 'debug_bot',
+      'Chromium Mac Goma RBE ToT': 'release_bot',
+      'Chromium Mac Goma Staging': 'release_bot',
+      'Chromium Win Goma RBE Prod': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Prod (dbg)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Prod (dbg) (clobber)': 'debug_bot_x86',
+      'Chromium Win Goma RBE Staging': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE Staging (clobber)': 'release_bot_x86_minimal_symbols',
+      'Chromium Win Goma RBE ToT': 'release_bot_x86_minimal_symbols',
+      'CrWinGomaStaging': 'release_bot_x86_minimal_symbols',
+      'Linux Builder Goma Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma Latest Client': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Canary': 'gpu_tests_release_bot',
+      'Linux Builder Goma RBE Latest Client': 'gpu_tests_release_bot',
+      'Mac Builder (dbg) Goma Canary': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Canary (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder (dbg) Goma RBE Latest Client (clobber)': 'gpu_tests_debug_bot',
+      'Mac Builder Goma Canary': 'gpu_tests_release_bot_minimal_symbols',
+      'Mac Builder Goma Latest Client': 'gpu_tests_release_bot_minimal_symbols',
+      'Win Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder (dbg) Goma RBE Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win Builder Goma RBE Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win cl.exe Goma Canary LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win cl.exe Goma Latest Client LocalOutputCache': 'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang',
+      'Win7 Builder (dbg) Goma Canary': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder (dbg) Goma Latest Client': 'gpu_tests_debug_bot_x86',
+      'Win7 Builder Goma Canary': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'Win7 Builder Goma Latest Client': 'gpu_tests_release_bot_x86_minimal_symbols',
+      'WinMSVC64 Goma Canary': 'win_msvc_release_bot',
+      'WinMSVC64 Goma Latest Client': 'win_msvc_release_bot',
+      'android-archive-dbg-goma-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-ats-latest': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-canary': 'android_without_codecs_debug_bot',
+      'android-archive-dbg-goma-rbe-latest': 'android_without_codecs_debug_bot',
+      'chromeos-amd64-generic-rel (Goma RBE FYI)': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-latest': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-canary': 'cros_chrome_sdk',
+      'chromeos-amd64-generic-rel-goma-rbe-latest': 'cros_chrome_sdk',
+      'fuchsia-fyi-arm64-rel (Goma RBE FYI)': 'release_bot_fuchsia_arm64',
+      'fuchsia-fyi-x64-rel (Goma RBE FYI)': 'release_bot_fuchsia',
+      'ios-device-goma-canary-clobber': 'ios_error',
+      'ios-device-goma-latest-clobber': 'ios_error',
+      'ios-device-goma-rbe-canary-clobber': 'ios_error',
+      'ios-device-goma-rbe-latest-clobber': 'ios_error',
+      'linux-archive-rel-goma-canary': 'release_bot',
+      'linux-archive-rel-goma-canary-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-latest': 'release_bot',
+      'linux-archive-rel-goma-latest-localoutputcache': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-ats-latest': 'release_bot',
+      'linux-archive-rel-goma-rbe-canary': 'release_bot',
+      'linux-archive-rel-goma-rbe-latest': 'release_bot',
+      'mac-archive-rel-goma-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-canary-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-latest-localoutputcache': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-canary': 'release_bot_mac_strip_minimal_symbols',
+      'mac-archive-rel-goma-rbe-latest': 'release_bot_mac_strip_minimal_symbols',
+      'win32-archive-rel-goma-canary-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'win32-archive-rel-goma-latest-localoutputcache': 'release_bot_x86_minimal_symbols_enable_archive_compression',
+      'android-asan': 'android_clang_asan_release_trybot',
+      'android-bfcache-debug': 'android_debug_static_bot',
+      'android-binary-size': 'android_binary_size',
+      'android-cronet-arm-dbg': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android-deterministic-dbg': 'android_debug_bot',
+      'android-deterministic-rel': 'android_without_codecs_release_trybot',
+      'android-kitkat-arm-rel': 'android_release_trybot_fastbuild',
+      'android-marshmallow-arm64-rel': 'gpu_tests_android_release_trybot_arm64_resource_whitelisting_fastbuild_java_coverage',
+      'android-marshmallow-x86-fyi-rel': 'android_release_trybot_x86_resource_whitelisting',
+      'android-opus-kitkat-arm-rel': 'android_release_trybot',
+      'android-oreo-arm64-cts-networkservice-dbg': 'android_debug_trybot_arm64',
+      'android-oreo-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google',
+      'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
+      'android-webview-marshmallow-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-nougat-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-oreo-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-fyi-rel': 'android_release_trybot_arm64_webview_google',
+      'android_angle_deqp_rel_ng': 'deqp_android_release_trybot_arm64',
+      'android_angle_rel_ng': 'gpu_tests_android_release_trybot_arm64',
+      'android_angle_vk32_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot',
+      'android_angle_vk32_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'android_angle_vk64_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'android_angle_vk64_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'android_archive_rel_ng': 'android_release_trybot',
+      'android_arm64_dbg_recipe': 'android_debug_trybot_compile_only_arm64_fastbuild',
+      'android_blink_rel': 'android_release_trybot',
+      'android_cfi_rel_ng': 'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'android_clang_dbg_recipe': 'android_clang_asan_debug_trybot_compile_only_fastbuild',
+      'android_compile_dbg': 'android_debug_trybot_compile_only',
+      'android_compile_x64_dbg': 'android_debug_trybot_compile_only_x64',
+      'android_compile_x86_dbg': 'android_debug_trybot_compile_only_x86',
+      'android_cronet': 'android_cronet_release_trybot_arm_no_neon',
+      'android_cronet_tester': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android_mojo': 'android_release_trybot_arm64',
+      'android_n5x_swarming_dbg': 'android_debug_trybot_arm64',
+      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot_arm64',
+      'android_unswarmed_pixel_aosp': 'android_debug_trybot_arm64',
+      'cast_shell_android': 'android_cast_debug_static_bot_compile_only',
+      'cast_shell_audio_linux': 'cast_audio_release_trybot',
+      'cast_shell_linux': 'cast_release_trybot',
+      'chromeos-amd64-generic-cfi-thin-lto-rel': 'cros_chrome_sdk_cfi_thin_lto',
+      'chromeos-amd64-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-amd64-generic-rel': 'cros_chrome_sdk',
+      'chromeos-arm-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-arm-generic-rel': 'cros_chrome_sdk_dcheck_always_on',
+      'chromeos-kevin-compile-rel': 'cros_chrome_sdk',
+      'chromeos-kevin-rel': 'cros_chrome_sdk',
+      'chromium_presubmit': 'presubmit',
+      'closure_compilation': 'closure_compilation',
+      'dawn-linux-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-mac-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x86-deps-rel': 'dawn_tests_release_trybot_x86',
+      'fuchsia-angle-rel': 'gpu_fyi_tests_release_trybot_fuchsia',
+      'fuchsia-arm64-cast': 'release_trybot_fuchsia_arm64_cast',
+      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
+      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
+      'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
+      'fuchsia-x64-cast': 'release_trybot_fuchsia_cast',
+      'fuchsia_arm64': 'release_trybot_fuchsia_arm64',
+      'fuchsia_x64': 'release_trybot_fuchsia',
+      'gpu-fyi-try-android-l-nexus-5-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-l-nexus-6-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-deqp-64': 'deqp_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-skgl-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-6p-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-9-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-n-nvidia-shield-tv-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-p-pixel-2-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-p-pixel-2-skv-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-32': 'deqp_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-64': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-android-q-pixel-2-vk-32': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-vk-64': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-linux-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-linux-nvidia-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-nvidia-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-tsn': 'gpu_fyi_tests_release_trybot_tsan',
+      'gpu-fyi-try-mac-amd-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-amd-pro-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-amd-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-asan': 'gpu_fyi_tests_release_trybot_asan',
+      'gpu-fyi-try-mac-intel-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win-xr-builder-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-intel-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dbg-64': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-dbg-64': 'gpu_fyi_tests_dx12vk_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-rel-64': 'gpu_fyi_tests_dx12vk_release_trybot',
+      'gpu-fyi-try-win10-nvidia-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-rel-32': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'gpu-fyi-try-win10-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-skgl-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win7-amd-dbg-32': 'gpu_fyi_tests_debug_trybot_x86',
+      'gpu-fyi-try-win7-amd-dqp-32': 'deqp_release_trybot_x86',
+      'gpu-fyi-try-win7-amd-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win7-nvidia-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-try-linux-nvidia-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-linux-nvidia-rel': 'gpu_tests_release_trybot',
+      'gpu-try-mac-amd-retina-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-mac-intel-dbg': 'gpu_tests_debug_bot',
+      'ios-device': 'ios_error',
+      'ios-device-xcode-clang': 'ios_error',
+      'ios-simulator': 'ios_error',
+      'ios-simulator-cr-recipe': 'ios_simulator_debug_static_bot',
+      'ios-simulator-cronet': 'ios_error',
+      'ios-simulator-full-configs': 'ios_error',
+      'ios-simulator-xcode-clang': 'ios_error',
+      'layout_test_leak_detection': 'release_trybot',
+      'leak_detection_linux': 'release_trybot',
+      'linux-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'linux-annotator-rel': 'release_trybot',
+      'linux-bfcache-debug': 'debug_bot',
+      'linux-blink-heap-concurrent-marking-tsan-rel': 'release_trybot_tsan',
+      'linux-blink-heap-verification-try': 'release_trybot_enable_blink_heap_verification',
+      'linux-blink-rel': 'release_bot_minimal_symbols',
+      'linux-chromeos-compile-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-rel': 'chromeos_with_codecs_release_trybot_code_coverage',
+      'linux-clang-tidy-dbg': 'debug_bot',
+      'linux-clang-tidy-rel': 'release_trybot',
+      'linux-dawn-rel': 'dawn_tests_release_trybot',
+      'linux-dcheck-off-rel': 'release_trybot_dcheck_off',
+      'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
+      'linux-layout-tests-fragment-item': 'release_trybot',
+      'linux-layout-tests-fragment-paint': 'release_trybot',
+      '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',
+      'linux-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-x64': 'deqp_release_trybot',
+      'linux-swangle-try-x86': 'deqp_release_trybot_x86',
+      'linux-trusty-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange',
+      'linux-viz-rel': 'release_trybot',
+      'linux-webkit-msan-rel': 'msan_release_bot',
+      'linux-wpt-fyi-rel': 'release_trybot',
+      'linux_android_dbg_ng': 'android_debug_bot',
+      'linux_angle_deqp_rel_ng': 'deqp_release_trybot',
+      'linux_angle_ozone_rel_ng': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
+      'linux_arm': 'release_trybot_arm',
+      'linux_chromium_archive_rel_ng': 'release_bot',
+      'linux_chromium_asan_rel_ng': 'asan_lsan_release_trybot',
+      'linux_chromium_cfi_rel_ng': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'linux_chromium_chromeos_asan_rel_ng': 'asan_lsan_chromeos_release_trybot',
+      'linux_chromium_chromeos_msan_rel_ng': 'chromeos_msan_release_bot',
+      'linux_chromium_clobber_deterministic': 'release_trybot',
+      'linux_chromium_clobber_rel_ng': 'release_trybot',
+      'linux_chromium_compile_dbg_32_ng': 'debug_bot_x86',
+      'linux_chromium_compile_dbg_ng': 'debug_bot',
+      'linux_chromium_compile_rel_ng': 'release_trybot',
+      'linux_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'linux_chromium_msan_rel_ng': 'msan_release_bot',
+      'linux_chromium_tsan_rel_ng': 'tsan_disable_nacl_release_trybot',
+      'linux_chromium_ubsan_rel_ng': 'ubsan_vptr_release_bot',
+      'linux_layout_tests_composite_after_paint': 'release_trybot',
+      'linux_layout_tests_layout_ng_disabled': 'release_trybot',
+      'linux_mojo': 'release_trybot',
+      'linux_mojo_chromeos': 'chromeos_with_codecs_release_trybot',
+      'linux_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'linux_upload_clang': 'release_bot',
+      'linux_vr': 'vr_release_trybot',
+      'mac-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'mac-dawn-rel': 'dawn_tests_release_trybot',
+      'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac10.10-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.11-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.12-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13_retina-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.14-blink-rel': 'release_bot_minimal_symbols',
+      'mac_chromium_10.10': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.12_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.13_rel_ng': 'release_trybot',
+      'mac_chromium_10.14_rel_ng': 'release_trybot',
+      'mac_chromium_archive_rel_ng': 'release_bot_mac_strip_minimal_symbols',
+      'mac_chromium_asan_rel_ng': 'asan_dcheck_disable_nacl_release_bot',
+      'mac_chromium_compile_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_chromium_compile_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'mac_upload_clang': 'release_bot',
+      'try-nougat-phone-tester': 'android_debug_trybot_arm64',
+      'win-angle-deqp-rel-32': 'deqp_release_trybot_x86',
+      'win-angle-deqp-rel-64': 'deqp_release_trybot',
+      'win-angle-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'win-angle-rel-64': 'gpu_fyi_tests_release_trybot',
+      'win-annotator-rel': 'release_trybot',
+      'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release',
+      'win-celab-try-rel': 'release_bot_minimal_symbols',
+      'win-dawn-rel': 'dawn_tests_release_trybot',
+      'win-libfuzzer-asan-rel': 'libfuzzer_windows_asan_release_trybot',
+      'win-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-x64': 'deqp_release_trybot',
+      'win-swangle-try-x86': 'deqp_release_trybot_x86',
+      'win10-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win10_chromium_x64_dbg_ng': 'gpu_tests_debug_bot',
+      'win10_chromium_x64_rel_ng': 'gpu_tests_release_trybot_resource_whitelisting',
+      'win10_chromium_x64_rel_ng_exp': 'release_trybot',
+      'win7-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_archive': 'release_trybot_x86',
+      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86_compile_only',
+      'win_chromium_compile_rel_ng': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_chromium_dbg_ng': 'gpu_tests_debug_bot_x86',
+      'win_chromium_x64_rel_ng': 'gpu_tests_release_trybot',
+      'win_mojo': 'release_trybot_x86',
+      'win_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'win_upload_clang': 'release_bot',
+      'win_x64_archive': 'release_trybot',
+      'WebRTC Chromium Android Builder': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+    'try': {
+      'android-asan': 'android_clang_asan_release_trybot',
+      'android-bfcache-debug': 'android_debug_static_bot',
+      'android-binary-size': 'android_binary_size',
+      'android-cronet-arm-dbg': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android-deterministic-dbg': 'android_debug_bot',
+      'android-deterministic-rel': 'android_without_codecs_release_trybot',
+      'android-kitkat-arm-rel': 'android_release_trybot_fastbuild',
+      'android-marshmallow-arm64-rel': 'gpu_tests_android_release_trybot_arm64_resource_whitelisting_fastbuild_java_coverage',
+      'android-marshmallow-x86-fyi-rel': 'android_release_trybot_x86_resource_whitelisting',
+      'android-opus-kitkat-arm-rel': 'android_release_trybot',
+      'android-oreo-arm64-cts-networkservice-dbg': 'android_debug_trybot_arm64',
+      'android-oreo-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-dbg': 'android_debug_trybot_arm64',
+      'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google',
+      'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
+      'android-webview-marshmallow-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-nougat-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-oreo-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-dbg': 'android_release_trybot_arm64_webview_google',
+      'android-webview-pie-arm64-fyi-rel': 'android_release_trybot_arm64_webview_google',
+      'android_angle_deqp_rel_ng': 'deqp_android_release_trybot_arm64',
+      'android_angle_rel_ng': 'gpu_tests_android_release_trybot_arm64',
+      'android_angle_vk32_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot',
+      'android_angle_vk32_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'android_angle_vk64_deqp_rel_ng': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'android_angle_vk64_rel_ng': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'android_archive_rel_ng': 'android_release_trybot',
+      'android_arm64_dbg_recipe': 'android_debug_trybot_compile_only_arm64_fastbuild',
+      'android_blink_rel': 'android_release_trybot',
+      'android_cfi_rel_ng': 'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'android_clang_dbg_recipe': 'android_clang_asan_debug_trybot_compile_only_fastbuild',
+      'android_compile_dbg': 'android_debug_trybot_compile_only',
+      'android_compile_x64_dbg': 'android_debug_trybot_compile_only_x64',
+      'android_compile_x86_dbg': 'android_debug_trybot_compile_only_x86',
+      'android_cronet': 'android_cronet_release_trybot_arm_no_neon',
+      'android_cronet_tester': 'android_cronet_debug_static_bot_arm_no_neon',
+      'android_mojo': 'android_release_trybot_arm64',
+      'android_n5x_swarming_dbg': 'android_debug_trybot_arm64',
+      'android_optional_gpu_tests_rel': 'gpu_tests_android_release_trybot_arm64',
+      'android_unswarmed_pixel_aosp': 'android_debug_trybot_arm64',
+      'cast_shell_android': 'android_cast_debug_static_bot_compile_only',
+      'cast_shell_audio_linux': 'cast_audio_release_trybot',
+      'cast_shell_linux': 'cast_release_trybot',
+      'chromeos-amd64-generic-cfi-thin-lto-rel': 'cros_chrome_sdk_cfi_thin_lto',
+      'chromeos-amd64-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-amd64-generic-rel': 'cros_chrome_sdk',
+      'chromeos-arm-generic-dbg': 'cros_chrome_sdk_dbg',
+      'chromeos-arm-generic-rel': 'cros_chrome_sdk_dcheck_always_on',
+      'chromeos-kevin-compile-rel': 'cros_chrome_sdk',
+      'chromeos-kevin-rel': 'cros_chrome_sdk',
+      'chromium_presubmit': 'presubmit',
+      'closure_compilation': 'closure_compilation',
+      'dawn-linux-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-mac-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x64-deps-rel': 'dawn_tests_release_trybot',
+      'dawn-win10-x86-deps-rel': 'dawn_tests_release_trybot_x86',
+      'fuchsia-angle-rel': 'gpu_fyi_tests_release_trybot_fuchsia',
+      'fuchsia-arm64-cast': 'release_trybot_fuchsia_arm64_cast',
+      'fuchsia-compile-x64-dbg': 'debug_bot_fuchsia_compile_only',
+      'fuchsia-fyi-arm64-rel': 'release_trybot_fuchsia_arm64',
+      'fuchsia-fyi-x64-dbg': 'debug_bot_fuchsia',
+      'fuchsia-fyi-x64-rel': 'release_trybot_fuchsia',
+      'fuchsia-x64-cast': 'release_trybot_fuchsia_cast',
+      'fuchsia_arm64': 'release_trybot_fuchsia_arm64',
+      'fuchsia_x64': 'release_trybot_fuchsia',
+      'gpu-fyi-try-android-l-nexus-5-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-l-nexus-6-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-deqp-64': 'deqp_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-skgl-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-6p-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-9-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-n-nvidia-shield-tv-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-fyi-try-android-p-pixel-2-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-p-pixel-2-skv-32': 'gpu_tests_android_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-32': 'deqp_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-deqp-vk-64': 'deqp_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-android-q-pixel-2-vk-32': 'gpu_tests_android_vulkan_ndk_release_trybot',
+      'gpu-fyi-try-android-q-pixel-2-vk-64': 'gpu_tests_android_vulkan_ndk_release_trybot_arm64',
+      'gpu-fyi-try-linux-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-intel-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-linux-nvidia-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-linux-nvidia-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-skv': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-linux-nvidia-tsn': 'gpu_fyi_tests_release_trybot_tsan',
+      'gpu-fyi-try-mac-amd-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-amd-pro-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-amd-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-amd-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-asan': 'gpu_fyi_tests_release_trybot_asan',
+      'gpu-fyi-try-mac-intel-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-intel-dqp': 'deqp_release_trybot',
+      'gpu-fyi-try-mac-intel-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-intel-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-dbg': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-exp': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-mac-nvidia-retina-rel': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win-xr-builder-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-intel-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-intel-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dbg-64': 'gpu_fyi_tests_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-dbg-64': 'gpu_fyi_tests_dx12vk_debug_trybot',
+      'gpu-fyi-try-win10-nvidia-dx12vk-rel-64': 'gpu_fyi_tests_dx12vk_release_trybot',
+      'gpu-fyi-try-win10-nvidia-exp-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-rel-32': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'gpu-fyi-try-win10-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win10-nvidia-skgl-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-fyi-try-win7-amd-dbg-32': 'gpu_fyi_tests_debug_trybot_x86',
+      'gpu-fyi-try-win7-amd-dqp-32': 'deqp_release_trybot_x86',
+      'gpu-fyi-try-win7-amd-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-dqp-64': 'deqp_release_trybot',
+      'gpu-fyi-try-win7-nvidia-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'gpu-fyi-try-win7-nvidia-rel-64': 'gpu_fyi_tests_release_trybot',
+      'gpu-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
+      'gpu-try-linux-nvidia-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-linux-nvidia-rel': 'gpu_tests_release_trybot',
+      'gpu-try-mac-amd-retina-dbg': 'gpu_tests_debug_bot',
+      'gpu-try-mac-intel-dbg': 'gpu_tests_debug_bot',
+      'ios-device': 'ios_error',
+      'ios-device-xcode-clang': 'ios_error',
+      'ios-simulator': 'ios_error',
+      'ios-simulator-cr-recipe': 'ios_simulator_debug_static_bot',
+      'ios-simulator-cronet': 'ios_error',
+      'ios-simulator-full-configs': 'ios_error',
+      'ios-simulator-xcode-clang': 'ios_error',
+      'layout_test_leak_detection': 'release_trybot',
+      'leak_detection_linux': 'release_trybot',
+      'linux-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'linux-annotator-rel': 'release_trybot',
+      'linux-bfcache-debug': 'debug_bot',
+      'linux-blink-heap-concurrent-marking-tsan-rel': 'release_trybot_tsan',
+      'linux-blink-heap-verification-try': 'release_trybot_enable_blink_heap_verification',
+      'linux-blink-rel': 'release_bot_minimal_symbols',
+      'linux-chromeos-compile-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-dbg': 'chromeos_with_codecs_debug_bot',
+      'linux-chromeos-rel': 'chromeos_with_codecs_release_trybot_code_coverage',
+      'linux-clang-tidy-dbg': 'debug_bot',
+      'linux-clang-tidy-rel': 'release_trybot',
+      'linux-dawn-rel': 'dawn_tests_release_trybot',
+      'linux-dcheck-off-rel': 'release_trybot_dcheck_off',
+      'linux-gcc-rel': 'release_bot_x86_minimal_symbols_no_clang_cxx11',
+      'linux-layout-tests-fragment-item': 'release_trybot',
+      'linux-layout-tests-fragment-paint': 'release_trybot',
+      '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',
+      'linux-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'linux-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'linux-swangle-try-x64': 'deqp_release_trybot',
+      'linux-swangle-try-x86': 'deqp_release_trybot_x86',
+      'linux-trusty-rel': 'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange',
+      'linux-viz-rel': 'release_trybot',
+      'linux-webkit-msan-rel': 'msan_release_bot',
+      'linux-wpt-fyi-rel': 'release_trybot',
+      'linux_android_dbg_ng': 'android_debug_bot',
+      'linux_angle_deqp_rel_ng': 'deqp_release_trybot',
+      'linux_angle_ozone_rel_ng': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
+      'linux_arm': 'release_trybot_arm',
+      'linux_chromium_archive_rel_ng': 'release_bot',
+      'linux_chromium_asan_rel_ng': 'asan_lsan_release_trybot',
+      'linux_chromium_cfi_rel_ng': 'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma',
+      'linux_chromium_chromeos_asan_rel_ng': 'asan_lsan_chromeos_release_trybot',
+      'linux_chromium_chromeos_msan_rel_ng': 'chromeos_msan_release_bot',
+      'linux_chromium_clobber_deterministic': 'release_trybot',
+      'linux_chromium_clobber_rel_ng': 'release_trybot',
+      'linux_chromium_compile_dbg_32_ng': 'debug_bot_x86',
+      'linux_chromium_compile_dbg_ng': 'debug_bot',
+      'linux_chromium_compile_rel_ng': 'release_trybot',
+      'linux_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'linux_chromium_msan_rel_ng': 'msan_release_bot',
+      'linux_chromium_tsan_rel_ng': 'tsan_disable_nacl_release_trybot',
+      'linux_chromium_ubsan_rel_ng': 'ubsan_vptr_release_bot',
+      'linux_layout_tests_composite_after_paint': 'release_trybot',
+      'linux_layout_tests_layout_ng_disabled': 'release_trybot',
+      'linux_mojo': 'release_trybot',
+      'linux_mojo_chromeos': 'chromeos_with_codecs_release_trybot',
+      'linux_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'linux_upload_clang': 'release_bot',
+      'linux_vr': 'vr_release_trybot',
+      'mac-angle-rel': 'gpu_fyi_tests_release_trybot',
+      'mac-dawn-rel': 'dawn_tests_release_trybot',
+      'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac-rel': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac10.10-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.11-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.12-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.13_retina-blink-rel': 'release_bot_minimal_symbols',
+      'mac10.14-blink-rel': 'release_bot_minimal_symbols',
+      'mac_chromium_10.10': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.12_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_10.13_rel_ng': 'release_trybot',
+      'mac_chromium_10.14_rel_ng': 'release_trybot',
+      'mac_chromium_archive_rel_ng': 'release_bot_mac_strip_minimal_symbols',
+      'mac_chromium_asan_rel_ng': 'asan_dcheck_disable_nacl_release_bot',
+      'mac_chromium_compile_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_chromium_compile_rel_ng': 'gpu_tests_release_trybot_deterministic_mac',
+      'mac_chromium_dbg_ng': 'gpu_tests_debug_bot',
+      'mac_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'mac_upload_clang': 'release_bot',
+      'try-nougat-phone-tester': 'android_debug_trybot_arm64',
+      'win-angle-deqp-rel-32': 'deqp_release_trybot_x86',
+      'win-angle-deqp-rel-64': 'deqp_release_trybot',
+      'win-angle-rel-32': 'gpu_fyi_tests_release_trybot_x86',
+      'win-angle-rel-64': 'gpu_fyi_tests_release_trybot',
+      'win-annotator-rel': 'release_trybot',
+      'win-asan': 'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release',
+      'win-celab-try-rel': 'release_bot_minimal_symbols',
+      'win-dawn-rel': 'dawn_tests_release_trybot',
+      'win-libfuzzer-asan-rel': 'libfuzzer_windows_asan_release_trybot',
+      'win-swangle-try-tot-angle-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-angle-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-tot-swiftshader-x64': 'deqp_release_trybot',
+      'win-swangle-try-tot-swiftshader-x86': 'deqp_release_trybot_x86',
+      'win-swangle-try-x64': 'deqp_release_trybot',
+      'win-swangle-try-x86': 'deqp_release_trybot_x86',
+      'win10-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win10_chromium_x64_dbg_ng': 'gpu_tests_debug_bot',
+      'win10_chromium_x64_rel_ng': 'gpu_tests_release_trybot_resource_whitelisting',
+      'win10_chromium_x64_rel_ng_exp': 'release_trybot',
+      'win7-blink-rel': 'release_bot_x86_minimal_symbols',
+      'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_archive': 'release_trybot_x86',
+      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86_compile_only',
+      'win_chromium_compile_rel_ng': 'gpu_tests_release_trybot_x86_resource_whitelisting',
+      'win_chromium_dbg_ng': 'gpu_tests_debug_bot_x86',
+      'win_chromium_x64_rel_ng': 'gpu_tests_release_trybot',
+      'win_mojo': 'release_trybot_x86',
+      'win_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot',
+      'win_upload_clang': 'release_bot',
+      'win_x64_archive': 'release_trybot',
+      'WebRTC Chromium Android Builder': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+    'webrtc': {
+      'WebRTC Chromium Android Builder': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+    'webrtc.fyi': {
+      'WebRTC Chromium FYI Android Builder': 'android_release_bot_minimal_symbols',
+      'WebRTC Chromium FYI Android Builder (dbg)': 'android_debug_static_bot',
+      'WebRTC Chromium FYI Android Builder ARM64 (dbg)': 'android_debug_static_bot_arm64',
+      'WebRTC Chromium FYI Linux Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Linux Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Mac Builder': 'gpu_tests_release_bot',
+      'WebRTC Chromium FYI Mac Builder (dbg)': 'debug_bot',
+      'WebRTC Chromium FYI Win Builder': 'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs',
+      'WebRTC Chromium FYI Win Builder (dbg)': 'debug_bot_x86_no_com_init_hooks_with_codecs'
+    },
+
+  },
+
+  'public_artifact_builders': {
+    'ci': [
+      'android-archive-rel',
+      'android-archive-dbg',
+      'linux-archive-rel',
+      'linux-archive-dbg',
+      'mac-archive-rel',
+      'mac-archive-dbg',
+      'win32-archive-rel',
+      'win32-archive-dbg',
+      'win-archive-rel',
+      'win-archive-dbg'
+    ],
+  },
+
+  'configs': {
+    'afl_asan_shared_release_bot': [
+      'afl',
+      'asan',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'shared_release_bot',
+      'disable_seed_corpus'
+    ],
+    'android_binary_size': [
+      'android',
+      'android_config_check',
+      'chrome_with_codecs',
+      'goma',
+      'minimal_symbols',
+      'official_optimize',
+      'stable_channel'
+    ],
+    'android_cast_debug_static_bot': [
+      'android',
+      'cast',
+      'clang',
+      'debug_static_bot'
+    ],
+    'android_cast_debug_static_bot_compile_only': [
+      'android',
+      'cast',
+      'clang',
+      'debug_static_bot',
+      'compile_only'
+    ],
+    'android_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_goma': [
+      'android',
+      'cfi_full',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on',
+      'goma'
+    ],
+    'android_clang_asan_debug_bot': [
+      'android',
+      'clang',
+      'asan',
+      'debug_bot',
+      'strip_debug_info'
+    ],
+    'android_clang_asan_debug_trybot_compile_only_fastbuild': [
+      'android',
+      'clang',
+      'asan',
+      'debug_bot',
+      'compile_only',
+      'android_fastbuild'
+    ],
+    'android_clang_asan_release_trybot': [
+      'android',
+      'clang',
+      'asan',
+      'release_trybot',
+      'strip_debug_info',
+      'minimal_symbols'
+    ],
+    'android_clang_tot_asan': [
+      'android_without_codecs',
+      'clang_tot',
+      'asan',
+      'shared',
+      'debug',
+      'minimal_symbols',
+      'strip_debug_info'
+    ],
+    'android_clang_tot_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on': [
+      'android_without_codecs',
+      'clang_tot',
+      'cfi_full',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on'
+    ],
+    'android_clang_tot_dbg': [
+      'android_without_codecs',
+      'clang_tot',
+      'shared',
+      'debug'
+    ],
+    'android_clang_tot_release_arm64': [
+      'android_without_codecs',
+      'clang_tot',
+      'release',
+      'arm64'
+    ],
+    'android_clang_tot_release_minimal_symbols': [
+      'android',
+      'release',
+      'static',
+      'minimal_symbols',
+      'strip_debug_info',
+      'clang_tot'
+    ],
+    'android_clang_tot_release_minimal_symbols_official_optimize': [
+      'android',
+      'release',
+      'static',
+      'minimal_symbols',
+      'official_optimize',
+      'clang_tot'
+    ],
+    'android_clang_tot_x64': [
+      'android_without_codecs',
+      'clang_tot',
+      'shared',
+      'x64',
+      'release',
+      'dcheck_always_on'
+    ],
+    'android_cronet_debug_static_bot_arm64': [
+      'android',
+      'cronet',
+      'debug_static_bot',
+      'arm64'
+    ],
+    'android_cronet_debug_static_bot_arm_no_neon': [
+      'android',
+      'cronet',
+      'debug_static_bot',
+      'arm_no_neon',
+      'release_java'
+    ],
+    'android_cronet_debug_static_bot_x86': [
+      'android',
+      'cronet',
+      'debug_static_bot',
+      'x86'
+    ],
+    'android_cronet_release_bot_minimal_symbols_arm64': [
+      'android',
+      'cronet',
+      'official_optimize',
+      'release_bot',
+      'minimal_symbols',
+      'arm64',
+      'strip_debug_info'
+    ],
+    'android_cronet_release_bot_minimal_symbols_arm_no_neon': [
+      'android',
+      'cronet',
+      'official_optimize',
+      'release_bot',
+      'minimal_symbols',
+      'arm_no_neon',
+      'strip_debug_info'
+    ],
+    'android_cronet_release_bot_minimal_symbols_arm_no_neon_clang_asan': [
+      'android',
+      'cronet',
+      'release_bot',
+      'minimal_symbols',
+      'arm_no_neon',
+      'clang',
+      'asan',
+      'strip_debug_info'
+    ],
+    'android_cronet_release_bot_minimal_symbols_x86': [
+      'android',
+      'cronet',
+      'official_optimize',
+      'release_bot',
+      'minimal_symbols',
+      'x86',
+      'strip_debug_info'
+    ],
+    'android_cronet_release_trybot_arm_no_neon': [
+      'android',
+      'cronet',
+      'release_trybot',
+      'arm_no_neon'
+    ],
+    'android_debug_bot': [
+      'android',
+      'debug_bot'
+    ],
+    'android_debug_static_bot': [
+      'android',
+      'debug_static_bot'
+    ],
+    'android_debug_static_bot_arm64': [
+      'android',
+      'debug_static_bot',
+      'arm64'
+    ],
+    'android_debug_static_bot_x64': [
+      'android',
+      'debug_static_bot',
+      'x64'
+    ],
+    'android_debug_static_bot_x86': [
+      'android',
+      'debug_static_bot',
+      'x86'
+    ],
+    'android_debug_trybot_arm64': [
+      'android',
+      'debug_bot',
+      'arm64'
+    ],
+    'android_debug_trybot_compile_only': [
+      'android',
+      'debug_bot',
+      'compile_only',
+      'java_warnings_as_errors'
+    ],
+    'android_debug_trybot_compile_only_arm64_fastbuild': [
+      'android',
+      'debug_bot',
+      'compile_only',
+      'arm64',
+      'android_fastbuild'
+    ],
+    'android_debug_trybot_compile_only_x64': [
+      'android',
+      'debug_bot',
+      'compile_only',
+      'x64'
+    ],
+    'android_debug_trybot_compile_only_x86': [
+      'android',
+      'debug_bot',
+      'compile_only',
+      'x86'
+    ],
+    'android_incremental_debug_bot': [
+      'android',
+      'incremental',
+      'debug_bot'
+    ],
+    'android_release_bot_minimal_symbols': [
+      'android',
+      'release_bot',
+      'minimal_symbols',
+      'strip_debug_info'
+    ],
+    'android_release_bot_minimal_symbols_arm64': [
+      'android',
+      'release_bot',
+      'minimal_symbols',
+      'arm64',
+      'strip_debug_info'
+    ],
+    'android_release_bot_minimal_symbols_arm64_webview_google': [
+      'android',
+      'release_bot',
+      'minimal_symbols',
+      'arm64',
+      'strip_debug_info',
+      'webview_google'
+    ],
+    'android_release_trybot': [
+      'android',
+      'release_trybot',
+      'strip_debug_info'
+    ],
+    'android_release_trybot_arm64': [
+      'android',
+      'release_trybot',
+      'arm64',
+      'strip_debug_info'
+    ],
+    'android_release_trybot_arm64_webview_google': [
+      'android',
+      'release_trybot',
+      'arm64',
+      'strip_debug_info',
+      'webview_google'
+    ],
+    'android_release_trybot_fastbuild': [
+      'android',
+      'release_trybot',
+      'strip_debug_info',
+      'android_fastbuild',
+      'android_no_proguard'
+    ],
+    'android_release_trybot_x86': [
+      'android',
+      'release_trybot',
+      'strip_debug_info',
+      'x86'
+    ],
+    'android_release_trybot_x86_resource_whitelisting': [
+      'android',
+      'release_trybot',
+      'x86',
+      'resource_whitelisting'
+    ],
+    'android_webview_google_debug_static_bot': [
+      'android',
+      'debug_static_bot',
+      'webview_google'
+    ],
+    'android_webview_google_debug_static_bot_arm64': [
+      'android',
+      'debug_static_bot',
+      'arm64',
+      'webview_google'
+    ],
+    'android_without_codecs_debug_bot': [
+      'android_without_codecs',
+      'debug_bot'
+    ],
+    'android_without_codecs_release_bot_minimal_symbols': [
+      'android_without_codecs',
+      'release_bot',
+      'minimal_symbols',
+      'strip_debug_info'
+    ],
+    'android_without_codecs_release_trybot': [
+      'android_without_codecs',
+      'release_trybot',
+      'strip_debug_info'
+    ],
+    'asan_clang_shared_v8_heap_minimal_symbols_release_tot': [
+      'asan',
+      'clang_tot',
+      'shared',
+      'v8_heap',
+      'minimal_symbols',
+      'release'
+    ],
+    'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release': [
+      'asan',
+      'fuzzer',
+      'static',
+      'v8_heap',
+      'minimal_symbols',
+      'release_bot'
+    ],
+    'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release_tot': [
+      'asan',
+      'clang_tot',
+      'fuzzer',
+      'static',
+      'v8_heap',
+      'minimal_symbols',
+      'release'
+    ],
+    'asan_dcheck_disable_nacl_release_bot': [
+      'asan',
+      'dcheck_always_on',
+      'disable_nacl',
+      'release_bot'
+    ],
+    'asan_disable_nacl_clang_tot_minimal_symbols_static_release': [
+      'asan',
+      'disable_nacl',
+      'clang_tot',
+      'minimal_symbols',
+      'static',
+      'release'
+    ],
+    'asan_disable_nacl_fuzzer_v8_heap_chrome_with_codecs_release_bot': [
+      'asan',
+      'disable_nacl',
+      'fuzzer',
+      'v8_heap',
+      'chrome_with_codecs',
+      'release_bot'
+    ],
+    'asan_disable_nacl_fuzzer_v8_heap_release_bot': [
+      'asan',
+      'disable_nacl',
+      'fuzzer',
+      'v8_heap',
+      'release_bot'
+    ],
+    'asan_fuzzer_v8_heap_chromeos_codecs_release_bot_hybrid': [
+      'asan',
+      'fuzzer',
+      'v8_heap',
+      'chromeos_codecs',
+      'release_bot',
+      'hybrid'
+    ],
+    'asan_fuzzer_v8_heap_release_bot_hybrid': [
+      'asan',
+      'fuzzer',
+      'v8_heap',
+      'release_bot',
+      'hybrid'
+    ],
+    'asan_v8_heap_debug_bot_hybrid': [
+      'asan',
+      'v8_heap',
+      'debug_bot',
+      'hybrid'
+    ],
+    'asan_minimal_symbols_disable_nacl_release_bot_dcheck_always_on': [
+      'asan',
+      'minimal_symbols',
+      'disable_nacl',
+      'release_bot',
+      'dcheck_always_on'
+    ],
+    'asan_fuzzer_v8_heap_chrome_with_codecs_release_bot': [
+      'clang',
+      'asan',
+      'fuzzer',
+      'v8_heap',
+      'chrome_with_codecs',
+      'release_bot'
+    ],
+    'asan_fuzzer_v8_heap_release_bot': [
+      'clang',
+      'asan',
+      'fuzzer',
+      'v8_heap',
+      'release_bot'
+    ],
+    'asan_lsan_chromeos_release_trybot': [
+      'asan',
+      'lsan',
+      'chromeos',
+      'release_trybot'
+    ],
+    'asan_lsan_debug_bot': [
+      'asan',
+      'lsan',
+      'debug_bot'
+    ],
+    'asan_lsan_fuzzer_v8_heap_chromeos_codecs_release_bot': [
+      'asan',
+      'lsan',
+      'v8_heap',
+      'chromeos_codecs',
+      'release_bot'
+    ],
+    'asan_lsan_fuzzer_v8_heap_release_bot': [
+      'asan',
+      'lsan',
+      'fuzzer',
+      'v8_heap',
+      'release_bot'
+    ],
+    'asan_lsan_release_bot': [
+      'asan',
+      'lsan',
+      'release_bot'
+    ],
+    'asan_lsan_release_trybot': [
+      'asan',
+      'lsan',
+      'release_trybot'
+    ],
+    'cast_release_bot': [
+      'cast',
+      'cast_exo',
+      'release_bot',
+      'minimal_symbols'
+    ],
+    'cast_release_trybot': [
+      'cast',
+      'cast_exo',
+      'release_trybot'
+    ],
+    'cast_audio_release_bot': [
+      'cast',
+      'cast_audio',
+      'release_bot',
+      'minimal_symbols'
+    ],
+    'cast_audio_release_trybot': [
+      'cast',
+      'cast_audio',
+      'release_trybot'
+    ],
+    'cfi_full_cfi_icall_cfi_diag_recover_release_static': [
+      'cfi_full',
+      'cfi_icall',
+      'cfi_diag',
+      'cfi_recover',
+      'thin_lto',
+      'release',
+      'static'
+    ],
+    'cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on_goma': [
+      'cfi_full',
+      'cfi_icall',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on',
+      'goma'
+    ],
+    'chromeos_asan_lsan_fuzzer_v8_heap_release_bot': [
+      'chromeos',
+      'asan',
+      'lsan',
+      'fuzzer',
+      'v8_heap',
+      'release_bot'
+    ],
+    'chromeos_msan_release_bot': [
+      'chromeos',
+      'msan',
+      'release_bot'
+    ],
+    'chromeos_with_codecs_debug_bot': [
+      'chromeos_with_codecs',
+      'debug_bot'
+    ],
+    'chromeos_with_codecs_release_bot': [
+      'chromeos_with_codecs',
+      'release_bot'
+    ],
+    'chromeos_with_codecs_release_bot_coverage': [
+      'chromeos_with_codecs',
+      'release_bot',
+      'use_clang_coverage'
+    ],
+    'chromeos_with_codecs_release_trybot': [
+      'chromeos_with_codecs',
+      'release_trybot',
+      'no_symbols'
+    ],
+    'chromeos_with_codecs_release_trybot_code_coverage': [
+      'chromeos_with_codecs',
+      'release_trybot',
+      'no_symbols',
+      'use_clang_coverage',
+      'partial_code_coverage_instrumentation'
+    ],
+    'clang_code_coverage': [
+      'release_bot',
+      'clang',
+      'use_clang_coverage',
+      'no_symbols'
+    ],
+    'clang_code_coverage_ios': [
+      'use_clang_coverage',
+      'debug_static_bot',
+      'x64',
+      'ios'
+    ],
+    'clang_tot_asan_lsan_static_release': [
+      'clang_tot',
+      'asan',
+      'lsan',
+      'static',
+      'release'
+    ],
+    'clang_tot_cfi_full_cfi_icall_cfi_diag_thin_lto_release_static_dcheck_always_on': [
+      'clang_tot',
+      'cfi_full',
+      'cfi_icall',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on'
+    ],
+    'clang_tot_win_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on': [
+      'clang_tot',
+      'cfi_full',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on',
+      'win_linker_timing'
+    ],
+    'clang_tot_win_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on_x86': [
+      'clang_tot',
+      'cfi_full',
+      'cfi_diag',
+      'thin_lto',
+      'release',
+      'static',
+      'dcheck_always_on',
+      'x86',
+      'win_linker_timing'
+    ],
+    'clang_tot_ubsan_no_recover_hack_static_release': [
+      'clang_tot',
+      'ubsan_no_recover_hack',
+      'static',
+      'release'
+    ],
+    'clang_tot_linux_full_symbols_shared_release': [
+      'clang_tot',
+      'full_symbols',
+      'shared',
+      'release'
+    ],
+    'clang_tot_msan_release': [
+      'clang_tot',
+      'msan',
+      'release'
+    ],
+    'clang_tot_minimal_symbols_shared_release': [
+      'clang_tot',
+      'minimal_symbols',
+      'shared',
+      'release'
+    ],
+    'clang_tot_coverage_minimal_symbols_release': [
+      'clang_tot',
+      'use_clang_coverage',
+      'minimal_symbols',
+      'release'
+    ],
+    'clang_tot_shared_release_dcheck': [
+      'clang_tot',
+      'shared',
+      'release',
+      'dcheck_always_on'
+    ],
+    'clang_tot_minimal_symbols_shared_release_x86_dcheck': [
+      'clang_tot',
+      'minimal_symbols',
+      'shared',
+      'release',
+      'x86',
+      'dcheck_always_on'
+    ],
+    'clang_tot_official_optimize_minimal_symbols_static_release_libcxx': [
+      'clang_tot',
+      'official_optimize',
+      'minimal_symbols',
+      'static',
+      'release',
+      'libcxx'
+    ],
+    'clang_tot_release_minimal_symbols_thin_lto_static': [
+      'clang_tot',
+      'release',
+      'minimal_symbols',
+      'thin_lto',
+      'static'
+    ],
+    'clang_tot_shared_debug': [
+      'clang_tot',
+      'shared',
+      'debug'
+    ],
+    'clang_tot_shared_debug_x86': [
+      'clang_tot',
+      'shared',
+      'debug',
+      'x86'
+    ],
+    'clang_tot_tsan_release': [
+      'clang_tot',
+      'tsan',
+      'release'
+    ],
+    'clang_tot_win_release_cross': [
+      'clang_tot',
+      'win_cross',
+      'minimal_symbols',
+      'shared',
+      'release',
+      'dcheck_always_on'
+    ],
+    'closure_compilation': [
+      'error'
+    ],
+    'cros_chrome_sdk': [
+      'cros_chrome_sdk'
+    ],
+    'cros_chrome_sdk_dbg': [
+      'cros_chrome_sdk',
+      'debug'
+    ],
+    'cros_chrome_sdk_dcheck_always_on': [
+      'cros_chrome_sdk',
+      'dcheck_always_on'
+    ],
+    'cros_chrome_sdk_cfi_thin_lto': [
+      'cros_chrome_sdk',
+      'cfi_full',
+      'thin_lto'
+    ],
+    'dawn_tests_release_trybot': [
+      'dawn_tests',
+      'release_trybot'
+    ],
+    'dawn_tests_release_trybot_x86': [
+      'dawn_tests',
+      'release_trybot',
+      'x86'
+    ],
+    'debug_bot': [
+      'debug_bot'
+    ],
+    'debug_bot_deterministic': [
+      'debug_bot',
+      'mac_deterministic_build'
+    ],
+    'debug_bot_local_build': [
+      'debug_bot_local_build'
+    ],
+    'debug_bot_enable_blink_animation_use_time_delta': [
+      'debug_bot',
+      'enable_blink_animation_use_time_delta'
+    ],
+    'debug_bot_fuchsia': [
+      'debug_bot',
+      'fuchsia'
+    ],
+    'debug_bot_fuchsia_compile_only': [
+      'debug_bot',
+      'fuchsia',
+      'compile_only'
+    ],
+    'debug_bot_x86': [
+      'debug_bot',
+      'x86'
+    ],
+    'debug_bot_x86_no_com_init_hooks_with_codecs': [
+      'debug_bot',
+      'x86',
+      'no_com_init_hooks',
+      'chrome_with_codecs'
+    ],
+    'deqp_android_release_trybot_arm64': [
+      'angle_deqp_tests',
+      'android',
+      'release_trybot',
+      'arm64'
+    ],
+    'deqp_android_vulkan_ndk_release_trybot': [
+      'angle_deqp_tests',
+      'android',
+      'vulkan_ndk',
+      'release_trybot'
+    ],
+    'deqp_android_vulkan_ndk_release_trybot_arm64': [
+      'angle_deqp_tests',
+      'android',
+      'vulkan_ndk',
+      'release_trybot',
+      'arm64'
+    ],
+    'deqp_release_trybot_x86': [
+      'angle_deqp_tests',
+      'release_trybot',
+      'x86'
+    ],
+    'deqp_release_trybot': [
+      'angle_deqp_tests',
+      'release_trybot'
+    ],
+    'gpu_fyi_tests_debug_trybot': [
+      'gpu_fyi_tests',
+      'debug_bot'
+    ],
+    'gpu_fyi_tests_dx12vk_debug_trybot': [
+      'gpu_fyi_tests',
+      'dx12vk',
+      'debug_bot'
+    ],
+    'gpu_fyi_tests_debug_trybot_x86': [
+      'gpu_fyi_tests',
+      'debug_bot',
+      'x86'
+    ],
+    'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot': [
+      'gpu_fyi_tests',
+      'ozone_linux',
+      'system_gbm_libdrm',
+      'release_trybot'
+    ],
+    'gpu_fyi_tests_release_trybot': [
+      'gpu_fyi_tests',
+      'release_trybot'
+    ],
+    'gpu_fyi_tests_release_trybot_asan': [
+      'gpu_fyi_tests',
+      'release_trybot',
+      'asan',
+      'disable_nacl'
+    ],
+    'gpu_fyi_tests_release_trybot_tsan': [
+      'gpu_fyi_tests',
+      'release_trybot',
+      'tsan',
+      'disable_nacl'
+    ],
+    'gpu_fyi_tests_release_trybot_x86': [
+      'gpu_fyi_tests',
+      'release_trybot',
+      'x86'
+    ],
+    'gpu_fyi_tests_release_trybot_fuchsia': [
+      'gpu_fyi_tests',
+      'release_trybot',
+      'fuchsia'
+    ],
+    'gpu_fyi_tests_dx12vk_release_trybot': [
+      'gpu_fyi_tests',
+      'dx12vk',
+      'release_trybot'
+    ],
+    'gpu_tests_android_release_bot_minimal_symbols_arm64_fastbuild_java_coverage': [
+      'gpu_tests',
+      'android',
+      'release_bot',
+      'minimal_symbols',
+      'arm64',
+      'resource_whitelisting',
+      'static_angle',
+      'android_fastbuild',
+      'webview_google',
+      'android_no_proguard',
+      'use_java_coverage'
+    ],
+    'gpu_tests_android_release_bot_minimal_symbols_arm64_fastbuild_native_coverage': [
+      'gpu_tests',
+      'android',
+      'release_bot',
+      'minimal_symbols',
+      'arm64',
+      'static_angle',
+      'android_fastbuild',
+      'webview_google',
+      'android_no_proguard',
+      'use_clang_coverage'
+    ],
+    'gpu_tests_android_release_trybot': [
+      'gpu_tests',
+      'android',
+      'release_trybot',
+      'static_angle'
+    ],
+    'gpu_tests_android_release_trybot_arm64': [
+      'gpu_tests',
+      'android',
+      'release_trybot',
+      'arm64',
+      'static_angle'
+    ],
+    'gpu_tests_android_release_trybot_arm64_fastbuild': [
+      'gpu_tests',
+      'android',
+      'release_trybot',
+      'arm64',
+      'static_angle',
+      'android_fastbuild'
+    ],
+    'gpu_tests_android_release_trybot_arm64_resource_whitelisting_fastbuild_java_coverage': [
+      'gpu_tests',
+      'android',
+      'release_trybot',
+      'arm64',
+      'static_angle',
+      'resource_whitelisting',
+      'android_fastbuild',
+      'webview_google',
+      'android_no_proguard',
+      'use_java_coverage',
+      'partial_code_coverage_instrumentation'
+    ],
+    'gpu_tests_android_vulkan_ndk_release_trybot': [
+      'gpu_tests',
+      'android',
+      'vulkan_ndk',
+      'release_trybot',
+      'static_angle'
+    ],
+    'gpu_tests_android_vulkan_ndk_release_trybot_arm64': [
+      'gpu_tests',
+      'android',
+      'vulkan_ndk',
+      'release_trybot',
+      'arm64',
+      'static_angle'
+    ],
+    'gpu_tests_debug_bot': [
+      'gpu_tests',
+      'debug_bot'
+    ],
+    'gpu_tests_debug_bot_x86': [
+      'gpu_tests',
+      'debug_bot',
+      'x86'
+    ],
+    'gpu_tests_debug_trybot_x86_compile_only': [
+      'gpu_tests',
+      'debug_bot',
+      'x86',
+      'compile_only'
+    ],
+    'gpu_tests_release_bot_minimal_symbols': [
+      'gpu_tests',
+      'release_bot',
+      'minimal_symbols'
+    ],
+    'gpu_tests_release_bot_x86_minimal_symbols': [
+      'gpu_tests',
+      'release_bot',
+      'x86',
+      'minimal_symbols'
+    ],
+    'gpu_tests_release_trybot': [
+      'gpu_tests',
+      'release_trybot'
+    ],
+    'gpu_tests_release_trybot_deterministic_mac': [
+      'gpu_tests',
+      'release_trybot',
+      'mac_deterministic_build'
+    ],
+    'gpu_tests_release_trybot_resource_whitelisting': [
+      'gpu_tests',
+      'release_trybot',
+      'resource_whitelisting'
+    ],
+    'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange': [
+      'gpu_tests',
+      'release_trybot',
+      'no_symbols',
+      'use_dummy_lastchange'
+    ],
+    'gpu_tests_release_trybot_no_symbols_use_dummy_lastchange_code_coverage': [
+      'gpu_tests',
+      'release_trybot',
+      'no_symbols',
+      'use_dummy_lastchange',
+      'use_clang_coverage',
+      'partial_code_coverage_instrumentation'
+    ],
+    'gpu_tests_release_trybot_x86_resource_whitelisting': [
+      'gpu_tests',
+      'release_trybot',
+      'x86',
+      'resource_whitelisting'
+    ],
+    'gpu_tests_release_bot': [
+      'gpu_tests',
+      'release_bot'
+    ],
+    'ios_error': [
+      'error'
+    ],
+    'ios_simulator_debug_static_bot': [
+      'ios_simulator',
+      'debug_static_bot'
+    ],
+    'libfuzzer_asan_debug_bot': [
+      'libfuzzer',
+      'asan',
+      'debug_bot',
+      'shared',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_debug_bot_v8_arm64': [
+      'libfuzzer',
+      'asan',
+      'debug_bot',
+      'shared',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'v8_simulate_arm64',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_debug_bot_x86': [
+      'libfuzzer',
+      'asan',
+      'debug_bot',
+      'shared',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'x86',
+      'x86_host',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_debug_bot_x86_v8_arm': [
+      'libfuzzer',
+      'asan',
+      'debug_bot',
+      'shared',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'x86_host',
+      'v8_simulate_arm',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_chromeos_asan_release_bot': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chromeos_with_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_release_bot': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing'
+    ],
+    'libfuzzer_asan_release_bot_v8_arm64': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'v8_simulate_arm64',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_release_bot_x86': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'x86',
+      'x86_host',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_release_bot_x86_v8_arm': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'x86_host',
+      'v8_simulate_arm',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_asan_release_trybot': [
+      'libfuzzer',
+      'asan',
+      'shared_release_trybot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing'
+    ],
+    'libfuzzer_asan_clang_tot_release': [
+      'libfuzzer',
+      'asan',
+      'clang_tot',
+      'release',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl'
+    ],
+    'libfuzzer_msan_release_bot': [
+      'libfuzzer',
+      'msan',
+      'shared_release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_ubsan_release_bot': [
+      'libfuzzer',
+      'ubsan_security',
+      'release_bot',
+      'chromeos_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing',
+      'disable_seed_corpus'
+    ],
+    'libfuzzer_mac_asan_shared_release_bot': [
+      'libfuzzer',
+      'asan',
+      'shared_release_bot',
+      'chrome_with_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'optimize_for_fuzzing'
+    ],
+    'libfuzzer_windows_asan_release_bot': [
+      'libfuzzer',
+      'asan',
+      'release_bot',
+      'chrome_with_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'minimal_symbols'
+    ],
+    'libfuzzer_windows_asan_clang_tot_release_bot': [
+      'libfuzzer',
+      'asan',
+      'clang_tot',
+      'release',
+      'chrome_with_codecs',
+      'pdf_xfa',
+      'disable_nacl',
+      'minimal_symbols'
+    ],
+    'libfuzzer_windows_asan_release_trybot': [
+      'libfuzzer',
+      'asan',
+      'release_trybot',
+      'chrome_with_codecs',
+      'pdf_xfa',
+      'disable_nacl'
+    ],
+    'msan_release_bot': [
+      'msan',
+      'release_bot'
+    ],
+    'msan_no_origins_release_bot': [
+      'msan_no_origins',
+      'release_bot'
+    ],
+    'ozone_linux_release_trybot': [
+      'ozone_linux',
+      'release_trybot'
+    ],
+    'presubmit': [
+      'error'
+    ],
+    'release_bot': [
+      'release_bot'
+    ],
+    'release_bot_enable_blink_heap_verification_dcheck_always_on': [
+      'release_bot',
+      'enable_blink_heap_verification',
+      'dcheck_always_on'
+    ],
+    'release_bot_fuchsia': [
+      'release_bot',
+      'fuchsia'
+    ],
+    'release_bot_fuchsia_arm64': [
+      'release_bot',
+      'fuchsia',
+      'arm64'
+    ],
+    'release_bot_mac_strip_minimal_symbols': [
+      'release_bot',
+      'mac_strip',
+      'minimal_symbols'
+    ],
+    'release_bot_mac_strip_minimal_symbols_deterministic': [
+      'release_bot',
+      'mac_strip',
+      'minimal_symbols',
+      'mac_deterministic_build'
+    ],
+    'release_bot_minimal_symbols': [
+      'release_bot',
+      'minimal_symbols'
+    ],
+    'release_bot_x86_minimal_symbols': [
+      'release_bot',
+      'x86',
+      'minimal_symbols'
+    ],
+    'release_bot_minimal_symbols_enable_archive_compression': [
+      'release_bot',
+      'minimal_symbols',
+      'enable_archive_compression'
+    ],
+    'release_bot_x86_minimal_symbols_enable_archive_compression': [
+      'release_bot',
+      'x86',
+      'minimal_symbols',
+      'enable_archive_compression'
+    ],
+    'release_bot_x86_minimal_symbols_enable_archive_compression_no_clang': [
+      'no_clang',
+      'release_bot',
+      'x86',
+      'minimal_symbols',
+      'enable_archive_compression'
+    ],
+    'release_bot_x86_minimal_symbols_no_com_init_hooks_with_codecs': [
+      'release_bot',
+      'x86',
+      'minimal_symbols',
+      'no_com_init_hooks',
+      'chrome_with_codecs'
+    ],
+    'release_trybot': [
+      'release_trybot'
+    ],
+    'release_trybot_dcheck_off': [
+      'release_trybot',
+      'dcheck_off'
+    ],
+    'release_trybot_arm': [
+      'release_trybot',
+      'arm'
+    ],
+    'release_trybot_enable_blink_heap_verification': [
+      'release_trybot',
+      'enable_blink_heap_verification'
+    ],
+    'release_trybot_fuchsia': [
+      'release_trybot',
+      'fuchsia'
+    ],
+    'release_trybot_fuchsia_cast': [
+      'release_trybot',
+      'fuchsia',
+      'cast'
+    ],
+    'release_trybot_fuchsia_arm64': [
+      'release_trybot',
+      'fuchsia',
+      'arm64'
+    ],
+    'release_trybot_fuchsia_arm64_cast': [
+      'release_trybot',
+      'fuchsia',
+      'arm64',
+      'cast'
+    ],
+    'release_trybot_tsan': [
+      'release_trybot',
+      'tsan'
+    ],
+    'release_trybot_x86': [
+      'release_trybot',
+      'x86'
+    ],
+    'release_bot_x86_minimal_symbols_no_clang_cxx11': [
+      'release_bot',
+      'x86',
+      'minimal_symbols',
+      'no_clang',
+      'use_cxx11',
+      'no_goma'
+    ],
+    'tsan_disable_nacl_debug_bot': [
+      'tsan',
+      'disable_nacl',
+      'debug_bot'
+    ],
+    'tsan_disable_nacl_release_bot': [
+      'tsan',
+      'disable_nacl',
+      'release_bot'
+    ],
+    'tsan_disable_nacl_release_trybot': [
+      'tsan',
+      'disable_nacl',
+      'release_trybot'
+    ],
+    'ubsan_release_bot': [
+      'ubsan',
+      'release_bot'
+    ],
+    'ubsan_vptr_release_bot': [
+      'ubsan_vptr',
+      'ubsan_no_recover_hack',
+      'release_bot'
+    ],
+    'vr_release_bot': [
+      'vr',
+      'release_bot',
+      'ozone'
+    ],
+    'vr_release_trybot': [
+      'vr',
+      'release_trybot',
+      'ozone'
+    ],
+    'win_msvc_release_bot': [
+      'no_clang',
+      'release_bot'
+    ],
+    'win32_arm64_release_bot': [
+      'arm64',
+      'disable_nacl',
+      'minimal_symbols',
+      'release_bot'
+    ],
+  },
+
+  'mixins': {
+    'afl': {
+      'gn_args': 'use_afl=true'
+    },
+
+    'android': {
+      'mixins': [
+        'android_without_codecs',
+        'chrome_with_codecs'
+      ],
+    },
+
+    'android_config_check': {
+      'gn_args': 'check_android_configuration = true'
+    },
+
+    'android_fastbuild': {
+      'gn_args': 'use_errorprone_java_compiler=false disable_android_lint=true'
+    },
+
+    'android_no_proguard': {
+      'gn_args': 'is_java_debug=true'
+    },
+
+    'android_without_codecs': {
+      'gn_args': 'target_os="android"'
+    },
+
+    'angle_deqp_tests': {
+      'gn_args': 'build_angle_deqp_tests=true'
+    },
+
+    'arm': {
+      'gn_args': 'target_cpu="arm"'
+    },
+
+    'arm64': {
+      'gn_args': 'target_cpu="arm64"'
+    },
+
+    'arm_no_neon': {
+      'mixins': [
+        'arm'
+      ],
+      'gn_args': 'arm_use_neon=false'
+    },
+
+    'asan': {
+      'gn_args': 'is_asan=true'
+    },
+
+    'cast': {
+      'gn_args': 'is_chromecast=true'
+    },
+
+    'cast_audio': {
+      'gn_args': 'is_cast_audio_only=true'
+    },
+
+    'cast_exo': {
+      'gn_args': 'enable_cast_wayland_server=true'
+    },
+
+    'cfi': {
+      'gn_args': 'is_cfi=true'
+    },
+
+    'cfi_full': {
+      'mixins': [
+        'cfi'
+      ],
+      'gn_args': 'use_cfi_cast=true'
+    },
+
+    'cfi_icall': {
+      'gn_args': 'use_cfi_icall=true'
+    },
+
+    'cfi_diag': {
+      'gn_args': 'use_cfi_diag=true'
+    },
+
+    'cfi_recover': {
+      'gn_args': 'use_cfi_recover=true'
+    },
+
+    'chrome_with_codecs': {
+      'mixins': [
+        'ffmpeg_branding_chrome',
+        'proprietary_codecs'
+      ],
+    },
+
+    'chromeos': {
+      'gn_args': 'target_os="chromeos"'
+    },
+
+    'chromeos_codecs': {
+      'mixins': [
+        'ffmpeg_branding_chromeos',
+        'proprietary_codecs'
+      ],
+    },
+
+    'chromeos_with_codecs': {
+      'mixins': [
+        'chromeos',
+        'chromeos_codecs'
+      ],
+    },
+
+    'clang_tot': {
+      'gn_args': 'llvm_force_head_revision=true',
+      'mixins': [
+        'clang'
+      ],
+    },
+
+    'clang': {
+      'gn_args': 'is_clang=true'
+    },
+
+    'compile_only': {
+      'mixins': [
+        'no_symbols'
+      ],
+    },
+
+    'cronet': {
+      'gn_args': 'disable_file_support=true disable_ftp_support=true enable_websockets=false use_platform_icu_alternatives=true use_partition_alloc=false enable_reporting=true include_transport_security_state_preload_list=false use_crash_key_stubs=true use_hashed_jni_names=true clang_use_default_sample_profile=false enable_resource_whitelist_generation=false'
+    },
+
+    'cros_chrome_sdk': {
+      'cros_passthrough': True,
+      'gn_args': 'ozone_platform_headless=true'
+    },
+
+    'dawn_tests': {
+      'gn_args': 'use_dawn=true'
+    },
+
+    'dcheck_always_on': {
+      'gn_args': 'dcheck_always_on=true'
+    },
+
+    'dcheck_off': {
+      'gn_args': 'dcheck_always_on=false'
+    },
+
+    'debug': {
+      'gn_args': 'is_debug=true'
+    },
+
+    'debug_bot': {
+      'mixins': [
+        'debug',
+        'shared',
+        'goma',
+        'minimal_symbols'
+      ],
+    },
+
+    'debug_bot_local_build': {
+      'mixins': [
+        'debug',
+        'shared',
+        'minimal_symbols'
+      ],
+    },
+
+    'debug_static_bot': {
+      'mixins': [
+        'debug',
+        'static',
+        'minimal_symbols',
+        'goma'
+      ],
+    },
+
+    'disable_nacl': {
+      'gn_args': 'enable_nacl=false'
+    },
+
+    'disable_seed_corpus': {
+      'gn_args': 'archive_seed_corpus=false'
+    },
+
+    'dx12vk': {
+      'mixins': [
+        'enable_vulkan'
+      ],
+    },
+
+    'enable_blink_animation_use_time_delta': {
+      'gn_args': 'blink_animation_use_time_delta=true'
+    },
+
+    'enable_blink_heap_verification': {
+      'gn_args': 'enable_blink_heap_verification=true'
+    },
+
+    'enable_archive_compression': {
+      'gn_args': 'skip_archive_compression=false'
+    },
+
+    'java_warnings_as_errors': {
+      'gn_args': 'java_warnings_as_errors=true'
+    },
+
+    'enable_vulkan': {
+      'gn_args': 'enable_vulkan=true'
+    },
+
+    'error': {
+      'gn_args': 'error'
+    },
+
+    'ffmpeg_branding_chrome': {
+      'gn_args': 'ffmpeg_branding="Chrome"'
+    },
+
+    'ffmpeg_branding_chromeos': {
+      'gn_args': 'ffmpeg_branding="ChromeOS"'
+    },
+
+    'fuchsia': {
+      'gn_args': 'target_os="fuchsia"'
+    },
+
+    'full_symbols': {
+      'gn_args': 'symbol_level=2'
+    },
+
+    'fuzzer': {
+      'gn_args': 'enable_ipc_fuzzer=true'
+    },
+
+    'goma': {
+      'gn_args': 'use_goma=true'
+    },
+
+    'gpu_fyi_tests': {
+      'mixins': [
+        'gpu_tests',
+        'internal_gles_conform_tests'
+      ],
+    },
+
+    'gpu_tests': {
+      'mixins': [
+        'chrome_with_codecs'
+      ],
+    },
+
+    'hybrid': {
+      'gn_args': 'v8_target_cpu="arm" target_cpu="x86"',
+      'mixins': [
+        'disable_nacl'
+      ],
+    },
+
+    'incremental': {
+      'gn_args': 'incremental_install=true'
+    },
+
+    'internal_gles_conform_tests': {
+      'gn_args': 'internal_gles2_conform_tests=true build_angle_gles1_conform_tests=true'
+    },
+
+    'ios': {
+      'gn_args': 'target_os="ios"'
+    },
+
+    'ios_simulator': {
+      'mixins': [
+        'ios'
+      ],
+      'gn_args': 'target_cpu="x64"'
+    },
+
+    'libcxx': {
+      'gn_args': 'use_custom_libcxx=true'
+    },
+
+    'libfuzzer': {
+      'gn_args': 'use_libfuzzer=true'
+    },
+
+    'lsan': {
+      'gn_args': 'is_lsan=true'
+    },
+
+    'mac_deterministic_build': {
+      'gn_args': 'mac_deterministic_build=true'
+    },
+
+    'mac_strip': {
+      'gn_args': 'enable_stripping=true'
+    },
+
+    'minimal_symbols': {
+      'gn_args': 'symbol_level=1'
+    },
+
+    'msan': {
+      'gn_args': 'is_msan=true msan_track_origins=2'
+    },
+
+    'msan_no_origins': {
+      'gn_args': 'is_msan=true msan_track_origins=0'
+    },
+
+    'no_clang': {
+      'gn_args': 'is_clang=false'
+    },
+
+    'no_com_init_hooks': {
+      'gn_args': 'com_init_check_hook_disabled=true'
+    },
+
+    'no_goma': {
+      'gn_args': 'use_goma=false'
+    },
+
+    'no_symbols': {
+      'gn_args': 'symbol_level=0'
+    },
+
+    'official_optimize': {
+      'gn_args': 'is_official_build=true is_debug=false'
+    },
+
+    'optimize_for_fuzzing': {
+      'gn_args': 'optimize_for_fuzzing=true'
+    },
+
+    'ozone': {
+      'gn_args': 'use_ozone=true'
+    },
+
+    'ozone_linux': {
+      'gn_args': 'use_ozone=true ozone_platform="headless"'
+    },
+
+    'partial_code_coverage_instrumentation': {
+      'gn_args': 'coverage_instrumentation_input_file="//.code-coverage/files_to_instrument.txt"'
+    },
+
+    'pdf_xfa': {
+      'gn_args': 'pdf_enable_xfa=true'
+    },
+
+    'proprietary_codecs': {
+      'gn_args': 'proprietary_codecs=true'
+    },
+
+    'release': {
+      'gn_args': 'is_debug=false'
+    },
+
+    'release_bot': {
+      'mixins': [
+        'release',
+        'static',
+        'goma'
+      ],
+    },
+
+    'release_java': {
+      'gn_args': 'is_java_debug=false'
+    },
+
+    'release_trybot': {
+      'mixins': [
+        'release_bot',
+        'minimal_symbols',
+        'dcheck_always_on'
+      ],
+    },
+
+    'resource_whitelisting': {
+      'gn_args': 'enable_resource_whitelist_generation=true'
+    },
+
+    'shared': {
+      'gn_args': 'is_component_build=true'
+    },
+
+    'shared_release_bot': {
+      'mixins': [
+        'shared',
+        'release',
+        'goma'
+      ],
+    },
+
+    'shared_release_trybot': {
+      'mixins': [
+        'shared_release_bot',
+        'minimal_symbols',
+        'dcheck_always_on'
+      ],
+    },
+
+    'stable_channel': {
+      'gn_args': 'android_channel="stable"'
+    },
+
+    'static': {
+      'gn_args': 'is_component_build=false'
+    },
+
+    'static_angle': {
+      'gn_args': 'use_static_angle=true'
+    },
+
+    'strip_debug_info': {
+      'gn_args': 'strip_debug_info=true'
+    },
+
+    'system_gbm_libdrm': {
+      'gn_args': 'use_system_libdrm=true use_system_minigbm=true'
+    },
+
+    'thin_lto': {
+      'gn_args': 'use_thin_lto=true'
+    },
+
+    'tsan': {
+      'gn_args': 'is_tsan=true'
+    },
+
+    'ubsan': {
+      'gn_args': 'is_ubsan=true'
+    },
+
+    'ubsan_no_recover_hack': {
+      'mixins': [
+        'ubsan_vptr'
+      ],
+      'gn_args': 'is_ubsan_no_recover=true'
+    },
+
+    'ubsan_security': {
+      'gn_args': 'is_ubsan_security=true'
+    },
+
+    'ubsan_vptr': {
+      'gn_args': 'is_ubsan_vptr=true'
+    },
+
+    'use_clang_coverage': {
+      'gn_args': 'use_clang_coverage=true'
+    },
+
+    'use_cxx11': {
+      'gn_args': 'use_cxx11=true'
+    },
+
+    'use_dummy_lastchange': {
+      'gn_args': 'use_dummy_lastchange=true'
+    },
+
+    'use_java_coverage': {
+      'gn_args': 'use_jacoco_coverage=true'
+    },
+
+    'v8_simulate_arm': {
+      'gn_args': 'target_cpu="x86" v8_target_cpu="arm"'
+    },
+
+    'v8_simulate_arm64': {
+      'gn_args': 'target_cpu="x64" v8_target_cpu="arm64"'
+    },
+
+    'v8_heap': {
+      'gn_args': 'v8_enable_verify_heap=true'
+    },
+
+    'vr': {
+      'gn_args': 'enable_vr=true'
+    },
+
+    'vulkan_ndk': {
+      'gn_args': 'android32_ndk_api_level=26 android64_ndk_api_level=26'
+    },
+
+    'webview_google': {
+      'gn_args': 'system_webview_package_name="com.google.android.webview"'
+    },
+
+    'win_cross': {
+      'gn_args': 'target_os="win"'
+    },
+
+    'win_linker_timing': {
+      'gn_args': 'win_linker_timing=true'
+    },
+
+    'x64': {
+      'gn_args': 'target_cpu="x64"'
+    },
+
+    'x86': {
+      'gn_args': 'target_cpu="x86"'
+    },
+
+    'x86_host': {
+      'gn_args': 'host_cpu="x86"'
+    },
+  },
+}
diff --git a/tools/mb/mb_unittest.py b/tools/mb/mb_unittest.py
index 99cba481..1ccc2bc4 100755
--- a/tools/mb/mb_unittest.py
+++ b/tools/mb/mb_unittest.py
@@ -1,11 +1,12 @@
 #!/usr/bin/python
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2020 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.
 
 """Tests for mb.py."""
 
 from __future__ import print_function
+from __future__ import absolute_import
 
 import json
 import os
@@ -14,7 +15,10 @@
 import sys
 import unittest
 
-import mb
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.abspath(__file__)), '..'))
+
+from mb import mb
 
 
 class FakeMBW(mb.MetaBuildWrapper):
@@ -24,7 +28,9 @@
     # Override vars for test portability.
     if win32:
       self.chromium_src_dir = 'c:\\fake_src'
-      self.default_config = 'c:\\fake_src\\tools\\mb\\mb_config.pyl'
+      self.default_config_master = 'c:\\fake_src\\tools\\mb\\mb_config.pyl'
+
+      self.default_config_bucket = 'c:\\fake_src\\tools\\mb\\mb_config_bucket.pyl'  # pylint: disable=line-too-long
       self.default_isolate_map = ('c:\\fake_src\\testing\\buildbot\\'
                                   'gn_isolate_map.pyl')
       self.platform = 'win32'
@@ -33,7 +39,8 @@
       self.cwd = 'c:\\fake_src\\out\\Default'
     else:
       self.chromium_src_dir = '/fake_src'
-      self.default_config = '/fake_src/tools/mb/mb_config.pyl'
+      self.default_config_master = '/fake_src/tools/mb/mb_config.pyl'
+      self.default_config_bucket = '/fake_src/tools/mb/mb_config_bucket.pyl'
       self.default_isolate_map = '/fake_src/testing/buildbot/gn_isolate_map.pyl'
       self.executable = '/usr/bin/python'
       self.platform = 'linux2'
@@ -119,7 +126,7 @@
     self.buf += contents
 
   def close(self):
-     self.files[self.name] = self.buf
+    self.files[self.name] = self.buf
 
 
 TEST_CONFIG = """\
@@ -172,6 +179,55 @@
 }
 """
 
+TEST_CONFIG_BUCKET = """\
+{
+  'public_artifact_builders': {},
+  'buckets': {
+    'ci': {
+      'fake_builder': 'rel_bot',
+      'fake_debug_builder': 'debug_goma',
+      'fake_simplechrome_builder': 'cros_chrome_sdk',
+      'fake_args_bot': '//build/args/bots/fake_master/fake_args_bot.gn',
+      'fake_multi_phase': { 'phase_1': 'phase_1', 'phase_2': 'phase_2'},
+      'fake_args_file': 'args_file_goma',
+    }
+  },
+  'configs': {
+    'args_file_goma': ['args_file', 'goma'],
+    'cros_chrome_sdk': ['cros_chrome_sdk'],
+    'rel_bot': ['rel', 'goma', 'fake_feature1'],
+    'debug_goma': ['debug', 'goma'],
+    'phase_1': ['phase_1'],
+    'phase_2': ['phase_2'],
+  },
+  'mixins': {
+    'cros_chrome_sdk': {
+      'cros_passthrough': True,
+    },
+    'fake_feature1': {
+      'gn_args': 'enable_doom_melon=true',
+    },
+    'goma': {
+      'gn_args': 'use_goma=true',
+    },
+    'args_file': {
+      'args_file': '//build/args/fake.gn',
+    },
+    'phase_1': {
+      'gn_args': 'phase=1',
+    },
+    'phase_2': {
+      'gn_args': 'phase=2',
+    },
+    'rel': {
+      'gn_args': 'is_debug=false',
+    },
+    'debug': {
+      'gn_args': 'is_debug=true',
+    },
+  },
+}
+"""
 
 TEST_BAD_CONFIG = """\
 {
@@ -199,6 +255,35 @@
 }
 """
 
+TEST_BAD_CONFIG_BUCKET = """\
+{
+  'public_artifact_builders': {
+      'fake_bucket_a': ['fake_builder_a', 'fake_builder_b'],
+  },
+  'configs': {
+    'rel_bot_1': ['rel', 'chrome_with_codecs'],
+    'rel_bot_2': ['rel', 'bad_nested_config'],
+  },
+  'buckets': {
+    'fake_bucket_a': {
+      'fake_builder_a': 'rel_bot_1',
+      'fake_builder_b': 'rel_bot_2',
+    },
+  },
+  'mixins': {
+    'chrome_with_codecs': {
+      'gn_args': 'proprietary_codecs=true',
+    },
+    'bad_nested_config': {
+      'mixins': ['chrome_with_codecs'],
+    },
+    'rel': {
+      'gn_args': 'is_debug=false',
+    },
+  },
+}
+"""
+
 
 TEST_ARGS_FILE_TWICE_CONFIG = """\
 {
@@ -220,6 +305,26 @@
 """
 
 
+TEST_ARGS_FILE_TWICE_CONFIG_BUCKET = """\
+{
+  'public_artifact_builders': {},
+  'buckets': {
+    'chromium': {},
+    'fake_bucket': {
+      'fake_args_file_twice': 'args_file_twice',
+    },
+  },
+  'configs': {
+    'args_file_twice': ['args_file', 'args_file'],
+  },
+  'mixins': {
+    'args_file': {
+      'args_file': '//build/args/fake.gn',
+    },
+  },
+}
+"""
+
 TEST_DUP_CONFIG = """\
 {
   'masters': {
@@ -241,6 +346,27 @@
 }
 """
 
+TEST_DUP_CONFIG_BUCKET = """\
+{
+  'public_artifact_builders': {},
+  'buckets': {
+    'ci': {},
+    'fake_bucket': {
+      'fake_builder': 'some_config',
+      'other_builder': 'some_other_config',
+    },
+  },
+  'configs': {
+    'some_config': ['args_file'],
+    'some_other_config': ['args_file'],
+  },
+  'mixins': {
+    'args_file': {
+      'args_file': '//build/args/fake.gn',
+    },
+  },
+}
+"""
 
 TRYSERVER_CONFIG = """\
 {
@@ -264,7 +390,8 @@
 class UnitTest(unittest.TestCase):
   def fake_mbw(self, files=None, win32=False):
     mbw = FakeMBW(win32=win32)
-    mbw.files.setdefault(mbw.default_config, TEST_CONFIG)
+    mbw.files.setdefault(mbw.default_config_master, TEST_CONFIG)
+    mbw.files.setdefault(mbw.default_config_bucket, TEST_CONFIG_BUCKET)
     mbw.files.setdefault(
       mbw.ToAbsPath('//testing/buildbot/gn_isolate_map.pyl'),
       '''{
@@ -442,9 +569,18 @@
         ('import("//build/args/fake.gn")\n'
          'use_goma = true\n'))
 
+  def test_gen_args_file_twice_bucket(self):
+    mbw = self.fake_mbw()
+    mbw.files[mbw.default_config_bucket] = TEST_ARGS_FILE_TWICE_CONFIG_BUCKET
+    self.check([
+        'gen', '-u', 'fake_bucket', '-b', 'fake_args_file_twice', '//out/Debug'
+    ],
+               mbw=mbw,
+               ret=1)
+
   def test_gen_args_file_twice(self):
     mbw = self.fake_mbw()
-    mbw.files[mbw.default_config] = TEST_ARGS_FILE_TWICE_CONFIG
+    mbw.files[mbw.default_config_master] = TEST_ARGS_FILE_TWICE_CONFIG
     self.check(['gen', '-m', 'fake_master', '-b', 'fake_args_file_twice',
                 '//out/Debug'], mbw=mbw, ret=1)
 
@@ -793,18 +929,44 @@
                     'enable_doom_melon = true\n'
                     'use_goma = true\n'))
 
+  def test_recursive_lookup_bucket(self):
+    files = {
+        '/fake_src/build/args/fake.gn': ('enable_doom_melon = true\n'
+                                         'enable_antidoom_banana = true\n')
+    }
+    self.check(['lookup', '-u', 'ci', '-b', 'fake_args_file', '--recursive'],
+               files=files,
+               ret=0,
+               out=('enable_antidoom_banana = true\n'
+                    'enable_doom_melon = true\n'
+                    'use_goma = true\n'))
+
   def test_validate(self):
     mbw = self.fake_mbw()
     self.check(['validate'], mbw=mbw, ret=0)
 
   def test_bad_validate(self):
     mbw = self.fake_mbw()
-    mbw.files[mbw.default_config] = TEST_BAD_CONFIG
-    self.check(['validate'], mbw=mbw, ret=1)
+    mbw.files[mbw.default_config_master] = TEST_BAD_CONFIG
+    self.check(['validate', '-f', mbw.default_config_master], mbw=mbw, ret=1)
+
+  def test_bad_validate_bucket(self):
+    mbw = self.fake_mbw()
+    mbw.files[mbw.default_config_bucket] = TEST_BAD_CONFIG_BUCKET
+    self.check(['validate', '-f', mbw.default_config_bucket], mbw=mbw, ret=1)
 
   def test_duplicate_validate(self):
     mbw = self.fake_mbw()
-    mbw.files[mbw.default_config] = TEST_DUP_CONFIG
+    mbw.files[mbw.default_config_master] = TEST_DUP_CONFIG
+    self.check(['validate'], mbw=mbw, ret=1)
+    self.assertIn(
+        'Duplicate configs detected. When evaluated fully, the '
+        'following configs are all equivalent: \'some_config\', '
+        '\'some_other_config\'.', mbw.out)
+
+  def test_duplicate_validate_bucket(self):
+    mbw = self.fake_mbw()
+    mbw.files[mbw.default_config_bucket] = TEST_DUP_CONFIG_BUCKET
     self.check(['validate'], mbw=mbw, ret=1)
     self.assertIn('Duplicate configs detected. When evaluated fully, the '
                   'following configs are all equivalent: \'some_config\', '
diff --git a/tools/mb/mb_validation_unittest.py b/tools/mb/mb_validation_unittest.py
new file mode 100755
index 0000000..49a408d
--- /dev/null
+++ b/tools/mb/mb_validation_unittest.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# Copyright 2020 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.
+"""Tests for mb_validate.py."""
+
+from __future__ import print_function
+from __future__ import absolute_import
+
+import sys
+import ast
+import os
+import unittest
+
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.abspath(__file__)), '..'))
+
+from mb import mb
+from mb import mb_unittest
+from mb.lib import validation
+
+TEST_UNREFERENCED_MIXIN_CONFIG = """\
+{
+  'public_artifact_builders': {},
+  'configs': {
+    'rel_bot_1': ['rel'],
+    'rel_bot_2': ['rel'],
+  },
+  'buckets': {
+    'fake_bucket_a': {
+      'fake_builder_a': 'rel_bot_1',
+      'fake_builder_b': 'rel_bot_2',
+    },
+  },
+  'mixins': {
+    'unreferenced_mixin': {
+      'gn_args': 'proprietary_codecs=true',
+    },
+    'rel': {
+      'gn_args': 'is_debug=false',
+    },
+  },
+}
+"""
+
+TEST_UNKNOWNMIXIN_CONFIG = """\
+{
+  'public_artifact_builders': {},
+  'configs': {
+    'rel_bot_1': ['rel'],
+    'rel_bot_2': ['rel', 'unknown_mixin'],
+  },
+  'buckets': {
+    'fake_bucket_a': {
+      'fake_builder_a': 'rel_bot_1',
+      'fake_builder_b': 'rel_bot_2',
+    },
+  },
+  'mixins': {
+    'rel': {
+      'gn_args': 'is_debug=false',
+    },
+  },
+}
+"""
+
+TEST_UNKNOWN_NESTED_MIXIN_CONFIG = """\
+{
+  'public_artifact_builders': {},
+  'configs': {
+    'rel_bot_1': ['rel', 'nested_mixin'],
+    'rel_bot_2': ['rel'],
+  },
+  'buckets': {
+    'fake_bucket_a': {
+      'fake_builder_a': 'rel_bot_1',
+      'fake_builder_b': 'rel_bot_2',
+    },
+  },
+  'mixins': {
+    'nested_mixin': {
+      'mixins': {
+        'unknown_mixin': {
+          'gn_args': 'proprietary_codecs=true',
+        },
+      },
+    },
+    'rel': {
+      'gn_args': 'is_debug=false',
+    },
+  },
+}
+"""
+
+
+class UnitTest(unittest.TestCase):
+  def test_GetAllConfigsMaster(self):
+    configs = ast.literal_eval(mb_unittest.TEST_CONFIG)
+    all_configs = validation.GetAllConfigsMaster(configs['masters'])
+    self.assertEqual(all_configs['rel_bot'], 'fake_master')
+    self.assertEqual(all_configs['debug_goma'], 'fake_master')
+
+  def test_GetAllConfigsBucket(self):
+    configs = ast.literal_eval(mb_unittest.TEST_CONFIG_BUCKET)
+    all_configs = validation.GetAllConfigsBucket(configs['buckets'])
+    self.assertEqual(all_configs['rel_bot'], 'ci')
+    self.assertEqual(all_configs['debug_goma'], 'ci')
+
+  def test_CheckAllConfigsAndMixinsReferenced_ok(self):
+    configs = ast.literal_eval(mb_unittest.TEST_CONFIG_BUCKET)
+    errs = []
+    all_configs = validation.GetAllConfigsBucket(configs['buckets'])
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  config_configs, mixins)
+
+    self.assertEqual(errs, [])
+
+  def test_CheckAllConfigsAndMixinsReferenced_unreferenced(self):
+    configs = ast.literal_eval(TEST_UNREFERENCED_MIXIN_CONFIG)
+    errs = []
+    all_configs = validation.GetAllConfigsMaster(configs['buckets'])
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  config_configs, mixins)
+
+    self.assertIn('Unreferenced mixin "unreferenced_mixin".', errs)
+
+  def test_CheckAllConfigsAndMixinsReferenced_unknown(self):
+    configs = ast.literal_eval(TEST_UNKNOWNMIXIN_CONFIG)
+    errs = []
+    all_configs = validation.GetAllConfigsMaster(configs['buckets'])
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  config_configs, mixins)
+    self.assertIn(
+        'Unknown mixin "unknown_mixin" '
+        'referenced by config "rel_bot_2".', errs)
+
+  def test_CheckAllConfigsAndMixinsReferenced_unknown_nested(self):
+    configs = ast.literal_eval(TEST_UNKNOWN_NESTED_MIXIN_CONFIG)
+    errs = []
+    all_configs = validation.GetAllConfigsMaster(configs['buckets'])
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  config_configs, mixins)
+
+    self.assertIn(
+        'Unknown mixin "unknown_mixin" '
+        'referenced by mixin "nested_mixin".', errs)
+
+  def test_CheckAllConfigsAndMixinsReferenced_unused(self):
+    configs = ast.literal_eval(TEST_UNKNOWN_NESTED_MIXIN_CONFIG)
+    errs = []
+    all_configs = validation.GetAllConfigsMaster(configs['buckets'])
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+
+    validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
+                                                  config_configs, mixins)
+
+    self.assertIn(
+        'Unknown mixin "unknown_mixin" '
+        'referenced by mixin "nested_mixin".', errs)
+
+  def test_EnsureNoProprietaryMixinsBucket(self):
+    bad_configs = ast.literal_eval(mb_unittest.TEST_BAD_CONFIG_BUCKET)
+    errs = []
+    default_config = 'fake_config_file'
+    config_file = 'fake_config_file'
+    public_artifact_builders = bad_configs['public_artifact_builders']
+    buckets = bad_configs['buckets']
+    mixins = bad_configs['mixins']
+    config_configs = bad_configs['configs']
+
+    validation.EnsureNoProprietaryMixinsBucket(
+        errs, default_config, config_file, public_artifact_builders, buckets,
+        config_configs, mixins)
+
+    self.assertIn(
+        'Public artifact builder "fake_builder_a" '
+        'can not contain the "chrome_with_codecs" mixin.', errs)
+    self.assertIn(
+        'Public artifact builder "fake_builder_b" '
+        'can not contain the "chrome_with_codecs" mixin.', errs)
+    self.assertEqual(len(errs), 2)
+
+  def test_EnsureNoProprietaryMixinsMaster(self):
+    bad_configs = ast.literal_eval(mb_unittest.TEST_BAD_CONFIG)
+    errs = []
+    default_config = 'fake_config_file'
+    config_file = 'fake_config_file'
+    buckets = bad_configs['masters']
+    mixins = bad_configs['mixins']
+    config_configs = bad_configs['configs']
+
+    validation.EnsureNoProprietaryMixinsMaster(
+        errs, default_config, config_file, buckets, config_configs, mixins)
+
+    self.assertIn(
+        'Public artifact builder "a" '
+        'can not contain the "chrome_with_codecs" mixin.', errs)
+    self.assertIn(
+        'Public artifact builder "b" '
+        'can not contain the "chrome_with_codecs" mixin.', errs)
+    self.assertEqual(len(errs), 2)
+
+  def test_CheckDuplicateConfigs_ok(self):
+    configs = ast.literal_eval(mb_unittest.TEST_CONFIG_BUCKET)
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+    grouping = configs['buckets']
+    errs = []
+
+    validation.CheckDuplicateConfigs(errs, config_configs, mixins, grouping,
+                                     mb.FlattenConfig)
+    self.assertEqual(errs, [])
+
+  @unittest.skip('bla')
+  def test_CheckDuplicateConfigs_dups(self):
+    configs = ast.literal_eval(mb_unittest.TEST_DUP_CONFIG_BUCKET)
+    config_configs = configs['configs']
+    mixins = configs['mixins']
+    grouping = configs['buckets']
+    errs = []
+
+    validation.CheckDuplicateConfigs(errs, config_configs, mixins, grouping,
+                                     mb.FlattenConfig)
+    self.assertIn(
+        'Duplicate configs detected. When evaluated fully, the '
+        'following configs are all equivalent: \'some_config\', '
+        '\'some_other_config\'. Please consolidate these configs '
+        'into only one unique name per configuration value.', errs)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 8ec7e2a6..389c842 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -38222,6 +38222,8 @@
   <int value="345664265" label="BlinkHeapIncrementalMarking:disabled"/>
   <int value="346711293" label="enable-save-password-bubble"/>
   <int value="347981012" label="TabToGTSAnimation:disabled"/>
+  <int value="348449023"
+      label="DesktopPWAsLocalUpdatingThrottlePersistence:enabled"/>
   <int value="348854923" label="v8-cache-strategies-for-cache-storage"/>
   <int value="350399958" label="ModuleScriptsImportMetaUrl:disabled"/>
   <int value="350616049" label="IntentPicker:disabled"/>
@@ -38615,6 +38617,8 @@
   <int value="842432903" label="CaptureThumbnailOnNavigatingAway:enabled"/>
   <int value="842789526" label="CloudPrinterHandler:disabled"/>
   <int value="843896452" label="UserActivationV2:enabled"/>
+  <int value="844587371"
+      label="DesktopPWAsLocalUpdatingThrottlePersistence:disabled"/>
   <int value="846951416" label="CopylessPaste:enabled"/>
   <int value="848324390" label="enable-lock-screen-apps"/>
   <int value="849980462" label="RemoveNtpFakebox:disabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 50237db..7ea64f27 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -5718,7 +5718,8 @@
 </histogram>
 
 <histogram name="Apps.AppListFolderOpened" enum="AppListFolderOpened"
-    expires_after="M81">
+    expires_after="2020-01-31">
+  <owner>mmourgos@chromium.org</owner>
   <owner>newcomer@chromium.org</owner>
   <summary>
     The number of times folders are opened in the app list. This is logged when
@@ -6128,10 +6129,11 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.AppsInFolders" units="Apps" expires_after="M81">
+<histogram name="Apps.AppsInFolders" units="Apps" expires_after="2021-01-31">
 <!-- Name completed by histogram_suffixes
      name="AppListFolderExperiment" -->
 
+  <owner>mmourgos@chromium.org</owner>
   <owner>newcomer@chromium.org</owner>
   <summary>
     The total number of apps in folders ignoring OEM folders. This is logged
@@ -6141,11 +6143,12 @@
 </histogram>
 
 <histogram base="true" name="Apps.ContextMenuExecuteCommand"
-    enum="ChromeOSUICommands" expires_after="2020-06-28">
+    enum="ChromeOSUICommands" expires_after="2021-01-31">
 <!-- Name completed by histogram_suffixes
      name="ContextMenuFromApp" -->
 
   <owner>newcomer@chromium.org</owner>
+  <owner>mmourgos@chromium.org</owner>
   <summary>
     The number of times a certain command was executed by a context menu, split
     by whether the context menu came from an app, or another part of the system
@@ -6484,8 +6487,10 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.NumberOfFolders" units="folder(s)" expires_after="M81">
-  <owner>newcomer@google.com</owner>
+<histogram name="Apps.NumberOfFolders" units="folder(s)"
+    expires_after="2021-01-31">
+  <owner>mmourgos@chromium.org</owner>
+  <owner>newcomer@chromium.org</owner>
   <summary>
     The number of folders that users have in their Launcher. Includes the OEM
     folder. This metric is recorded every time the launcher is shown.
@@ -8501,9 +8506,9 @@
 </histogram>
 
 <histogram name="Ash.Shelf.Menu.NumItemsEnabledUponSelection" units="Count"
-    expires_after="2020-01-26">
-  <owner>bruthig@google.com</owner>
-  <owner>tdanderson@google.com</owner>
+    expires_after="2021-01-26">
+  <owner>anasalazar@google.com</owner>
+  <owner>mmourgos@google.com</owner>
   <summary>
     Tracks the number of menu items that are enabled in a shelf item's secondary
     menu. This metric is only recorded when a menu item is selected.
@@ -25428,6 +25433,10 @@
 
 <histogram name="ContentSettings.MixedScript"
     enum="ContentSettingMixedScriptAction" expires_after="M80">
+  <obsolete>
+    Deprecated 2020-01-06. Histogram had already expired and metrics are no
+    longer required after the mixed content shield removal.
+  </obsolete>
   <owner>estark@chromium.org</owner>
   <summary>
     Tracks whether the mixed content shield was shown, and how the user
@@ -152961,6 +152970,16 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.NigoriConfigurationWithInvalidatedCredentials"
+    enum="Boolean" expires_after="M82">
+  <owner>mmoskvitin@google.com</owner>
+  <owner>mastiz@chromium.org</owner>
+  <summary>
+    Recorded when Nigori-only configuration pending while user credentials were
+    invalidated.
+  </summary>
+</histogram>
+
 <histogram name="Sync.NigoriMigrationAttemptedBeforeNotMigrated" enum="Boolean"
     expires_after="2020-02-01">
   <owner>mmoskvitin@google.com</owner>
@@ -154218,6 +154237,9 @@
 </histogram>
 
 <histogram name="Sync.USSLoadModelsTime" units="ms" expires_after="M81">
+  <obsolete>
+    Deprecated in M82.
+  </obsolete>
   <owner>pavely@chromium.org</owner>
   <summary>Time it took sync to load models for USS datatypes.</summary>
 </histogram>
diff --git a/chrome/android/java/res/drawable-hdpi/ic_expand_less_black_24dp.png b/ui/android/java/res/drawable-hdpi/ic_expand_less_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/ic_expand_less_black_24dp.png
rename to ui/android/java/res/drawable-hdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-hdpi/ic_expand_more_black_24dp.png b/ui/android/java/res/drawable-hdpi/ic_expand_more_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/ic_expand_more_black_24dp.png
rename to ui/android/java/res/drawable-hdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/ic_expand_less_black_24dp.png b/ui/android/java/res/drawable-mdpi/ic_expand_less_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/ic_expand_less_black_24dp.png
rename to ui/android/java/res/drawable-mdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/ic_expand_more_black_24dp.png b/ui/android/java/res/drawable-mdpi/ic_expand_more_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/ic_expand_more_black_24dp.png
rename to ui/android/java/res/drawable-mdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml b/ui/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml
similarity index 100%
rename from chrome/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml
rename to ui/android/java/res/drawable-v21/transition_expand_less_expand_more_black_24dp.xml
diff --git a/chrome/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml b/ui/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml
similarity index 100%
rename from chrome/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml
rename to ui/android/java/res/drawable-v21/transition_expand_more_expand_less_black_24dp.xml
diff --git a/chrome/android/java/res/drawable-xhdpi/ic_expand_less_black_24dp.png b/ui/android/java/res/drawable-xhdpi/ic_expand_less_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/ic_expand_less_black_24dp.png
rename to ui/android/java/res/drawable-xhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/ic_expand_more_black_24dp.png b/ui/android/java/res/drawable-xhdpi/ic_expand_more_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/ic_expand_more_black_24dp.png
rename to ui/android/java/res/drawable-xhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/ic_expand_less_black_24dp.png b/ui/android/java/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
rename to ui/android/java/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/ic_expand_more_black_24dp.png b/ui/android/java/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
rename to ui/android/java/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png b/ui/android/java/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
rename to ui/android/java/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png b/ui/android/java/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
rename to ui/android/java/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
Binary files differ
diff --git a/ui/events/blink/BUILD.gn b/ui/events/blink/BUILD.gn
index e419867d..9a0ee69 100644
--- a/ui/events/blink/BUILD.gn
+++ b/ui/events/blink/BUILD.gn
@@ -13,9 +13,7 @@
     "blink_features.h",
   ]
 
-  deps = [
-    "//base",
-  ]
+  deps = [ "//base" ]
 }
 
 jumbo_source_set("blink") {
@@ -70,9 +68,7 @@
     "web_input_event_traits.h",
   ]
 
-  public_deps = [
-    ":blink_features",
-  ]
+  public_deps = [ ":blink_features" ]
 
   deps = [
     "//cc:cc",
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 9aded63..342c2e7 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -213,6 +213,8 @@
       "gl_image_egl.h",
       "gl_surface_egl.cc",
       "gl_surface_egl.h",
+      "shared_gl_fence_egl.cc",
+      "shared_gl_fence_egl.h",
     ]
 
     if (is_linux || use_ozone) {
@@ -410,9 +412,7 @@
       "$root_out_dir/libEGL.dylib",
       "$root_out_dir/libGLESv2.dylib",
     ]
-    outputs = [
-      "$root_out_dir/egl_intermediates/{{source_file_part}}",
-    ]
+    outputs = [ "$root_out_dir/egl_intermediates/{{source_file_part}}" ]
     deps = [
       "//third_party/angle:libEGL",
       "//third_party/angle:libGLESv2",
@@ -425,9 +425,7 @@
         "$root_out_dir/libswiftshader_libEGL.dylib",
         "$root_out_dir/libswiftshader_libGLESv2.dylib",
       ]
-      outputs = [
-        "$root_out_dir/egl_intermediates/{{source_file_part}}",
-      ]
+      outputs = [ "$root_out_dir/egl_intermediates/{{source_file_part}}" ]
       deps = [
         "//third_party/swiftshader/src/OpenGL/libEGL:swiftshader_libEGL",
         "//third_party/swiftshader/src/OpenGL/libGLESv2:swiftshader_libGLESv2",
@@ -504,17 +502,11 @@
 source_set("run_all_unittests") {
   testonly = true
 
-  sources = [
-    "test/run_all_unittests.cc",
-  ]
+  sources = [ "test/run_all_unittests.cc" ]
 
-  deps = [
-    "//base",
-  ]
+  deps = [ "//base" ]
 
-  public_deps = [
-    "//base/test:test_support",
-  ]
+  public_deps = [ "//base/test:test_support" ]
 
   if (use_ozone) {
     deps += [
@@ -591,9 +583,7 @@
     "//ui/platform_window:platform_impls",
   ]
 
-  data_deps = [
-    "//third_party/mesa_headers",
-  ]
+  data_deps = [ "//third_party/mesa_headers" ]
 
   if (use_x11) {
     sources += [ "gl_context_glx_unittest.cc" ]
@@ -605,9 +595,7 @@
 # We can't run this test on real Chrome OS hardware for Ozone, so new target.
 group("gl_unittests_ozone") {
   testonly = true
-  data_deps = [
-    ":gl_unittests",
-  ]
+  data_deps = [ ":gl_unittests" ]
 }
 
 if (is_android) {
@@ -621,8 +609,6 @@
       "../android/java/src/org/chromium/ui/gl/SurfaceTextureListener.java",
       "../android/java/src/org/chromium/ui/gl/SurfaceTexturePlatformWrapper.java",
     ]
-    public_deps = [
-      ":surface_jni_headers",
-    ]
+    public_deps = [ ":surface_jni_headers" ]
   }
 }
diff --git a/ui/gl/init/BUILD.gn b/ui/gl/init/BUILD.gn
index 5f5149e..921e567 100644
--- a/ui/gl/init/BUILD.gn
+++ b/ui/gl/init/BUILD.gn
@@ -30,9 +30,7 @@
     "//ui/gl:buildflags",
   ]
 
-  public_deps = [
-    "//ui/gl",
-  ]
+  public_deps = [ "//ui/gl" ]
 
   if (is_android) {
     sources += [
diff --git a/ui/gl/mojom/BUILD.gn b/ui/gl/mojom/BUILD.gn
index 648b511..564e3423 100644
--- a/ui/gl/mojom/BUILD.gn
+++ b/ui/gl/mojom/BUILD.gn
@@ -5,21 +5,13 @@
 import("//mojo/public/tools/bindings/mojom.gni")
 
 mojom("mojom") {
-  sources = [
-    "gpu_preference.mojom",
-  ]
+  sources = [ "gpu_preference.mojom" ]
 
-  public_deps = [
-    "//mojo/public/mojom/base",
-  ]
+  public_deps = [ "//mojo/public/mojom/base" ]
 }
 
 mojom("test_interfaces") {
-  sources = [
-    "traits_test_service.mojom",
-  ]
+  sources = [ "traits_test_service.mojom" ]
 
-  public_deps = [
-    ":mojom",
-  ]
+  public_deps = [ ":mojom" ]
 }
diff --git a/ui/gl/shared_gl_fence_egl.cc b/ui/gl/shared_gl_fence_egl.cc
new file mode 100644
index 0000000..5891c41
--- /dev/null
+++ b/ui/gl/shared_gl_fence_egl.cc
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gl/shared_gl_fence_egl.h"
+
+#include "base/logging.h"
+#include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_fence_egl.h"
+
+namespace gl {
+
+SharedGLFenceEGL::SharedGLFenceEGL() : egl_fence_(GLFenceEGL::Create()) {
+  // GLFenceEGL::Create() is not supposed to fail.
+  DCHECK(egl_fence_);
+}
+
+SharedGLFenceEGL::~SharedGLFenceEGL() = default;
+
+void SharedGLFenceEGL::ServerWait() {
+  base::AutoLock lock(lock_);
+
+#if DCHECK_IS_ON()
+  if (!gl_api_) {
+    gl_api_ = gl::g_current_gl_context;
+  } else if (gl_api_ != gl::g_current_gl_context) {
+    LOG(FATAL) << "This object should be shared among consumers on the same GL "
+                  "context";
+  }
+#endif
+
+  // If there is a fence, we do a wait on it. Once it has been waited upon, we
+  // clear the fence and all future call to this method will be a no-op since we
+  // do not need to wait on that same fence any more.
+  if (egl_fence_) {
+    egl_fence_->ServerWait();
+    egl_fence_.reset();
+  }
+}
+
+}  // namespace gl
diff --git a/ui/gl/shared_gl_fence_egl.h b/ui/gl/shared_gl_fence_egl.h
new file mode 100644
index 0000000..9acdd3ce
--- /dev/null
+++ b/ui/gl/shared_gl_fence_egl.h
@@ -0,0 +1,53 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GL_SHARED_GL_FENCE_EGL_H_
+#define UI_GL_SHARED_GL_FENCE_EGL_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "ui/gl/gl_export.h"
+
+namespace gl {
+
+class GLFenceEGL;
+class GLApi;
+
+// This class is an optimized way to share an egl fence among multiple
+// consumers. Once the shared |egl_fence_| has been waited upon by any of the
+// consumer, all the future waits to the same fence becomes no-op since we don't
+// need to wait again on the same fence any more. This saves un-neccesary gl
+// calls issued to do wait by each consumer.
+// This object should only be shared among consumers of the same GL context
+// which is true for Webview case.
+// TODO(vikassoni): Add logic to handle consumers from different GL context.
+class GL_EXPORT SharedGLFenceEGL
+    : public base::RefCountedThreadSafe<SharedGLFenceEGL> {
+ public:
+  SharedGLFenceEGL();
+
+  // Issues a ServerWait on the |egl_fence_|.
+  void ServerWait();
+
+ protected:
+  virtual ~SharedGLFenceEGL();
+
+ private:
+  friend class base::RefCountedThreadSafe<SharedGLFenceEGL>;
+
+  std::unique_ptr<GLFenceEGL> egl_fence_ GUARDED_BY(lock_);
+
+  // A lock that guard against multiple threads trying to access |egl_fence_|.
+  base::Lock lock_;
+
+  // GLApi on which all the consumers for this object should be on.
+  gl::GLApi* gl_api_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedGLFenceEGL);
+};
+
+}  // namespace gl
+
+#endif  // UI_GL_SHARED_GL_FENCE_EGL_H_
diff --git a/ui/message_center/OWNERS b/ui/message_center/OWNERS
index c03aa04d..8138435d 100644
--- a/ui/message_center/OWNERS
+++ b/ui/message_center/OWNERS
@@ -1,7 +1,6 @@
 dewittj@chromium.org
 estade@chromium.org
 peter@chromium.org
-stevenjb@chromium.org
 yoshiki@chromium.org
 tengs@chromium.org
 
diff --git a/ui/platform_window/x11/x11_window.cc b/ui/platform_window/x11/x11_window.cc
index 3bfbbdc..10220f6 100644
--- a/ui/platform_window/x11/x11_window.cc
+++ b/ui/platform_window/x11/x11_window.cc
@@ -248,13 +248,14 @@
   // about state changes asynchronously, which leads to a wrong return value in
   // DesktopWindowTreeHostPlatform::IsFullscreen, for example, and media
   // files can never be set to fullscreen. Wayland does the same.
+  auto new_state = PlatformWindowState::kNormal;
   if (fullscreen)
-    state_ = PlatformWindowState::kFullScreen;
+    new_state = PlatformWindowState::kFullScreen;
   else if (IsMaximized())
-    state_ = PlatformWindowState::kMaximized;
-  else
-    state_ = PlatformWindowState::kNormal;
+    new_state = PlatformWindowState::kMaximized;
 
+  bool was_fullscreen = IsFullscreen();
+  state_ = new_state;
   SetFullscreen(fullscreen);
 
   if (unmaximize_and_remaximize)
@@ -273,7 +274,15 @@
     SetRestoredBoundsInPixels(bounds_in_pixels);
     bounds_in_pixels = display.bounds();
   } else {
-    bounds_in_pixels = GetRestoredBoundsInPixels();
+    // Exiting "browser fullscreen mode", but the X11 window is not necessarily
+    // in fullscreen state (e.g: a WM keybinding might have been used to toggle
+    // fullscreen state). So check whether the window is in fullscreen state
+    // before trying to restore its bounds (saved before entering in browser
+    // fullscreen mode).
+    if (was_fullscreen)
+      bounds_in_pixels = GetRestoredBoundsInPixels();
+    else
+      SetRestoredBoundsInPixels({});
   }
   // Do not go through SetBounds as long as it adjusts bounds and sets them to X
   // Server. Instead, we just store the bounds and notify the client that the
@@ -525,21 +534,43 @@
 }
 
 void X11Window::OnXWindowStateChanged() {
-  // Propagate the window state information to the client. Note that the order
-  // of checks is important here, because window can have several properties
-  // at the same time.
-  PlatformWindowState old_state = state_;
-  if (IsMinimized()) {
-    state_ = PlatformWindowState::kMinimized;
-  } else if (IsFullscreen()) {
-    state_ = PlatformWindowState::kFullScreen;
-  } else if (IsMaximized()) {
-    state_ = PlatformWindowState::kMaximized;
-  } else {
-    state_ = PlatformWindowState::kNormal;
-  }
+  // Determine the new window state information to be propagated to the client.
+  // Note that the order of checks is important here, because window can have
+  // several properties at the same time.
+  auto new_state = PlatformWindowState::kNormal;
+  if (IsMinimized())
+    new_state = PlatformWindowState::kMinimized;
+  else if (IsFullscreen())
+    new_state = PlatformWindowState::kFullScreen;
+  else if (IsMaximized())
+    new_state = PlatformWindowState::kMaximized;
 
-  if (restored_bounds_in_pixels_.IsEmpty()) {
+  // fullscreen state is set syschronously at ToggleFullscreen() and must be
+  // kept and propagated to the client only when explicitly requested by upper
+  // layers, as it means we are in "browser fullscreen mode" (where
+  // decorations, omnibar, buttons, etc are hidden), which is different from
+  // the case where the request comes from the window manager (or any other
+  // process), handled by this method. In this case, we follow EWMH guidelines:
+  // Optimize the whole application for fullscreen usage. Window decorations
+  // (e.g. borders) should be hidden, but the functionalily of the application
+  // should not change. Further details:
+  // https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html
+  bool browser_fullscreen_mode = state_ == PlatformWindowState::kFullScreen;
+  bool window_fullscreen_mode = new_state == PlatformWindowState::kFullScreen;
+  // So, we ignore fullscreen state transitions in 2 cases:
+  // 1. If |new_state| is kFullScreen but |state_| is not, which means the
+  // fullscreen request is coming from an external process. So the browser
+  // window must occupies the entire screen but not transitioning to browser
+  // fullscreen mode.
+  // 2. if |state_| is kFullScreen but |new_state| is not, we have been
+  // requested to exit fullscreen by other process (e.g: via WM keybinding),
+  // in this case we must keep on "browser fullscreen mode" bug the platform
+  // window gets back to its previous state (e.g: unmaximized, tiled in TWMs,
+  // etc).
+  if (window_fullscreen_mode != browser_fullscreen_mode)
+    return;
+
+  if (GetRestoredBoundsInPixels().IsEmpty()) {
     if (IsMaximized()) {
       // The request that we become maximized originated from a different
       // process. |bounds_in_pixels_| already contains our maximized bounds. Do
@@ -554,8 +585,10 @@
     SetRestoredBoundsInPixels(gfx::Rect());
   }
 
-  if (old_state != state_)
+  if (new_state != state_) {
+    state_ = new_state;
     platform_window_delegate_->OnWindowStateChanged(state_);
+  }
 }
 
 void X11Window::OnXWindowDamageEvent(const gfx::Rect& damage_rect) {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc
index 1c43e58..8195a8015 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc
@@ -320,6 +320,8 @@
   if (!ui::WmSupportsHint(gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")))
     return;
 
+  Display* display = gfx::GetXDisplay();
+
   std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
   auto* non_client_view = static_cast<ShapedNonClientFrameView*>(
       widget->non_client_view()->frame_view());
@@ -343,10 +345,8 @@
   EXPECT_FALSE(non_client_view->GetAndResetLayoutRequest());
 
   // Emulate the window manager exiting fullscreen via a window manager
-  // accelerator key. It should affect the widget's fullscreen state.
+  // accelerator key.
   {
-    Display* display = gfx::GetXDisplay();
-
     XEvent xclient;
     memset(&xclient, 0, sizeof(xclient));
     xclient.type = ClientMessage;
@@ -364,6 +364,30 @@
     WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
     waiter.Wait();
   }
+  // Ensure it continues in browser fullscreen mode and bounds are restored to
+  // |initial_bounds|.
+  EXPECT_TRUE(widget->IsFullscreen());
+  EXPECT_EQ(initial_bounds.ToString(),
+            widget->GetWindowBoundsInScreen().ToString());
+
+  // Emulate window resize (through X11 configure events) while in browser
+  // fullscreen mode and ensure bounds are tracked correctly.
+  initial_bounds.set_size({400, 400});
+  {
+    XWindowChanges changes = {0};
+    changes.width = initial_bounds.width();
+    changes.height = initial_bounds.height();
+    XConfigureWindow(display, xid, CWHeight | CWWidth, &changes);
+    // Ensure that the task which is posted when a window is resized is run.
+    base::RunLoop().RunUntilIdle();
+  }
+  EXPECT_TRUE(widget->IsFullscreen());
+  EXPECT_EQ(initial_bounds.ToString(),
+            widget->GetWindowBoundsInScreen().ToString());
+
+  // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
+  // state and clean things up.
+  widget->SetFullscreen(false);
   EXPECT_FALSE(widget->IsFullscreen());
   EXPECT_EQ(initial_bounds.ToString(),
             widget->GetWindowBoundsInScreen().ToString());
diff --git a/ui/webui/PLATFORM_OWNERS b/ui/webui/PLATFORM_OWNERS
index b60b4c4..72b9e1c 100644
--- a/ui/webui/PLATFORM_OWNERS
+++ b/ui/webui/PLATFORM_OWNERS
@@ -7,6 +7,5 @@
 hcarmona@chromium.org
 michaelpg@chromium.org
 rbpotter@chromium.org
-stevenjb@chromium.org
 tommycli@chromium.org
 xiyuan@chromium.org
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js b/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js
index 10ed18a..3684444 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_entry.js
@@ -24,7 +24,7 @@
    * @private
    */
   isLast_(index) {
-    return index == this.model.subnodes.length - 1;
+    return index === this.model.subnodes.length - 1;
   },
 
   getPolicyIndicatorType_() {
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_list.js b/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
index eab9717e..5a5b6a5 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
@@ -52,7 +52,7 @@
    * @private
    */
   getDescription_() {
-    if (this.certificates.length == 0) {
+    if (this.certificates.length === 0) {
       return this.i18n('certificateManagerNoCertificates');
     }
 
@@ -75,7 +75,7 @@
    * @private
    */
   canImport_() {
-    return !this.isKiosk_ && this.certificateType != CertificateType.OTHER &&
+    return !this.isKiosk_ && this.certificateType !== CertificateType.OTHER &&
         this.importAllowed;
   },
 
@@ -85,8 +85,8 @@
    * @private
    */
   canImportAndBind_() {
-    return !this.isGuest_ && this.certificateType == CertificateType.PERSONAL &&
-        this.importAllowed;
+    return !this.isGuest_ &&
+        this.certificateType === CertificateType.PERSONAL && this.importAllowed;
   },
   // </if>
 
@@ -151,18 +151,18 @@
   handleImport_(useHardwareBacked, anchor) {
     const browserProxy =
         certificate_manager.CertificatesBrowserProxyImpl.getInstance();
-    if (this.certificateType == CertificateType.PERSONAL) {
+    if (this.certificateType === CertificateType.PERSONAL) {
       browserProxy.importPersonalCertificate(useHardwareBacked)
           .then(showPasswordPrompt => {
             if (showPasswordPrompt) {
               this.dispatchImportActionEvent_(null, anchor);
             }
           }, this.onRejected_.bind(this, anchor));
-    } else if (this.certificateType == CertificateType.CA) {
+    } else if (this.certificateType === CertificateType.CA) {
       browserProxy.importCaCertificate().then(certificateName => {
         this.dispatchImportActionEvent_({name: certificateName}, anchor);
       }, this.onRejected_.bind(this, anchor));
-    } else if (this.certificateType == CertificateType.SERVER) {
+    } else if (this.certificateType === CertificateType.SERVER) {
       browserProxy.importServerCertificate().catch(
           this.onRejected_.bind(this, anchor));
     } else {
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js
index baeecb2..f5c627d0 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.js
@@ -162,7 +162,7 @@
    * @private
    */
   isTabSelected_(selectedIndex, tabIndex) {
-    return selectedIndex == tabIndex;
+    return selectedIndex === tabIndex;
   },
 
   /** @override */
@@ -171,26 +171,26 @@
       this.dialogModel_ = event.detail.subnode;
       this.dialogModelCertificateType_ = event.detail.certificateType;
 
-      if (event.detail.action == CertificateAction.IMPORT) {
-        if (event.detail.certificateType == CertificateType.PERSONAL) {
+      if (event.detail.action === CertificateAction.IMPORT) {
+        if (event.detail.certificateType === CertificateType.PERSONAL) {
           this.openDialog_(
               'certificate-password-decryption-dialog',
               'showPasswordDecryptionDialog_', event.detail.anchor);
-        } else if (event.detail.certificateType == CertificateType.CA) {
+        } else if (event.detail.certificateType === CertificateType.CA) {
           this.openDialog_(
               'ca-trust-edit-dialog', 'showCaTrustEditDialog_',
               event.detail.anchor);
         }
       } else {
-        if (event.detail.action == CertificateAction.EDIT) {
+        if (event.detail.action === CertificateAction.EDIT) {
           this.openDialog_(
               'ca-trust-edit-dialog', 'showCaTrustEditDialog_',
               event.detail.anchor);
-        } else if (event.detail.action == CertificateAction.DELETE) {
+        } else if (event.detail.action === CertificateAction.DELETE) {
           this.openDialog_(
               'certificate-delete-confirmation-dialog',
               'showDeleteConfirmationDialog_', event.detail.anchor);
-        } else if (event.detail.action == CertificateAction.EXPORT_PERSONAL) {
+        } else if (event.detail.action === CertificateAction.EXPORT_PERSONAL) {
           this.openDialog_(
               'certificate-password-encryption-dialog',
               'showPasswordEncryptionDialog_', event.detail.anchor);
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js b/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js
index cdb6c53..18800e4 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_password_encryption_dialog.js
@@ -63,7 +63,7 @@
   /** @private */
   validate_() {
     const isValid =
-        this.password_ != '' && this.password_ == this.confirmPassword_;
+        this.password_ !== '' && this.password_ === this.confirmPassword_;
     this.$.ok.disabled = !isValid;
   },
 });
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js b/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js
index 3d4ecf48..29bffc5 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_subentry.js
@@ -96,7 +96,7 @@
    */
   onExportTap_(event) {
     this.closePopupMenu_();
-    if (this.certificateType == CertificateType.PERSONAL) {
+    if (this.certificateType === CertificateType.PERSONAL) {
       this.browserProxy_.exportPersonalCertificate(this.model.id).then(() => {
         this.dispatchCertificateActionEvent_(CertificateAction.EXPORT_PERSONAL);
       }, this.onRejected_.bind(this));
@@ -121,7 +121,7 @@
    * @private
    */
   canExport_(certificateType, model) {
-    if (certificateType == CertificateType.PERSONAL) {
+    if (certificateType === CertificateType.PERSONAL) {
       return model.extractable;
     }
     return true;
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js b/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
index a5c0a9a9..1cb7cf5 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
@@ -263,10 +263,10 @@
    */
   onBluetoothPrivateOnPairing_(event) {
     if (!this.pairingDevice ||
-        event.device.address != this.pairingDevice.address) {
+        event.device.address !== this.pairingDevice.address) {
       return;
     }
-    if (event.pairing == PairingEventType.KEYS_ENTERED &&
+    if (event.pairing === PairingEventType.KEYS_ENTERED &&
         event.passkey === undefined && this.pairingEvent_) {
       // 'keysEntered' event might not include the updated passkey so preserve
       // the current one.
@@ -282,7 +282,7 @@
    * @private
    */
   onBluetoothDeviceChanged_(device) {
-    if (!this.pairingDevice || device.address != this.pairingDevice.address) {
+    if (!this.pairingDevice || device.address !== this.pairingDevice.address) {
       return;
     }
     this.pairingDevice = device;
@@ -344,7 +344,7 @@
    */
   showEnterPincode_() {
     return !!this.pairingEvent_ &&
-        this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE;
+        this.pairingEvent_.pairing === PairingEventType.REQUEST_PINCODE;
   },
 
   /**
@@ -353,7 +353,7 @@
    */
   showEnterPasskey_() {
     return !!this.pairingEvent_ &&
-        this.pairingEvent_.pairing == PairingEventType.REQUEST_PASSKEY;
+        this.pairingEvent_.pairing === PairingEventType.REQUEST_PASSKEY;
   },
 
   /**
@@ -366,10 +366,10 @@
     }
     const pairing = this.pairingEvent_.pairing;
     return (
-        pairing == PairingEventType.DISPLAY_PINCODE ||
-        pairing == PairingEventType.DISPLAY_PASSKEY ||
-        pairing == PairingEventType.CONFIRM_PASSKEY ||
-        pairing == PairingEventType.KEYS_ENTERED);
+        pairing === PairingEventType.DISPLAY_PINCODE ||
+        pairing === PairingEventType.DISPLAY_PASSKEY ||
+        pairing === PairingEventType.CONFIRM_PASSKEY ||
+        pairing === PairingEventType.KEYS_ENTERED);
   },
 
   /**
@@ -378,7 +378,7 @@
    */
   showAcceptReject_() {
     return !!this.pairingEvent_ &&
-        this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY;
+        this.pairingEvent_.pairing === PairingEventType.CONFIRM_PASSKEY;
   },
 
   /**
@@ -390,8 +390,8 @@
       return false;
     }
     const pairing = this.pairingEvent_.pairing;
-    return pairing == PairingEventType.REQUEST_PINCODE ||
-        pairing == PairingEventType.REQUEST_PASSKEY;
+    return pairing === PairingEventType.REQUEST_PINCODE ||
+        pairing === PairingEventType.REQUEST_PASSKEY;
   },
 
   /**
@@ -403,7 +403,7 @@
       return false;
     }
     const inputId =
-        (this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE) ?
+        (this.pairingEvent_.pairing === PairingEventType.REQUEST_PINCODE) ?
         '#pincode' :
         '#passkey';
     const crInput = /** @type {!CrInputElement} */ (this.$$(inputId));
@@ -419,7 +419,7 @@
   showDismiss_() {
     return (!!this.pairingDevice && this.pairingDevice.paired) ||
         (!!this.pairingEvent_ &&
-         this.pairingEvent_.pairing == PairingEventType.COMPLETE);
+         this.pairingEvent_.pairing === PairingEventType.COMPLETE);
   },
 
   /** @private */
@@ -448,11 +448,11 @@
     const options =
         /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ (
             {device: this.pairingDevice, response: response});
-    if (response == chrome.bluetoothPrivate.PairingResponse.CONFIRM) {
+    if (response === chrome.bluetoothPrivate.PairingResponse.CONFIRM) {
       const pairing = this.pairingEvent_.pairing;
-      if (pairing == PairingEventType.REQUEST_PINCODE) {
+      if (pairing === PairingEventType.REQUEST_PINCODE) {
         options.pincode = this.$$('#pincode').value;
-      } else if (pairing == PairingEventType.REQUEST_PASSKEY) {
+      } else if (pairing === PairingEventType.REQUEST_PASSKEY) {
         options.passkey = parseInt(this.$$('#passkey').value, 10);
       }
     }
@@ -477,8 +477,8 @@
    */
   getEventDesc_(eventType) {
     assert(eventType);
-    if (eventType == PairingEventType.COMPLETE ||
-        eventType == PairingEventType.REQUEST_AUTHORIZATION) {
+    if (eventType === PairingEventType.COMPLETE ||
+        eventType === PairingEventType.REQUEST_AUTHORIZATION) {
       return 'bluetoothStartConnecting';
     }
     return 'bluetooth_' + /** @type {string} */ (eventType);
@@ -495,15 +495,15 @@
     }
     let digit = '0';
     const pairing = this.pairingEvent_.pairing;
-    if (pairing == PairingEventType.DISPLAY_PINCODE &&
+    if (pairing === PairingEventType.DISPLAY_PINCODE &&
         this.pairingEvent_.pincode &&
         index < this.pairingEvent_.pincode.length) {
       digit = this.pairingEvent_.pincode[index];
     } else if (
         this.pairingEvent_.passkey &&
-        (pairing == PairingEventType.DISPLAY_PASSKEY ||
-         pairing == PairingEventType.KEYS_ENTERED ||
-         pairing == PairingEventType.CONFIRM_PASSKEY)) {
+        (pairing === PairingEventType.DISPLAY_PASSKEY ||
+         pairing === PairingEventType.KEYS_ENTERED ||
+         pairing === PairingEventType.CONFIRM_PASSKEY)) {
       const passkeyString =
           String(this.pairingEvent_.passkey).padStart(this.digits_.length, '0');
       digit = passkeyString[index];
@@ -520,22 +520,23 @@
     if (!this.pairingEvent_) {
       return '';
     }
-    if (this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY) {
+    if (this.pairingEvent_.pairing === PairingEventType.CONFIRM_PASSKEY) {
       return 'confirm';
     }
     let cssClass = 'display';
-    if (this.pairingEvent_.pairing == PairingEventType.DISPLAY_PASSKEY) {
-      if (index == 0) {
+    if (this.pairingEvent_.pairing === PairingEventType.DISPLAY_PASSKEY) {
+      if (index === 0) {
         cssClass += ' next';
       } else {
         cssClass += ' untyped';
       }
     } else if (
-        this.pairingEvent_.pairing == PairingEventType.KEYS_ENTERED &&
+        this.pairingEvent_.pairing === PairingEventType.KEYS_ENTERED &&
         this.pairingEvent_.enteredKey) {
       const enteredKey = this.pairingEvent_.enteredKey;  // 1-7
       const lastKey = this.digits_.length;               // 6
-      if ((index == -1 && enteredKey > lastKey) || (index + 1 == enteredKey)) {
+      if ((index === -1 && enteredKey > lastKey) ||
+          (index + 1 === enteredKey)) {
         cssClass += ' next';
       } else if (index > enteredKey) {
         cssClass += ' untyped';
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/provisioning_page.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/provisioning_page.js
index 56ba629b..d8ad4937 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/provisioning_page.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/provisioning_page.js
@@ -187,7 +187,7 @@
 
     // The <webview> requested information about this device. Reply by posting a
     // message back to it.
-    if (messageType == 'requestDeviceInfoMsg') {
+    if (messageType === 'requestDeviceInfoMsg') {
       this.getPortalWebview().contentWindow.postMessage(
           {
             carrier: this.cellularMetadata.carrier,
@@ -200,8 +200,8 @@
     }
 
     // The <webview> provided an update on the status of the activation attempt.
-    if (messageType == 'reportTransactionStatusMsg') {
-      const success = status == 'ok';
+    if (messageType === 'reportTransactionStatusMsg') {
+      const success = status === 'ok';
       this.fire('on-carrier-portal-result', success);
       return;
     }
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/webview_post_util.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/webview_post_util.js
index 4f5b582..088b8264 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/webview_post_util.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/webview_post_util.js
@@ -37,9 +37,9 @@
       '  var pairs = postData.split(\'&\');' +
       '  pairs.forEach(pairStr => {' +
       '    var pair = pairStr.split(\'=\');' +
-      '    if (pair.length == 2)' +
+      '    if (pair.length === 2)' +
       '      addInputElement(form, pair[0], pair[1]);' +
-      '    else if (pair.length == 1)' +
+      '    else if (pair.length === 1)' +
       '      addInputElement(form, pair[0], true);' +
       '  });' +
       '}' +
@@ -76,7 +76,7 @@
    */
   function initializeWebviewRedirectForm(
       webview, paymentUrl, postData, webviewSrc, commitEvent) {
-    if (!commitEvent.isTopLevel || commitEvent.url != webviewSrc) {
+    if (!commitEvent.isTopLevel || commitEvent.url !== webviewSrc) {
       return;
     }
 
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js
index 5c61904..a259fdb 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js
@@ -175,7 +175,7 @@
       this.mojoInterfaceProvider_.getMojoServiceRemote()
           .getEligibleActiveHostDevices()
           .then((responseParams) => {
-            if (responseParams.eligibleHostDevices.length == 0) {
+            if (responseParams.eligibleHostDevices.length === 0) {
               console.warn('Potential host list is empty.');
               return;
             }
@@ -214,7 +214,7 @@
     /** @private */
     onBackwardNavigationRequested_() {
       // The back button is only visible on the password page.
-      assert(this.visiblePageName == PageName.PASSWORD);
+      assert(this.visiblePageName === PageName.PASSWORD);
 
       this.$$('password-page').clearPasswordTextInput();
       this.visiblePageName = PageName.START;
@@ -259,7 +259,7 @@
     /** @private */
     setHostDevice_() {
       // An authentication token must be set if a password is required.
-      assert(this.delegate.isPasswordRequiredToSetHost() == !!this.authToken_);
+      assert(this.delegate.isPasswordRequiredToSetHost() === !!this.authToken_);
 
       const deviceId = /** @type {string} */ (this.selectedDeviceId_);
       this.delegate.setHostDevice(deviceId, this.authToken_)
@@ -305,7 +305,7 @@
      * @private
      */
     shouldForwardButtonBeDisabled_() {
-      return (this.visiblePageName == PageName.PASSWORD) &&
+      return (this.visiblePageName === PageName.PASSWORD) &&
           this.passwordPageForwardButtonDisabled_;
     },
 
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js
index 298c1fd..bce2ad0e9 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.js
@@ -148,7 +148,7 @@
    */
   onInputKeypress_(e) {
     // We are only listening for the user trying to enter their password.
-    if (e.key != 'Enter') {
+    if (e.key !== 'Enter') {
       return;
     }
 
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js
index 85b0e9a..ebc520f 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.js
@@ -36,7 +36,7 @@
 
   /** @private */
   getMessageHtml_() {
-    const validNodeFn = (node, value) => node.tagName == 'A';
+    const validNodeFn = (node, value) => node.tagName === 'A';
     return this.i18nAdvanced(
         'setupSucceededPageMessage',
         {attrs: {'id': validNodeFn, 'href': validNodeFn}});
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js
index cec01dc..69b1f03 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/start_setup_page.js
@@ -116,7 +116,7 @@
    * @private
    */
   doesDeviceListHaveOneElement_(devices) {
-    return devices.length == 1;
+    return devices.length === 1;
   },
 
   /**
@@ -135,7 +135,7 @@
    * @private
    */
   getDeviceOptionClass_(connectivityStatus) {
-    return connectivityStatus ==
+    return connectivityStatus ===
             chromeos.deviceSync.mojom.ConnectivityStatus.kOffline ?
         'offline-device-name' :
         '';
@@ -147,7 +147,7 @@
    * @private
    */
   getDeviceNameWithConnectivityStatus_(device) {
-    return device.connectivityStatus ==
+    return device.connectivityStatus ===
             chromeos.deviceSync.mojom.ConnectivityStatus.kOffline ?
         this.i18n(
             'startSetupPageOfflineDeviceOption',
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js b/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js
index 07d4835..c168c49 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js
@@ -127,7 +127,7 @@
 
     // Test whether |activeApn| is in the current APN list in managedProperties.
     const activeApnInList = activeApn && apnList.some(function(a) {
-      return a.accessPointName == activeApn.accessPointName;
+      return a.accessPointName === activeApn.accessPointName;
     });
 
     // If |activeApn| is specified and not in the list, use the active
@@ -199,7 +199,7 @@
     // non-default value has been set for Other.
     if (this.isOtherSelected_(accessPointName) &&
         (!this.otherApn_ || !this.otherApn_.accessPointName ||
-         this.otherApn_.accessPointName == kDefaultAccessPointName)) {
+         this.otherApn_.accessPointName === kDefaultAccessPointName)) {
       this.selectedApn_ = accessPointName;
       return;
     }
@@ -214,7 +214,7 @@
   onOtherApnChange_(event) {
     // TODO(benchan/stevenjb): Move the toUpperCase logic to shill or
     // onc_translator_onc_to_shill.cc.
-    const value = (event.detail.field == 'accessPointName') ?
+    const value = (event.detail.field === 'accessPointName') ?
         event.detail.value.toUpperCase() :
         event.detail.value;
     this.set('otherApn_.' + event.detail.field, value);
@@ -238,7 +238,7 @@
   sendApnChange_(accessPointName) {
     const apnList = this.getApnList_();
     let apn = this.findApnInList_(apnList, accessPointName);
-    if (apn == undefined) {
+    if (apn === undefined) {
       apn = this.createApnObject_();
       if (this.otherApn_) {
         apn.accessPointName = this.otherApn_.accessPointName;
@@ -260,7 +260,7 @@
     }
     const apnList = this.getApnList_();
     const apn = this.findApnInList_(apnList, accessPointName);
-    return apn == undefined;
+    return apn === undefined;
   },
 
   /**
@@ -281,7 +281,7 @@
    */
   findApnInList_(apnList, accessPointName) {
     return apnList.find(function(a) {
-      return a.accessPointName == accessPointName;
+      return a.accessPointName === accessPointName;
     });
   },
 
@@ -292,7 +292,7 @@
    * @private
    */
   isApnItemSelected_(item) {
-    return item.accessPointName == this.selectedApn_;
+    return item.accessPointName === this.selectedApn_;
   }
 });
 })();
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_choose_mobile.js b/ui/webui/resources/cr_components/chromeos/network/network_choose_mobile.js
index a2d52c6..325a4085 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_choose_mobile.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_choose_mobile.js
@@ -88,7 +88,7 @@
     // Set selectedMobileNetworkId_ after the dom-repeat has been stamped.
     this.async(() => {
       let selected = this.mobileNetworkList_.find(function(mobileNetwork) {
-        return mobileNetwork.status == 'current';
+        return mobileNetwork.status === 'current';
       });
       if (!selected) {
         selected = this.mobileNetworkList_[0];
@@ -103,8 +103,8 @@
    * @private
    */
   getMobileNetworkIsDisabled_(foundNetwork) {
-    return foundNetwork.status != 'available' &&
-        foundNetwork.status != 'current';
+    return foundNetwork.status !== 'available' &&
+        foundNetwork.status !== 'current';
   },
 
   /**
@@ -113,7 +113,7 @@
    * @private
    */
   getEnableScanButton_(properties) {
-    return properties.connectionState ==
+    return properties.connectionState ===
         chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected &&
         !!this.deviceState && !this.deviceState.scanning;
   },
@@ -126,7 +126,7 @@
   getEnableSelectNetwork_(properties) {
     return (
         !!this.deviceState && !this.deviceState.scanning &&
-        properties.connectionState ==
+        properties.connectionState ===
             chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected &&
         !!properties.typeProperties.cellular.foundNetworks &&
         properties.typeProperties.cellular.foundNetworks.length > 0);
@@ -147,7 +147,7 @@
     if (this.scanRequested_) {
       return this.i18n('networkCellularScanCompleted');
     }
-    if (properties.connectionState !=
+    if (properties.connectionState !==
         chromeos.networkConfig.mojom.ConnectionStateType.kNotConnected) {
       return this.i18n('networkCellularScanConnectedHelp');
     }
@@ -183,7 +183,7 @@
    */
   onChange_(event) {
     const target = /** @type {!HTMLSelectElement} */ (event.target);
-    if (!target.value || target.value == 'none') {
+    if (!target.value || target.value === 'none') {
       return;
     }
 
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config.js b/ui/webui/resources/cr_components/chromeos/network/network_config.js
index 7004cb7..c2f89a60 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config.js
@@ -373,7 +373,7 @@
       const managedProperties =
           OncMojo.getDefaultManagedProperties(mojoType, this.guid, this.name);
       // Allow securityType_ to be set externally (e.g. in tests).
-      if (mojoType == mojom.NetworkType.kWiFi &&
+      if (mojoType === mojom.NetworkType.kWiFi &&
           this.securityType_ !== undefined) {
         managedProperties.typeProperties.wifi.security = this.securityType_;
       }
@@ -384,7 +384,7 @@
       });
     }
 
-    if (this.mojoType_ == mojom.NetworkType.kVPN ||
+    if (this.mojoType_ === mojom.NetworkType.kVPN ||
         (this.globalPolicy_ &&
          this.globalPolicy_.allowOnlyPolicyNetworksToConnect)) {
       this.autoConnect_ = false;
@@ -425,7 +425,7 @@
     this.error = '';
 
     const propertiesToSet = this.getPropertiesToSet_();
-    if (this.managedProperties_.source == mojom.OncSource.kNone) {
+    if (this.managedProperties_.source === mojom.OncSource.kNone) {
       if (!this.autoConnect_) {
         // Note: Do not set autoConnect to true, the connection manager will do
         // that on a successful connection (unless set to false here).
@@ -490,7 +490,7 @@
   onNetworkCertificatesChanged() {
     this.networkConfig_.getNetworkCertificates().then(response => {
       const isOpenVpn = !!this.configProperties_.typeConfig.vpn &&
-          this.configProperties_.typeConfig.vpn.type == mojom.VpnType.kOpenVPN;
+          this.configProperties_.typeConfig.vpn.type === mojom.VpnType.kOpenVPN;
 
       const caCerts = response.serverCas.slice();
       if (!isOpenVpn) {
@@ -591,12 +591,12 @@
     this.managedEapProperties_ = this.getManagedEap_(managedProperties);
     this.mojoType_ = managedProperties.type;
 
-    if (this.mojoType_ == mojom.NetworkType.kVPN) {
+    if (this.mojoType_ === mojom.NetworkType.kVPN) {
       let saveCredentials = false;
       const vpn = managedProperties.typeProperties.vpn;
-      if (vpn.type == mojom.VpnType.kOpenVPN) {
+      if (vpn.type === mojom.VpnType.kOpenVPN) {
         saveCredentials = this.getActiveBoolean_(vpn.openVpn.saveCredentials);
-      } else if (vpn.type == mojom.VpnType.kL2TPIPsec) {
+      } else if (vpn.type === mojom.VpnType.kL2TPIPsec) {
         saveCredentials = this.getActiveBoolean_(vpn.ipSec.saveCredentials) ||
             this.getActiveBoolean_(vpn.l2tp.saveCredentials);
       }
@@ -613,7 +613,7 @@
    * @private
    */
   getSecurityItems_() {
-    if (this.mojoType_ == mojom.NetworkType.kWiFi) {
+    if (this.mojoType_ === mojom.NetworkType.kWiFi) {
       return [
         mojom.SecurityType.kNone,
         mojom.SecurityType.kWepPsk,
@@ -634,10 +634,10 @@
       return;
     }
     const source = this.managedProperties_.source;
-    if (source != mojom.OncSource.kNone) {
+    if (source !== mojom.OncSource.kNone) {
       // Configured networks can not change whether they are shared.
-      this.shareNetwork_ = source == mojom.OncSource.kDevice ||
-          source == mojom.OncSource.kDevicePolicy;
+      this.shareNetwork_ = source === mojom.OncSource.kDevice ||
+          source === mojom.OncSource.kDevicePolicy;
       return;
     }
     if (!this.shareIsVisible_()) {
@@ -646,8 +646,8 @@
     }
     if (this.shareAllowEnable) {
       // New insecure WiFi networks are always shared.
-      if (this.mojoType_ == mojom.NetworkType.kWiFi &&
-          this.managedProperties_.typeProperties.wifi.security ==
+      if (this.mojoType_ === mojom.NetworkType.kWiFi &&
+          this.managedProperties_.typeProperties.wifi.security ===
               mojom.SecurityType.kNone) {
         this.shareNetwork_ = true;
         return;
@@ -780,7 +780,7 @@
                 managedProperties.typeProperties.ethernet.eap) :
             undefined;
         security = eap ? mojom.SecurityType.kWpaEap : mojom.SecurityType.kNone;
-        const auth = security == mojom.SecurityType.kWpaEap ? '8021X' : 'None';
+        const auth = security === mojom.SecurityType.kWpaEap ? '8021X' : 'None';
         configProperties.typeConfig.ethernet.authentication = auth;
         configProperties.typeConfig.ethernet.eap = eap;
         break;
@@ -790,13 +790,13 @@
         const configVpn = configProperties.typeConfig.vpn;
         configVpn.host = OncMojo.getActiveString(vpn.host);
         configVpn.type = vpnType;
-        if (vpnType == mojom.VpnType.kL2TPIPsec) {
+        if (vpnType === mojom.VpnType.kL2TPIPsec) {
           assert(vpn.ipSec);
           configVpn.ipSec = this.getIPSecConfigProperties_(vpn.ipSec);
           assert(vpn.l2tp);
           configVpn.l2tp = this.getL2TPConfigProperties_(vpn.l2tp);
         } else {
-          assert(vpnType == mojom.VpnType.kOpenVPN);
+          assert(vpnType === mojom.VpnType.kOpenVPN);
           assert(vpn.openVpn);
           configVpn.openVpn = this.getOpenVPNConfigProperties_(vpn.openVpn);
         }
@@ -807,14 +807,14 @@
       configProperties.autoConnect = {value: autoConnect};
     }
     // Request certificates the first time |configProperties_| is set.
-    const requestCertificates = this.configProperties_ == undefined;
+    const requestCertificates = this.configProperties_ === undefined;
     this.configProperties_ = configProperties;
     this.securityType_ = security;
     this.set('eapProperties_', this.getEap_(this.configProperties_));
     if (!this.eapProperties_) {
       this.showEap_ = null;
     }
-    if (managedProperties.type == mojom.NetworkType.kVPN) {
+    if (managedProperties.type === mojom.NetworkType.kVPN) {
       this.vpnType_ = this.getVpnTypeFromProperties_(this.configProperties_);
     }
     if (requestCertificates) {
@@ -833,14 +833,14 @@
     }
     const type = this.mojoType_;
     const security = this.securityType_;
-    if (type == mojom.NetworkType.kWiFi) {
+    if (type === mojom.NetworkType.kWiFi) {
       this.configProperties_.typeConfig.wifi.security = security;
-    } else if (type == mojom.NetworkType.kEthernet) {
-      const auth = security == mojom.SecurityType.kWpaEap ? '8021X' : 'None';
+    } else if (type === mojom.NetworkType.kEthernet) {
+      const auth = security === mojom.SecurityType.kWpaEap ? '8021X' : 'None';
       this.configProperties_.typeConfig.ethernet.authentication = auth;
     }
     let eap;
-    if (security == mojom.SecurityType.kWpaEap) {
+    if (security === mojom.SecurityType.kWpaEap) {
       eap = this.getEap_(this.configProperties_, true);
       eap.outer = eap.outer || 'LEAP';
     }
@@ -870,20 +870,20 @@
   /** @private */
   updateEapCerts_() {
     // EAP is used for all configurable types except VPN.
-    if (this.mojoType_ == mojom.NetworkType.kVPN) {
+    if (this.mojoType_ === mojom.NetworkType.kVPN) {
       return;
     }
     const eap = this.eapProperties_;
     const pem = eap && eap.serverCaPems ? eap.serverCaPems[0] : '';
     const certId =
-        eap && eap.clientCertType == 'PKCS11Id' ? eap.clientCertPkcs11Id : '';
+        eap && eap.clientCertType === 'PKCS11Id' ? eap.clientCertPkcs11Id : '';
     this.setSelectedCerts_(pem, certId);
   },
 
   /** @private */
   updateShowEap_() {
     if (!this.eapProperties_ ||
-        this.securityType_ == mojom.SecurityType.kNone) {
+        this.securityType_ === mojom.SecurityType.kNone) {
       this.showEap_ = null;
       this.updateCertError_();
       return;
@@ -894,13 +894,13 @@
       case mojom.NetworkType.kEthernet:
         this.showEap_ = {
           Outer: true,
-          Inner: outer == 'PEAP' || outer == 'EAP-TTLS',
-          ServerCA: outer != 'LEAP',
-          SubjectMatch: outer == 'EAP-TLS',
-          UserCert: outer == 'EAP-TLS',
+          Inner: outer === 'PEAP' || outer === 'EAP-TTLS',
+          ServerCA: outer !== 'LEAP',
+          SubjectMatch: outer === 'EAP-TLS',
+          UserCert: outer === 'EAP-TLS',
           Identity: true,
-          Password: outer != 'EAP-TLS',
-          AnonymousIdentity: outer == 'PEAP' || outer == 'EAP-TTLS',
+          Password: outer !== 'EAP-TLS',
+          AnonymousIdentity: outer === 'PEAP' || outer === 'EAP-TTLS',
         };
         break;
     }
@@ -971,8 +971,8 @@
   getVpnTypeFromProperties_(properties) {
     const vpn = properties.typeConfig.vpn;
     assert(vpn);
-    if (vpn.type == mojom.VpnType.kL2TPIPsec) {
-      return vpn.ipSec.authenticationType == 'Cert' ?
+    if (vpn.type === mojom.VpnType.kL2TPIPsec) {
+      return vpn.ipSec.authenticationType === 'Cert' ?
           VPNConfigType.L2TP_IPSEC_CERT :
           VPNConfigType.L2TP_IPSEC_PSK;
     }
@@ -1028,7 +1028,7 @@
         delete vpn.ipSec;
         break;
     }
-    if (vpn.type == mojom.VpnType.kL2TPIPsec && !vpn.l2tp) {
+    if (vpn.type === mojom.VpnType.kL2TPIPsec && !vpn.l2tp) {
       vpn.l2tp = {
         lcpEchoDisabled: false,
         password: '',
@@ -1041,25 +1041,25 @@
 
   /** @private */
   updateVpnIPsecCerts_() {
-    if (this.vpnType_ != VPNConfigType.L2TP_IPSEC_CERT) {
+    if (this.vpnType_ !== VPNConfigType.L2TP_IPSEC_CERT) {
       return;
     }
     const ipSec = this.configProperties_.typeConfig.vpn.ipSec;
     const pem = ipSec.serverCaPems ? ipSec.serverCaPems[0] : undefined;
     const certId =
-        ipSec.clientCertType == 'PKCS11Id' ? ipSec.clientCertPkcs11Id : '';
+        ipSec.clientCertType === 'PKCS11Id' ? ipSec.clientCertPkcs11Id : '';
     this.setSelectedCerts_(pem, certId);
   },
 
   /** @private */
   updateOpenVPNCerts_() {
-    if (this.vpnType_ != VPNConfigType.OPEN_VPN) {
+    if (this.vpnType_ !== VPNConfigType.OPEN_VPN) {
       return;
     }
     const openVpn = this.configProperties_.typeConfig.vpn.openVpn;
     const pem = openVpn.serverCaPems ? openVpn.serverCaPems[0] : undefined;
     const certId =
-        openVpn.clientCertType == 'PKCS11Id' ? openVpn.clientCertPkcs11Id : '';
+        openVpn.clientCertType === 'PKCS11Id' ? openVpn.clientCertPkcs11Id : '';
     this.setSelectedCerts_(pem, certId);
   },
 
@@ -1069,8 +1069,8 @@
     // change it.
     /** @const */ const noCertsError = 'networkErrorNoUserCertificate';
     /** @const */ const noValidCertsError = 'networkErrorNotHardwareBacked';
-    if (this.error && this.error != noCertsError &&
-        this.error != noValidCertsError) {
+    if (this.error && this.error !== noCertsError &&
+        this.error !== noValidCertsError) {
       return;
     }
 
@@ -1080,7 +1080,7 @@
       this.setError_('');
       return;
     }
-    if (!this.userCerts_.length || this.userCerts_[0].hash == NO_CERTS_HASH) {
+    if (!this.userCerts_.length || this.userCerts_[0].hash === NO_CERTS_HASH) {
       this.setError_(noCertsError);
       return;
     }
@@ -1105,7 +1105,7 @@
   setSelectedCerts_(pem, certId) {
     if (pem) {
       const serverCa = this.serverCaCerts_.find(function(cert) {
-        return cert.pemOrId == pem;
+        return cert.pemOrId === pem;
       });
       if (serverCa) {
         this.selectedServerCaHash_ = serverCa.hash;
@@ -1139,7 +1139,7 @@
       return undefined;
     }
     return certs.find((cert) => {
-      return cert.hash == hash;
+      return cert.hash === hash;
     });
   },
 
@@ -1158,7 +1158,7 @@
     // Only device-wide certificates can be used for shared networks that
     // require a certificate.
     this.deviceCertsOnly_ =
-        this.shareNetwork_ && !!eap && eap.outer == 'EAP-TLS';
+        this.shareNetwork_ && !!eap && eap.outer === 'EAP-TLS';
 
     // Validate selected Server CA.
     const caCert =
@@ -1174,7 +1174,7 @@
         // certificate, or DO_NOT_CHECK (i.e. skip DEFAULT_HASH). See
         /// onNetworkCertificatesChanged() for how certificates are added.
         let cert = this.serverCaCerts_[0];
-        if (cert.hash == DEFAULT_HASH && this.serverCaCerts_[1]) {
+        if (cert.hash === DEFAULT_HASH && this.serverCaCerts_[1]) {
           cert = this.serverCaCerts_[1];
         }
         this.selectedServerCaHash_ = cert.hash;
@@ -1225,7 +1225,7 @@
         }
       }
     }
-    if (this.securityType_ == mojom.SecurityType.kWpaEap) {
+    if (this.securityType_ === mojom.SecurityType.kWpaEap) {
       return this.eapIsConfigured_();
     }
     return true;
@@ -1242,7 +1242,7 @@
    * @private
    */
   isWiFi_(networkType) {
-    return networkType == mojom.NetworkType.kWiFi;
+    return networkType === mojom.NetworkType.kWiFi;
   },
 
   /** @private */
@@ -1261,8 +1261,8 @@
    * @private
    */
   securityIsVisible_(networkType) {
-    return networkType == mojom.NetworkType.kWiFi ||
-        networkType == mojom.NetworkType.kEthernet;
+    return networkType === mojom.NetworkType.kWiFi ||
+        networkType === mojom.NetworkType.kEthernet;
   },
 
   /**
@@ -1271,7 +1271,7 @@
    */
   securityIsEnabled_() {
     // WiFi Security type cannot be changed once configured.
-    return !this.guid || this.mojoType_ == mojom.NetworkType.kEthernet;
+    return !this.guid || this.mojoType_ === mojom.NetworkType.kEthernet;
   },
 
   /**
@@ -1282,8 +1282,8 @@
     if (!this.managedProperties_) {
       return false;
     }
-    return this.managedProperties_.source == mojom.OncSource.kNone &&
-        this.managedProperties_.type == mojom.NetworkType.kWiFi;
+    return this.managedProperties_.source === mojom.OncSource.kNone &&
+        this.managedProperties_.type === mojom.NetworkType.kWiFi;
   },
 
   /**
@@ -1295,13 +1295,13 @@
       return false;
     }
     if (!this.shareAllowEnable ||
-        this.managedProperties_.source != mojom.OncSource.kNone) {
+        this.managedProperties_.source !== mojom.OncSource.kNone) {
       return false;
     }
 
     // Insecure WiFi networks are always shared.
-    if (this.mojoType_ == mojom.NetworkType.kWiFi &&
-        this.securityType_ == mojom.SecurityType.kNone) {
+    if (this.mojoType_ === mojom.NetworkType.kWiFi &&
+        this.securityType_ === mojom.SecurityType.kNone) {
       return false;
     }
     return true;
@@ -1314,7 +1314,7 @@
   configCanAutoConnect_() {
     // Only WiFi can choose whether or not to autoConnect.
     return loadTimeData.getBoolean('showHiddenNetworkWarning') &&
-        this.mojoType_ == mojom.NetworkType.kWiFi;
+        this.mojoType_ === mojom.NetworkType.kWiFi;
   },
 
   /**
@@ -1357,7 +1357,7 @@
    */
   selectedUserCertHashIsValid_() {
     return !!this.selectedUserCertHash_ &&
-        this.selectedUserCertHash_ != NO_CERTS_HASH;
+        this.selectedUserCertHash_ !== NO_CERTS_HASH;
   },
 
   /**
@@ -1372,7 +1372,7 @@
     if (!eap) {
       return false;
     }
-    if (eap.outer != 'EAP-TLS') {
+    if (eap.outer !== 'EAP-TLS') {
       return true;
     }
     // EAP TLS networks can be shared only for device-wide certificates.
@@ -1429,18 +1429,18 @@
     if (eap) {
       this.setEapProperties_(eap);
     }
-    if (this.mojoType_ == mojom.NetworkType.kVPN) {
+    if (this.mojoType_ === mojom.NetworkType.kVPN) {
       const vpnConfig = propertiesToSet.typeConfig.vpn;
       // VPN.Host can be an IP address but will not be recognized as such if
       // there is initial whitespace, so trim it.
-      if (vpnConfig.host != undefined) {
+      if (vpnConfig.host !== undefined) {
         vpnConfig.host = vpnConfig.host.trim();
       }
-      if (vpnConfig.type == mojom.VpnType.kOpenVPN) {
+      if (vpnConfig.type === mojom.VpnType.kOpenVPN) {
         this.setOpenVPNProperties_(propertiesToSet);
         delete propertiesToSet.ipSec;
         delete propertiesToSet.l2tp;
-      } else if (vpnConfig.type == mojom.VpnType.kL2TPIPsec) {
+      } else if (vpnConfig.type === mojom.VpnType.kL2TPIPsec) {
         this.setVpnIPsecProperties_(propertiesToSet);
         delete propertiesToSet.openVpn;
       }
@@ -1454,7 +1454,7 @@
    */
   getServerCaPems_() {
     const caHash = this.selectedServerCaHash_ || '';
-    if (!caHash || caHash == DO_NOT_CHECK_HASH || caHash == DEFAULT_HASH) {
+    if (!caHash || caHash === DO_NOT_CHECK_HASH || caHash === DEFAULT_HASH) {
       return [];
     }
     const serverCa = this.findCert_(this.serverCaCerts_, caHash);
@@ -1468,7 +1468,7 @@
   getUserCertPkcs11Id_() {
     const userCertHash = this.selectedUserCertHash_ || '';
     if (!this.selectedUserCertHashIsValid_() ||
-        userCertHash == NO_USER_CERT_HASH) {
+        userCertHash === NO_USER_CERT_HASH) {
       return '';
     }
     const userCert = this.findCert_(this.userCerts_, userCertHash);
@@ -1480,7 +1480,7 @@
    * @private
    */
   setEapProperties_(eap) {
-    eap.useSystemCas = this.selectedServerCaHash_ == DEFAULT_HASH;
+    eap.useSystemCas = this.selectedServerCaHash_ === DEFAULT_HASH;
 
     eap.serverCaPems = this.getServerCaPems_();
 
@@ -1525,7 +1525,7 @@
     assert(vpn.ipSec);
     assert(vpn.l2tp);
 
-    if (vpn.ipSec.authenticationType == 'Cert') {
+    if (vpn.ipSec.authenticationType === 'Cert') {
       vpn.ipSec.clientCertType = 'PKCS11Id';
       vpn.ipSec.clientCertPkcs11Id = this.getUserCertPkcs11Id_();
       vpn.ipSec.serverCaPems = this.getServerCaPems_();
@@ -1562,7 +1562,7 @@
 
     // Only attempt a connection if the network is not yet connected.
     if (connect &&
-        this.managedProperties_.connectionState ==
+        this.managedProperties_.connectionState ===
             mojom.ConnectionStateType.kNotConnected) {
       this.startConnect_(this.guid);
     } else {
@@ -1600,10 +1600,10 @@
   startConnect_(guid) {
     this.networkConfig_.startConnect(guid).then(response => {
       const result = response.result;
-      if (result == mojom.StartConnectResult.kSuccess ||
-          result == mojom.StartConnectResult.kInvalidGuid ||
-          result == mojom.StartConnectResult.kInvalidState ||
-          result == mojom.StartConnectResult.kCanceled) {
+      if (result === mojom.StartConnectResult.kSuccess ||
+          result === mojom.StartConnectResult.kInvalidGuid ||
+          result === mojom.StartConnectResult.kInvalidState ||
+          result === mojom.StartConnectResult.kCanceled) {
         // Connect succeeded, or is in progress completed or canceled.
         // Close the dialog.
         this.close_();
@@ -1625,9 +1625,9 @@
    */
   computeConfigRequiresPassphrase_(mojoType, securityType) {
     // Note: 'Passphrase' is only used by WiFi; Ethernet uses EAP.Password.
-    return mojoType == mojom.NetworkType.kWiFi &&
-        (securityType == mojom.SecurityType.kWepPsk ||
-         securityType == mojom.SecurityType.kWpaPsk);
+    return mojoType === mojom.NetworkType.kWiFi &&
+        (securityType === mojom.SecurityType.kWepPsk ||
+         securityType === mojom.SecurityType.kWpaPsk);
   },
 
   /**
@@ -1636,10 +1636,10 @@
    * @private
    */
   getEapInnerItems_(outer) {
-    if (outer == 'PEAP') {
+    if (outer === 'PEAP') {
       return this.eapInnerItemsPeap_;
     }
-    if (outer == 'EAP-TTLS') {
+    if (outer === 'EAP-TTLS') {
       return this.eapInnerItemsTtls_;
     }
     return [];
@@ -1662,7 +1662,7 @@
   getManagedSecurity_(managedProperties) {
     const policySource =
         OncMojo.getEnforcedPolicySourceFromOncSource(managedProperties.source);
-    if (policySource == mojom.PolicySource.kNone) {
+    if (policySource === mojom.PolicySource.kNone) {
       return undefined;
     }
     switch (managedProperties.type) {
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config_input.js b/ui/webui/resources/cr_components/chromeos/network/network_config_input.js
index e86772a..24ce74ce 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config_input.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config_input.js
@@ -41,7 +41,7 @@
    * @private
    */
   onKeypress_(event) {
-    if (event.key != 'Enter') {
+    if (event.key !== 'Enter') {
       return;
     }
     event.stopPropagation();
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config_select.js b/ui/webui/resources/cr_components/chromeos/network/network_config_select.js
index 35583fb8..e194462 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config_select.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config_select.js
@@ -66,7 +66,7 @@
     // Wait for the dom-repeat to populate the <option> entries.
     this.async(function() {
       const select = this.$$('select');
-      if (select.value != this.value) {
+      if (select.value !== this.value) {
         select.value = this.value;
       }
     });
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_icon.js b/ui/webui/resources/cr_components/chromeos/network/network_icon.js
index de13ea9e..e01e0d9 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_icon.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_icon.js
@@ -83,30 +83,30 @@
     }
     const mojom = chromeos.networkConfig.mojom;
     const type = this.networkState.type;
-    if (type == mojom.NetworkType.kEthernet) {
+    if (type === mojom.NetworkType.kEthernet) {
       return 'ethernet';
     }
-    if (type == mojom.NetworkType.kVPN) {
+    if (type === mojom.NetworkType.kVPN) {
       return 'vpn';
     }
 
     const prefix = OncMojo.networkTypeIsMobile(type) ? 'cellular-' : 'wifi-';
     if (!this.isListItem && !this.networkState.guid) {
       const device = this.deviceState;
-      if (!device || device.deviceState == mojom.DeviceStateType.kEnabled ||
-          device.deviceState == mojom.DeviceStateType.kEnabling) {
+      if (!device || device.deviceState === mojom.DeviceStateType.kEnabled ||
+          device.deviceState === mojom.DeviceStateType.kEnabling) {
         return prefix + 'no-network';
       }
       return prefix + 'off';
     }
 
     const connectionState = this.networkState.connectionState;
-    if (connectionState == mojom.ConnectionStateType.kConnecting) {
+    if (connectionState === mojom.ConnectionStateType.kConnecting) {
       return prefix + 'connecting';
     }
 
     if (!this.isListItem &&
-        connectionState == mojom.ConnectionStateType.kNotConnected) {
+        connectionState === mojom.ConnectionStateType.kNotConnected) {
       return prefix + 'not-connected';
     }
 
@@ -154,9 +154,9 @@
       networkTypeString = this.i18nDynamic(locale, 'OncTypeWiFi');
     }
 
-    // When isListItem == true, we want to describe the network and signal
+    // When isListItem === true, we want to describe the network and signal
     // strength regardless of connection state (i.e. when picking a Wi-Fi
-    // network to connect to. If isListItem == false we try to describe the
+    // network to connect to. If isListItem === false we try to describe the
     // current connection state and describe signal strength only if connected.
 
     if (!this.isListItem && !this.networkState.guid) {
@@ -216,7 +216,7 @@
    * @private
    */
   showTechnology_() {
-    return this.getTechnology_() != '' && this.showTechnologyBadge;
+    return this.getTechnology_() !== '' && this.showTechnologyBadge;
   },
 
   /**
@@ -227,11 +227,11 @@
     if (!this.networkState) {
       return '';
     }
-    if (this.networkState.type ==
+    if (this.networkState.type ===
         chromeos.networkConfig.mojom.NetworkType.kCellular) {
       const technology = this.getTechnologyId_(
           this.networkState.typeState.cellular.networkTechnology);
-      if (technology != '') {
+      if (technology !== '') {
         return 'network:' + technology;
       }
     }
@@ -278,11 +278,11 @@
     }
     const mojom = chromeos.networkConfig.mojom;
     if (!this.isListItem &&
-        this.networkState.connectionState ==
+        this.networkState.connectionState ===
             mojom.ConnectionStateType.kNotConnected) {
       return false;
     }
-    return this.networkState.type == mojom.NetworkType.kWiFi &&
-        this.networkState.typeState.wifi.security != mojom.SecurityType.kNone;
+    return this.networkState.type === mojom.NetworkType.kWiFi &&
+        this.networkState.typeState.wifi.security !== mojom.SecurityType.kNone;
   },
 });
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js b/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
index 5c240240..97cc34bf 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js
@@ -34,7 +34,7 @@
       netmask += '.';
     }
     let value = 0;
-    if (remainder != 0) {
+    if (remainder !== 0) {
       value = ((2 << (remainder - 1)) - 1) << (8 - remainder);
     }
     netmask += value.toString();
@@ -51,34 +51,34 @@
   'use strict';
   let prefixLength = 0;
   const tokens = netmask.split('.');
-  if (tokens.length != 4) {
+  if (tokens.length !== 4) {
     return -1;
   }
   for (let i = 0; i < tokens.length; ++i) {
     const token = tokens[i];
     // If we already found the last mask and the current one is not
     // '0' then the netmask is invalid. For example, 255.224.255.0
-    if (prefixLength / 8 != i) {
-      if (token != '0') {
+    if (prefixLength / 8 !== i) {
+      if (token !== '0') {
         return chromeos.networkConfig.mojom.NO_ROUTING_PREFIX;
       }
-    } else if (token == '255') {
+    } else if (token === '255') {
       prefixLength += 8;
-    } else if (token == '254') {
+    } else if (token === '254') {
       prefixLength += 7;
-    } else if (token == '252') {
+    } else if (token === '252') {
       prefixLength += 6;
-    } else if (token == '248') {
+    } else if (token === '248') {
       prefixLength += 5;
-    } else if (token == '240') {
+    } else if (token === '240') {
       prefixLength += 4;
-    } else if (token == '224') {
+    } else if (token === '224') {
       prefixLength += 3;
-    } else if (token == '192') {
+    } else if (token === '192') {
       prefixLength += 2;
-    } else if (token == '128') {
+    } else if (token === '128') {
       prefixLength += 1;
-    } else if (token == '0') {
+    } else if (token === '0') {
       prefixLength += 0;
     } else {
       // mask is not a valid number.
@@ -149,7 +149,7 @@
     }
 
     const properties = this.managedProperties;
-    if (newValue.guid != (oldValue && oldValue.guid)) {
+    if (newValue.guid !== (oldValue && oldValue.guid)) {
       this.savedStaticIp_ = undefined;
     }
 
@@ -157,7 +157,7 @@
     if (properties.ipAddressConfigType) {
       const ipConfigType =
           OncMojo.getActiveValue(properties.ipAddressConfigType);
-      this.automatic_ = ipConfigType != 'Static';
+      this.automatic_ = ipConfigType !== 'Static';
     }
 
     if (properties.ipConfigs || properties.staticIpConfig) {
@@ -238,7 +238,7 @@
     const result = {};
     for (const key in ipconfig) {
       const value = ipconfig[key];
-      if (key == 'routingPrefix') {
+      if (key === 'routingPrefix') {
         const netmask = getRoutingPrefixAsNetmask(value);
         if (netmask !== undefined) {
           result.routingPrefix = netmask;
@@ -261,9 +261,9 @@
     const result = {};
     for (const key in ipconfig) {
       const value = ipconfig[key];
-      if (key == 'routingPrefix') {
+      if (key === 'routingPrefix') {
         const routingPrefix = getRoutingPrefixAsLength(value);
-        if (routingPrefix != chromeos.networkConfig.mojom.NO_ROUTING_PREFIX) {
+        if (routingPrefix !== chromeos.networkConfig.mojom.NO_ROUTING_PREFIX) {
           result.routingPrefix = routingPrefix;
         }
       } else {
@@ -282,7 +282,7 @@
       return false;
     }
     for (let i = 0; i < this.ipConfigFields_.length; ++i) {
-      if (this.get(this.ipConfigFields_[i], this.ipConfig_) != undefined) {
+      if (this.get(this.ipConfigFields_[i], this.ipConfig_) !== undefined) {
         return true;
       }
     }
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list.js b/ui/webui/resources/cr_components/chromeos/network/network_list.js
index 3e0d4fa..dae8ff4 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list.js
@@ -85,10 +85,10 @@
   updateListItems_() {
     this.saveScroll(this.$.networkList);
     const beforeNetworks = this.customItems.filter(function(item) {
-      return item.showBeforeNetworksList == true;
+      return item.showBeforeNetworksList === true;
     });
     const afterNetworks = this.customItems.filter(function(item) {
-      return item.showBeforeNetworksList == false;
+      return item.showBeforeNetworksList === false;
     });
     this.listItems_ = beforeNetworks.concat(this.networks, afterNetworks);
     this.restoreScroll(this.$.networkList);
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
index fcbd4a7..9c61bf8 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
@@ -117,7 +117,7 @@
       return;
     }
     const connectionState = this.networkState.connectionState;
-    if (connectionState == this.connectionState_) {
+    if (connectionState === this.connectionState_) {
       return;
     }
     this.connectionState_ = connectionState;
@@ -268,7 +268,7 @@
       return '';
     }
     const connectionState = this.networkState.connectionState;
-    if (this.networkState.type == mojom.NetworkType.kCellular) {
+    if (this.networkState.type === mojom.NetworkType.kCellular) {
       if (this.shouldShowNotAvailableText_()) {
         return this.i18n('networkListItemNotAvailable');
       }
@@ -284,7 +284,7 @@
       // and Online.
       return this.i18n('networkListItemConnected');
     }
-    if (connectionState == mojom.ConnectionStateType.kConnecting) {
+    if (connectionState === mojom.ConnectionStateType.kConnecting) {
       return this.i18n('networkListItemConnecting');
     }
     return '';
@@ -324,9 +324,9 @@
   onKeydown_(event) {
     // The only key event handled by this element is pressing Enter when the
     // subpage arrow is focused.
-    if (event.key != 'Enter' ||
+    if (event.key !== 'Enter' ||
         !this.isSubpageButtonVisible_(this.networkState, this.showButtons) ||
-        this.$$('#subpage-button') != this.shadowRoot.activeElement) {
+        this.$$('#subpage-button') !== this.shadowRoot.activeElement) {
       return;
     }
 
@@ -370,8 +370,8 @@
     // If cellular activation is not currently available and |this.networkState|
     // describes an unactivated cellular network, the text should be shown.
     const mojom = chromeos.networkConfig.mojom;
-    return this.networkState.type == mojom.NetworkType.kCellular &&
-        this.networkState.typeState.cellular.activationState !=
+    return this.networkState.type === mojom.NetworkType.kCellular &&
+        this.networkState.typeState.cellular.activationState !==
         mojom.ActivationStateType.kActivated;
   },
 });
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
index ee3ec9f..38bffd2 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
@@ -81,12 +81,12 @@
     const matches = [];
     for (let i = 0; i < nameservers.length; ++i) {
       const nameserver = nameservers[i];
-      if (nameserver == this.EMPTY_NAMESERVER) {
+      if (nameserver === this.EMPTY_NAMESERVER) {
         continue;
       }
       let valid = false;
       for (let j = 0; j < this.GOOGLE_NAMESERVERS.length; ++j) {
-        if (nameserver == this.GOOGLE_NAMESERVERS[j]) {
+        if (nameserver === this.GOOGLE_NAMESERVERS[j]) {
           valid = true;
           matches[j] = true;
           break;
@@ -110,7 +110,7 @@
       return;
     }
 
-    if (!oldValue || newValue.guid != oldValue.guid) {
+    if (!oldValue || newValue.guid !== oldValue.guid) {
       this.savedNameservers_ = [];
     }
 
@@ -125,7 +125,7 @@
     const configType =
         OncMojo.getActiveValue(this.managedProperties.nameServersConfigType);
     let type;
-    if (configType == 'Static') {
+    if (configType === 'Static') {
       if (this.isGoogleNameservers_(nameservers)) {
         type = 'google';
         nameservers = this.GOOGLE_NAMESERVERS;  // Use consistent order.
@@ -147,7 +147,7 @@
    * @private
    */
   setNameservers_(nameserversType, nameservers, sendNameservers) {
-    if (nameserversType == 'custom') {
+    if (nameserversType === 'custom') {
       // Add empty entries for unset custom nameservers.
       for (let i = nameservers.length; i < this.MAX_NAMESERVERS; ++i) {
         nameservers[i] = this.EMPTY_NAMESERVER;
@@ -198,7 +198,7 @@
     if (!managedProperties) {
       return false;
     }
-    if (nameserversType != 'custom') {
+    if (nameserversType !== 'custom') {
       return false;
     }
     if (managedProperties.nameServersConfigType &&
@@ -222,10 +222,10 @@
    * @private
    */
   showNameservers_(nameserversType, type, nameservers) {
-    if (nameserversType != type) {
+    if (nameserversType !== type) {
       return false;
     }
-    return type == 'custom' || nameservers.length > 0;
+    return type === 'custom' || nameservers.length > 0;
   },
 
   /**
@@ -245,7 +245,7 @@
   onTypeChange_() {
     const type = this.$$('#nameserverType').selected;
     this.nameserversType_ = type;
-    if (type == 'custom') {
+    if (type === 'custom') {
       // Restore the saved nameservers.
       this.setNameservers_(type, this.savedNameservers_, true /* send */);
       return;
@@ -258,7 +258,7 @@
    * @private
    */
   onValueChange_() {
-    if (this.nameserversType_ != 'custom') {
+    if (this.nameserversType_ !== 'custom') {
       // If a user inputs Google nameservers in the custom nameservers fields,
       // |nameserversType| will change to 'google' so don't send the values.
       return;
@@ -273,7 +273,7 @@
   sendNameServers_() {
     const type = this.nameserversType_;
 
-    if (type == 'custom') {
+    if (type === 'custom') {
       const nameservers = new Array(this.MAX_NAMESERVERS);
       for (let i = 0; i < this.MAX_NAMESERVERS; ++i) {
         const nameserverInput = this.$$('#nameserver' + i);
@@ -285,13 +285,13 @@
         field: 'nameServers',
         value: nameservers,
       });
-    } else if (type == 'google') {
+    } else if (type === 'google') {
       this.nameservers_ = this.GOOGLE_NAMESERVERS;
       this.fire('nameservers-change', {
         field: 'nameServers',
         value: this.GOOGLE_NAMESERVERS,
       });
-    } else {  // type == automatic
+    } else {  // type === automatic
       // If not connected, properties will clear. Otherwise they may or may not
       // change so leave them as-is.
       if (!OncMojo.connectionStateIsConnected(
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_password_input.js b/ui/webui/resources/cr_components/chromeos/network/network_password_input.js
index 6da270a9..752814ef 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_password_input.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_password_input.js
@@ -65,7 +65,7 @@
    * @private
    */
   isShowingPlaceholder_() {
-    return this.value == FAKE_CREDENTIAL;
+    return this.value === FAKE_CREDENTIAL;
   },
 
   /**
@@ -105,7 +105,7 @@
    * @private
    */
   onKeypress_(event) {
-    if (event.target.id == 'input' && event.key == 'Enter') {
+    if (event.target.id === 'input' && event.key === 'Enter') {
       event.stopPropagation();
       this.fire('enter');
     }
@@ -120,8 +120,8 @@
       return;
     }
 
-    if (event.key.indexOf('Arrow') < 0 && event.key != 'Home' &&
-        event.key != 'End') {
+    if (event.key.indexOf('Arrow') < 0 && event.key !== 'Home' &&
+        event.key !== 'End') {
       return;
     }
 
@@ -140,7 +140,7 @@
       return;
     }
 
-    if (document.activeElement != event.target) {
+    if (document.activeElement !== event.target) {
       // Focus the field and select the placeholder text if not already focused.
       this.focus();
     }
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js b/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
index 3d66a2a7..b5da62f 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
@@ -71,13 +71,13 @@
     }
     const key = event.target.id;
     let curValue = this.getProperty_(key);
-    if (typeof curValue == 'object' && !Array.isArray(curValue)) {
+    if (typeof curValue === 'object' && !Array.isArray(curValue)) {
       // Extract the property from an ONC managed dictionary.
       curValue = OncMojo.getActiveValue(
           /** @type{OncMojo.ManagedProperty} */ (curValue));
     }
     const newValue = this.getValueFromEditField_(key, event.target.value);
-    if (newValue == curValue) {
+    if (newValue === curValue) {
       return;
     }
     this.fire('property-change', {field: key, value: newValue});
@@ -99,29 +99,29 @@
     const subKeys = key.split('.');
     subKeys.forEach(subKey => {
       // Check for exceptions to CamelCase vs camelCase naming conventions.
-      if (subKey == 'ipv4' || subKey == 'ipv6') {
+      if (subKey === 'ipv4' || subKey === 'ipv6') {
         result += subKey;
-      } else if (subKey == 'apn') {
+      } else if (subKey === 'apn') {
         result += 'APN';
-      } else if (subKey == 'ipAddress') {
+      } else if (subKey === 'ipAddress') {
         result += 'IPAddress';
-      } else if (subKey == 'ipSec') {
+      } else if (subKey === 'ipSec') {
         result += 'IPSec';
-      } else if (subKey == 'l2tp') {
+      } else if (subKey === 'l2tp') {
         result += 'L2TP';
-      } else if (subKey == 'modelId') {
+      } else if (subKey === 'modelId') {
         result += 'ModelID';
-      } else if (subKey == 'openVpn') {
+      } else if (subKey === 'openVpn') {
         result += 'OpenVPN';
-      } else if (subKey == 'otp') {
+      } else if (subKey === 'otp') {
         result += 'OTP';
-      } else if (subKey == 'ssid') {
+      } else if (subKey === 'ssid') {
         result += 'SSID';
-      } else if (subKey == 'serverCa') {
+      } else if (subKey === 'serverCa') {
         result += 'ServerCA';
-      } else if (subKey == 'vpn') {
+      } else if (subKey === 'vpn') {
         result += 'VPN';
-      } else if (subKey == 'wifi') {
+      } else if (subKey === 'wifi') {
         result += 'WiFi';
       } else {
         result += subKey.charAt(0).toUpperCase() + subKey.slice(1);
@@ -181,8 +181,8 @@
       // Unspecified properties in policy configurations are not user
       // modifiable. https://crbug.com/819837.
       const source = this.propertyDict.source;
-      return source != chromeos.networkConfig.mojom.OncSource.kUserPolicy &&
-          source != chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
+      return source !== chromeos.networkConfig.mojom.OncSource.kUserPolicy &&
+          source !== chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
     }
     return !this.isNetworkPolicyEnforced(property);
   },
@@ -194,8 +194,8 @@
    */
   isEditType_(key) {
     const editType = this.editFieldTypes[key];
-    return editType == 'String' || editType == 'StringArray' ||
-        editType == 'Password';
+    return editType === 'String' || editType === 'StringArray' ||
+        editType === 'Password';
   },
 
   /**
@@ -222,7 +222,7 @@
    * @private
    */
   getEditInputType_(key) {
-    return this.editFieldTypes[key] == 'Password' ? 'password' : 'text';
+    return this.editFieldTypes[key] === 'Password' ? 'password' : 'text';
   },
 
   /**
@@ -256,7 +256,7 @@
         this.propertyDict.source) {
       const policySource = OncMojo.getEnforcedPolicySourceFromOncSource(
           this.propertyDict.source);
-      if (policySource != chromeos.networkConfig.mojom.PolicySource.kNone) {
+      if (policySource !== chromeos.networkConfig.mojom.PolicySource.kNone) {
         // If the dictionary is policy controlled, provide an empty property
         // object with the network policy source. See https://crbug.com/819837
         // for more info.
@@ -280,7 +280,7 @@
     if (value === undefined || value === null) {
       return '';
     }
-    if (typeof value == 'object' && !Array.isArray(value)) {
+    if (typeof value === 'object' && !Array.isArray(value)) {
       // Extract the property from an ONC managed dictionary
       value = OncMojo.getActiveValue(
           /** @type {!OncMojo.ManagedProperty} */ (value));
@@ -293,28 +293,28 @@
     if (customValue) {
       return customValue;
     }
-    if (typeof value == 'boolean') {
+    if (typeof value === 'boolean') {
       return value.toString();
     }
 
     let valueStr;
-    if (typeof value == 'number') {
+    if (typeof value === 'number') {
       // Special case typed managed properties.
-      if (key == 'cellular.activationState') {
+      if (key === 'cellular.activationState') {
         valueStr = OncMojo.getActivationStateTypeString(
             /** @type{!chromeos.networkConfig.mojom.ActivationStateType}*/ (
                 value));
-      } else if (key == 'vpn.type') {
+      } else if (key === 'vpn.type') {
         valueStr = OncMojo.getVpnTypeString(
             /** @type{!chromeos.networkConfig.mojom.VpnType}*/ (value));
-      } else if (key == 'wifi.security') {
+      } else if (key === 'wifi.security') {
         valueStr = OncMojo.getSecurityTypeString(
             /** @type{!chromeos.networkConfig.mojom.SecurityType}*/ (value));
       } else {
         return value.toString();
       }
     } else {
-      assert(typeof value == 'string');
+      assert(typeof value === 'string');
       valueStr = /** @type {string} */ (value);
     }
     const oncKey = this.getOncKey_(key, this.prefix) + '_' + valueStr;
@@ -333,7 +333,7 @@
    */
   getValueFromEditField_(key, fieldValue) {
     const editType = this.editFieldTypes[key];
-    if (editType == 'StringArray') {
+    if (editType === 'StringArray') {
       return fieldValue.toString().split(/, */);
     }
     return fieldValue;
@@ -346,13 +346,13 @@
    *     does not correspond to a custom property, an empty string is returned.
    */
   getCustomPropertyValue_(key, value) {
-    if (key == 'tether.batteryPercentage') {
-      assert(typeof value == 'number');
+    if (key === 'tether.batteryPercentage') {
+      assert(typeof value === 'number');
       return this.i18n('OncTether-BatteryPercentage_Value', value.toString());
     }
 
-    if (key == 'tether.signalStrength') {
-      assert(typeof value == 'number');
+    if (key === 'tether.signalStrength') {
+      assert(typeof value === 'number');
       // Possible |signalStrength| values should be 0, 25, 50, 75, and 100. Add
       // <= checks for robustness.
       if (value <= 24) {
@@ -370,9 +370,9 @@
       return this.i18n('OncTether-SignalStrength_VeryStrong');
     }
 
-    if (key == 'tether.carrier') {
-      assert(typeof value == 'string');
-      return (!value || value == 'unknown-carrier') ?
+    if (key === 'tether.carrier') {
+      assert(typeof value === 'string');
+      return (!value || value === 'unknown-carrier') ?
           this.i18n('OncTether-Carrier_Unknown') :
           value;
     }
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_proxy.js b/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
index 8c366dbd..d1fecc1f 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_proxy.js
@@ -121,7 +121,7 @@
    * @private
    */
   managedPropertiesChanged_(newValue, oldValue) {
-    if ((newValue && newValue.guid) != (oldValue && oldValue.guid)) {
+    if ((newValue && newValue.guid) !== (oldValue && oldValue.guid)) {
       // Clear saved manual properties and exclude domains if we're updating
       // to show a different network.
       this.savedManual_ = undefined;
@@ -208,11 +208,11 @@
         /** @type {!chromeos.networkConfig.mojom.ManagedProxySettings} */ (
             Object.assign({}, inputProxy));
     const type = proxy.type.activeValue;
-    if (type == 'PAC') {
+    if (type === 'PAC') {
       if (!proxy.pac) {
         proxy.pac = OncMojo.createManagedString('');
       }
-    } else if (type == 'Manual') {
+    } else if (type === 'Manual') {
       proxy.manual = proxy.manual || this.savedManual_ || {};
       if (!proxy.manual.httpProxy) {
         proxy.manual.httpProxy = this.createDefaultProxyLocation_(80);
@@ -250,7 +250,7 @@
     const proxy = proxySettings ? this.validateProxy_(proxySettings) :
                                   this.createDefaultProxySettings_();
 
-    if (proxy.type.activeValue == 'WPAD') {
+    if (proxy.type.activeValue === 'WPAD') {
       // Set the Web Proxy Auto Discovery URL for display purposes.
       const ipv4 = this.managedProperties ?
           OncMojo.getIPConfigForType(this.managedProperties, 'IPv4') :
@@ -325,7 +325,7 @@
   sendProxyChange_() {
     const mojom = chromeos.networkConfig.mojom;
     const proxyType = OncMojo.getActiveString(this.proxy_.type);
-    if (!proxyType || (proxyType == 'PAC' && !this.proxy_.pac)) {
+    if (!proxyType || (proxyType === 'PAC' && !this.proxy_.pac)) {
       return;
     }
 
@@ -334,7 +334,7 @@
       excludeDomains: OncMojo.getActiveValue(this.proxy_.excludeDomains),
     });
 
-    if (proxyType == 'Manual') {
+    if (proxyType === 'Manual') {
       let manual = {};
       if (this.proxy_.manual) {
         this.savedManual_ =
@@ -371,7 +371,7 @@
         }
       }
       proxy.manual = manual;
-    } else if (proxyType == 'PAC') {
+    } else if (proxyType === 'PAC') {
       proxy.pac = OncMojo.getActiveString(this.proxy_.pac);
     }
     this.fire('proxy-change', proxy);
@@ -456,7 +456,7 @@
    * @private
    */
   onAddProxyExclusionKeypress_(event) {
-    if (event.key != 'Enter') {
+    if (event.key !== 'Enter') {
       return;
     }
     event.stopPropagation();
@@ -483,13 +483,13 @@
    * @private
    */
   getProxyTypeDesc_(proxyType) {
-    if (proxyType == 'Manual') {
+    if (proxyType === 'Manual') {
       return this.i18n('networkProxyTypeManual');
     }
-    if (proxyType == 'PAC') {
+    if (proxyType === 'PAC') {
       return this.i18n('networkProxyTypePac');
     }
-    if (proxyType == 'WPAD') {
+    if (proxyType === 'WPAD') {
       return this.i18n('networkProxyTypeWpad');
     }
     return this.i18n('networkProxyTypeDirect');
@@ -531,8 +531,8 @@
       return false;
     }
     const source = this.managedProperties.source;
-    return source == chromeos.networkConfig.mojom.OncSource.kDevice ||
-        source == chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
+    return source === chromeos.networkConfig.mojom.OncSource.kDevice ||
+        source === chromeos.networkConfig.mojom.OncSource.kDevicePolicy;
   },
 
   /**
@@ -556,11 +556,11 @@
   /**
    * @param {string} property The property to test
    * @param {string} value The value to test against
-   * @return {boolean} True if property == value
+   * @return {boolean} True if property === value
    * @private
    */
   matches_(property, value) {
-    return property == value;
+    return property === value;
   },
 });
 })();
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_select.js b/ui/webui/resources/cr_components/chromeos/network/network_select.js
index eabea4d..a06d5c9 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_select.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_select.js
@@ -167,12 +167,12 @@
         defaultNetwork = state;
         break;
       }
-      if (state.connectionState == mojom.ConnectionStateType.kConnecting &&
+      if (state.connectionState === mojom.ConnectionStateType.kConnecting &&
           !defaultNetwork) {
         defaultNetwork = state;
         // Do not break here in case a non WiFi network is connecting but a
         // WiFi network is connected.
-      } else if (state.type == mojom.NetworkType.kWiFi) {
+      } else if (state.type === mojom.NetworkType.kWiFi) {
         break;  // Non connecting or connected WiFI networks are always last.
       }
     }
@@ -186,7 +186,7 @@
    */
   getNetwork(guid) {
     return this.networkStateList_.find(function(network) {
-      return network.guid == guid;
+      return network.guid === guid;
     });
   },
 
@@ -215,7 +215,7 @@
    */
   onGetNetworkStateList_(deviceStates, networkStates) {
     this.cellularDeviceState_ = deviceStates.find(function(device) {
-      return device.type == mojom.NetworkType.kCellular;
+      return device.type === mojom.NetworkType.kCellular;
     });
     if (this.cellularDeviceState_) {
       this.ensureCellularNetwork_(networkStates);
@@ -227,8 +227,8 @@
 
     if ((!defaultNetwork && !this.defaultNetworkState_) ||
         (defaultNetwork && this.defaultNetworkState_ &&
-         defaultNetwork.guid == this.defaultNetworkState_.guid &&
-         defaultNetwork.connectionState ==
+         defaultNetwork.guid === this.defaultNetworkState_.guid &&
+         defaultNetwork.connectionState ===
              this.defaultNetworkState_.connectionState)) {
       return;  // No change to network or ConnectionState
     }
@@ -248,13 +248,13 @@
    */
   ensureCellularNetwork_(networkStates) {
     if (networkStates.find(function(network) {
-          return network.type == mojom.NetworkType.kCellular;
+          return network.type === mojom.NetworkType.kCellular;
         })) {
       return;
     }
     const deviceState = this.cellularDeviceState_.deviceState;
-    if (deviceState == mojom.DeviceStateType.kDisabled ||
-        deviceState == mojom.DeviceStateType.kProhibited) {
+    if (deviceState === mojom.DeviceStateType.kDisabled ||
+        deviceState === mojom.DeviceStateType.kProhibited) {
       return;  // No Cellular network
     }
 
@@ -268,7 +268,7 @@
 
     // Insert the Cellular network after the Ethernet network if it exists.
     const idx = (networkStates.length > 0 &&
-                 networkStates[0].type == mojom.NetworkType.kEthernet) ?
+                 networkStates[0].type === mojom.NetworkType.kEthernet) ?
         1 :
         0;
     networkStates.splice(idx, 0, cellular);
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
index 1fb2ec66..39c0b991 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
@@ -173,9 +173,9 @@
     if (!simLockStatus) {
       return;
     }
-    this.pukRequired_ = simLockStatus.lockType == 'sim-puk';
+    this.pukRequired_ = simLockStatus.lockType === 'sim-puk';
     const lockEnabled = simLockStatus.lockEnabled;
-    if (lockEnabled != this.lockEnabled_) {
+    if (lockEnabled !== this.lockEnabled_) {
       this.setLockEnabled_ = lockEnabled;
       this.updateLockEnabled_();
     } else {
@@ -497,7 +497,7 @@
 
   /** @private */
   getErrorMsg_() {
-    if (this.error_ == ErrorType.NONE) {
+    if (this.error_ === ErrorType.NONE) {
       return '';
     }
     const retriesLeft = (this.simUnlockSent_ && this.deviceState &&
@@ -505,19 +505,19 @@
         this.deviceState.simLockStatus.retriesLeft :
         0;
 
-    if (this.error_ == ErrorType.INCORRECT_PIN) {
+    if (this.error_ === ErrorType.INCORRECT_PIN) {
       return this.i18n('networkSimErrorIncorrectPin', retriesLeft);
     }
-    if (this.error_ == ErrorType.INCORRECT_PUK) {
+    if (this.error_ === ErrorType.INCORRECT_PUK) {
       return this.i18n('networkSimErrorIncorrectPuk', retriesLeft);
     }
-    if (this.error_ == ErrorType.MISMATCHED_PIN) {
+    if (this.error_ === ErrorType.MISMATCHED_PIN) {
       return this.i18n('networkSimErrorPinMismatch');
     }
-    if (this.error_ == ErrorType.INVALID_PIN) {
+    if (this.error_ === ErrorType.INVALID_PIN) {
       return this.i18n('networkSimErrorInvalidPin', retriesLeft);
     }
-    if (this.error_ == ErrorType.INVALID_PUK) {
+    if (this.error_ === ErrorType.INVALID_PUK) {
       return this.i18n('networkSimErrorInvalidPuk', retriesLeft);
     }
     assertNotReached();
@@ -543,7 +543,7 @@
       this.focusDialogInput_();
       return false;
     }
-    if (opt_pin2 != undefined && pin1 != opt_pin2) {
+    if (opt_pin2 !== undefined && pin1 !== opt_pin2) {
       this.error_ = ErrorType.MISMATCHED_PIN;
       this.focusDialogInput_();
       return false;
diff --git a/ui/webui/resources/cr_components/chromeos/network/onc_mojo.js b/ui/webui/resources/cr_components/chromeos/network/onc_mojo.js
index 247e50e..c9234ed 100644
--- a/ui/webui/resources/cr_components/chromeos/network/onc_mojo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/onc_mojo.js
@@ -378,29 +378,29 @@
    * @return {number|string}
    */
   static getTypeString(key, value) {
-    if (key == 'activationState') {
+    if (key === 'activationState') {
       return OncMojo.getActivationStateTypeString(
           /** @type {!chromeos.networkConfig.mojom.ActivationStateType} */ (
               value));
     }
-    if (key == 'connectionState') {
+    if (key === 'connectionState') {
       return OncMojo.getConnectionStateTypeString(
           /** @type {!chromeos.networkConfig.mojom.ConnectionStateType} */ (
               value));
     }
-    if (key == 'deviceState') {
+    if (key === 'deviceState') {
       return OncMojo.getDeviceStateTypeString(
           /** @type {!chromeos.networkConfig.mojom.DeviceStateType} */ (value));
     }
-    if (key == 'type') {
+    if (key === 'type') {
       return OncMojo.getNetworkTypeString(
           /** @type {!chromeos.networkConfig.mojom.NetworkType} */ (value));
     }
-    if (key == 'source') {
+    if (key === 'source') {
       return OncMojo.getOncSourceString(
           /** @type {!chromeos.networkConfig.mojom.OncSource} */ (value));
     }
-    if (key == 'security') {
+    if (key === 'security') {
       return OncMojo.getSecurityTypeString(
           /** @type {!chromeos.networkConfig.mojom.SecurityType} */ (value));
     }
@@ -463,7 +463,7 @@
       return OncMojo.getNetworkTypeDisplayName(network.type);
     }
     const mojom = chromeos.networkConfig.mojom;
-    if (network.type == mojom.NetworkType.kVPN &&
+    if (network.type === mojom.NetworkType.kVPN &&
         network.typeState.vpn.providerName) {
       return OncMojo.getVpnDisplayName(
           network.name, network.typeState.vpn.providerName);
@@ -480,7 +480,7 @@
       return OncMojo.getNetworkTypeDisplayName(network.type);
     }
     const mojom = chromeos.networkConfig.mojom;
-    if (network.type == mojom.NetworkType.kVPN &&
+    if (network.type === mojom.NetworkType.kVPN &&
         network.typeProperties.vpn.providerName) {
       return OncMojo.getVpnDisplayName(
           network.name.activeValue, network.typeProperties.vpn.providerName);
@@ -626,13 +626,14 @@
         networkState.typeState.cellular.networkTechnology =
             cellularProperties.networkTechnology || '';
         networkState.typeState.cellular.roaming =
-            cellularProperties.roamingState == 'Roaming';
+            cellularProperties.roamingState === 'Roaming';
         networkState.typeState.cellular.signalStrength =
             cellularProperties.signalStrength;
         break;
       case mojom.NetworkType.kEthernet:
         networkState.typeState.ethernet.authentication =
-            properties.typeProperties.ethernet.authentication == '8021X' ?
+            OncMojo.getActiveValue(
+                properties.typeProperties.ethernet.authentication) === '8021X' ?
             mojom.AuthenticationType.k8021x :
             mojom.AuthenticationType.kNone;
         break;
@@ -827,14 +828,14 @@
     const ipConfigs = properties.ipConfigs;
     let ipConfig;
     if (ipConfigs) {
-      ipConfig = ipConfigs.find(ipconfig => ipconfig.type == desiredType);
-      if (ipConfig && desiredType != 'IPv4') {
+      ipConfig = ipConfigs.find(ipconfig => ipconfig.type === desiredType);
+      if (ipConfig && desiredType !== 'IPv4') {
         return ipConfig;
       }
     }
 
     // Only populate static ip config properties for IPv4.
-    if (desiredType != 'IPv4') {
+    if (desiredType !== 'IPv4') {
       return undefined;
     }
 
@@ -849,7 +850,7 @@
 
     // Merge the appropriate static values into the result.
     if (properties.ipAddressConfigType &&
-        properties.ipAddressConfigType.activeValue == 'Static') {
+        properties.ipAddressConfigType.activeValue === 'Static') {
       if (staticIpConfig.gateway) {
         ipConfig.gateway = staticIpConfig.gateway.activeValue;
       }
@@ -864,7 +865,7 @@
       }
     }
     if (properties.nameServersConfigType &&
-        properties.nameServersConfigType.activeValue == 'Static') {
+        properties.nameServersConfigType.activeValue === 'Static') {
       if (staticIpConfig.nameServers) {
         ipConfig.nameServers = staticIpConfig.nameServers.activeValue;
       }
@@ -885,21 +886,21 @@
    */
   static ipConfigPropertiesMatch(staticValue, newValue) {
     // If the existing type is unset, or the types do not match, return false.
-    if (!staticValue.type || staticValue.type.activeValue != newValue.type) {
+    if (!staticValue.type || staticValue.type.activeValue !== newValue.type) {
       return false;
     }
     if (newValue.gateway !== undefined &&
         (!staticValue.gateway ||
-         staticValue.gateway.activeValue != newValue.gateway)) {
+         staticValue.gateway.activeValue !== newValue.gateway)) {
       return false;
     }
     if (newValue.ipAddress !== undefined &&
         (!staticValue.ipAddress ||
-         staticValue.ipAddress.activeValue != newValue.ipAddress)) {
+         staticValue.ipAddress.activeValue !== newValue.ipAddress)) {
       return false;
     }
     if (!staticValue.routingPrefix ||
-        staticValue.routingPrefix.activeValue != newValue.routingPrefix) {
+        staticValue.routingPrefix.activeValue !== newValue.routingPrefix) {
       return false;
     }
     return true;
@@ -927,38 +928,38 @@
         'DHCP';
     let staticIpConfig = OncMojo.getIPConfigForType(managedProperties, 'IPv4');
     let nameServers = staticIpConfig ? staticIpConfig.nameServers : undefined;
-    if (field == 'ipAddressConfigType') {
+    if (field === 'ipAddressConfigType') {
       const newIpConfigType = /** @type {string} */ (newValue);
-      if (newIpConfigType == ipConfigType) {
+      if (newIpConfigType === ipConfigType) {
         return null;
       }
       ipConfigType = newIpConfigType;
-    } else if (field == 'nameServersConfigType') {
+    } else if (field === 'nameServersConfigType') {
       const newNsConfigType = /** @type {string} */ (newValue);
-      if (newNsConfigType == nsConfigType) {
+      if (newNsConfigType === nsConfigType) {
         return null;
       }
       nsConfigType = newNsConfigType;
-    } else if (field == 'staticIpConfig') {
+    } else if (field === 'staticIpConfig') {
       const ipConfigValue =
           /** @type {!mojom.IPConfigProperties} */ (newValue);
       if (!ipConfigValue.type || !ipConfigValue.ipAddress) {
         console.error('Invalid StaticIPConfig: ' + JSON.stringify(newValue));
         return null;
       }
-      if (ipConfigType == 'Static' && staticIpConfig &&
+      if (ipConfigType === 'Static' && staticIpConfig &&
           OncMojo.ipConfigPropertiesMatch(staticIpConfig, ipConfigValue)) {
         return null;
       }
       ipConfigType = 'Static';
       staticIpConfig = ipConfigValue;
-    } else if (field == 'nameServers') {
+    } else if (field === 'nameServers') {
       const newNameServers = /** @type {!Array<string>} */ (newValue);
       if (!newNameServers || !newNameServers.length) {
         console.error('Invalid NameServers: ' + JSON.stringify(newValue));
       }
-      if (nsConfigType == 'Static' &&
-          JSON.stringify(nameServers) == JSON.stringify(newNameServers)) {
+      if (nsConfigType === 'Static' &&
+          JSON.stringify(nameServers) === JSON.stringify(newNameServers)) {
         return null;
       }
       nsConfigType = 'Static';
@@ -972,11 +973,11 @@
     const config = OncMojo.getDefaultConfigProperties(managedProperties.type);
     config.ipAddressConfigType = ipConfigType;
     config.nameServersConfigType = nsConfigType;
-    if (ipConfigType == 'Static') {
+    if (ipConfigType === 'Static') {
       assert(staticIpConfig && staticIpConfig.type && staticIpConfig.ipAddress);
       config.staticIpConfig = staticIpConfig;
     }
-    if (nsConfigType == 'Static') {
+    if (nsConfigType === 'Static') {
       assert(nameServers && nameServers.length);
       config.staticIpConfig = config.staticIpConfig ||
           /** @type{!mojom.IPConfigProperties}*/ ({routingPrefix: 0});
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/OWNERS b/ui/webui/resources/cr_components/chromeos/quick_unlock/OWNERS
index 940c88e..1c8319a 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/OWNERS
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/OWNERS
@@ -1,4 +1,3 @@
 alemate@chromium.org
-stevenjb@chromium.org
 
 # COMPONENT: UI>Settings
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
index 7bc96290..0f5754b 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
@@ -303,7 +303,7 @@
     // character in front of the caret.
     let selectionStart = this.selectionStart_;
     const selectionEnd = this.selectionEnd_;
-    if (selectionStart == selectionEnd && selectionStart) {
+    if (selectionStart === selectionEnd && selectionStart) {
       selectionStart--;
     }
 
@@ -409,7 +409,7 @@
 
     // Valid if the key is CTRL+A to allow users to quickly select the entire
     // PIN.
-    if (event.keyCode == 65 && event.ctrlKey) {
+    if (event.keyCode === 65 && event.ctrlKey) {
       return true;
     }
 
@@ -442,13 +442,13 @@
   onInputKeyDown_(event) {
     // Up/down pressed, swallow the event to prevent the input value from
     // being incremented or decremented.
-    if (event.keyCode == 38 || event.keyCode == 40) {
+    if (event.keyCode === 38 || event.keyCode === 40) {
       event.preventDefault();
       return;
     }
 
     // Enter pressed.
-    if (event.keyCode == 13) {
+    if (event.keyCode === 13) {
       this.firePinSubmitEvent_();
       event.preventDefault();
       return;
@@ -509,7 +509,7 @@
     // Since we still support users entering their passwords through the PIN
     // keyboard, we swap the input box to rtl when we think it is a password
     // (just numbers), if the document direction is rtl.
-    return (document.dir == 'rtl') && !Number.isInteger(+password);
+    return (document.dir === 'rtl') && !Number.isInteger(+password);
   },
 
   /**
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js b/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js
index e29f0360b..ce605e0 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/setup_pin_keyboard.js
@@ -172,7 +172,7 @@
    * @return {boolean}
    */
   canSubmit_() {
-    return this.initialPin_ == this.pinKeyboardValue_;
+    return this.initialPin_ === this.pinKeyboardValue_;
   },
 
   /**
@@ -215,8 +215,8 @@
         this.processPinRequirements_.bind(this, messageId));
     this.problemClass_ = problemClass;
     this.updateStyles();
-    this.enableSubmit =
-        problemClass != ProblemType.ERROR && messageId != MessageType.TOO_SHORT;
+    this.enableSubmit = problemClass !== ProblemType.ERROR &&
+        messageId !== MessageType.TOO_SHORT;
   },
 
   /** @private */
@@ -241,14 +241,14 @@
     }
 
     if (!message.errors.length ||
-        message.errors[0] !=
+        message.errors[0] !==
             chrome.quickUnlockPrivate.CredentialProblem.TOO_SHORT) {
       this.pinHasPassedMinimumLength_ = true;
     }
 
     if (message.warnings.length) {
       assert(
-          message.warnings[0] ==
+          message.warnings[0] ===
           chrome.quickUnlockPrivate.CredentialProblem.TOO_WEAK);
       this.showProblem_(MessageType.TOO_WEAK, ProblemType.WARNING);
     }
@@ -356,7 +356,7 @@
    * @return {boolean}
    */
   hasError_(problemMessageId, problemClass) {
-    return !!problemMessageId && problemClass == ProblemType.ERROR;
+    return !!problemMessageId && problemClass === ProblemType.ERROR;
   },
 
   /**
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
index e323e98..56c44e1 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/add_smb_share_dialog.js
@@ -196,7 +196,7 @@
    * @private
    */
   shouldShowCredentialUI_() {
-    return this.authenticationMethod_ == SmbAuthMethod.CREDENTIALS;
+    return this.authenticationMethod_ === SmbAuthMethod.CREDENTIALS;
   },
 
   /**
@@ -215,7 +215,7 @@
     this.inProgress_ = false;
 
     // Success case. Close dialog.
-    if (result == SmbMountResult.SUCCESS) {
+    if (result === SmbMountResult.SUCCESS) {
       this.$.dialog.close();
       return;
     }
@@ -296,7 +296,7 @@
    * @private
    */
   shouldShowCredentialError_() {
-    return this.currentMountError_ == MountErrorType.CREDENTIAL_ERROR;
+    return this.currentMountError_ === MountErrorType.CREDENTIAL_ERROR;
   },
 
   /**
@@ -304,7 +304,7 @@
    * @private
    */
   shouldShowGeneralError_() {
-    return this.currentMountError_ == MountErrorType.GENERAL_ERROR;
+    return this.currentMountError_ === MountErrorType.GENERAL_ERROR;
   },
 
   /**
@@ -312,7 +312,7 @@
    * @private
    */
   shouldShowPathError_() {
-    return this.currentMountError_ == MountErrorType.PATH_ERROR;
+    return this.currentMountError_ === MountErrorType.PATH_ERROR;
   },
 
   /**
diff --git a/ui/webui/resources/cr_components/chromeos/smb_shares/smb_browser_proxy.js b/ui/webui/resources/cr_components/chromeos/smb_shares/smb_browser_proxy.js
index 4a663ca6..1456587 100644
--- a/ui/webui/resources/cr_components/chromeos/smb_shares/smb_browser_proxy.js
+++ b/ui/webui/resources/cr_components/chromeos/smb_shares/smb_browser_proxy.js
@@ -77,8 +77,8 @@
         shouldOpenFileManagerAfterMount, saveCredentials) {
       return cr.sendWithPromise(
           'smbMount', smbUrl, smbName, username, password,
-          authMethod == SmbAuthMethod.KERBEROS, shouldOpenFileManagerAfterMount,
-          saveCredentials);
+          authMethod === SmbAuthMethod.KERBEROS,
+          shouldOpenFileManagerAfterMount, saveCredentials);
     }
 
     /** @override */
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_lottie/cr_lottie.js b/ui/webui/resources/cr_elements/chromeos/cr_lottie/cr_lottie.js
index 00623132..2e65ac5 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_lottie/cr_lottie.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_lottie/cr_lottie.js
@@ -135,7 +135,7 @@
   isValidUrl_(maybeValidUrl) {
     const url = new URL(maybeValidUrl, document.location.href);
     return url.protocol === 'chrome:' ||
-        (url.protocol == 'data:' &&
+        (url.protocol === 'data:' &&
          url.pathname.startsWith('application/json;'));
   },
 
@@ -156,7 +156,7 @@
     xhr.responseType = responseType;
     xhr.send();
     xhr.onreadystatechange = function() {
-      if (xhr.readyState == 4 && xhr.status == 200) {
+      if (xhr.readyState === 4 && xhr.status === 200) {
         successCallback(xhr.response);
       }
     };
@@ -196,14 +196,14 @@
    * @private
    */
   onMessage_(event) {
-    if (event.data.name == 'initialized' && event.data.success) {
+    if (event.data.name === 'initialized' && event.data.success) {
       this.isAnimationLoaded_ = true;
       this.fire('cr-lottie-initialized');
-    } else if (event.data.name == 'playing') {
+    } else if (event.data.name === 'playing') {
       this.fire('cr-lottie-playing');
-    } else if (event.data.name == 'paused') {
+    } else if (event.data.name === 'paused') {
       this.fire('cr-lottie-paused');
-    } else if (event.data.name == 'resized') {
+    } else if (event.data.name === 'resized') {
       this.fire('cr-lottie-resized', event.data.size);
     }
   },
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
index 15b9c167..3e85f28 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_camera.js
@@ -275,7 +275,7 @@
     });
 
     /** No need for further processing if single frame. */
-    if (encodedImages.length == 1) {
+    if (encodedImages.length === 1) {
       return encodedImages[0];
     }
 
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
index 39b3bbc5..0bf67b97 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_list.js
@@ -130,7 +130,7 @@
    */
   setSelectedImageUrl(imageUrl) {
     const image = this.$.selector.items.find(function(image) {
-      return image.dataset.url == imageUrl;
+      return image.dataset.url === imageUrl;
     });
     if (image) {
       this.setSelectedImage_(image);
@@ -145,7 +145,7 @@
    * @param {number=} imageIndex
    */
   setOldImageUrl(imageUrl, imageIndex) {
-    if (imageUrl == CrPicture.kDefaultImageUrl || imageIndex === 0) {
+    if (imageUrl === CrPicture.kDefaultImageUrl || imageIndex === 0) {
       // Treat the default image as empty so it does not show in the list.
       this.oldImageUrl_ = '';
       this.setSelectedImageUrl(CrPicture.kDefaultImageUrl);
@@ -160,7 +160,7 @@
       this.$.selector.select(this.$.selector.indexOf(this.$.cameraImage));
     } else if (
         this.fallbackImage_ &&
-        this.fallbackImage_.dataset.type != CrPicture.SelectionTypes.OLD) {
+        this.fallbackImage_.dataset.type !== CrPicture.SelectionTypes.OLD) {
       this.selectImage_(this.fallbackImage_, true /* activate */);
     } else {
       this.selectImage_(this.$.profileImage, true /* activate */);
@@ -188,13 +188,15 @@
       case 'left':
         do {
           selector.selectPrevious();
-        } while (this.selectedItem.hidden && this.selectedItem != prevSelected);
+        } while (this.selectedItem.hidden &&
+                 this.selectedItem !== prevSelected);
         break;
       case 'down':
       case 'right':
         do {
           selector.selectNext();
-        } while (this.selectedItem.hidden && this.selectedItem != prevSelected);
+        } while (this.selectedItem.hidden &&
+                 this.selectedItem !== prevSelected);
         break;
       default:
         return;
@@ -210,7 +212,7 @@
     this.fallbackImage_ = image;
     // If the user is currently taking a photo, do not change the focus.
     if (!this.selectedItem ||
-        this.selectedItem.dataset.type != CrPicture.SelectionTypes.CAMERA) {
+        this.selectedItem.dataset.type !== CrPicture.SelectionTypes.CAMERA) {
       this.$.selector.select(this.$.selector.indexOf(image));
       this.selectedItem = image;
     }
@@ -230,15 +232,15 @@
    */
   selectImage_(selected, activate) {
     this.cameraSelected_ =
-        selected.dataset.type == CrPicture.SelectionTypes.CAMERA;
+        selected.dataset.type === CrPicture.SelectionTypes.CAMERA;
     this.selectedItem = selected;
 
-    if (selected.dataset.type == CrPicture.SelectionTypes.CAMERA) {
+    if (selected.dataset.type === CrPicture.SelectionTypes.CAMERA) {
       if (activate) {
         this.fire('focus-action', selected);
       }
     } else if (
-        activate || selected.dataset.type != CrPicture.SelectionTypes.FILE) {
+        activate || selected.dataset.type !== CrPicture.SelectionTypes.FILE) {
       this.fire('image-activate', selected);
     }
   },
@@ -251,7 +253,7 @@
     event.stopPropagation();
     const type = event.detail.item.dataset.type;
     // Don't change focus when activating the camera via mouse.
-    const activate = type != CrPicture.SelectionTypes.CAMERA;
+    const activate = type !== CrPicture.SelectionTypes.CAMERA;
     this.selectImage_(event.detail.item, activate);
   },
 
@@ -289,7 +291,7 @@
      * Extract first frame from image by creating a single frame PNG using
      * url as input if base64 encoded and potentially animated.
      */
-    if (url.split(',')[0] == 'data:image/png;base64') {
+    if (url.split(',')[0] === 'data:image/png;base64') {
       return CrPngBehavior.convertImageSequenceToPng([url]);
     }
 
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.js
index 8791403..7b4ff52 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_picture_pane.js
@@ -18,13 +18,13 @@
     /** Whether the camera is present / available */
     cameraPresent: Boolean,
 
-    /** Image source to show when imageType != CAMERA. */
+    /** Image source to show when imageType !== CAMERA. */
     imageSrc: {
       type: String,
       observer: 'imageSrcChanged_',
     },
 
-    /** Image URL to use when imageType != CAMERA. */
+    /** Image URL to use when imageType !== CAMERA. */
     imageUrl: {
       type: String,
       value: '',
@@ -84,7 +84,7 @@
    */
   getCameraActive_() {
     return this.cameraPresent &&
-        this.imageType == CrPicture.SelectionTypes.CAMERA;
+        this.imageType === CrPicture.SelectionTypes.CAMERA;
   },
 
   /** @private */
@@ -145,7 +145,7 @@
    * @private
    */
   showDiscard_() {
-    return this.imageType == CrPicture.SelectionTypes.OLD;
+    return this.imageType === CrPicture.SelectionTypes.OLD;
   },
 
   /** @private */
diff --git a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_png_behavior.js b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_png_behavior.js
index 895ac39e6..980a110 100644
--- a/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_png_behavior.js
+++ b/ui/webui/resources/cr_elements/chromeos/cr_picture/cr_png_behavior.js
@@ -220,7 +220,7 @@
    */
   isEncodedPngDataUrlAnimated(url) {
     const decoded = atob(url.substr('data:image/png;base64,'.length));
-    return decoded.substr(37, 4) == 'acTL';
+    return decoded.substr(37, 4) === 'acTL';
   },
 
   /**
@@ -359,7 +359,7 @@
 
     /** Check signature. */
     const signature = bytes.subarray(0, PNG_SIGNATURE.length);
-    if (signature.toString() != PNG_SIGNATURE.toString()) {
+    if (signature.toString() !== PNG_SIGNATURE.toString()) {
       console.error('Bad PNG signature');
     }
 
@@ -407,7 +407,7 @@
       const chunk = bytes.subarray(i + 8, i + 8 + length);
 
       /** We should have enough bytes left for length. */
-      if (length != chunk.length) {
+      if (length !== chunk.length) {
         console.error('Unexpectedly reached end of file');
       }
 
@@ -433,38 +433,38 @@
           const interlace = chunk[12];
 
           /** Initialize size and colour if this is the first frame. */
-          if (png.frames == 0) {
+          if (png.frames === 0) {
             png.width = width;
             png.height = height;
             png.colour = colour;
           }
 
           /** Check that header matches our expectations. */
-          if (width != png.width) {
+          if (width !== png.width) {
             console.error('Bad PNG width: ' + width);
           }
-          if (height != png.height) {
+          if (height !== png.height) {
             console.error('Bad PNG height: ' + height);
           }
-          if (depth != PNG_BIT_DEPTH) {
+          if (depth !== PNG_BIT_DEPTH) {
             console.error('Bad PNG bit depth: ' + depth);
           }
-          if (colour != png.colour) {
+          if (colour !== png.colour) {
             console.error('Bad PNG colour type: ' + colour);
           }
-          if (compression != PNG_COMPRESSION_METHOD) {
+          if (compression !== PNG_COMPRESSION_METHOD) {
             console.error('Bad PNG compression method: ' + compression);
           }
-          if (filter != PNG_FILTER_METHOD) {
+          if (filter !== PNG_FILTER_METHOD) {
             console.error('Bad PNG filter method: ' + filter);
           }
-          if (interlace != PNG_INTERLACE_METHOD) {
+          if (interlace !== PNG_INTERLACE_METHOD) {
             console.error('Bad PNG interlace method: ' + interlace);
           }
           break;
         case 'IDAT':
           /** Append as IDAT chunk if this is the first frame. */
-          if (png.frames == 0) {
+          if (png.frames === 0) {
             /**
              * http://www.w3.org/TR/2003/REC-PNG-20031110/#11IDAT
              *
diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
index 5afec01b..21ba352b 100644
--- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
+++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
@@ -226,7 +226,7 @@
    * @private
    */
   onClick_(e) {
-    if (e.target == this) {
+    if (e.target === this) {
       this.close();
       e.stopPropagation();
     }
@@ -238,29 +238,29 @@
    */
   onKeyDown_(e) {
     e.stopPropagation();
-    if (e.key == 'Tab' || e.key == 'Escape') {
+    if (e.key === 'Tab' || e.key === 'Escape') {
       this.close();
       e.preventDefault();
       return;
     }
 
-    if (e.key != 'Enter' && e.key != 'ArrowUp' && e.key != 'ArrowDown') {
+    if (e.key !== 'Enter' && e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {
       return;
     }
 
     const query = '.dropdown-item:not([disabled]):not([hidden])';
     const options = Array.from(this.querySelectorAll(query));
-    if (options.length == 0) {
+    if (options.length === 0) {
       return;
     }
 
     const focused = getDeepActiveElement();
     const index = options.findIndex(
-        option => cr.ui.FocusRow.getFocusableElement(option) == focused);
+        option => cr.ui.FocusRow.getFocusableElement(option) === focused);
 
-    if (e.key == 'Enter') {
+    if (e.key === 'Enter') {
       // If a menu item has focus, don't change focus or close menu on 'Enter'.
-      if (index != -1) {
+      if (index !== -1) {
         return;
       }
 
@@ -272,7 +272,7 @@
     }
 
     e.preventDefault();
-    this.updateFocus_(options, index, e.key != 'ArrowUp');
+    this.updateFocus_(options, index, e.key !== 'ArrowUp');
 
     if (!this.hasMousemoveListener_) {
       this.hasMousemoveListener_ = true;
@@ -303,7 +303,7 @@
     const numOptions = options.length;
     assert(numOptions > 0);
     let index;
-    if (focusedIndex == -1) {
+    if (focusedIndex === -1) {
       index = next ? 0 : numOptions - 1;
     } else {
       const delta = next ? 1 : -1;
@@ -341,7 +341,7 @@
 
     let height = rect.height;
     if (opt_config &&
-        opt_config.anchorAlignmentY == AnchorAlignment.AFTER_END) {
+        opt_config.anchorAlignmentY === AnchorAlignment.AFTER_END) {
       // When an action menu is positioned after the end of an element, the
       // action menu can appear too far away from the anchor element, typically
       // because anchors tend to have padding. So we offset the height a bit
@@ -443,7 +443,7 @@
     const right = left + c.width;
 
     // Flip the X anchor in RTL.
-    const rtl = getComputedStyle(this).direction == 'rtl';
+    const rtl = getComputedStyle(this).direction === 'rtl';
     if (rtl) {
       c.anchorAlignmentX *= -1;
     }
diff --git a/ui/webui/resources/cr_elements/cr_button/cr_button.js b/ui/webui/resources/cr_elements/cr_button/cr_button.js
index 8876216..12df8ec 100644
--- a/ui/webui/resources/cr_elements/cr_button/cr_button.js
+++ b/ui/webui/resources/cr_elements/cr_button/cr_button.js
@@ -83,7 +83,7 @@
    * @private
    */
   disabledChanged_(newValue, oldValue) {
-    if (!newValue && oldValue == undefined) {
+    if (!newValue && oldValue === undefined) {
       return;
     }
     if (this.disabled) {
@@ -108,7 +108,7 @@
    * @private
    */
   onKeyDown_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
@@ -120,7 +120,7 @@
     }
 
     this.getRipple().uiDownAction();
-    if (e.key == 'Enter') {
+    if (e.key === 'Enter') {
       this.click();
       // Delay was chosen manually as a good time period for the ripple to be
       // visible.
@@ -133,14 +133,14 @@
    * @private
    */
   onKeyUp_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
-    if (e.key == ' ') {
+    if (e.key === ' ') {
       this.click();
       this.getRipple().uiUpAction();
     }
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
index 4b1386f..dd96835 100644
--- a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
+++ b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
@@ -98,7 +98,7 @@
    * @private
    */
   onClick_(e) {
-    if (this.disabled || e.target.tagName == 'A') {
+    if (this.disabled || e.target.tagName === 'A') {
       return;
     }
 
@@ -116,7 +116,7 @@
    * @private
    */
   onKeyDown_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
@@ -126,7 +126,7 @@
       return;
     }
 
-    if (e.key == 'Enter') {
+    if (e.key === 'Enter') {
       this.click();
     }
   },
@@ -136,12 +136,12 @@
    * @private
    */
   onKeyUp_(e) {
-    if (e.key == ' ' || e.key == 'Enter') {
+    if (e.key === ' ' || e.key === 'Enter') {
       e.preventDefault();
       e.stopPropagation();
     }
 
-    if (e.key == ' ') {
+    if (e.key === ' ') {
       this.click();
     }
   },
diff --git a/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js b/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
index 3474f7d..8aa826b 100644
--- a/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
+++ b/ui/webui/resources/cr_elements/cr_container_shadow_behavior.js
@@ -110,7 +110,7 @@
         this.sides_.forEach(side => {
           if (target === this.intersectionProbes_.get(side)) {
             this.dropShadows_.get(side).classList.toggle(
-                'has-shadow', entry.intersectionRatio == 0);
+                'has-shadow', entry.intersectionRatio === 0);
           }
         });
       }
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
index 7930f473..aa4bfb61 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
@@ -270,7 +270,7 @@
    * @private
    */
   onKeypress_(e) {
-    if (e.key != 'Enter') {
+    if (e.key !== 'Enter') {
       return;
     }
 
@@ -281,7 +281,7 @@
     // trigger searching.
     const accept = e.target === this ||
         e.composedPath().some(
-            el => el.tagName == 'CR-INPUT' && el.type != 'search');
+            el => el.tagName === 'CR-INPUT' && el.type !== 'search');
     if (!accept) {
       return;
     }
@@ -304,7 +304,7 @@
       return;
     }
 
-    if (this.ignoreEnterKey && e.key == 'Enter') {
+    if (this.ignoreEnterKey && e.key === 'Enter') {
       return;
     }
 
@@ -316,7 +316,7 @@
   onPointerdown_(e) {
     // Only show pulse animation if user left-clicked outside of the dialog
     // contents.
-    if (e.button != 0 || e.composedPath()[0].tagName !== 'DIALOG') {
+    if (e.button !== 0 || e.composedPath()[0].tagName !== 'DIALOG') {
       return;
     }
 
diff --git a/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js b/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
index 116be281..0396618 100644
--- a/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
+++ b/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
@@ -91,7 +91,7 @@
 
   /** @return {boolean} */
   wasCanceled() {
-    return !this.open && this.$.dialog.returnValue == 'canceled';
+    return !this.open && this.$.dialog.returnValue === 'canceled';
   },
 
   /**
diff --git a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
index 801fd33..df323de1 100644
--- a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
+++ b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
@@ -113,7 +113,7 @@
    * @private
    */
   disabledChanged_(newValue, oldValue) {
-    if (!newValue && oldValue == undefined) {
+    if (!newValue && oldValue === undefined) {
       return;
     }
     if (this.disabled) {
@@ -160,7 +160,7 @@
    * @private
    */
   onKeyDown_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
@@ -170,7 +170,7 @@
       return;
     }
 
-    if (e.key == 'Enter') {
+    if (e.key === 'Enter') {
       this.click();
     }
   },
@@ -180,12 +180,12 @@
    * @private
    */
   onKeyUp_(e) {
-    if (e.key == ' ' || e.key == 'Enter') {
+    if (e.key === ' ' || e.key === 'Enter') {
       e.preventDefault();
       e.stopPropagation();
     }
 
-    if (e.key == ' ') {
+    if (e.key === ' ') {
       this.click();
     }
   },
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.js b/ui/webui/resources/cr_elements/cr_input/cr_input.js
index a3df7b1d..75601fd 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -257,7 +257,7 @@
    * @private
    */
   placeholderChanged_() {
-    if (this.placeholder || this.placeholder == '') {
+    if (this.placeholder || this.placeholder === '') {
       this.inputElement.setAttribute('placeholder', this.placeholder);
     } else {
       this.inputElement.removeAttribute('placeholder');
@@ -282,7 +282,7 @@
    * @return {boolean} Whether the <input> element was focused.
    */
   focusInput() {
-    if (this.shadowRoot.activeElement == this.inputElement) {
+    if (this.shadowRoot.activeElement === this.inputElement) {
       return false;
     }
     this.inputElement.focus();
diff --git a/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector_grid.js b/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector_grid.js
index 3e1f78d..ee7a9c0 100644
--- a/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector_grid.js
+++ b/ui/webui/resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector_grid.js
@@ -56,7 +56,7 @@
    */
   moveFocusRow_(items, direction) {
     let offset =
-        (direction == 'ArrowDown' || direction == 'ArrowRight') ? 1 : -1;
+        (direction === 'ArrowDown' || direction === 'ArrowRight') ? 1 : -1;
     const style = getComputedStyle(this);
     const avatarSpacing =
         parseInt(style.getPropertyValue('--avatar-spacing'), 10);
@@ -67,11 +67,11 @@
 
     const focusIndex =
         Array.prototype.slice.call(items).findIndex(function(item) {
-          return Polymer.dom(item).getOwnerRoot().activeElement == item;
+          return Polymer.dom(item).getOwnerRoot().activeElement === item;
         });
 
     let nextItem = null;
-    if (direction == 'ArrowDown' || direction == 'ArrowUp') {
+    if (direction === 'ArrowDown' || direction === 'ArrowUp') {
       for (let i = offset; Math.abs(i) <= rows; i += offset) {
         nextItem = items[(focusIndex + i * rowSize + gridSize) % gridSize];
         if (nextItem) {
@@ -82,7 +82,7 @@
         // end.
       }
     } else {
-      if (style.direction == 'rtl') {
+      if (style.direction === 'rtl') {
         offset *= -1;
       }
       let nextIndex = (focusIndex + offset) % items.length;
@@ -93,6 +93,6 @@
     }
 
     nextItem.focus();
-    assert(Polymer.dom(nextItem).getOwnerRoot().activeElement == nextItem);
+    assert(Polymer.dom(nextItem).getOwnerRoot().activeElement === nextItem);
   }
 });
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
index 3b7412c3..696aca04 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
+++ b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
@@ -10,7 +10,7 @@
    */
   function isEnabled(radio) {
     return radio.matches(':not([disabled]):not([hidden])') &&
-        radio.style.display != 'none' && radio.style.visibility != 'hidden';
+        radio.style.display !== 'none' && radio.style.visibility !== 'hidden';
   }
 
   Polymer({
@@ -142,22 +142,22 @@
         return;
       }
 
-      if (event.key == ' ' || event.key == 'Enter') {
+      if (event.key === ' ' || event.key === 'Enter') {
         event.preventDefault();
         this.select_(/** @type {!CrRadioButtonElement} */ (event.target));
         return;
       }
 
       const enabledRadios = this.buttons_.filter(isEnabled);
-      if (enabledRadios.length == 0) {
+      if (enabledRadios.length === 0) {
         return;
       }
 
       let selectedIndex;
       const max = enabledRadios.length - 1;
-      if (event.key == 'Home') {
+      if (event.key === 'Home') {
         selectedIndex = 0;
-      } else if (event.key == 'End') {
+      } else if (event.key === 'End') {
         selectedIndex = max;
       } else if (this.deltaKeyMap_.has(event.key)) {
         const delta = this.deltaKeyMap_.get(event.key);
@@ -176,7 +176,7 @@
 
       const radio = enabledRadios[selectedIndex];
       const name = `${radio.name}`;
-      if (this.selected != name) {
+      if (this.selected !== name) {
         event.preventDefault();
         this.selected = name;
         radio.focus();
@@ -237,7 +237,7 @@
       }
 
       const name = `${button.name}`;
-      if (this.selected != name) {
+      if (this.selected !== name) {
         this.selected = name;
       }
     },
@@ -258,8 +258,8 @@
       }
       let noneMadeFocusable = true;
       this.buttons_.forEach(radio => {
-        radio.checked = this.selected != undefined &&
-            radio.name == this.selected;
+        radio.checked =
+            this.selected !== undefined && radio.name === this.selected;
         const disabled = this.disabled || !isEnabled(radio);
         const canBeFocused = radio.checked && !disabled;
         if (canBeFocused) {
diff --git a/ui/webui/resources/cr_elements/cr_scrollable_behavior.js b/ui/webui/resources/cr_elements/cr_scrollable_behavior.js
index 5bb4a96..430ce17 100644
--- a/ui/webui/resources/cr_elements/cr_scrollable_behavior.js
+++ b/ui/webui/resources/cr_elements/cr_scrollable_behavior.js
@@ -101,7 +101,7 @@
         // |scrollHeight| is updated to be greater than 1px, another resize is
         // needed to correctly calculate the number of physical iron-list items
         // to render.
-        if (scrollHeight != lastScrollHeight) {
+        if (scrollHeight !== lastScrollHeight) {
           const ironList = /** @type {!IronListElement} */ (node);
           ironList.notifyResize();
         }
@@ -112,7 +112,7 @@
           });
         }
       });
-      if (checkAgain.length == 0) {
+      if (checkAgain.length === 0) {
         window.clearInterval(this.intervalId_);
         this.intervalId_ = null;
       } else {
@@ -150,7 +150,7 @@
       const scrollTop = list.savedScrollTops.shift();
       // Ignore scrollTop of 0 in case it was intermittent (we do not need to
       // explicitly scroll to 0).
-      if (scrollTop != 0) {
+      if (scrollTop !== 0) {
         list.scroll(0, scrollTop);
       }
     });
diff --git a/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js b/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
index b5256c4..86adfdd 100644
--- a/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
+++ b/ui/webui/resources/cr_elements/cr_search_field/cr_search_field_behavior.js
@@ -57,7 +57,7 @@
     if (!updated) {
       // If the input is only whitespace and value is empty, |hasSearchText|
       // needs to be updated.
-      if (value == '' && this.hasSearchText) {
+      if (value === '' && this.hasSearchText) {
         this.hasSearchText = false;
       }
       return;
@@ -100,7 +100,7 @@
    * after any change, whether the result of user input or JS modification.
    */
   onSearchTermInput() {
-    this.hasSearchText = this.$.searchInput.value != '';
+    this.hasSearchText = this.$.searchInput.value !== '';
     this.scheduleSearch_();
   },
 
@@ -129,7 +129,7 @@
    */
   updateEffectiveValue_(value) {
     const effectiveValue = value.replace(/\s+/g, ' ').replace(/^\s/, '');
-    if (effectiveValue == this.effectiveValue_) {
+    if (effectiveValue === this.effectiveValue_) {
       return false;
     }
 
diff --git a/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js b/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
index 4a9988cb..012377f 100644
--- a/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
+++ b/ui/webui/resources/cr_elements/cr_searchable_drop_down/cr_searchable_drop_down.js
@@ -192,7 +192,7 @@
     // keyboard, the selection will shift. But once the user moves the mouse,
     // selection should be updated based on the location of the mouse cursor.
     const selectedItem = this.findSelectedItem_();
-    if (item == selectedItem) {
+    if (item === selectedItem) {
       return;
     }
 
@@ -246,7 +246,7 @@
       if (this.readonly) {
         return;
       }
-      if (event.code == 'Enter') {
+      if (event.code === 'Enter') {
         this.openDropdown_();
         // Stop the default submit action.
         event.preventDefault();
@@ -265,10 +265,10 @@
       case 'ArrowDown': {
         const selected = this.findSelectedItemIndex_();
         const items = dropdown.getElementsByClassName('list-item');
-        if (items.length == 0) {
+        if (items.length === 0) {
           break;
         }
-        this.updateSelected_(items, selected, event.code == 'ArrowDown');
+        this.updateSelected_(items, selected, event.code === 'ArrowDown');
         break;
       }
       case 'Enter': {
@@ -322,7 +322,7 @@
   updateSelected_(items, currentIndex, moveDown) {
     const numItems = items.length;
     let nextIndex = 0;
-    if (currentIndex == -1) {
+    if (currentIndex === -1) {
       nextIndex = moveDown ? 0 : numItems - 1;
     } else {
       const delta = moveDown ? 1 : -1;
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
index 47aba12..d673ad3 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
@@ -38,7 +38,7 @@
   function getAriaValue(tick) {
     if (Number.isFinite(/** @type {number} */ (tick))) {
       return /** @type {number} */ (tick);
-    } else if (tick.ariaValue != undefined) {
+    } else if (tick.ariaValue !== undefined) {
       return /** @type {number} */ (tick.ariaValue);
     } else {
       return tick.value;
@@ -209,7 +209,7 @@
      * @private
      */
     computeDisabled_() {
-      return this.disabled || this.ticks.length == 1;
+      return this.disabled || this.ticks.length === 1;
     },
 
     /**
@@ -291,15 +291,15 @@
 
       /** @type {number|undefined} */
       let newValue;
-      if (event.key == 'Home') {
+      if (event.key === 'Home') {
         newValue = this.min;
-      } else if (event.key == 'End') {
+      } else if (event.key === 'End') {
         newValue = this.max;
       } else if (this.deltaKeyMap_.has(event.key)) {
         newValue = this.value + this.deltaKeyMap_.get(event.key);
       }
 
-      if (newValue == undefined) {
+      if (newValue === undefined) {
         return;
       }
 
@@ -317,7 +317,7 @@
      * @private
      */
     onKeyUp_(event) {
-      if (event.key == 'Home' || event.key == 'End' ||
+      if (event.key === 'Home' || event.key === 'End' ||
           this.deltaKeyMap_.has(event.key)) {
         setTimeout(() => {
           this.updatingFromKey = false;
@@ -333,7 +333,7 @@
      */
     onPointerDown_(event) {
       if (this.disabled_ ||
-          event.buttons != 1 && event.pointerType == 'mouse') {
+          event.buttons !== 1 && event.pointerType === 'mouse') {
         return;
       }
 
@@ -353,7 +353,7 @@
         // If the left-button on the mouse is pressed by itself, then update.
         // Otherwise stop capturing the mouse events because the drag operation
         // is complete.
-        if (e.buttons != 1 && e.pointerType == 'mouse') {
+        if (e.buttons !== 1 && e.pointerType === 'mouse') {
           stopDragging();
           return;
         }
@@ -363,8 +363,8 @@
       this.draggingEventTracker_.add(this, 'pointerdown', stopDragging);
       this.draggingEventTracker_.add(this, 'pointerup', stopDragging);
       this.draggingEventTracker_.add(this, 'keydown', e => {
-        if (e.key == 'Escape' || e.key == 'Tab' || e.key == 'Home' ||
-            e.key == 'End' || this.deltaKeyMap_.has(e.key)) {
+        if (e.key === 'Escape' || e.key === 'Tab' || e.key === 'Home' ||
+            e.key === 'End' || this.deltaKeyMap_.has(e.key)) {
           stopDragging();
         }
       });
@@ -389,8 +389,8 @@
 
     /** @private */
     onValueMinMaxChange_() {
-      if (this.value == undefined || this.min == undefined ||
-          this.max == undefined) {
+      if (this.value === undefined || this.min === undefined ||
+          this.max === undefined) {
         return;
       }
       this.updateValue_(this.value);
@@ -437,7 +437,7 @@
         value = Math.round(value);
       }
       value = clamp(this.min, this.max, value);
-      if (this.value == value) {
+      if (this.value === value) {
         return false;
       }
       this.value = value;
diff --git a/ui/webui/resources/cr_elements/cr_splitter/cr_splitter.js b/ui/webui/resources/cr_elements/cr_splitter/cr_splitter.js
index c24aaa5f..da79cc56 100644
--- a/ui/webui/resources/cr_elements/cr_splitter/cr_splitter.js
+++ b/ui/webui/resources/cr_elements/cr_splitter/cr_splitter.js
@@ -170,7 +170,7 @@
    */
   onTouchStart_(e) {
     e = /** @type {!TouchEvent} */ (e);
-    if (e.touches.length == 1) {
+    if (e.touches.length === 1) {
       this.startDrag(e.touches[0].clientX, true);
       e.preventDefault();
     }
@@ -191,7 +191,7 @@
    * @param {!TouchEvent} e The touch event.
    */
   handleTouchMove_(e) {
-    if (e.touches.length == 1) {
+    if (e.touches.length === 1) {
       this.handleMove_(e.touches[0].clientX);
     }
   },
@@ -259,7 +259,7 @@
     const doc = targetElement.ownerDocument;
     const computedWidth =
         parseFloat(doc.defaultView.getComputedStyle(targetElement).width);
-    if (this.startWidth_ != computedWidth) {
+    if (this.startWidth_ !== computedWidth) {
       this.dispatchEvent(new CustomEvent('resize'));
     }
 
diff --git a/ui/webui/resources/cr_elements/cr_tabs/cr_tabs.js b/ui/webui/resources/cr_elements/cr_tabs/cr_tabs.js
index 974fc4e..b0b555e 100644
--- a/ui/webui/resources/cr_elements/cr_tabs/cr_tabs.js
+++ b/ui/webui/resources/cr_elements/cr_tabs/cr_tabs.js
@@ -73,13 +73,13 @@
     this.classList.add('keyboard-focus');
     const count = this.tabNames.length;
     let newSelection;
-    if (e.key == 'Home') {
+    if (e.key === 'Home') {
       newSelection = 0;
-    } else if (e.key == 'End') {
+    } else if (e.key === 'End') {
       newSelection = count - 1;
-    } else if (e.key == 'ArrowLeft' || e.key == 'ArrowRight') {
-      const delta = e.key == 'ArrowLeft' ? (this.isRtl_ ? 1 : -1) :
-                                           (this.isRtl_ ? -1 : 1);
+    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
+      const delta = e.key === 'ArrowLeft' ? (this.isRtl_ ? 1 : -1) :
+                                            (this.isRtl_ ? -1 : 1);
       newSelection = (count + this.selected + delta) % count;
     } else {
       return;
@@ -125,8 +125,8 @@
     // freeze in an expanded state since no transitionend events will be fired
     // for subsequent selection changes. Call transition end method to prevent
     // this.
-    if (this.$.selectionBar.style.transform == 'translateX(0%) scaleX(1)' &&
-        leftPercent == 0 && widthRatio == 1) {
+    if (this.$.selectionBar.style.transform === 'translateX(0%) scaleX(1)' &&
+        leftPercent === 0 && widthRatio === 1) {
       this.onSelectionBarTransitionEnd_();
       return;
     }
@@ -140,12 +140,12 @@
     const tabs = this.shadowRoot.querySelectorAll('.tab');
     // Tabs are not rendered yet by dom-repeat. Skip this update since
     // dom-repeat will fire a dom-change event when it is ready.
-    if (tabs.length == 0) {
+    if (tabs.length === 0) {
       return;
     }
 
     tabs.forEach((tab, i) => {
-      const isSelected = this.selected == i;
+      const isSelected = this.selected === i;
       if (isSelected) {
         tab.focus();
       }
@@ -154,7 +154,7 @@
       tab.setAttribute('tabindex', isSelected ? 0 : -1);
     });
 
-    if (this.selected == undefined) {
+    if (this.selected === undefined) {
       return;
     }
 
@@ -164,7 +164,7 @@
 
     // If there is no previously selected tab or the tab has not changed,
     // underline the selected tab instantly.
-    if (oldValue == null || oldValue == this.selected) {
+    if (oldValue === null || oldValue === this.selected) {
       // When handling the initial 'dom-change' event, it's possible for the
       // selected tab to exist and not yet be fully rendered. This will result
       // in the selection bar not rendering correctly.
diff --git a/ui/webui/resources/cr_elements/cr_toast/cr_toast.js b/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
index d1009f5..85d797e 100644
--- a/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
+++ b/ui/webui/resources/cr_elements/cr_toast/cr_toast.js
@@ -34,12 +34,12 @@
    * @private
    */
   resetAutoHide_() {
-    if (this.hideTimeoutId_ != null) {
+    if (this.hideTimeoutId_ !== null) {
       window.clearTimeout(this.hideTimeoutId_);
       this.hideTimeoutId_ = null;
     }
 
-    if (this.open && this.duration != 0) {
+    if (this.open && this.duration !== 0) {
       this.hideTimeoutId_ = window.setTimeout(() => {
         this.open = false;
       }, this.duration);
@@ -63,8 +63,8 @@
     // is changed. If neither is changed, we will still need to reset auto-hide.
     let shouldResetAutoHide = true;
 
-    if (typeof(duration) != 'undefined' && duration >= 0 &&
-        this.duration != duration) {
+    if (typeof (duration) !== 'undefined' && duration >= 0 &&
+        this.duration !== duration) {
       this.duration = duration;
       shouldResetAutoHide = false;
     }
diff --git a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
index a896dfb4..50fc1af7e 100644
--- a/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
+++ b/ui/webui/resources/cr_elements/cr_toast/cr_toast_manager.js
@@ -62,7 +62,7 @@
       const content = this.$.content;
       content.textContent = '';
       pieces.forEach(function(p) {
-        if (p.value.length == 0) {
+        if (p.value.length === 0) {
           return;
         }
 
diff --git a/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js b/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
index 4bfd1c5..f86716c 100644
--- a/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
+++ b/ui/webui/resources/cr_elements/cr_toggle/cr_toggle.js
@@ -128,7 +128,7 @@
    */
   onPointerDown_(e) {
     // Don't do anything if this was not a primary button click or touch event.
-    if (e.button != 0) {
+    if (e.button !== 0) {
       return;
     }
 
@@ -185,7 +185,7 @@
    * @private
    */
   onKeyDown_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
@@ -195,7 +195,7 @@
       return;
     }
 
-    if (e.key == 'Enter') {
+    if (e.key === 'Enter') {
       this.toggleState_(/* fromKeyboard= */ true);
     }
   },
@@ -205,14 +205,14 @@
    * @private
    */
   onKeyUp_(e) {
-    if (e.key != ' ' && e.key != 'Enter') {
+    if (e.key !== ' ' && e.key !== 'Enter') {
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
-    if (e.key == ' ') {
+    if (e.key === ' ') {
       this.toggleState_(/* fromKeyboard= */ true);
     }
   },
diff --git a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js
index 2116a0e..b37b262 100644
--- a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js
+++ b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js
@@ -116,7 +116,7 @@
 
   /** @private */
   onSearchTermKeydown_(e) {
-    if (e.key == 'Escape') {
+    if (e.key === 'Escape') {
       this.showingSearch = false;
     }
   },
@@ -126,7 +126,7 @@
    * @private
    */
   showSearch_(e) {
-    if (e.target != this.$.clearSearch) {
+    if (e.target !== this.$.clearSearch) {
       this.showingSearch = true;
     }
   },
@@ -148,7 +148,7 @@
    */
   showingSearchChanged_(current, previous) {
     // Prevent unnecessary 'search-changed' event from firing on startup.
-    if (previous == undefined) {
+    if (previous === undefined) {
       return;
     }
 
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.js b/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.js
index c4e3b98d..6f99752f 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.js
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.js
@@ -82,7 +82,7 @@
    * @private
    */
   getIndicatorVisible_(type) {
-    return type != CrPolicyIndicatorType.NONE;
+    return type !== CrPolicyIndicatorType.NONE;
   },
 
   /**
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior_mojo.js b/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior_mojo.js
index 7cd96a86..752cace 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior_mojo.js
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_network_behavior_mojo.js
@@ -15,8 +15,8 @@
   isNetworkPolicyControlled(property) {
     assert(property);
     const mojom = chromeos.networkConfig.mojom;
-    return property.policySource != mojom.PolicySource.kNone &&
-        property.policySource != mojom.PolicySource.kActiveExtension;
+    return property.policySource !== mojom.PolicySource.kNone &&
+        property.policySource !== mojom.PolicySource.kActiveExtension;
   },
 
   /**
@@ -25,7 +25,7 @@
    */
   isExtensionControlled(property) {
     assert(property);
-    return property.policySource ==
+    return property.policySource ===
         chromeos.networkConfig.mojom.PolicySource.kActiveExtension;
   },
 
@@ -36,7 +36,7 @@
    */
   isControlled(property) {
     assert(property);
-    return property.policySource !=
+    return property.policySource !==
         chromeos.networkConfig.mojom.PolicySource.kNone;
   },
 
@@ -47,9 +47,9 @@
   isEditable(property) {
     assert(property);
     const mojom = chromeos.networkConfig.mojom;
-    return property.policySource != mojom.PolicySource.kUserPolicyEnforced &&
-        property.policySource != mojom.PolicySource.kDevicePolicyEnforced &&
-        property.policySource != mojom.PolicySource.kActiveExtension;
+    return property.policySource !== mojom.PolicySource.kUserPolicyEnforced &&
+        property.policySource !== mojom.PolicySource.kDevicePolicyEnforced &&
+        property.policySource !== mojom.PolicySource.kActiveExtension;
   },
 
   /**
@@ -61,8 +61,8 @@
       return false;
     }
     const mojom = chromeos.networkConfig.mojom;
-    return property.policySource == mojom.PolicySource.kUserPolicyEnforced ||
-        property.policySource == mojom.PolicySource.kDevicePolicyEnforced;
+    return property.policySource === mojom.PolicySource.kUserPolicyEnforced ||
+        property.policySource === mojom.PolicySource.kDevicePolicyEnforced;
   },
 
   /**
@@ -74,8 +74,9 @@
       return false;
     }
     const mojom = chromeos.networkConfig.mojom;
-    return property.policySource == mojom.PolicySource.kUserPolicyRecommended ||
-        property.policySource == mojom.PolicySource.kDevicePolicyRecommended;
+    return property.policySource ===
+        mojom.PolicySource.kUserPolicyRecommended ||
+        property.policySource === mojom.PolicySource.kDevicePolicyRecommended;
   },
 
   /**
@@ -84,8 +85,8 @@
    * @protected
    */
   isPolicySource(source) {
-    return source == chromeos.networkConfig.mojom.OncSource.kDevicePolicy ||
-        source == chromeos.networkConfig.mojom.OncSource.kUserPolicy;
+    return source === chromeos.networkConfig.mojom.OncSource.kDevicePolicy ||
+        source === chromeos.networkConfig.mojom.OncSource.kUserPolicy;
   },
 
   /**
@@ -94,10 +95,10 @@
    * @private
    */
   getIndicatorTypeForSource(source) {
-    if (source == chromeos.networkConfig.mojom.OncSource.kDevicePolicy) {
+    if (source === chromeos.networkConfig.mojom.OncSource.kDevicePolicy) {
       return CrPolicyIndicatorType.DEVICE_POLICY;
     }
-    if (source == chromeos.networkConfig.mojom.OncSource.kUserPolicy) {
+    if (source === chromeos.networkConfig.mojom.OncSource.kUserPolicy) {
       return CrPolicyIndicatorType.USER_POLICY;
     }
     return CrPolicyIndicatorType.NONE;
@@ -113,15 +114,15 @@
       return CrPolicyIndicatorType.NONE;
     }
     const mojom = chromeos.networkConfig.mojom;
-    if (property.policySource == mojom.PolicySource.kUserPolicyEnforced ||
-        property.policySource == mojom.PolicySource.kUserPolicyRecommended) {
+    if (property.policySource === mojom.PolicySource.kUserPolicyEnforced ||
+        property.policySource === mojom.PolicySource.kUserPolicyRecommended) {
       return CrPolicyIndicatorType.USER_POLICY;
     }
-    if (property.policySource == mojom.PolicySource.kDevicePolicyEnforced ||
-        property.policySource == mojom.PolicySource.kDevicePolicyRecommended) {
+    if (property.policySource === mojom.PolicySource.kDevicePolicyEnforced ||
+        property.policySource === mojom.PolicySource.kDevicePolicyRecommended) {
       return CrPolicyIndicatorType.DEVICE_POLICY;
     }
-    if (property.policySource == mojom.PolicySource.kActiveExtension) {
+    if (property.policySource === mojom.PolicySource.kActiveExtension) {
       return CrPolicyIndicatorType.EXTENSION;
     }
     return CrPolicyIndicatorType.NONE;
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator_mojo.js b/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator_mojo.js
index 62d3851..59af73c1 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator_mojo.js
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator_mojo.js
@@ -76,7 +76,7 @@
     }
 
     const matches = !!this.property &&
-        this.property.activeValue == this.property.policyValue;
+        this.property.activeValue === this.property.policyValue;
     return this.getIndicatorTooltip(this.indicatorType, '', matches);
   }
 });
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_pref_behavior.js b/ui/webui/resources/cr_elements/policy/cr_policy_pref_behavior.js
index bbde5da..89f0df03 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_pref_behavior.js
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_pref_behavior.js
@@ -25,7 +25,7 @@
    */
   isPrefEnforced() {
     return !!this.pref &&
-        this.pref.enforcement == chrome.settingsPrivate.Enforcement.ENFORCED;
+        this.pref.enforcement === chrome.settingsPrivate.Enforcement.ENFORCED;
   },
 
   /**
@@ -36,11 +36,12 @@
       return false;
     }
     if (this.noExtensionIndicator &&
-        this.pref.controlledBy ==
+        this.pref.controlledBy ===
             chrome.settingsPrivate.ControlledBy.EXTENSION) {
       return false;
     }
     return this.isPrefEnforced() ||
-        this.pref.enforcement == chrome.settingsPrivate.Enforcement.RECOMMENDED;
+        this.pref.enforcement ===
+        chrome.settingsPrivate.Enforcement.RECOMMENDED;
   },
 };
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js b/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
index a135918..d2e6c025 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.js
@@ -46,10 +46,10 @@
    *     and |enforcement|.
    */
   getIndicatorTypeForPref_(controlledBy, enforcement) {
-    if (enforcement == chrome.settingsPrivate.Enforcement.RECOMMENDED) {
+    if (enforcement === chrome.settingsPrivate.Enforcement.RECOMMENDED) {
       return CrPolicyIndicatorType.RECOMMENDED;
     }
-    if (enforcement == chrome.settingsPrivate.Enforcement.ENFORCED) {
+    if (enforcement === chrome.settingsPrivate.Enforcement.ENFORCED) {
       switch (controlledBy) {
         case chrome.settingsPrivate.ControlledBy.EXTENSION:
           return CrPolicyIndicatorType.EXTENSION;
@@ -67,7 +67,7 @@
           return CrPolicyIndicatorType.CHILD_RESTRICTION;
       }
     }
-    if (enforcement == chrome.settingsPrivate.Enforcement.PARENT_SUPERVISED) {
+    if (enforcement === chrome.settingsPrivate.Enforcement.PARENT_SUPERVISED) {
       return CrPolicyIndicatorType.PARENT;
     }
     return CrPolicyIndicatorType.NONE;
@@ -83,7 +83,7 @@
       return '';
     }
 
-    const matches = this.pref && this.pref.value == this.pref.recommendedValue;
+    const matches = this.pref && this.pref.value === this.pref.recommendedValue;
     return this.getIndicatorTooltip(
         indicatorType, this.pref.controlledByName || '', matches);
   },
diff --git a/ui/webui/resources/js/action_link.js b/ui/webui/resources/js/action_link.js
index 8a40063..e67182c 100644
--- a/ui/webui/resources/js/action_link.js
+++ b/ui/webui/resources/js/action_link.js
@@ -37,7 +37,7 @@
     }
 
     this.addEventListener('keydown', function(e) {
-      if (!this.disabled && e.key == 'Enter' && !this.href) {
+      if (!this.disabled && e.key === 'Enter' && !this.href) {
         // Schedule a click asynchronously because other 'keydown' handlers
         // may still run later (e.g. document.addEventListener('keydown')).
         // Specifically options dialogs break when this timeout isn't here.
@@ -64,7 +64,7 @@
       document.addEventListener('mouseup', removePreventDefault);
 
       // If focus started via mouse press, don't show an outline.
-      if (document.activeElement != this) {
+      if (document.activeElement !== this) {
         this.classList.add('no-outline');
       }
     });
@@ -90,7 +90,7 @@
 
   /** @override */
   setAttribute(attr, val) {
-    if (attr.toLowerCase() == 'disabled') {
+    if (attr.toLowerCase() === 'disabled') {
       this.disabled = true;
     } else {
       HTMLAnchorElement.prototype.setAttribute.apply(this, arguments);
@@ -99,7 +99,7 @@
 
   /** @override */
   removeAttribute(attr) {
-    if (attr.toLowerCase() == 'disabled') {
+    if (attr.toLowerCase() === 'disabled') {
       this.disabled = false;
     } else {
       HTMLAnchorElement.prototype.removeAttribute.apply(this, arguments);
diff --git a/ui/webui/resources/js/cr.js b/ui/webui/resources/js/cr.js
index 525d7ed..08db0e4 100644
--- a/ui/webui/resources/js/cr.js
+++ b/ui/webui/resources/js/cr.js
@@ -157,7 +157,7 @@
         return function(value) {
           const oldValue = this[name];
           if (value !== oldValue) {
-            if (value == undefined) {
+            if (value === undefined) {
               this.removeAttribute(attributeName);
             } else {
               this.setAttribute(attributeName, value);
@@ -206,7 +206,7 @@
    * @suppress {deprecated}
    */
   function defineProperty(obj, name, opt_kind, opt_setHook) {
-    if (typeof obj == 'function') {
+    if (typeof obj === 'function') {
       obj = obj.prototype;
     }
 
diff --git a/ui/webui/resources/js/cr/ui.js b/ui/webui/resources/js/cr/ui.js
index 31b651a..a054874a 100644
--- a/ui/webui/resources/js/cr/ui.js
+++ b/ui/webui/resources/js/cr/ui.js
@@ -14,7 +14,7 @@
    */
   function decorate(source, constr) {
     let elements;
-    if (typeof source == 'string') {
+    if (typeof source === 'string') {
       elements = document.querySelectorAll(source);
     } else {
       elements = [source];
@@ -65,7 +65,7 @@
    */
   function define(tagNameOrFunction) {
     let createFunction, tagName;
-    if (typeof tagNameOrFunction == 'function') {
+    if (typeof tagNameOrFunction === 'function') {
       createFunction = tagNameOrFunction;
       tagName = '';
     } else {
@@ -119,7 +119,7 @@
     const win = doc.defaultView;
     const computedStyle = win.getComputedStyle(el);
     const parentComputedStyle = win.getComputedStyle(parentEl);
-    const rtl = computedStyle.direction == 'rtl';
+    const rtl = computedStyle.direction === 'rtl';
 
     // To get the max width we get the width of the treeItem minus the position
     // of the input.
@@ -198,7 +198,7 @@
         doc.removeEventListener('click', onclick, true);
       }
     }
-    // The following 'click' event (if e.type == 'mouseup') mustn't be taken
+    // The following 'click' event (if e.type === 'mouseup') mustn't be taken
     // into account (it mustn't stop tracking clicks). Start event listening
     // after zero timeout.
     setTimeout(function() {
diff --git a/ui/webui/resources/js/cr/ui/array_data_model.js b/ui/webui/resources/js/cr/ui/array_data_model.js
index 72c3ffb..cc35657 100644
--- a/ui/webui/resources/js/cr/ui/array_data_model.js
+++ b/ui/webui/resources/js/cr/ui/array_data_model.js
@@ -189,7 +189,7 @@
           this.doSort_(this.sortStatus.field, this.sortStatus.direction);
       if (sortPermutation) {
         const splicePermutation = deletePermutation.map(function(element) {
-          return element != -1 ? sortPermutation[element] : -1;
+          return element !== -1 ? sortPermutation[element] : -1;
         });
         this.dispatchPermutedEvent_(splicePermutation);
         spliceEvent.index = sortPermutation[index];
@@ -322,8 +322,8 @@
       setTimeout(function() {
         // If the sort status has been changed, sorting has already done
         // on the change event.
-        if (field == self.sortStatus.field &&
-            direction == self.sortStatus.direction) {
+        if (field === self.sortStatus.field &&
+            direction === self.sortStatus.direction) {
           self.sort(field, direction);
         }
       }, 0);
@@ -360,7 +360,7 @@
         positions[this.indexes_[i]] = i;
       }
       const sorted = this.indexes_.every(function(element, index, array) {
-        return index == 0 || compareFunction(element, array[index - 1]) >= 0;
+        return index === 0 || compareFunction(element, array[index - 1]) >= 0;
       });
       if (!sorted) {
         this.indexes_.sort(compareFunction);
@@ -369,7 +369,7 @@
       const sortPermutation = [];
       let changed = false;
       for (let i = 0; i < this.length; i++) {
-        if (positions[this.indexes_[i]] != i) {
+        if (positions[this.indexes_[i]] !== i) {
           changed = true;
         }
         sortPermutation[positions[this.indexes_[i]]] = i;
@@ -424,7 +424,7 @@
       if (field !== null) {
         compareFunction = this.createCompareFunction_(field);
       }
-      const dirMultiplier = direction == 'desc' ? -1 : 1;
+      const dirMultiplier = direction === 'desc' ? -1 : 1;
 
       return function(index1, index2) {
         const item1 = this.array_[index1];
@@ -434,7 +434,7 @@
         if (typeof (compareFunction) === 'function') {
           compareResult = compareFunction.call(null, item1, item2);
         }
-        if (compareResult != 0) {
+        if (compareResult !== 0) {
           return dirMultiplier * compareResult;
         }
         return dirMultiplier *
diff --git a/ui/webui/resources/js/cr/ui/bubble.js b/ui/webui/resources/js/cr/ui/bubble.js
index 79959e3..62eb346 100644
--- a/ui/webui/resources/js/cr/ui/bubble.js
+++ b/ui/webui/resources/js/cr/ui/bubble.js
@@ -137,13 +137,13 @@
         return;
       }
 
-      this.arrowAtRight_ = location == cr.ui.ArrowLocation.TOP_END ||
-          location == cr.ui.ArrowLocation.BOTTOM_END;
-      if (document.documentElement.dir == 'rtl') {
+      this.arrowAtRight_ = location === cr.ui.ArrowLocation.TOP_END ||
+          location === cr.ui.ArrowLocation.BOTTOM_END;
+      if (document.documentElement.dir === 'rtl') {
         this.arrowAtRight_ = !this.arrowAtRight_;
       }
-      this.arrowAtTop_ = location == cr.ui.ArrowLocation.TOP_START ||
-          location == cr.ui.ArrowLocation.TOP_END;
+      this.arrowAtTop_ = location === cr.ui.ArrowLocation.TOP_START ||
+          location === cr.ui.ArrowLocation.TOP_END;
     },
 
     /**
@@ -174,7 +174,7 @@
 
       let left;
       let top;
-      if (this.bubbleAlignment_ == cr.ui.BubbleAlignment.ENTIRELY_VISIBLE) {
+      if (this.bubbleAlignment_ === cr.ui.BubbleAlignment.ENTIRELY_VISIBLE) {
         // Work out horizontal placement. The bubble is initially positioned so
         // that the arrow tip points toward the midpoint of the anchor and is
         // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as
@@ -186,7 +186,7 @@
         const maxLeftPos =
             documentWidth - bubble.width - BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
         const minLeftPos = BubbleBase.MIN_VIEWPORT_EDGE_MARGIN;
-        if (document.documentElement.dir == 'rtl') {
+        if (document.documentElement.dir === 'rtl') {
           left = Math.min(Math.max(left, minLeftPos), maxLeftPos);
         } else {
           left = Math.max(Math.min(left, maxLeftPos), minLeftPos);
@@ -219,7 +219,7 @@
           this.updateArrowPosition_(false, false, arrowTip);
         } else if (
             offsetTop > offsetBottom ||
-            offsetTop == offsetBottom && this.arrowAtTop_) {
+            offsetTop === offsetBottom && this.arrowAtTop_) {
           top = anchor.bottom + offsetTop;
           this.updateArrowPosition_(true, true, arrowTip);
         } else {
@@ -227,7 +227,7 @@
           this.updateArrowPosition_(true, false, arrowTip);
         }
       } else {
-        if (this.bubbleAlignment_ ==
+        if (this.bubbleAlignment_ ===
             cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) {
           left = this.arrowAtRight_ ? anchor.right - bubble.width : anchor.left;
         } else {
@@ -283,7 +283,7 @@
      */
     handleEvent(event) {
       // Close the bubble when the user presses <Esc>.
-      if (event.type == 'keydown' && event.keyCode == 27) {
+      if (event.type === 'keydown' && event.keyCode === 27) {
         this.hide();
         event.preventDefault();
         event.stopPropagation();
@@ -411,9 +411,9 @@
     handleEvent(event) {
       BubbleBase.prototype.handleEvent.call(this, event);
 
-      if (event.type == 'mousedown') {
+      if (event.type === 'mousedown') {
         // Dismiss the bubble when the user clicks on the close button.
-        if (event.target == this.querySelector('.bubble-close')) {
+        if (event.target === this.querySelector('.bubble-close')) {
           this.handleCloseEvent_();
           // Dismiss the bubble when the user clicks outside it after the
           // specified delay has passed.
@@ -509,7 +509,7 @@
         case 'mousedown':
         case 'click':
           target = assertInstanceof(event.target, Node);
-          if (event.button == 0 && this.anchorNode_.contains(target)) {
+          if (event.button === 0 && this.anchorNode_.contains(target)) {
             break;
           }
         // Close the bubble when the underlying document is scrolled.
diff --git a/ui/webui/resources/js/cr/ui/bubble_button.js b/ui/webui/resources/js/cr/ui/bubble_button.js
index d623d19..b1accdd7 100644
--- a/ui/webui/resources/js/cr/ui/bubble_button.js
+++ b/ui/webui/resources/js/cr/ui/bubble_button.js
@@ -50,7 +50,7 @@
       switch (event.type) {
         // Toggle the bubble on left click. Let any other clicks propagate.
         case 'click':
-          if (event.button != 0) {
+          if (event.button !== 0) {
             return;
           }
           break;
diff --git a/ui/webui/resources/js/cr/ui/card_slider.js b/ui/webui/resources/js/cr/ui/card_slider.js
index 49c3f67..af7e590 100644
--- a/ui/webui/resources/js/cr/ui/card_slider.js
+++ b/ui/webui/resources/js/cr/ui/card_slider.js
@@ -120,13 +120,13 @@
     initialize(ignoreMouseWheelEvents) {
       const view = this.container_.ownerDocument.defaultView;
       assert(
-          view.getComputedStyle(this.container_).display == '-webkit-box',
+          view.getComputedStyle(this.container_).display === '-webkit-box',
           'Container should be display -webkit-box.');
       assert(
-          view.getComputedStyle(this.frame_).overflow == 'hidden',
+          view.getComputedStyle(this.frame_).overflow === 'hidden',
           'Frame should be overflow hidden.');
       assert(
-          view.getComputedStyle(this.container_).position == 'static',
+          view.getComputedStyle(this.container_).position === 'static',
           'Container should be position static.');
 
       this.updateCardWidths_();
@@ -166,7 +166,7 @@
      * @param {number} newCardWidth Width all cards should have, in pixels.
      */
     resize(newCardWidth) {
-      if (newCardWidth != this.cardWidth_) {
+      if (newCardWidth !== this.cardWidth_) {
         this.cardWidth_ = newCardWidth;
 
         this.updateCardWidths_();
@@ -205,7 +205,7 @@
      */
     updateSelectedCardAttributes_() {
       for (let i = 0; i < this.cards_.length; i++) {
-        if (i == this.currentCard_) {
+        if (i === this.currentCard_) {
           this.cards_[i].classList.add('selected-card');
           this.cards_[i].removeAttribute('aria-hidden');
         } else {
@@ -271,7 +271,7 @@
      * @private
      */
     onMouseWheel_(e) {
-      if (e.wheelDeltaX == 0) {
+      if (e.wheelDeltaX === 0) {
         return;
       }
 
@@ -318,12 +318,12 @@
       }
 
       // We got a mouse wheel event, so cancel any pending scroll wheel timeout.
-      if (this.scrollClearTimeout_ != null) {
+      if (this.scrollClearTimeout_ !== null) {
         clearTimeout(this.scrollClearTimeout_);
       }
       // If we didn't use up all the scroll, hold onto it for a little bit, but
       // drop it after a delay.
-      if (this.mouseWheelScrollAmount_ != 0) {
+      if (this.mouseWheelScrollAmount_ !== 0) {
         this.scrollClearTimeout_ =
             setTimeout(this.clearMouseWheelScroll_.bind(this), 500);
       }
@@ -347,7 +347,7 @@
      */
     onTransitionEnd_(e) {
       // Ignore irrelevant transitions that might bubble up.
-      if (e.target !== this.container_ || e.propertyName != 'transform') {
+      if (e.target !== this.container_ || e.propertyName !== 'transform') {
         return;
       }
       this.fireChangeEndedEvent_(true);
@@ -384,7 +384,7 @@
 
       this.updateSelectedCardAttributes_();
 
-      if (this.currentCard_ == -1) {
+      if (this.currentCard_ === -1) {
         this.currentCard_ = 0;
       } else if (index <= this.currentCard_) {
         this.selectCard(this.currentCard_ + 1, false, true, true);
@@ -449,7 +449,7 @@
       this.assertValidIndex_(index);
       const removed = this.cards_.splice(index, 1).pop();
 
-      if (this.cards_.length == 0) {
+      if (this.cards_.length === 0) {
         this.currentCard_ = -1;
       } else if (index < this.currentCard_) {
         this.selectCard(this.currentCard_ - 1, false, true);
@@ -509,7 +509,7 @@
       let isChangingCard =
           !this.cards_[newCardIndex].classList.contains('selected-card');
 
-      if (typeof opt_forceChange != 'undefined' && opt_forceChange) {
+      if (typeof opt_forceChange !== 'undefined' && opt_forceChange) {
         isChangingCard = true;
       }
 
@@ -550,7 +550,7 @@
      */
     selectCardByValue(newCard, opt_animate) {
       const i = this.cards_.indexOf(newCard);
-      assert(i != -1);
+      assert(i !== -1);
       this.selectCard(i, opt_animate);
     },
 
@@ -570,7 +570,7 @@
 
       // If there's no change, return something to let the caller know there
       // won't be a transition occuring.
-      if (prevLeft == this.currentLeft_ && this.deltaX_ == 0) {
+      if (prevLeft === this.currentLeft_ && this.deltaX_ === 0) {
         return false;
       }
 
@@ -640,7 +640,7 @@
       // If dragging beyond the first or last card then apply a backoff so the
       // dragging feels stickier than usual.
       if (!this.currentCard && deltaX > 0 ||
-          this.currentCard == (this.cards_.length - 1) && deltaX < 0) {
+          this.currentCard === (this.cards_.length - 1) && deltaX < 0) {
         deltaX /= 2;
       }
       this.translateTo_(this.currentLeft_ + deltaX);
@@ -659,7 +659,7 @@
       const newX = this.currentLeft_ + deltaX;
       let newCardIndex = Math.round(-newX / this.cardWidth_);
 
-      if (newCardIndex == this.currentCard &&
+      if (newCardIndex === this.currentCard &&
           Math.abs(velocity) > CardSlider.TRANSITION_VELOCITY_THRESHOLD_) {
         // The drag wasn't far enough to change cards but the velocity was
         // high enough to transition anyways. If the velocity is to the left
diff --git a/ui/webui/resources/js/cr/ui/context_menu_handler.js b/ui/webui/resources/js/cr/ui/context_menu_handler.js
index 6673058..febb541 100644
--- a/ui/webui/resources/js/cr/ui/context_menu_handler.js
+++ b/ui/webui/resources/js/cr/ui/context_menu_handler.js
@@ -82,7 +82,7 @@
         return;
       }
 
-      if (opt_hideType == cr.ui.HideType.DELAYED) {
+      if (opt_hideType === cr.ui.HideType.DELAYED) {
         menu.classList.add('hide-delayed');
       } else {
         menu.classList.remove('hide-delayed');
@@ -144,7 +144,8 @@
         case 'keydown':
           this.keyIsDown_ = !e.ctrlKey && !e.altKey &&
               // context menu key or Shift-F10
-              (e.keyCode == 93 && !e.shiftKey || e.key == 'F10' && e.shiftKey);
+              (e.keyCode === 93 && !e.shiftKey ||
+               e.key === 'F10' && e.shiftKey);
           break;
 
         case 'keyup':
@@ -153,7 +154,7 @@
       }
 
       // Context menu is handled even when we have no menu.
-      if (e.type != 'contextmenu' && !this.menu) {
+      if (e.type !== 'contextmenu' && !this.menu) {
         return;
       }
 
@@ -161,7 +162,7 @@
         case 'mousedown':
           if (!this.menu.contains(e.target)) {
             this.hideMenu();
-            if (e.button == 0 /* Left button */ && (cr.isLinux || cr.isMac)) {
+            if (e.button === 0 /* Left button */ && (cr.isLinux || cr.isMac)) {
               // Emulate Mac and Linux, which swallow native 'mousedown' events
               // that close menus.
               e.preventDefault();
@@ -179,7 +180,7 @@
           break;
 
         case 'keydown':
-          if (e.key == 'Escape') {
+          if (e.key === 'Escape') {
             this.hideMenu();
             e.stopPropagation();
             e.preventDefault();
@@ -232,7 +233,7 @@
      *     the contextMenu property to.
      */
     addContextMenuProperty(elementOrClass) {
-      const target = typeof elementOrClass == 'function' ?
+      const target = typeof elementOrClass === 'function' ?
           elementOrClass.prototype :
           elementOrClass;
 
@@ -244,7 +245,7 @@
       target.__defineSetter__('contextMenu', function(menu) {
         const oldContextMenu = this.contextMenu;
 
-        if (typeof menu == 'string' && menu[0] == '#') {
+        if (typeof menu === 'string' && menu[0] === '#') {
           menu = this.ownerDocument.getElementById(menu.slice(1));
           cr.ui.decorate(menu, Menu);
         }
diff --git a/ui/webui/resources/js/cr/ui/controlled_indicator.js b/ui/webui/resources/js/cr/ui/controlled_indicator.js
index 5b86f1d..ad7c855 100644
--- a/ui/webui/resources/js/cr/ui/controlled_indicator.js
+++ b/ui/webui/resources/js/cr/ui/controlled_indicator.js
@@ -78,7 +78,7 @@
 
       if (this.hasAttribute('text' + this.controlledBy)) {
         text = this.getAttribute('text' + this.controlledBy);
-      } else if (this.controlledBy == 'extension' && this['extensionName']) {
+      } else if (this.controlledBy === 'extension' && this['extensionName']) {
         text = defaultStrings['extensionWithName'];
       }
 
diff --git a/ui/webui/resources/js/cr/ui/dialogs.js b/ui/webui/resources/js/cr/ui/dialogs.js
index f5a0ef5..055f606 100644
--- a/ui/webui/resources/js/cr/ui/dialogs.js
+++ b/ui/webui/resources/js/cr/ui/dialogs.js
@@ -124,7 +124,7 @@
   /** @protected */
   BaseDialog.prototype.onContainerKeyDown = function(event) {
     // Handle Escape.
-    if (event.keyCode == 27 && !this.cancelButton.disabled) {
+    if (event.keyCode === 27 && !this.cancelButton.disabled) {
       this.onCancelClick_(event);
       event.stopPropagation();
       // Prevent the event from being handled by the container of the dialog.
@@ -135,7 +135,7 @@
 
   /** @private */
   BaseDialog.prototype.onContainerMouseDown_ = function(event) {
-    if (event.target == this.container) {
+    if (event.target === this.container) {
       const classList = this.container.classList;
       // Start 'pulse' animation.
       classList.remove('pulse');
@@ -381,7 +381,7 @@
 
   /** @private */
   PromptDialog.prototype.onKeyDown_ = function(event) {
-    if (event.keyCode == 13) {  // Enter
+    if (event.keyCode === 13) {  // Enter
       this.onOkClick_(event);
       event.preventDefault();
     }
diff --git a/ui/webui/resources/js/cr/ui/drag_wrapper.js b/ui/webui/resources/js/cr/ui/drag_wrapper.js
index 2ab9aae..af78b67 100644
--- a/ui/webui/resources/js/cr/ui/drag_wrapper.js
+++ b/ui/webui/resources/js/cr/ui/drag_wrapper.js
@@ -83,7 +83,7 @@
      * @private
      */
     onDragEnter_(e) {
-      if (++this.dragEnters_ == 1) {
+      if (++this.dragEnters_ === 1) {
         if (this.delegate_.shouldAcceptDrag(e)) {
           this.target_.classList.add('drag-target');
           this.delegate_.doDragEnter(e);
diff --git a/ui/webui/resources/js/cr/ui/focus_grid.js b/ui/webui/resources/js/cr/ui/focus_grid.js
index b3130f8..48824fc0 100644
--- a/ui/webui/resources/js/cr/ui/focus_grid.js
+++ b/ui/webui/resources/js/cr/ui/focus_grid.js
@@ -52,7 +52,7 @@
       }
 
       this.rows.forEach(function(r) {
-        r.makeActive(r == row);
+        r.makeActive(r === row);
       });
     }
 
@@ -63,13 +63,13 @@
 
       let newRow = -1;
 
-      if (e.key == 'ArrowUp') {
+      if (e.key === 'ArrowUp') {
         newRow = rowIndex - 1;
-      } else if (e.key == 'ArrowDown') {
+      } else if (e.key === 'ArrowDown') {
         newRow = rowIndex + 1;
-      } else if (e.key == 'PageUp') {
+      } else if (e.key === 'PageUp') {
         newRow = 0;
-      } else if (e.key == 'PageDown') {
+      } else if (e.key === 'PageDown') {
         newRow = this.rows.length - 1;
       }
 
@@ -121,7 +121,7 @@
      */
     getRowForRoot(root) {
       for (let i = 0; i < this.rows.length; ++i) {
-        if (this.rows[i].root == root) {
+        if (this.rows[i].root === root) {
           return this.rows[i];
         }
       }
@@ -146,7 +146,7 @@
       row.delegate = row.delegate || this;
 
       const nextRowIndex = nextRow ? this.rows.indexOf(nextRow) : -1;
-      if (nextRowIndex == -1) {
+      if (nextRowIndex === -1) {
         this.rows.push(row);
       } else {
         this.rows.splice(nextRowIndex, 0, row);
@@ -172,7 +172,7 @@
      *     grid.
      */
     ensureRowActive(preferredRow) {
-      if (this.rows.length == 0) {
+      if (this.rows.length === 0) {
         return;
       }
 
diff --git a/ui/webui/resources/js/cr/ui/focus_manager.js b/ui/webui/resources/js/cr/ui/focus_manager.js
index c731542..ab0f22f 100644
--- a/ui/webui/resources/js/cr/ui/focus_manager.js
+++ b/ui/webui/resources/js/cr/ui/focus_manager.js
@@ -57,8 +57,8 @@
               // Reject all hidden nodes. FILTER_REJECT also rejects these
               // nodes' children, so non-hidden elements that are descendants of
               // hidden <div>s will correctly be rejected.
-              if (node.hidden || style.display == 'none' ||
-                  style.visibility == 'hidden') {
+              if (node.hidden || style.display === 'none' ||
+                  style.visibility === 'hidden') {
                 return NodeFilter.FILTER_REJECT;
               }
 
@@ -126,15 +126,15 @@
       if (!element) {
         return null;
       }
-      if (element.tagName != 'INPUT' || element.type != 'radio' ||
-          element.name == '') {
+      if (element.tagName !== 'INPUT' || element.type !== 'radio' ||
+          element.name === '') {
         return element;
       }
       if (!element.checked) {
         for (let i = 0; i < focusableElements.length; i++) {
           const e = focusableElements[i];
-          if (e && e.tagName == 'INPUT' && e.type == 'radio' &&
-              e.name == element.name && e.checked) {
+          if (e && e.tagName === 'INPUT' && e.type === 'radio' &&
+              e.name === element.name && e.checked) {
             element = e;
             break;
           }
@@ -177,7 +177,7 @@
     onDocumentKeyDown_(event) {
       /** @const */ const tabKeyCode = 9;
 
-      if (event.keyCode == tabKeyCode) {
+      if (event.keyCode === tabKeyCode) {
         // If the "Shift" key is held, focus is being transferred backward in
         // the page.
         this.focusDirBackwards_ = event.shiftKey ? true : false;
diff --git a/ui/webui/resources/js/cr/ui/focus_row.js b/ui/webui/resources/js/cr/ui/focus_row.js
index 04f140581..c297608 100644
--- a/ui/webui/resources/js/cr/ui/focus_row.js
+++ b/ui/webui/resources/js/cr/ui/focus_row.js
@@ -61,7 +61,7 @@
         assertInstanceof(current, Element);
 
         const style = window.getComputedStyle(current);
-        if (style.visibility == 'hidden' || style.display == 'none') {
+        if (style.visibility === 'hidden' || style.display === 'none') {
           return false;
         }
 
@@ -70,7 +70,7 @@
           return false;
         }
 
-        if (parent == current.ownerDocument ||
+        if (parent === current.ownerDocument ||
             parent instanceof DocumentFragment) {
           return true;
         }
@@ -116,7 +116,7 @@
       assert(type);
 
       let element;
-      if (typeof selectorOrElement == 'string') {
+      if (typeof selectorOrElement === 'string') {
         element = this.root.querySelector(selectorOrElement);
       } else {
         element = selectorOrElement;
@@ -186,7 +186,7 @@
      */
     getFirstFocusable(opt_type) {
       const element = this.getFocusableElements().find(
-          el => !opt_type || el.getAttribute('focus-type') == opt_type);
+          el => !opt_type || el.getAttribute('focus-type') === opt_type);
       return element || null;
     }
 
@@ -214,7 +214,7 @@
      * @param {boolean} active True if tab is allowed for this row.
      */
     makeActive(active) {
-      if (active == this.isActive()) {
+      if (active === this.isActive()) {
         return;
       }
 
@@ -298,13 +298,13 @@
           // Bubble up to focus on the previous element outside the row.
           return;
         }
-      } else if (e.key == 'ArrowLeft') {
+      } else if (e.key === 'ArrowLeft') {
         index = elementIndex + (isRTL() ? 1 : -1);
-      } else if (e.key == 'ArrowRight') {
+      } else if (e.key === 'ArrowRight') {
         index = elementIndex + (isRTL() ? -1 : 1);
-      } else if (e.key == 'Home') {
+      } else if (e.key === 'Home') {
         index = 0;
-      } else if (e.key == 'End') {
+      } else if (e.key === 'End') {
         index = elements.length - 1;
       } else {
         shouldStopPropagation = false;
diff --git a/ui/webui/resources/js/cr/ui/focus_row_behavior.js b/ui/webui/resources/js/cr/ui/focus_row_behavior.js
index 7f05bf6..f1c964b3 100644
--- a/ui/webui/resources/js/cr/ui/focus_row_behavior.js
+++ b/ui/webui/resources/js/cr/ui/focus_row_behavior.js
@@ -32,7 +32,7 @@
     onFocus(row, e) {
       const element = e.path[0];
       const focusableElement = cr.ui.FocusRow.getFocusableElement(element);
-      if (element != focusableElement) {
+      if (element !== focusableElement) {
         focusableElement.focus();
       }
       this.listItem_.lastFocused = focusableElement;
@@ -46,7 +46,7 @@
      */
     onKeydown(row, e) {
       // Prevent iron-list from changing the focus on enter.
-      if (e.key == 'Enter') {
+      if (e.key === 'Enter') {
         e.stopPropagation();
       }
 
@@ -291,7 +291,7 @@
      */
     addMutationObservers_(control) {
       let current = control;
-      while (current && current != this.root) {
+      while (current && current !== this.root) {
         const currentObserver = this.createObserver_();
         currentObserver.observe(current, {
           attributes: true,
@@ -343,12 +343,12 @@
     /** @private */
     ironListTabIndexChanged_() {
       if (this.row_) {
-        this.row_.makeActive(this.ironListTabIndex == 0);
+        this.row_.makeActive(this.ironListTabIndex === 0);
       }
 
       // If a new row is being focused, reset listBlurred. This means an item
       // has been removed and iron-list is about to focus the next item.
-      if (this.ironListTabIndex == 0) {
+      if (this.ironListTabIndex === 0) {
         this.listBlurred = false;
       }
     },
diff --git a/ui/webui/resources/js/cr/ui/focus_without_ink.js b/ui/webui/resources/js/cr/ui/focus_without_ink.js
index bb43929..33219cba 100644
--- a/ui/webui/resources/js/cr/ui/focus_without_ink.js
+++ b/ui/webui/resources/js/cr/ui/focus_without_ink.js
@@ -40,7 +40,7 @@
     }
 
     // Make sure the element is in the document we're listening to events on.
-    assert(document == toFocus.ownerDocument);
+    assert(document === toFocus.ownerDocument);
     const {noink} = toFocus;
     toFocus.noink = true;
     toFocus.focus();
diff --git a/ui/webui/resources/js/cr/ui/grid.js b/ui/webui/resources/js/cr/ui/grid.js
index 79d4553..69fdb08 100644
--- a/ui/webui/resources/js/cr/ui/grid.js
+++ b/ui/webui/resources/js/cr/ui/grid.js
@@ -142,9 +142,9 @@
       const horizontalPadding =
           parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
 
-      if (this.lastOffsetWidth_ == offsetWidth &&
-          this.lastOverflowY == overflowY &&
-          this.horizontalPadding_ == horizontalPadding) {
+      if (this.lastOffsetWidth_ === offsetWidth &&
+          this.lastOverflowY === overflowY &&
+          this.horizontalPadding_ === horizontalPadding) {
         this.lastOffsetHeight_ = offsetHeight;
         return;
       }
@@ -155,7 +155,7 @@
       this.horizontalPadding_ = horizontalPadding;
       this.columns_ = 0;
 
-      if (overflowY == 'auto' && offsetWidth > 0) {
+      if (overflowY === 'auto' && offsetWidth > 0) {
         // Column number may depend on whether scrollbar is present or not.
         const originalClientWidth = this.clientWidth;
         // At first make sure there is no scrollbar and calculate clientWidth
@@ -163,7 +163,7 @@
         this.style.overflowY = 'hidden';
         this.clientWidthWithoutScrollbar_ = this.clientWidth;
         this.clientHeight_ = this.clientHeight;
-        if (this.clientWidth != originalClientWidth) {
+        if (this.clientWidth !== originalClientWidth) {
           // If clientWidth changed then previously scrollbar was shown.
           this.clientWidthWithScrollbar_ = originalClientWidth;
         } else {
@@ -191,7 +191,7 @@
       return this.columns_ || 1;
     },
     set columns(value) {
-      if (value >= 0 && value != this.columns_) {
+      if (value >= 0 && value !== this.columns_) {
         this.columns_ = value;
         this.redraw();
       }
@@ -273,7 +273,7 @@
       const afterFiller = this.afterFiller_;
       const columns = this.columns;
 
-      for (let item = this.beforeFiller_.nextSibling; item != afterFiller;) {
+      for (let item = this.beforeFiller_.nextSibling; item !== afterFiller;) {
         const next = item.nextSibling;
         if (isSpacer(item)) {
           // Spacer found on a place it mustn't be.
@@ -287,7 +287,7 @@
         // Invisible pinned item could be outside of the
         // [firstIndex, lastIndex). Ignore it.
         if (index >= firstIndex && nextIndex < lastIndex &&
-            nextIndex % columns == 0) {
+            nextIndex % columns === 0) {
           if (isSpacer(next)) {
             // Leave the spacer on its place.
             item = next.nextSibling;
@@ -305,7 +305,7 @@
 
       function isSpacer(child) {
         return child.classList.contains('spacer') &&
-            child != afterFiller;  // Must not be removed.
+            child !== afterFiller;  // Must not be removed.
       }
     },
 
@@ -332,14 +332,14 @@
      */
     isItem(child) {
       // Non-items are before-, afterFiller and spacers added in mergeItems.
-      return child.nodeType == Node.ELEMENT_NODE &&
+      return child.nodeType === Node.ELEMENT_NODE &&
           !child.classList.contains('spacer');
     },
 
     redraw() {
       this.updateMetrics_();
       const itemCount = this.dataModel ? this.dataModel.length : 0;
-      if (this.lastItemCount_ != itemCount) {
+      if (this.lastItemCount_ !== itemCount) {
         this.lastItemCount_ = itemCount;
         // Force recalculation.
         this.columns_ = 0;
@@ -390,7 +390,7 @@
         return this.getIndexAfter(index);
       }
       const last = this.getLastIndex();
-      if (index == last) {
+      if (index === last) {
         return -1;
       }
       index += this.grid_.columns;
@@ -407,7 +407,7 @@
       if (this.isAccessibilityEnabled()) {
         return this.getIndexBefore(index);
       }
-      if (index == 0) {
+      if (index === 0) {
         return -1;
       }
       index -= this.grid_.columns;
@@ -431,7 +431,7 @@
      * @override
      */
     getIndexAfter(index) {
-      if (index == this.getLastIndex()) {
+      if (index === this.getLastIndex()) {
         return -1;
       }
       return index + 1;
diff --git a/ui/webui/resources/js/cr/ui/keyboard_shortcut_list.js b/ui/webui/resources/js/cr/ui/keyboard_shortcut_list.js
index 935427d..65f9c89 100644
--- a/ui/webui/resources/js/cr/ui/keyboard_shortcut_list.js
+++ b/ui/webui/resources/js/cr/ui/keyboard_shortcut_list.js
@@ -53,12 +53,12 @@
      * @return {boolean} Whether we found a match or not.
      */
     matchesEvent(e) {
-      if ((this.useKeyCode_ && e.keyCode == this.keyCode_) ||
-          e.key == this.key_) {
+      if ((this.useKeyCode_ && e.keyCode === this.keyCode_) ||
+          e.key === this.key_) {
         // All keyboard modifiers need to match.
         const mods = this.mods_;
         return ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].every(function(k) {
-          return e[k] == !!mods[k];
+          return e[k] === !!mods[k];
         });
       }
       return false;
diff --git a/ui/webui/resources/js/cr/ui/list.js b/ui/webui/resources/js/cr/ui/list.js
index dbe2545a..70f8ad9 100644
--- a/ui/webui/resources/js/cr/ui/list.js
+++ b/ui/webui/resources/js/cr/ui/list.js
@@ -109,7 +109,7 @@
       return this.itemConstructor_;
     },
     set itemConstructor(func) {
-      if (func != this.itemConstructor_) {
+      if (func !== this.itemConstructor_) {
         this.itemConstructor_ = func;
         this.cachedItems_ = {};
         this.redraw();
@@ -123,7 +123,7 @@
      * @type {ArrayDataModel}
      */
     set dataModel(dataModel) {
-      if (this.dataModel_ == dataModel) {
+      if (this.dataModel_ === dataModel) {
         return;
       }
 
@@ -186,7 +186,7 @@
     },
     set selectionModel(sm) {
       const oldSm = this.selectionModel_;
-      if (oldSm == sm) {
+      if (oldSm === sm) {
         return;
       }
 
@@ -218,7 +218,7 @@
       return this.autoExpands_;
     },
     set autoExpands(autoExpands) {
-      if (this.autoExpands_ == autoExpands) {
+      if (this.autoExpands_ === autoExpands) {
         return;
       }
       this.autoExpands_ = autoExpands;
@@ -233,7 +233,7 @@
       return this.fixedHeight_;
     },
     set fixedHeight(fixedHeight) {
-      if (this.fixedHeight_ == fixedHeight) {
+      if (this.fixedHeight_ === fixedHeight) {
         return;
       }
       this.fixedHeight_ = fixedHeight;
@@ -248,7 +248,7 @@
       const dataModel = this.dataModel;
       if (dataModel) {
         const index = this.selectionModel.selectedIndex;
-        if (index != -1) {
+        if (index !== -1) {
           return dataModel.item(index);
         }
       }
@@ -292,8 +292,8 @@
      * @return {boolean} True if a list item.
      */
     isItem(child) {
-      return child.nodeType == Node.ELEMENT_NODE &&
-          child != this.beforeFiller_ && child != this.afterFiller_;
+      return child.nodeType === Node.ELEMENT_NODE &&
+          child !== this.beforeFiller_ && child !== this.afterFiller_;
     },
 
     batchCount_: 0,
@@ -313,7 +313,7 @@
      */
     endBatchUpdates() {
       this.batchCount_--;
-      if (this.batchCount_ == 0) {
+      if (this.batchCount_ === 0) {
         this.redraw();
       }
     },
@@ -518,7 +518,7 @@
 
       // If the target was this element we need to make sure that the user did
       // not click on a border or a scrollbar.
-      if (target == this) {
+      if (target === this) {
         if (inViewport(target, e)) {
           this.selectionController_.handlePointerDownUp(e, -1);
         }
@@ -567,7 +567,7 @@
      */
     getListItemAncestor(element) {
       let container = element;
-      while (container && container.parentNode != this) {
+      while (container && container.parentNode !== this) {
         container = container.parentNode;
       }
       return container && assertInstanceof(container, HTMLLIElement);
@@ -603,7 +603,7 @@
 
       let target = /** @type {HTMLElement} */ (e.target);
 
-      if (target == this) {
+      if (target === this) {
         // Unlike the mouse events, we don't check if the touch is inside the
         // viewport because of these reasons:
         // - The scrollbars do not interact with touch.
@@ -651,17 +651,17 @@
      */
     handleLeadChange(e) {
       let element;
-      if (e.oldValue != -1) {
+      if (e.oldValue !== -1) {
         if ((element = this.getListItemByIndex(e.oldValue))) {
           element.lead = false;
         }
       }
 
-      if (e.newValue != -1) {
+      if (e.newValue !== -1) {
         if ((element = this.getListItemByIndex(e.newValue))) {
           element.lead = true;
         }
-        if (e.oldValue != e.newValue) {
+        if (e.oldValue !== e.newValue) {
           if (element) {
             this.setAttribute('aria-activedescendant', element.id);
           }
@@ -700,7 +700,7 @@
     handleDataModelPermuted_(e) {
       const newCachedItems = {};
       for (const index in this.cachedItems_) {
-        if (e.permutation[index] != -1) {
+        if (e.permutation[index] !== -1) {
           const newIndex = e.permutation[index];
           newCachedItems[newIndex] = this.cachedItems_[index];
           newCachedItems[newIndex].listIndex = newIndex;
@@ -711,7 +711,7 @@
 
       const newCachedItemHeights = {};
       for (const index in this.cachedItemHeights_) {
-        if (e.permutation[index] != -1) {
+        if (e.permutation[index] !== -1) {
           newCachedItemHeights[e.permutation[index]] =
               this.cachedItemHeights_[index];
         }
@@ -864,7 +864,7 @@
      */
     getIndexOfListItem(item) {
       const index = item.listIndex;
-      if (this.cachedItems_[index] == item) {
+      if (this.cachedItems_[index] === item) {
         return index;
       }
       return -1;
@@ -878,7 +878,7 @@
     createItem(value) {
       const item = new this.itemConstructor_(value);
       item.label = value;
-      if (typeof item.decorate == 'function') {
+      if (typeof item.decorate === 'function') {
         item.decorate();
       }
       return item;
@@ -1021,7 +1021,7 @@
 
       function remove() {
         const next = item.nextSibling;
-        if (item != self.pinnedItem_) {
+        if (item !== self.pinnedItem_) {
           self.removeChild(item);
         }
         item = next;
@@ -1029,16 +1029,16 @@
 
       let item;
       for (item = this.beforeFiller_.nextSibling;
-           item != this.afterFiller_ && currentIndex < lastIndex;) {
+           item !== this.afterFiller_ && currentIndex < lastIndex;) {
         if (!this.isItem(item)) {
           item = item.nextSibling;
           continue;
         }
 
         const index = item.listIndex;
-        if (this.cachedItems_[index] != item || index < currentIndex) {
+        if (this.cachedItems_[index] !== item || index < currentIndex) {
           remove();
-        } else if (index == currentIndex) {
+        } else if (index === currentIndex) {
           this.cachedItems_[currentIndex] = item;
           item = item.nextSibling;
           currentIndex++;
@@ -1047,7 +1047,7 @@
         }
       }
 
-      while (item != this.afterFiller_) {
+      while (item !== this.afterFiller_) {
         if (this.isItem(item)) {
           remove();
         } else {
@@ -1140,16 +1140,16 @@
      * Redraws the viewport.
      */
     redraw() {
-      if (this.batchCount_ != 0) {
+      if (this.batchCount_ !== 0) {
         return;
       }
 
       const dataModel = this.dataModel;
-      if (!dataModel || !this.autoExpands_ && this.clientHeight == 0) {
+      if (!dataModel || !this.autoExpands_ && this.clientHeight === 0) {
         this.cachedItems_ = {};
         this.firstIndex_ = 0;
         this.lastIndex_ = 0;
-        this.remainingSpace_ = this.clientHeight != 0;
+        this.remainingSpace_ = this.clientHeight !== 0;
         this.mergeItems(0, 0);
         return;
       }
@@ -1187,7 +1187,7 @@
       // it from cache. Note, that we restore the hidden status to false, since
       // the item is still in cache, and may be reused.
       if (this.pinnedItem_ &&
-          this.pinnedItem_ != this.cachedItems_[leadIndex]) {
+          this.pinnedItem_ !== this.cachedItems_[leadIndex]) {
         if (this.pinnedItem_.hidden) {
           this.removeChild(this.pinnedItem_);
           this.pinnedItem_.hidden = false;
@@ -1198,7 +1198,7 @@
       this.mergeItems(firstIndex, lastIndex);
 
       if (!this.pinnedItem_ && this.cachedItems_[leadIndex] &&
-          this.cachedItems_[leadIndex].parentNode == this) {
+          this.cachedItems_[leadIndex].parentNode === this) {
         this.pinnedItem_ = this.cachedItems_[leadIndex];
       }
 
@@ -1210,11 +1210,11 @@
 
       // We don't set the lead or selected properties until after adding all
       // items, in case they force relayout in response to these events.
-      if (leadIndex != -1 && this.cachedItems_[leadIndex]) {
+      if (leadIndex !== -1 && this.cachedItems_[leadIndex]) {
         this.cachedItems_[leadIndex].lead = true;
       }
       for (let y = firstIndex; y < lastIndex; y++) {
-        if (sm.getIndexSelected(y) != this.cachedItems_[y].selected) {
+        if (sm.getIndexSelected(y) !== this.cachedItems_[y].selected) {
           this.cachedItems_[y].selected = !this.cachedItems_[y].selected;
         }
       }
@@ -1298,18 +1298,18 @@
 
       const item =
           cachedItems[index] || this.createItem(this.dataModel.item(index));
-      if (this.pinnedItem_ != item && this.pinnedItem_ &&
+      if (this.pinnedItem_ !== item && this.pinnedItem_ &&
           this.pinnedItem_.hidden) {
         this.removeChild(this.pinnedItem_);
       }
       this.pinnedItem_ = item;
       cachedItems[index] = item;
       item.listIndex = index;
-      if (item.parentNode == this) {
+      if (item.parentNode === this) {
         return item;
       }
 
-      if (this.batchCount_ != 0) {
+      if (this.batchCount_ !== 0) {
         item.hidden = true;
       }
 
@@ -1378,7 +1378,7 @@
     const wasSelected = listItem && listItem.selected;
     this.handlePointerDownUp_(e);
 
-    if (e.defaultPrevented || e.button != 0) {
+    if (e.defaultPrevented || e.button !== 0) {
       return;
     }
 
@@ -1416,7 +1416,7 @@
     }
 
     const index = this.getIndexOfListItem(listItem);
-    if (index == -1) {
+    if (index === -1) {
       return;
     }
 
@@ -1434,7 +1434,7 @@
    * @return {boolean} True if we found a focusable element.
    */
   function containsFocusableElement(start, root) {
-    for (let element = start; element && element != root;
+    for (let element = start; element && element !== root;
          element = element.parentElement) {
       if (element.tabIndex >= 0 && !element.disabled) {
         return true;
diff --git a/ui/webui/resources/js/cr/ui/list_selection_controller.js b/ui/webui/resources/js/cr/ui/list_selection_controller.js
index cdc88ca..0a18b1d 100644
--- a/ui/webui/resources/js/cr/ui/list_selection_controller.js
+++ b/ui/webui/resources/js/cr/ui/list_selection_controller.js
@@ -35,7 +35,7 @@
      * @return {number} The index below or -1 if not found.
      */
     getIndexBelow(index) {
-      if (index == this.getLastIndex()) {
+      if (index === this.getLastIndex()) {
         return -1;
       }
       return index + 1;
@@ -81,7 +81,7 @@
      * @return {number} The next index or -1 if not found.
      */
     getNextIndex(index) {
-      if (index == this.getLastIndex()) {
+      if (index === this.getLastIndex()) {
         return -1;
       }
       return index + 1;
@@ -121,11 +121,11 @@
     handlePointerDownUp(e, index) {
       const sm = this.selectionModel;
       const anchorIndex = sm.anchorIndex;
-      const isDown = (e.type == 'mousedown');
+      const isDown = (e.type === 'mousedown');
 
       sm.beginChange();
 
-      if (index == -1) {
+      if (index === -1) {
         // On Mac we always clear the selection if the user clicks a blank area.
         // On Windows, we only clear the selection if neither Shift nor Ctrl are
         // pressed.
@@ -149,7 +149,7 @@
             sm.leadIndex = index;
             sm.anchorIndex = index;
           }
-        } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
+        } else if (e.shiftKey && anchorIndex !== -1 && anchorIndex !== index) {
           // Shift is done in mousedown.
           if (isDown) {
             sm.unselectAll();
@@ -162,7 +162,7 @@
           }
         } else {
           // Right click for a context menu needs to not clear the selection.
-          const isRightClick = e.button == 2;
+          const isRightClick = e.button === 2;
 
           // If the index is selected this is handled in mouseup.
           const indexSelected = sm.getIndexSelected(index);
@@ -199,21 +199,21 @@
       // If focus is in an input field of some kind, only handle navigation keys
       // that aren't likely to conflict with input interaction (e.g., text
       // editing, or changing the value of a checkbox or select).
-      if (tagName == 'INPUT') {
+      if (tagName === 'INPUT') {
         const inputType = e.target.type;
         // Just protect space (for toggling) for checkbox and radio.
-        if (inputType == 'checkbox' || inputType == 'radio') {
-          if (e.key == ' ') {
+        if (inputType === 'checkbox' || inputType === 'radio') {
+          if (e.key === ' ') {
             return;
           }
           // Protect all but the most basic navigation commands in anything
           // else.
-        } else if (e.key != 'ArrowUp' && e.key != 'ArrowDown') {
+        } else if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {
           return;
         }
       }
       // Similarly, don't interfere with select element handling.
-      if (tagName == 'SELECT') {
+      if (tagName === 'SELECT') {
         return;
       }
 
@@ -223,15 +223,15 @@
       let prevent = true;
 
       // Ctrl/Meta+A
-      if (sm.multiple && e.keyCode == 65 &&
+      if (sm.multiple && e.keyCode === 65 &&
           (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
         sm.selectAll();
         e.preventDefault();
         return;
       }
 
-      if (e.key == ' ') {
-        if (leadIndex != -1) {
+      if (e.key === ' ') {
+        if (leadIndex !== -1) {
           const selected = sm.getIndexSelected(leadIndex);
           if (e.ctrlKey || !selected) {
             sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
@@ -248,28 +248,28 @@
           newIndex = this.getLastIndex();
           break;
         case 'ArrowUp':
-          newIndex = leadIndex == -1 ? this.getLastIndex() :
-                                       this.getIndexAbove(leadIndex);
+          newIndex = leadIndex === -1 ? this.getLastIndex() :
+                                        this.getIndexAbove(leadIndex);
           break;
         case 'ArrowDown':
-          newIndex = leadIndex == -1 ? this.getFirstIndex() :
-                                       this.getIndexBelow(leadIndex);
+          newIndex = leadIndex === -1 ? this.getFirstIndex() :
+                                        this.getIndexBelow(leadIndex);
           break;
         case 'ArrowLeft':
         case 'MediaPreviousTrack':
-          newIndex = leadIndex == -1 ? this.getLastIndex() :
-                                       this.getIndexBefore(leadIndex);
+          newIndex = leadIndex === -1 ? this.getLastIndex() :
+                                        this.getIndexBefore(leadIndex);
           break;
         case 'ArrowRight':
         case 'MediaNextTrack':
-          newIndex = leadIndex == -1 ? this.getFirstIndex() :
-                                       this.getIndexAfter(leadIndex);
+          newIndex = leadIndex === -1 ? this.getFirstIndex() :
+                                        this.getIndexAfter(leadIndex);
           break;
         default:
           prevent = false;
       }
 
-      if (newIndex != -1) {
+      if (newIndex !== -1) {
         sm.beginChange();
 
         sm.leadIndex = newIndex;
@@ -278,7 +278,7 @@
           if (sm.multiple) {
             sm.unselectAll();
           }
-          if (anchorIndex == -1) {
+          if (anchorIndex === -1) {
             sm.setIndexSelected(newIndex, true);
             sm.anchorIndex = newIndex;
           } else {
diff --git a/ui/webui/resources/js/cr/ui/list_selection_model.js b/ui/webui/resources/js/cr/ui/list_selection_model.js
index 0bd052db..28ddbdb0 100644
--- a/ui/webui/resources/js/cr/ui/list_selection_model.js
+++ b/ui/webui/resources/js/cr/ui/list_selection_model.js
@@ -106,7 +106,7 @@
     }
 
     set selectedIndex(selectedIndex) {
-      this.selectedIndexes = selectedIndex != -1 ? [selectedIndex] : [];
+      this.selectedIndexes = selectedIndex !== -1 ? [selectedIndex] : [];
     }
 
     /**
@@ -116,7 +116,7 @@
      * @private
      */
     getNearestSelectedIndex_(index) {
-      if (index == -1) {
+      if (index === -1) {
         // If no index is provided, pick the first selected index if there is
         // one.
         if (this.selectedIndexes.length) {
@@ -150,7 +150,7 @@
 
       this.beginChange();
 
-      for (let index = start; index != end; index++) {
+      for (let index = start; index !== end; index++) {
         this.setIndexSelected(index, true);
       }
       this.setIndexSelected(end, true);
@@ -198,7 +198,7 @@
      */
     setIndexSelected(index, b) {
       const oldSelected = index in this.selectedIndexes_;
-      if (oldSelected == b) {
+      if (oldSelected === b) {
         return;
       }
 
@@ -249,14 +249,14 @@
         // Calls delayed |dispatchPropertyChange|s, only when |leadIndex| or
         // |anchorIndex| has been actually changed in the batch.
         this.leadIndex_ = this.adjustIndex_(this.leadIndex_);
-        if (this.leadIndex_ != this.oldLeadIndex_) {
+        if (this.leadIndex_ !== this.oldLeadIndex_) {
           cr.dispatchPropertyChange(
               this, 'leadIndex', this.leadIndex_, this.oldLeadIndex_);
         }
         this.oldLeadIndex_ = null;
 
         this.anchorIndex_ = this.adjustIndex_(this.anchorIndex_);
-        if (this.anchorIndex_ != this.oldAnchorIndex_) {
+        if (this.anchorIndex_ !== this.oldAnchorIndex_) {
           cr.dispatchPropertyChange(
               this, 'anchorIndex', this.anchorIndex_, this.oldAnchorIndex_);
         }
@@ -292,7 +292,7 @@
       const newValue = this.adjustIndex_(leadIndex);
       this.leadIndex_ = newValue;
       // Delays the call of dispatchPropertyChange if batch is running.
-      if (!this.changeCount_ && newValue != oldValue) {
+      if (!this.changeCount_ && newValue !== oldValue) {
         cr.dispatchPropertyChange(this, 'leadIndex', newValue, oldValue);
       }
     }
@@ -310,7 +310,7 @@
       const newValue = this.adjustIndex_(anchorIndex);
       this.anchorIndex_ = newValue;
       // Delays the call of dispatchPropertyChange if batch is running.
-      if (!this.changeCount_ && newValue != oldValue) {
+      if (!this.changeCount_ && newValue !== oldValue) {
         cr.dispatchPropertyChange(this, 'anchorIndex', newValue, oldValue);
       }
     }
@@ -356,24 +356,24 @@
                                    return permutation[oldIndex];
                                  })
                                  .filter(function(index) {
-                                   return index != -1;
+                                   return index !== -1;
                                  });
 
       // Will be adjusted in endChange.
-      if (oldLeadIndex != -1) {
+      if (oldLeadIndex !== -1) {
         this.leadIndex = permutation[oldLeadIndex];
       }
-      if (oldAnchorIndex != -1) {
+      if (oldAnchorIndex !== -1) {
         this.anchorIndex = permutation[oldAnchorIndex];
       }
 
       if (oldSelectedItemsCount && !this.selectedIndexes.length &&
-          this.length_ && oldLeadIndex != -1) {
+          this.length_ && oldLeadIndex !== -1) {
         // All selected items are deleted. We move selection to next item of
         // last selected item, following it to its new position.
         let newSelectedIndex = Math.min(oldLeadIndex, this.length_ - 1);
         for (let i = oldLeadIndex + 1; i < permutation.length; ++i) {
-          if (permutation[i] != -1) {
+          if (permutation[i] !== -1) {
             newSelectedIndex = permutation[i];
             break;
           }
diff --git a/ui/webui/resources/js/cr/ui/list_single_selection_model.js b/ui/webui/resources/js/cr/ui/list_single_selection_model.js
index 50af5df..da96825 100644
--- a/ui/webui/resources/js/cr/ui/list_single_selection_model.js
+++ b/ui/webui/resources/js/cr/ui/list_single_selection_model.js
@@ -45,7 +45,7 @@
      */
     get selectedIndexes() {
       const i = this.selectedIndex;
-      return i != -1 ? [this.selectedIndex] : [];
+      return i !== -1 ? [this.selectedIndex] : [];
     }
 
     set selectedIndexes(indexes) {
@@ -65,7 +65,7 @@
       const oldSelectedIndex = this.selectedIndex;
       const i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));
 
-      if (i != oldSelectedIndex) {
+      if (i !== oldSelectedIndex) {
         this.beginChange();
         this.selectedIndex_ = i;
         this.leadIndex = i >= 0 ? i : this.leadIndex;
@@ -115,14 +115,14 @@
      */
     setIndexSelected(index, b) {
       // Only allow selection
-      const oldSelected = index == this.selectedIndex_;
-      if (oldSelected == b) {
+      const oldSelected = index === this.selectedIndex_;
+      if (oldSelected === b) {
         return;
       }
 
       if (b) {
         this.selectedIndex = index;
-      } else if (index == this.selectedIndex_) {
+      } else if (index === this.selectedIndex_) {
         this.selectedIndex = -1;
       }
     }
@@ -133,7 +133,7 @@
      * @return {boolean} Whether an index is selected.
      */
     getIndexSelected(index) {
-      return index == this.selectedIndex_;
+      return index === this.selectedIndex_;
     }
 
     /**
@@ -155,7 +155,7 @@
     endChange() {
       this.changeCount_--;
       if (!this.changeCount_) {
-        if (this.selectedIndexBefore_ != this.selectedIndex_) {
+        if (this.selectedIndexBefore_ !== this.selectedIndex_) {
           const beforeChange = this.createChangeEvent('beforeChange');
           if (this.dispatchEvent(beforeChange)) {
             this.dispatchEvent(this.createChangeEvent('change'));
@@ -176,10 +176,10 @@
       e.changes =
           indexes
               .filter(function(index) {
-                return index != -1;
+                return index !== -1;
               })
               .map(function(index) {
-                return {index: index, selected: index == this.selectedIndex_};
+                return {index: index, selected: index === this.selectedIndex_};
               }, this);
 
       return e;
@@ -196,7 +196,7 @@
 
     set leadIndex(leadIndex) {
       const li = this.adjustIndex_(leadIndex);
-      if (li != this.leadIndex_) {
+      if (li !== this.leadIndex_) {
         const oldLeadIndex = this.leadIndex_;
         this.leadIndex_ = li;
         cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
@@ -237,12 +237,12 @@
      * @param {!Array<number>} permutation The reordering permutation.
      */
     adjustToReordering(permutation) {
-      if (this.leadIndex != -1) {
+      if (this.leadIndex !== -1) {
         this.leadIndex = permutation[this.leadIndex];
       }
 
       const oldSelectedIndex = this.selectedIndex;
-      if (oldSelectedIndex != -1) {
+      if (oldSelectedIndex !== -1) {
         this.selectedIndex = permutation[oldSelectedIndex];
       }
     }
diff --git a/ui/webui/resources/js/cr/ui/menu.js b/ui/webui/resources/js/cr/ui/menu.js
index 18d683f..5fb640f 100644
--- a/ui/webui/resources/js/cr/ui/menu.js
+++ b/ui/webui/resources/js/cr/ui/menu.js
@@ -91,7 +91,7 @@
      * @private
      */
     findMenuItem_(node) {
-      while (node && node.parentNode != this && !(node instanceof MenuItem)) {
+      while (node && node.parentNode !== this && !(node instanceof MenuItem)) {
         node = node.parentNode;
       }
       return node ? assertInstanceof(node, MenuItem) : null;
@@ -218,7 +218,7 @@
       }
       // A "position: fixed" element won't have an offsetParent, so we have to
       // do the full style computation.
-      return window.getComputedStyle(menuItem).display != 'none';
+      return window.getComputedStyle(menuItem).display !== 'none';
     },
 
     /**
@@ -254,7 +254,7 @@
           return;
         }
         let i = self.selectedIndex;
-        if (i == -1 && m == -1) {
+        if (i === -1 && m === -1) {
           // Edge case when needed to go the last item first.
           i = 0;
         }
@@ -269,7 +269,7 @@
 
           // Check not to enter into infinite loop if all items are hidden or
           // disabled.
-          if (i == startPosition) {
+          if (i === startPosition) {
             break;
           }
 
diff --git a/ui/webui/resources/js/cr/ui/menu_button.js b/ui/webui/resources/js/cr/ui/menu_button.js
index dd61af1..0f95dc5 100644
--- a/ui/webui/resources/js/cr/ui/menu_button.js
+++ b/ui/webui/resources/js/cr/ui/menu_button.js
@@ -72,7 +72,7 @@
       return this.menu_;
     },
     set menu(menu) {
-      if (typeof menu == 'string' && menu[0] == '#') {
+      if (typeof menu === 'string' && menu[0] === '#') {
         menu = assert(this.ownerDocument.getElementById(menu.slice(1)));
         cr.ui.decorate(menu, Menu);
       }
@@ -123,7 +123,7 @@
           }
           break;
         case 'mousedown':
-          if (e.currentTarget == this.ownerDocument) {
+          if (e.currentTarget === this.ownerDocument) {
             if (this.shouldDismissMenu_(e)) {
               this.hideMenuWithoutTakingFocus_();
             } else {
@@ -132,8 +132,8 @@
           } else {
             if (this.isMenuShown()) {
               this.hideMenuWithoutTakingFocus_();
-            } else if (e.button == 0) {  // Only show the menu when using left
-                                         // mouse button.
+            } else if (e.button === 0) {  // Only show the menu when using left
+                                          // mouse button.
               this.showMenu(false, {x: e.screenX, y: e.screenY});
 
               // Prevent the button from stealing focus on mousedown.
@@ -147,7 +147,7 @@
         case 'keydown':
           this.handleKeyDown(e);
           // If the menu is visible we let it handle all the keyboard events.
-          if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
+          if (this.isMenuShown() && e.currentTarget === this.ownerDocument) {
             this.menu.handleKeyDown(e);
             e.preventDefault();
             e.stopPropagation();
@@ -182,7 +182,7 @@
           }
           break;
         case 'scroll':
-          if (!(e.target == this.menu || this.menu.contains(e.target))) {
+          if (!(e.target === this.menu || this.menu.contains(e.target))) {
             this.hideMenu();
           }
           break;
@@ -280,7 +280,7 @@
       }
 
       this.removeAttribute('menu-shown');
-      if (opt_hideType == HideType.DELAYED) {
+      if (opt_hideType === HideType.DELAYED) {
         this.menu.classList.add('hide-delayed');
       } else {
         this.menu.classList.remove('hide-delayed');
diff --git a/ui/webui/resources/js/cr/ui/menu_item.js b/ui/webui/resources/js/cr/ui/menu_item.js
index 3358b2a..dc81baed 100644
--- a/ui/webui/resources/js/cr/ui/menu_item.js
+++ b/ui/webui/resources/js/cr/ui/menu_item.js
@@ -73,7 +73,7 @@
         this.command_.removeEventListener('checkedChange', this);
       }
 
-      if (typeof command == 'string' && command[0] == '#') {
+      if (typeof command === 'string' && command[0] === '#') {
         command = assert(this.ownerDocument.body.querySelector(command));
         cr.ui.decorate(command, Command);
       }
@@ -126,7 +126,7 @@
      * @return {boolean} Whether the menu item is a separator.
      */
     isSeparator() {
-      return this.tagName == 'HR';
+      return this.tagName === 'HR';
     },
 
     /**
@@ -143,7 +143,7 @@
 
       const shortcuts = this.command_.shortcut.split(/\s+/);
 
-      if (shortcuts.length == 0) {
+      if (shortcuts.length === 0) {
         return;
       }
 
@@ -173,11 +173,11 @@
         }
       });
 
-      if (ident == ' ') {
+      if (ident === ' ') {
         ident = 'Space';
       }
 
-      if (ident.length != 1) {
+      if (ident.length !== 1) {
         shortcutText +=
             loadTimeData.getString('SHORTCUT_' + ident.toUpperCase());
       } else {
diff --git a/ui/webui/resources/js/cr/ui/overlay.js b/ui/webui/resources/js/cr/ui/overlay.js
index 27f75ba..4790f5b 100644
--- a/ui/webui/resources/js/cr/ui/overlay.js
+++ b/ui/webui/resources/js/cr/ui/overlay.js
@@ -54,14 +54,14 @@
         }
 
         // Close the overlay on escape.
-        if (e.key == 'Escape') {
+        if (e.key === 'Escape') {
           cr.dispatchSimpleEvent(overlay, 'cancelOverlay');
         }
 
         // Execute the overlay's default button on enter, unless focus is on an
         // element that has standard behavior for the enter key.
         const forbiddenTagNames = /^(A|BUTTON|SELECT|TEXTAREA)$/;
-        if (e.key == 'Enter' &&
+        if (e.key === 'Enter' &&
             !forbiddenTagNames.test(document.activeElement.tagName)) {
           const button = getDefaultButton(overlay);
           if (button) {
@@ -135,7 +135,7 @@
     // Shake when the user clicks away.
     overlay.addEventListener('click', function(e) {
       // Only pulse if the overlay was the target of the click.
-      if (this != e.target) {
+      if (this !== e.target) {
         return;
       }
 
diff --git a/ui/webui/resources/js/cr/ui/position_util.js b/ui/webui/resources/js/cr/ui/position_util.js
index 0b9bd9f4..b9ab4ee 100644
--- a/ui/webui/resources/js/cr/ui/position_util.js
+++ b/ui/webui/resources/js/cr/ui/position_util.js
@@ -60,7 +60,7 @@
     const cs = ownerDoc.defaultView.getComputedStyle(popupElement);
     const docElement = ownerDoc.documentElement;
 
-    if (cs.position == 'fixed') {
+    if (cs.position === 'fixed') {
       // For 'fixed' positioned popups, the available rectangle should be based
       // on the viewport rather than the document.
       availRect = {
@@ -75,15 +75,15 @@
       availRect = popupElement.offsetParent.getBoundingClientRect();
     }
 
-    if (cs.direction == 'rtl') {
+    if (cs.direction === 'rtl') {
       opt_invertLeftRight = !opt_invertLeftRight;
     }
 
     // Flip BEFORE, AFTER based on alignment.
     if (opt_invertLeftRight) {
-      if (type == AnchorType.BEFORE) {
+      if (type === AnchorType.BEFORE) {
         type = AnchorType.AFTER;
-      } else if (type == AnchorType.AFTER) {
+      } else if (type === AnchorType.AFTER) {
         type = AnchorType.BEFORE;
       }
     }
diff --git a/ui/webui/resources/js/cr/ui/splitter.js b/ui/webui/resources/js/cr/ui/splitter.js
index a426bff..ef4b55b 100644
--- a/ui/webui/resources/js/cr/ui/splitter.js
+++ b/ui/webui/resources/js/cr/ui/splitter.js
@@ -178,7 +178,7 @@
      */
     handleTouchStart_(e) {
       e = /** @type {!TouchEvent} */ (e);
-      if (e.touches.length == 1) {
+      if (e.touches.length === 1) {
         this.startDrag(e.touches[0].clientX, true);
         e.preventDefault();
       }
@@ -199,7 +199,7 @@
      * @param {!TouchEvent} e The touch event.
      */
     handleTouchMove_(e) {
-      if (e.touches.length == 1) {
+      if (e.touches.length === 1) {
         this.handleMove_(e.touches[0].clientX);
       }
     },
@@ -212,7 +212,7 @@
      */
     handleMove_(clientX) {
       const rtl =
-          this.ownerDocument.defaultView.getComputedStyle(this).direction ==
+          this.ownerDocument.defaultView.getComputedStyle(this).direction ===
           'rtl';
       const dirMultiplier = rtl ? -1 : 1;
       const deltaX = dirMultiplier * (clientX - this.startX_);
@@ -266,7 +266,7 @@
       const doc = targetElement.ownerDocument;
       const computedWidth =
           parseFloat(doc.defaultView.getComputedStyle(targetElement).width);
-      if (this.startWidth_ != computedWidth) {
+      if (this.startWidth_ !== computedWidth) {
         cr.dispatchSimpleEvent(this, 'resize');
       }
 
diff --git a/ui/webui/resources/js/cr/ui/tabs.js b/ui/webui/resources/js/cr/ui/tabs.js
index 94cd1572..7394337a 100644
--- a/ui/webui/resources/js/cr/ui/tabs.js
+++ b/ui/webui/resources/js/cr/ui/tabs.js
@@ -11,7 +11,7 @@
    */
   function getTabBox(el) {
     return /** @type {cr.ui.TabBox} */ (findAncestor(el, function(node) {
-      return node.tagName == 'TABBOX';
+      return node.tagName === 'TABBOX';
     }));
   }
 
@@ -21,7 +21,7 @@
    * @return {boolean} Whether the element is a tab related element.
    */
   function isTabElement(el) {
-    return el.tagName == 'TAB' || el.tagName == 'TABPANEL';
+    return el.tagName === 'TAB' || el.tagName === 'TABPANEL';
   }
 
   /**
@@ -57,7 +57,7 @@
     if (element) {
       let i;
       for (i = 0; child = element.children[i]; i++) {
-        const isSelected = i == selectedIndex;
+        const isSelected = i === selectedIndex;
         child.selected = isSelected;
 
         // Update tabIndex for a11y
@@ -78,7 +78,7 @@
     if (element) {
       let i;
       for (i = 0; child = element.children[i]; i++) {
-        child.selected = i == selectedIndex;
+        child.selected = i === selectedIndex;
       }
     }
   }
@@ -106,7 +106,7 @@
      */
     handleSelectedChange_(e) {
       const target = /** @type {cr.ui.Tab|cr.ui.TabPanel}} */ (e.target);
-      if (e.newValue && isTabElement(target) && getTabBox(target) == this) {
+      if (e.newValue && isTabElement(target) && getTabBox(target) === this) {
         const index =
             Array.prototype.indexOf.call(target.parentElement.children, target);
         this.selectedIndex = index;
@@ -166,7 +166,7 @@
       }
 
       const cs = this.ownerDocument.defaultView.getComputedStyle(this);
-      if (cs.direction == 'rtl') {
+      if (cs.direction === 'rtl') {
         delta *= -1;
       }
 
diff --git a/ui/webui/resources/js/cr/ui/touch_handler.js b/ui/webui/resources/js/cr/ui/touch_handler.js
index a9d0d35..d713539 100644
--- a/ui/webui/resources/js/cr/ui/touch_handler.js
+++ b/ui/webui/resources/js/cr/ui/touch_handler.js
@@ -398,7 +398,7 @@
         e.touches = [];
         e.targetTouches = [];
         e.changedTouches = [touch];
-        if (e.type != 'mouseup') {
+        if (e.type !== 'mouseup') {
           e.touches[0] = touch;
           e.targetTouches[0] = touch;
         }
@@ -494,7 +494,7 @@
       // Sign up for end/cancel notifications for this touch.
       // Note that we do this on the document so that even if the user drags
       // their finger off the element, we'll still know what they're doing.
-      if (e.type == 'mousedown') {
+      if (e.type === 'mousedown') {
         this.events_.add(
             document, 'mouseup',
             this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
@@ -515,7 +515,7 @@
           !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
 
       // We want dragging notifications
-      if (e.type == 'mousedown') {
+      if (e.type === 'mousedown') {
         this.events_.add(
             document, 'mousemove',
             this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
@@ -548,7 +548,7 @@
       // A TouchList isn't actually an array, so we shouldn't use
       // Array.prototype.filter/some, etc.
       for (let i = 0; i < touches.length; i++) {
-        if (touches[i].identifier == this.activeTouch_) {
+        if (touches[i].identifier === this.activeTouch_) {
           return touches[i];
         }
       }
@@ -791,8 +791,8 @@
       this.disableTap_ = true;
 
       // Dispatch to the LONG_PRESS
-      assert(typeof this.startTouchX_ == 'number');
-      assert(typeof this.startTouchY_ == 'number');
+      assert(typeof this.startTouchX_ === 'number');
+      assert(typeof this.startTouchY_ === 'number');
       this.dispatchEventXY_(
           TouchHandler.EventType.LONG_PRESS, this.element_,
           /** @type {number} */ (this.startTouchX_),
@@ -829,7 +829,7 @@
       // we'll treat both cases the same and not depend on the target.
       /** @type {Element} */
       let touchedElement;
-      if (eventType == TouchHandler.EventType.TOUCH_START) {
+      if (eventType === TouchHandler.EventType.TOUCH_START) {
         touchedElement = assertInstanceof(touch.target, Element);
       } else {
         touchedElement = assert(this.element_.ownerDocument.elementFromPoint(
@@ -852,9 +852,9 @@
      */
     dispatchEventXY_(eventType, touchedElement, clientX, clientY) {
       const isDrag =
-          (eventType == TouchHandler.EventType.DRAG_START ||
-           eventType == TouchHandler.EventType.DRAG_MOVE ||
-           eventType == TouchHandler.EventType.DRAG_END);
+          (eventType === TouchHandler.EventType.DRAG_START ||
+           eventType === TouchHandler.EventType.DRAG_MOVE ||
+           eventType === TouchHandler.EventType.DRAG_END);
 
       // Drag events don't bubble - we're really just dragging the element,
       // not affecting its parent at all.
@@ -864,9 +864,9 @@
           eventType, bubbles, clientX, clientY, touchedElement);
 
       // Set enableDrag when it can be overridden
-      if (eventType == TouchHandler.EventType.TOUCH_START) {
+      if (eventType === TouchHandler.EventType.TOUCH_START) {
         event.enableDrag = false;
-      } else if (eventType == TouchHandler.EventType.DRAG_START) {
+      } else if (eventType === TouchHandler.EventType.DRAG_START) {
         event.enableDrag = true;
       }
 
diff --git a/ui/webui/resources/js/cr/ui/tree.js b/ui/webui/resources/js/cr/ui/tree.js
index 23842d3f..ac27151 100644
--- a/ui/webui/resources/js/cr/ui/tree.js
+++ b/ui/webui/resources/js/cr/ui/tree.js
@@ -149,7 +149,7 @@
     },
 
     handleMouseDown(e) {
-      if (e.button == 2) {  // right
+      if (e.button === 2) {  // right
         this.handleClick(e);
       }
     },
@@ -181,7 +181,7 @@
         return;
       }
 
-      const rtl = getComputedStyle(item).direction == 'rtl';
+      const rtl = getComputedStyle(item).direction === 'rtl';
 
       switch (e.key) {
         case 'ArrowUp':
@@ -198,7 +198,7 @@
             break;
           }
 
-          if (e.key == 'ArrowLeft' && !rtl || e.key == 'ArrowRight' && rtl) {
+          if (e.key === 'ArrowLeft' && !rtl || e.key === 'ArrowRight' && rtl) {
             if (item.expanded) {
               item.expanded = false;
             } else {
@@ -235,7 +235,7 @@
     },
     set selectedItem(item) {
       const oldSelectedItem = this.selectedItem_;
-      if (oldSelectedItem != item) {
+      if (oldSelectedItem !== item) {
         // Set the selectedItem_ before deselecting the old item since we only
         // want one change when moving between items.
         this.selectedItem_ = item;
@@ -351,7 +351,7 @@
      * @private
      */
     setDepth_(depth) {
-      if (depth != this.depth_) {
+      if (depth !== this.depth_) {
         const rowDepth = Math.max(0, depth - 1);
         if (!customRowElementDepthStyleHandler) {
           this.rowElement.style.paddingInlineStart = rowDepth * INDENT + 'px';
@@ -382,7 +382,7 @@
      */
     addAt(child, index) {
       this.lastElementChild.insertBefore(child, this.items[index]);
-      if (this.items.length == 1) {
+      if (this.items.length === 1) {
         this.hasChildren = true;
       }
       child.setDepth_(this.depth + 1);
@@ -402,7 +402,7 @@
       }
 
       this.lastElementChild.removeChild(/** @type {!cr.ui.TreeItem} */ (child));
-      if (this.items.length == 0) {
+      if (this.items.length === 0) {
         this.hasChildren = false;
       }
     },
@@ -439,7 +439,7 @@
       return this.hasAttribute('expanded');
     },
     set expanded(b) {
-      if (this.expanded == b) {
+      if (this.expanded === b) {
         return;
       }
 
@@ -518,7 +518,7 @@
       return this.hasAttribute('selected');
     },
     set selected(b) {
-      if (this.selected == b) {
+      if (this.selected === b) {
         return;
       }
       const rowItem = this.rowElement;
@@ -534,7 +534,7 @@
       } else {
         this.removeAttribute('selected');
         rowItem.removeAttribute('selected');
-        if (tree && tree.selectedItem == this) {
+        if (tree && tree.selectedItem === this) {
           tree.selectedItem = null;
         }
       }
@@ -586,7 +586,7 @@
      * @param {Event} e The click event.
      */
     handleClick(e) {
-      if (e.target.className == 'expand-icon') {
+      if (e.target.className === 'expand-icon') {
         this.expanded = !this.expanded;
       } else {
         this.selected = true;
@@ -600,7 +600,7 @@
      */
     set editing(editing) {
       const oldEditing = this.editing;
-      if (editing == oldEditing) {
+      if (editing === oldEditing) {
         return;
       }
 
@@ -678,7 +678,7 @@
           labelEl.textContent = this.oldLabel_;
         } else {
           labelEl.textContent = value;
-          if (value != this.oldLabel_) {
+          if (value !== this.oldLabel_) {
             cr.dispatchSimpleEvent(this, 'rename', true);
           }
         }
diff --git a/ui/webui/resources/js/event_tracker.js b/ui/webui/resources/js/event_tracker.js
index bb8a555b..bad9b7f 100644
--- a/ui/webui/resources/js/event_tracker.js
+++ b/ui/webui/resources/js/event_tracker.js
@@ -53,8 +53,8 @@
    */
   remove(target, eventType) {
     this.listeners_ = this.listeners_.filter(listener => {
-      if (listener.target == target &&
-          (!eventType || (listener.eventType == eventType))) {
+      if (listener.target === target &&
+          (!eventType || (listener.eventType === eventType))) {
         EventTracker.removeEventListener(listener);
         return false;
       }
diff --git a/ui/webui/resources/js/find_shortcut_behavior.js b/ui/webui/resources/js/find_shortcut_behavior.js
index 18c68aa3..5a70dd4 100644
--- a/ui/webui/resources/js/find_shortcut_behavior.js
+++ b/ui/webui/resources/js/find_shortcut_behavior.js
@@ -33,7 +33,7 @@
   const shortcutSlash = new cr.ui.KeyboardShortcutList('/');
 
   window.addEventListener('keydown', e => {
-    if (e.defaultPrevented || listeners.length == 0) {
+    if (e.defaultPrevented || listeners.length === 0) {
       return;
     }
 
diff --git a/ui/webui/resources/js/i18n_behavior.js b/ui/webui/resources/js/i18n_behavior.js
index 15af19a..f32995d 100644
--- a/ui/webui/resources/js/i18n_behavior.js
+++ b/ui/webui/resources/js/i18n_behavior.js
@@ -36,7 +36,7 @@
    * @private
    */
   i18nRaw_(id, var_args) {
-    return arguments.length == 1 ?
+    return arguments.length === 1 ?
         loadTimeData.getString(id) :
         loadTimeData.getStringF.apply(loadTimeData, arguments);
   },
diff --git a/ui/webui/resources/js/i18n_template_no_process.js b/ui/webui/resources/js/i18n_template_no_process.js
index 3e1c9e3..9778a6f 100644
--- a/ui/webui/resources/js/i18n_template_no_process.js
+++ b/ui/webui/resources/js/i18n_template_no_process.js
@@ -79,7 +79,7 @@
 
         // Allow a property of the form '.foo.bar' to assign a value into
         // element.foo.bar.
-        if (propName[0] == '.') {
+        if (propName[0] === '.') {
           const path = propName.slice(1).split('.');
           let targetObject = element;
           while (targetObject && path.length > 1) {
@@ -89,7 +89,7 @@
             targetObject[path] = value;
             // In case we set innerHTML (ignoring others) we need to recursively
             // check the content.
-            if (path == 'innerHTML') {
+            if (path[0] === 'innerHTML') {
               for (let i = 0; i < element.children.length; ++i) {
                 processWithoutCycles(element.children[i], data, visited, false);
               }
@@ -193,7 +193,7 @@
     for (let i = 0; i < attributeNames.length; i++) {
       const name = attributeNames[i];
       const attribute = element.getAttribute(name);
-      if (attribute != null) {
+      if (attribute !== null) {
         handlers[name](element, attribute, data, visited);
       }
     }
diff --git a/ui/webui/resources/js/icon.js b/ui/webui/resources/js/icon.js
index 123caae..0d2c740 100644
--- a/ui/webui/resources/js/icon.js
+++ b/ui/webui/resources/js/icon.js
@@ -83,7 +83,7 @@
 
       s += getUrlForCss(pathWithScaleFactor) + ' ' + scaleFactor + 'x';
 
-      if (i != supportedScaleFactors.length - 1) {
+      if (i !== supportedScaleFactors.length - 1) {
         s += ', ';
       }
     }
@@ -100,7 +100,7 @@
   /* #export */ function getImage(path) {
     const chromeThemePath = 'chrome://theme';
     const isChromeThemeUrl =
-        (path.slice(0, chromeThemePath.length) == chromeThemePath);
+        (path.slice(0, chromeThemePath.length) === chromeThemePath);
     return isChromeThemeUrl ? getImageSet(path + '@SCALEFACTORx') :
                               getUrlForCss(path);
   }
diff --git a/ui/webui/resources/js/ios/mojo_api.js b/ui/webui/resources/js/ios/mojo_api.js
index 9954018..b2540452 100644
--- a/ui/webui/resources/js/ios/mojo_api.js
+++ b/ui/webui/resources/js/ios/mojo_api.js
@@ -39,7 +39,7 @@
 Mojo.createMessagePipe = function() {
   const result =
       Mojo.internal.sendMessage({name: 'Mojo.createMessagePipe', args: {}});
-  if (result.result == Mojo.RESULT_OK) {
+  if (result.result === Mojo.RESULT_OK) {
     result.handle0 = new MojoHandle(result.handle0);
     result.handle1 = new MojoHandle(result.handle1);
   }
@@ -176,7 +176,7 @@
   const result = Mojo.internal.sendMessage(
       {name: 'MojoHandle.readMessage', args: {handle: this.nativeHandle_}});
 
-  if (result.result == Mojo.RESULT_OK) {
+  if (result.result === Mojo.RESULT_OK) {
     result.buffer = new Uint8Array(result.buffer).buffer;
     result.handles = result.handles.map(function(handle) {
       return new MojoHandle(handle);
diff --git a/ui/webui/resources/js/list_property_update_behavior.js b/ui/webui/resources/js/list_property_update_behavior.js
index c732325..6812f541 100644
--- a/ui/webui/resources/js/list_property_update_behavior.js
+++ b/ui/webui/resources/js/list_property_update_behavior.js
@@ -49,7 +49,7 @@
     if (!uidBasedUpdate) {
       list.forEach((item, index) => {
         const updatedItem = updatedList[index];
-        if (JSON.stringify(item) != JSON.stringify(updatedItem)) {
+        if (JSON.stringify(item) !== JSON.stringify(updatedItem)) {
           this.set([propertyPath, index], updatedItem);
           updated = true;
         }
diff --git a/ui/webui/resources/js/load_time_data.js b/ui/webui/resources/js/load_time_data.js
index 606b1a2..94545caf 100644
--- a/ui/webui/resources/js/load_time_data.js
+++ b/ui/webui/resources/js/load_time_data.js
@@ -76,7 +76,7 @@
     getValue(id) {
       expect(this.data_, 'No data. Did you remember to include strings.js?');
       const value = this.data_[id];
-      expect(typeof value != 'undefined', 'Could not find value for ' + id);
+      expect(typeof value !== 'undefined', 'Could not find value for ' + id);
       return value;
     },
 
@@ -138,7 +138,7 @@
       const varArgs = arguments;
       return label.replace(/\$(.|$|\n)/g, function(m) {
         assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
-        return m == '$$' ? '$' : varArgs[m[1]];
+        return m === '$$' ? '$' : varArgs[m[1]];
       });
     },
 
@@ -163,7 +163,7 @@
         // with $.
         if (!p.match(/^\$[1-9]$/)) {
           assert(
-              (p.match(/\$/g) || []).length % 2 == 0,
+              (p.match(/\$/g) || []).length % 2 === 0,
               'Unescaped $ found in localized string.');
           return {value: p.replace(/\$\$/g, '$'), arg: null};
         }
@@ -194,7 +194,7 @@
     getInteger(id) {
       const value = this.getValue(id);
       expectIsType(id, value, 'number');
-      expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
+      expect(value === Math.floor(value), 'Number isn\'t integer: ' + value);
       return /** @type {number} */ (value);
     },
 
@@ -204,7 +204,7 @@
      */
     overrideValues(replacements) {
       expect(
-          typeof replacements == 'object',
+          typeof replacements === 'object',
           'Replacements must be a dictionary object.');
       for (const key in replacements) {
         this.data_[key] = replacements[key];
@@ -232,7 +232,7 @@
    */
   function expectIsType(id, value, type) {
     expect(
-        typeof value == type, '[' + value + '] (' + id + ') is not a ' + type);
+        typeof value === type, '[' + value + '] (' + id + ') is not a ' + type);
   }
 
   expect(!loadTimeData, 'should only include this file once');
diff --git a/ui/webui/resources/js/parse_html_subset.js b/ui/webui/resources/js/parse_html_subset.js
index f716851..c5869640 100644
--- a/ui/webui/resources/js/parse_html_subset.js
+++ b/ui/webui/resources/js/parse_html_subset.js
@@ -18,13 +18,13 @@
   const allowedAttributes = {
     'href'(node, value) {
       // Only allow a[href] starting with chrome:// and https://
-      return node.tagName == 'A' &&
+      return node.tagName === 'A' &&
           (value.startsWith('chrome://') || value.startsWith('https://'));
     },
     'target'(node, value) {
       // Only allow a[target='_blank'].
-      // TODO(dbeam): are there valid use cases for target != '_blank'?
-      return node.tagName == 'A' && value == '_blank';
+      // TODO(dbeam): are there valid use cases for target !== '_blank'?
+      return node.tagName === 'A' && value === '_blank';
     },
   };
 
@@ -39,7 +39,7 @@
   function merge(var_args) {
     const clone = {};
     for (let i = 0; i < arguments.length; ++i) {
-      if (typeof arguments[i] == 'object') {
+      if (typeof arguments[i] === 'object') {
         for (const key in arguments[i]) {
           if (arguments[i].hasOwnProperty(key)) {
             clone[key] = arguments[i][key];
@@ -58,7 +58,7 @@
   }
 
   function assertElement(tags, node) {
-    if (tags.indexOf(node.tagName) == -1) {
+    if (tags.indexOf(node.tagName) === -1) {
       throw Error(node.tagName + ' is not supported');
     }
   }
diff --git a/ui/webui/resources/js/polymer_config.js b/ui/webui/resources/js/polymer_config.js
index 119d9479..3433f39 100644
--- a/ui/webui/resources/js/polymer_config.js
+++ b/ui/webui/resources/js/polymer_config.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-if (typeof Polymer == 'undefined') {
+if (typeof Polymer === 'undefined') {
   Polymer = {
     dom: 'shadow',
     lazyRegister: true,
diff --git a/ui/webui/resources/js/search_highlight_utils.js b/ui/webui/resources/js/search_highlight_utils.js
index 6169350..f76e24f 100644
--- a/ui/webui/resources/js/search_highlight_utils.js
+++ b/ui/webui/resources/js/search_highlight_utils.js
@@ -47,7 +47,7 @@
    */
   /* #export */ function findAndRemoveHighlights(node) {
     const wrappers = Array.from(node.querySelectorAll(`.${WRAPPER_CSS_CLASS}`));
-    assert(wrappers.length == 1);
+    assert(wrappers.length === 1);
     removeHighlights(wrappers);
   }
 
@@ -90,7 +90,7 @@
     tokens.push(text.substr(last.start + last.length));
 
     for (let i = 0; i < tokens.length; ++i) {
-      if (i % 2 == 0) {
+      if (i % 2 === 0) {
         wrapper.appendChild(document.createTextNode(tokens[i]));
       } else {
         const hitSpan = document.createElement('span');
diff --git a/ui/webui/resources/js/util.js b/ui/webui/resources/js/util.js
index 669939fc..d04b824 100644
--- a/ui/webui/resources/js/util.js
+++ b/ui/webui/resources/js/util.js
@@ -129,7 +129,7 @@
  * @return {boolean} True if Chrome is running an RTL UI.
  */
 /* #export */ function isRTL() {
-  return document.documentElement.dir == 'rtl';
+  return document.documentElement.dir === 'rtl';
 }
 
 /**
@@ -170,7 +170,7 @@
 /* #export */ function appendParam(url, key, value) {
   const param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
 
-  if (url.indexOf('?') == -1) {
+  if (url.indexOf('?') === -1) {
     return url + '?' + param;
   }
   return url + '&' + param;
@@ -439,5 +439,5 @@
  * @return {boolean} Whether the element is interactive via text input.
  */
 /* #export */ function isTextInputElement(el) {
-  return el.tagName == 'INPUT' || el.tagName == 'TEXTAREA';
+  return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA';
 }
diff --git a/ui/webui/resources/js/webui_resource_test.js b/ui/webui/resources/js/webui_resource_test.js
index 0ab10df3..0c6396b 100644
--- a/ui/webui/resources/js/webui_resource_test.js
+++ b/ui/webui/resources/js/webui_resource_test.js
@@ -82,7 +82,7 @@
 function assertArrayEquals(expected, observed) {
   const v1 = Array.prototype.slice.call(expected);
   const v2 = Array.prototype.slice.call(observed);
-  let equal = v1.length == v2.length;
+  let equal = v1.length === v2.length;
   if (equal) {
     for (let i = 0; i < v1.length; i++) {
       if (v1[i] !== v2[i]) {
@@ -104,7 +104,7 @@
  * @param {*} observed The actual result.
  */
 function assertDeepEquals(expected, observed, opt_message) {
-  if (typeof expected == 'object' && expected != null) {
+  if (typeof expected === 'object' && expected !== null) {
     assertNotEqual(null, observed);
     for (const key in expected) {
       assertTrue(key in observed, opt_message);
@@ -201,7 +201,7 @@
   testHarness = /** @type{!WebUiTestHarness} */ (testScope);
   for (const name in testScope) {
     // To avoid unnecessary getting properties, test name first.
-    if (/^test/.test(name) && typeof testScope[name] == 'function') {
+    if (/^test/.test(name) && typeof testScope[name] === 'function') {
       testCases.push(name);
     }
   }
@@ -290,7 +290,7 @@
  */
 function endTests(success) {
   const duration =
-      runnerStartTime == 0 ? 0 : performance.now() - runnerStartTime;
+      runnerStartTime === 0 ? 0 : performance.now() - runnerStartTime;
   console.log(
       'TEST all complete, status=' + (success ? 'PASS' : 'FAIL') +
       ', duration=' + Math.round(duration) + 'ms');
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index 611db45..6bf28780 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -100,6 +100,8 @@
     "browser/browser_process.h",
     "browser/content_browser_client_impl.cc",
     "browser/content_browser_client_impl.h",
+    "browser/download_impl.cc",
+    "browser/download_impl.h",
     "browser/download_manager_delegate_impl.cc",
     "browser/download_manager_delegate_impl.h",
     "browser/fake_permission_controller_delegate.cc",
@@ -141,6 +143,7 @@
     "common/weblayer_paths.h",
     "public/common/switches.cc",
     "public/common/switches.h",
+    "public/download.h",
     "public/download_delegate.h",
     "public/error_page_delegate.h",
     "public/fullscreen_delegate.h",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index d327d60..bb81182d 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -5,6 +5,7 @@
   "+components/autofill/core/browser",
   "+components/autofill/core/common",
   "+components/crash/content/browser",
+  "+components/download/public/common",
   "+components/embedder_support",
   "+components/network_time",
   "+components/prefs",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java
index 3ef5f94..e62844e 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/DownloadCallbackTest.java
@@ -5,8 +5,10 @@
 package org.chromium.weblayer.test;
 
 import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.util.Pair;
+import android.webkit.ValueCallback;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -18,9 +20,14 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.util.TestWebServer;
+import org.chromium.weblayer.Download;
 import org.chromium.weblayer.DownloadCallback;
+import org.chromium.weblayer.DownloadError;
+import org.chromium.weblayer.DownloadState;
+import org.chromium.weblayer.Profile;
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -41,7 +48,14 @@
         public String mUserAgent;
         public String mContentDisposition;
         public String mMimetype;
+        public String mLocation;
+        public @DownloadState int mState;
+        public @DownloadError int mError;
         public long mContentLength;
+        public boolean mIntercept;
+        public boolean mSeenStarted;
+        public boolean mSeenCompleted;
+        public boolean mSeenFailed;
 
         @Override
         public boolean onInterceptDownload(Uri uri, String userAgent, String contentDisposition,
@@ -51,10 +65,36 @@
             mContentDisposition = contentDisposition;
             mMimetype = mimetype;
             mContentLength = contentLength;
-            return true;
+            return mIntercept;
         }
 
-        public void waitForDownload() {
+        @Override
+        public void allowDownload(Uri uri, String requestMethod, Uri requestInitiator,
+                ValueCallback<Boolean> callback) {
+            callback.onReceiveValue(true);
+        }
+
+        @Override
+        public void onDownloadStarted(Download download) {
+            mSeenStarted = true;
+        }
+
+        @Override
+        public void onDownloadCompleted(Download download) {
+            mSeenCompleted = true;
+            mLocation = download.getLocation().toString();
+            mState = download.getState();
+            mError = download.getError();
+        }
+
+        @Override
+        public void onDownloadFailed(Download download) {
+            mSeenFailed = true;
+            mState = download.getState();
+            mError = download.getError();
+        }
+
+        public void waitForIntercept() {
             CriteriaHelper.pollInstrumentationThread(new Criteria() {
                 @Override
                 public boolean isSatisfied() {
@@ -62,6 +102,33 @@
                 }
             }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         }
+
+        public void waitForStarted() {
+            CriteriaHelper.pollInstrumentationThread(new Criteria() {
+                @Override
+                public boolean isSatisfied() {
+                    return mSeenStarted;
+                }
+            }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
+
+        public void waitForCompleted() {
+            CriteriaHelper.pollInstrumentationThread(new Criteria() {
+                @Override
+                public boolean isSatisfied() {
+                    return mSeenCompleted;
+                }
+            }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
+
+        public void waitForFailed() {
+            CriteriaHelper.pollInstrumentationThread(new Criteria() {
+                @Override
+                public boolean isSatisfied() {
+                    return mSeenFailed;
+                }
+            }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }
     }
 
     @Before
@@ -69,9 +136,17 @@
         mActivity = mActivityTestRule.launchShellWithUrl(null);
         Assert.assertNotNull(mActivity);
 
+        // Don't fill up the default download directory on the device.
+        String tempDownloadDirectory =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir()
+                + "/weblayer/Downloads";
+
         mCallback = new Callback();
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> { mActivity.getTab().setDownloadCallback(mCallback); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivity.getTab().setDownloadCallback(mCallback);
+            Profile profile = mActivity.getBrowser().getProfile();
+            profile.setDownloadDirectory(new File(tempDownloadDirectory));
+        });
     }
 
     /**
@@ -80,7 +155,8 @@
      */
     @Test
     @SmallTest
-    public void testDownloadByContentDisposition() throws Throwable {
+    public void testInterceptDownloadByContentDisposition() throws Throwable {
+        mCallback.mIntercept = true;
         final String data = "download data";
         final String contentDisposition = "attachment;filename=\"download.txt\"";
         final String mimetype = "text/plain";
@@ -96,7 +172,7 @@
             TestThreadUtils.runOnUiThreadBlocking(() -> {
                 mActivity.getTab().getNavigationController().navigate(Uri.parse(pageUrl));
             });
-            mCallback.waitForDownload();
+            mCallback.waitForIntercept();
 
             Assert.assertEquals(pageUrl, mCallback.mUrl);
             Assert.assertEquals(contentDisposition, mCallback.mContentDisposition);
@@ -114,12 +190,28 @@
      */
     @Test
     @SmallTest
-    public void testDownloadByLinkAttribute() {
+    public void testInterceptDownloadByLinkAttribute() {
+        mCallback.mIntercept = true;
         String pageUrl = mActivityTestRule.getTestDataURL("download.html");
         mActivityTestRule.navigateAndWait(pageUrl);
 
         EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
-        mCallback.waitForDownload();
+        mCallback.waitForIntercept();
         Assert.assertEquals(mActivityTestRule.getTestDataURL("lorem_ipsum.txt"), mCallback.mUrl);
     }
+
+    @Test
+    @SmallTest
+    public void testBasic() {
+        String url = mActivityTestRule.getTestDataURL("content-disposition.html");
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivity.getTab().getNavigationController().navigate(Uri.parse(url)); });
+        mCallback.waitForStarted();
+        mCallback.waitForCompleted();
+
+        Assert.assertTrue(mCallback.mLocation.contains(
+                "org.chromium.weblayer.shell/cache/weblayer/Downloads/"));
+        Assert.assertEquals(DownloadState.COMPLETE, mCallback.mState);
+        Assert.assertEquals(DownloadError.NO_ERROR, mCallback.mError);
+    }
 }
diff --git a/weblayer/browser/browser_context_impl.cc b/weblayer/browser/browser_context_impl.cc
index 564c4ca..5ab0267a 100644
--- a/weblayer/browser/browser_context_impl.cc
+++ b/weblayer/browser/browser_context_impl.cc
@@ -4,18 +4,46 @@
 
 #include "weblayer/browser/browser_context_impl.h"
 
+#include "components/download/public/common/in_progress_download_manager.h"
 #include "components/prefs/in_memory_pref_store.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/pref_service_factory.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/device_service.h"
+#include "content/public/browser/download_request_utils.h"
 #include "content/public/browser/resource_context.h"
 #include "weblayer/browser/fake_permission_controller_delegate.h"
 #include "weblayer/public/common/switches.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/path_utils.h"
+#elif defined(OS_WIN)
+#include <KnownFolders.h>
+#include <shlobj.h>
+#include "base/win/scoped_co_mem.h"
+#elif defined(OS_POSIX)
+#include "base/nix/xdg_util.h"
+#endif
+
 namespace weblayer {
 
+namespace {
+
+// Ignores origin security check. DownloadManagerImpl will provide its own
+// implementation when InProgressDownloadManager object is passed to it.
+bool IgnoreOriginSecurityCheck(const GURL& url) {
+  return true;
+}
+
+void BindWakeLockProvider(
+    mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) {
+  content::GetDeviceService().BindWakeLockProvider(std::move(receiver));
+}
+
+}  // namespace
+
 class ResourceContextImpl : public content::ResourceContext {
  public:
   ResourceContextImpl() = default;
@@ -29,7 +57,8 @@
                                        const base::FilePath& path)
     : profile_impl_(profile_impl),
       path_(path),
-      resource_context_(new ResourceContextImpl()) {
+      resource_context_(new ResourceContextImpl()),
+      download_delegate_(BrowserContext::GetDownloadManager(this)) {
   content::BrowserContext::Initialize(this, path_);
 
   CreateUserPrefService();
@@ -39,6 +68,26 @@
   NotifyWillBeDestroyed(this);
 }
 
+base::FilePath BrowserContextImpl::GetDefaultDownloadDirectory() {
+  // Note: if we wanted to productionize this on Windows/Linux, refactor
+  // src/chrome's GetDefaultDownloadDirectory.
+  base::FilePath download_dir;
+#if defined(OS_ANDROID)
+  base::android::GetDownloadsDirectory(&download_dir);
+#elif defined(OS_WIN)
+  base::win::ScopedCoMem<wchar_t> path_buf;
+  if (SUCCEEDED(
+          SHGetKnownFolderPath(FOLDERID_Downloads, 0, nullptr, &path_buf))) {
+    download_dir = base::FilePath(path_buf.get());
+  } else {
+    NOTREACHED();
+  }
+#else
+  download_dir = base::nix::GetXDGUserDirectory("DOWNLOAD", "Downloads");
+#endif
+  return download_dir;
+}
+
 #if !defined(OS_ANDROID)
 std::unique_ptr<content::ZoomLevelDelegate>
 BrowserContextImpl::CreateZoomLevelDelegate(const base::FilePath&) {
@@ -117,6 +166,22 @@
   return nullptr;
 }
 
+download::InProgressDownloadManager*
+BrowserContextImpl::RetriveInProgressDownloadManager() {
+  // Override this to provide a connection to the wake lock service.
+  auto* download_manager = new download::InProgressDownloadManager(
+      nullptr, base::FilePath(), nullptr,
+      base::BindRepeating(&IgnoreOriginSecurityCheck),
+      base::BindRepeating(&content::DownloadRequestUtils::IsURLSafe),
+      base::BindRepeating(&BindWakeLockProvider));
+
+#if defined(OS_ANDROID)
+  download_manager->set_default_download_dir(GetDefaultDownloadDirectory());
+#endif
+
+  return download_manager;
+}
+
 content::ContentIndexProvider* BrowserContextImpl::GetContentIndexProvider() {
   return nullptr;
 }
diff --git a/weblayer/browser/browser_context_impl.h b/weblayer/browser/browser_context_impl.h
index 17be4059..3973709d 100644
--- a/weblayer/browser/browser_context_impl.h
+++ b/weblayer/browser/browser_context_impl.h
@@ -6,7 +6,6 @@
 #define WEBLAYER_BROWSER_BROWSER_CONTEXT_IMPL_H_
 
 #include "base/files/file_path.h"
-#include "base/macros.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
@@ -25,6 +24,10 @@
  public:
   BrowserContextImpl(ProfileImpl* profile_impl, const base::FilePath& path);
   ~BrowserContextImpl() override;
+  BrowserContextImpl(const BrowserContextImpl&) = delete;
+  BrowserContextImpl& operator=(const BrowserContextImpl&) = delete;
+
+  static base::FilePath GetDefaultDownloadDirectory();
 
   // BrowserContext implementation:
 #if !defined(OS_ANDROID)
@@ -49,6 +52,8 @@
   content::BackgroundSyncController* GetBackgroundSyncController() override;
   content::BrowsingDataRemoverDelegate* GetBrowsingDataRemoverDelegate()
       override;
+  download::InProgressDownloadManager* RetriveInProgressDownloadManager()
+      override;
   content::ContentIndexProvider* GetContentIndexProvider() override;
 
   ProfileImpl* profile_impl() const { return profile_impl_; }
@@ -78,8 +83,6 @@
   std::unique_ptr<PrefService> user_pref_service_;
   std::unique_ptr<content::PermissionControllerDelegate>
       permission_controller_delegate_;
-
-  DISALLOW_COPY_AND_ASSIGN(BrowserContextImpl);
 };
 }  // namespace weblayer
 
diff --git a/weblayer/browser/download_browsertest.cc b/weblayer/browser/download_browsertest.cc
new file mode 100644
index 0000000..59a53c81
--- /dev/null
+++ b/weblayer/browser/download_browsertest.cc
@@ -0,0 +1,327 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "weblayer/test/weblayer_browser_test.h"
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/slow_download_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "weblayer/browser/browser_context_impl.h"
+#include "weblayer/browser/download_manager_delegate_impl.h"
+#include "weblayer/browser/profile_impl.h"
+#include "weblayer/browser/tab_impl.h"
+#include "weblayer/public/download.h"
+#include "weblayer/public/download_delegate.h"
+#include "weblayer/public/navigation_controller.h"
+#include "weblayer/shell/browser/shell.h"
+#include "weblayer/test/test_navigation_observer.h"
+
+namespace weblayer {
+
+namespace {
+
+class DownloadBrowserTest : public WebLayerBrowserTest,
+                            public DownloadDelegate {
+ public:
+  DownloadBrowserTest() = default;
+  ~DownloadBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+    allow_run_loop_ = std::make_unique<base::RunLoop>();
+    started_run_loop_ = std::make_unique<base::RunLoop>();
+    intercept_run_loop_ = std::make_unique<base::RunLoop>();
+    completed_run_loop_ = std::make_unique<base::RunLoop>();
+    failed_run_loop_ = std::make_unique<base::RunLoop>();
+
+    Tab* tab = shell()->tab();
+
+    tab->SetDownloadDelegate(this);
+
+    TabImpl* tab_impl = static_cast<TabImpl*>(tab);
+    auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
+    auto* download_manager_delegate =
+        content::BrowserContext::GetDownloadManager(browser_context)
+            ->GetDelegate();
+    static_cast<DownloadManagerDelegateImpl*>(download_manager_delegate)
+        ->set_download_dropped_closure_for_testing(base::BindRepeating(
+            &DownloadBrowserTest::DownloadDropped, base::Unretained(this)));
+  }
+
+  void WaitForAllow() { allow_run_loop_->Run(); }
+  void WaitForIntercept() { intercept_run_loop_->Run(); }
+  void WaitForStarted() { started_run_loop_->Run(); }
+  void WaitForCompleted() { completed_run_loop_->Run(); }
+  void WaitForFailed() { failed_run_loop_->Run(); }
+
+  void set_intercept() { intercept_ = true; }
+  void set_disallow() { allow_ = false; }
+  void set_started_callback(
+      base::OnceCallback<void(Download* download)> callback) {
+    started_callback_ = std::move(callback);
+  }
+  void set_failed_callback(
+      base::OnceCallback<void(Download* download)> callback) {
+    failed_callback_ = std::move(callback);
+  }
+  bool started() { return started_; }
+  base::FilePath download_location() { return download_location_; }
+  int64_t total_bytes() { return total_bytes_; }
+  DownloadError download_state() { return download_state_; }
+  int completed_count() { return completed_count_; }
+  int failed_count() { return failed_count_; }
+  int download_dropped_count() { return download_dropped_count_; }
+
+ private:
+  // DownloadDelegate implementation:
+  void AllowDownload(const GURL& url,
+                     const std::string& request_method,
+                     base::Optional<url::Origin> request_initiator,
+                     AllowDownloadCallback callback) override {
+    std::move(callback).Run(allow_);
+    allow_run_loop_->Quit();
+  }
+
+  bool InterceptDownload(const GURL& url,
+                         const std::string& user_agent,
+                         const std::string& content_disposition,
+                         const std::string& mime_type,
+                         int64_t content_length) override {
+    intercept_run_loop_->Quit();
+    return intercept_;
+  }
+
+  void DownloadStarted(Download* download) override {
+    started_ = true;
+    started_run_loop_->Quit();
+
+    CHECK_EQ(download->GetState(), DownloadState::kInProgress);
+
+    if (started_callback_)
+      std::move(started_callback_).Run(download);
+  }
+
+  void DownloadCompleted(Download* download) override {
+    completed_count_++;
+    download_location_ = download->GetLocation();
+    total_bytes_ = download->GetTotalBytes();
+    download_state_ = download->GetError();
+    CHECK_EQ(download->GetReceivedBytes(), total_bytes_);
+    CHECK_EQ(download->GetState(), DownloadState::kComplete);
+    completed_run_loop_->Quit();
+  }
+
+  void DownloadFailed(Download* download) override {
+    failed_count_++;
+    download_state_ = download->GetError();
+    failed_run_loop_->Quit();
+
+    if (failed_callback_)
+      std::move(failed_callback_).Run(download);
+  }
+
+  void DownloadDropped() { download_dropped_count_++; }
+
+  bool intercept_ = false;
+  bool allow_ = true;
+  bool started_ = false;
+  base::OnceCallback<void(Download* download)> started_callback_;
+  base::OnceCallback<void(Download* download)> failed_callback_;
+  base::FilePath download_location_;
+  int64_t total_bytes_ = 0;
+  DownloadError download_state_ = DownloadError::kNoError;
+  int completed_count_ = 0;
+  int failed_count_ = 0;
+  int download_dropped_count_ = 0;
+  std::unique_ptr<base::RunLoop> allow_run_loop_;
+  std::unique_ptr<base::RunLoop> intercept_run_loop_;
+  std::unique_ptr<base::RunLoop> started_run_loop_;
+  std::unique_ptr<base::RunLoop> completed_run_loop_;
+  std::unique_ptr<base::RunLoop> failed_run_loop_;
+};
+
+}  // namespace
+
+// Ensures that if the delegate disallows the downloads then WebLayer
+// doesn't download it.
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DisallowNoDownload) {
+  set_disallow();
+
+  GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
+
+  // Downloads always count as failed navigations.
+  TestNavigationObserver observer(
+      url, TestNavigationObserver::NavigationEvent::Failure, shell());
+  shell()->tab()->GetNavigationController()->Navigate(url);
+  observer.Wait();
+
+  WaitForAllow();
+
+  EXPECT_FALSE(started());
+  EXPECT_EQ(completed_count(), 0);
+  EXPECT_EQ(failed_count(), 0);
+  EXPECT_EQ(download_dropped_count(), 1);
+}
+
+// Ensures that if the delegate chooses to intercept downloads then WebLayer
+// doesn't download it.
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, InterceptNoDownload) {
+  set_intercept();
+
+  GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
+
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForIntercept();
+
+  EXPECT_FALSE(started());
+  EXPECT_EQ(completed_count(), 0);
+  EXPECT_EQ(failed_count(), 0);
+  EXPECT_EQ(download_dropped_count(), 1);
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Basic) {
+  GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
+
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForCompleted();
+
+  EXPECT_TRUE(started());
+  EXPECT_EQ(completed_count(), 1);
+  EXPECT_EQ(failed_count(), 0);
+  EXPECT_EQ(download_dropped_count(), 0);
+  EXPECT_EQ(download_state(), DownloadError::kNoError);
+
+  // Check that the size on disk matches what's expected.
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int64_t downloaded_file_size, original_file_size;
+    EXPECT_TRUE(base::GetFileSize(download_location(), &downloaded_file_size));
+    base::FilePath test_data_dir;
+    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+    EXPECT_TRUE(base::GetFileSize(
+        test_data_dir.Append(base::FilePath(
+            FILE_PATH_LITERAL("weblayer/test/data/content-disposition.html"))),
+        &original_file_size));
+    EXPECT_EQ(downloaded_file_size, total_bytes());
+  }
+
+  // Ensure browser tests don't write to the default machine download directory
+  // to avoid filing it up.
+  EXPECT_NE(BrowserContextImpl::GetDefaultDownloadDirectory(),
+            download_location().DirName());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, OverrideDownloadDirectory) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::ScopedTempDir download_dir;
+  ASSERT_TRUE(download_dir.CreateUniqueTempDir());
+
+  TabImpl* tab_impl = static_cast<TabImpl*>(shell()->tab());
+  auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
+  auto* browser_context_impl =
+      static_cast<BrowserContextImpl*>(browser_context);
+  browser_context_impl->profile_impl()->SetDownloadDirectory(
+      download_dir.GetPath());
+
+  GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
+
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForCompleted();
+
+  EXPECT_EQ(completed_count(), 1);
+  EXPECT_EQ(failed_count(), 0);
+  EXPECT_EQ(download_dir.GetPath(), download_location().DirName());
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Cancel) {
+  set_started_callback(base::BindLambdaForTesting([&](Download* download) {
+    download->Cancel();
+
+    // Also allow the download to complete.
+    GURL url = embedded_test_server()->GetURL(
+        content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
+    shell()->tab()->GetNavigationController()->Navigate(url);
+  }));
+
+  set_failed_callback(base::BindLambdaForTesting([](Download* download) {
+    CHECK_EQ(download->GetState(), DownloadState::kCancelled);
+  }));
+
+  // Create a request that doesn't complete right away to avoid flakiness.
+  GURL url(embedded_test_server()->GetURL(
+      content::SlowDownloadHttpResponse::kKnownSizeUrl));
+
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForFailed();
+  EXPECT_EQ(completed_count(), 0);
+  EXPECT_EQ(failed_count(), 1);
+  EXPECT_EQ(download_dropped_count(), 0);
+  EXPECT_EQ(download_state(), DownloadError::kCancelled);
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, PauseResume) {
+  set_started_callback(base::BindLambdaForTesting([&](Download* download) {
+    download->Pause();
+    GURL url = embedded_test_server()->GetURL(
+        content::SlowDownloadHttpResponse::kFinishSlowResponseUrl);
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(
+                       [](Download* download, Shell* shell, const GURL& url) {
+                         CHECK_EQ(download->GetState(), DownloadState::kPaused);
+                         download->Resume();
+
+                         // Also allow the download to complete.
+                         shell->tab()->GetNavigationController()->Navigate(url);
+                       },
+                       download, shell(), url));
+  }));
+
+  // Create a request that doesn't complete right away to avoid flakiness.
+  GURL url(embedded_test_server()->GetURL(
+      content::SlowDownloadHttpResponse::kKnownSizeUrl));
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForCompleted();
+  EXPECT_EQ(completed_count(), 1);
+  EXPECT_EQ(failed_count(), 0);
+  EXPECT_EQ(download_dropped_count(), 0);
+}
+
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, NetworkError) {
+  set_failed_callback(base::BindLambdaForTesting([](Download* download) {
+    CHECK_EQ(download->GetState(), DownloadState::kFailed);
+  }));
+
+  // Create a request that doesn't complete right away.
+  GURL url(embedded_test_server()->GetURL(
+      content::SlowDownloadHttpResponse::kKnownSizeUrl));
+
+  shell()->tab()->GetNavigationController()->Navigate(url);
+
+  WaitForStarted();
+  EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+
+  WaitForFailed();
+  EXPECT_EQ(completed_count(), 0);
+  EXPECT_EQ(failed_count(), 1);
+  EXPECT_EQ(download_dropped_count(), 0);
+  EXPECT_EQ(download_state(), DownloadError::kConnectivityError);
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/download_callback_proxy.cc b/weblayer/browser/download_callback_proxy.cc
index 161d7a6d..4602d1e 100644
--- a/weblayer/browser/download_callback_proxy.cc
+++ b/weblayer/browser/download_callback_proxy.cc
@@ -6,6 +6,7 @@
 
 #include "base/android/jni_string.h"
 #include "url/gurl.h"
+#include "weblayer/browser/download_impl.h"
 #include "weblayer/browser/java/jni/DownloadCallbackProxy_jni.h"
 #include "weblayer/browser/tab_impl.h"
 
@@ -45,6 +46,56 @@
       jstring_content_disposition, jstring_mime_type, content_length);
 }
 
+void DownloadCallbackProxy::AllowDownload(
+    const GURL& url,
+    const std::string& request_method,
+    base::Optional<url::Origin> request_initiator,
+    AllowDownloadCallback callback) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> jstring_url(
+      ConvertUTF8ToJavaString(env, url.spec()));
+  ScopedJavaLocalRef<jstring> jstring_method(
+      ConvertUTF8ToJavaString(env, request_method));
+  ScopedJavaLocalRef<jstring> jstring_request_initator;
+  if (request_initiator)
+    jstring_request_initator =
+        ConvertUTF8ToJavaString(env, request_initiator->Serialize());
+  // Make copy on the heap so we can pass the pointer through JNI. This will be
+  // deleted when it's run.
+  intptr_t callback_id = reinterpret_cast<intptr_t>(
+      new AllowDownloadCallback(std::move(callback)));
+  Java_DownloadCallbackProxy_allowDownload(
+      env, java_delegate_, jstring_url, jstring_method,
+      jstring_request_initator, callback_id);
+}
+
+void DownloadCallbackProxy::DownloadStarted(Download* download) {
+  DownloadImpl* download_impl = static_cast<DownloadImpl*>(download);
+  JNIEnv* env = AttachCurrentThread();
+  Java_DownloadCallbackProxy_createDownload(
+      env, java_delegate_, reinterpret_cast<jlong>(download_impl));
+  Java_DownloadCallbackProxy_downloadStarted(env, java_delegate_,
+                                             download_impl->java_download());
+}
+
+void DownloadCallbackProxy::DownloadProgressChanged(Download* download) {
+  DownloadImpl* download_impl = static_cast<DownloadImpl*>(download);
+  Java_DownloadCallbackProxy_downloadProgressChanged(
+      AttachCurrentThread(), java_delegate_, download_impl->java_download());
+}
+
+void DownloadCallbackProxy::DownloadCompleted(Download* download) {
+  DownloadImpl* download_impl = static_cast<DownloadImpl*>(download);
+  Java_DownloadCallbackProxy_downloadCompleted(
+      AttachCurrentThread(), java_delegate_, download_impl->java_download());
+}
+
+void DownloadCallbackProxy::DownloadFailed(Download* download) {
+  DownloadImpl* download_impl = static_cast<DownloadImpl*>(download);
+  Java_DownloadCallbackProxy_downloadFailed(
+      AttachCurrentThread(), java_delegate_, download_impl->java_download());
+}
+
 static jlong JNI_DownloadCallbackProxy_CreateDownloadCallbackProxy(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& proxy,
@@ -58,4 +109,12 @@
   delete reinterpret_cast<DownloadCallbackProxy*>(proxy);
 }
 
+static void JNI_DownloadCallbackProxy_AllowDownload(JNIEnv* env,
+                                                    jlong callback_id,
+                                                    jboolean allow) {
+  std::unique_ptr<AllowDownloadCallback> cb(
+      reinterpret_cast<AllowDownloadCallback*>(callback_id));
+  std::move(*cb).Run(allow);
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/download_callback_proxy.h b/weblayer/browser/download_callback_proxy.h
index b4980288..8731969 100644
--- a/weblayer/browser/download_callback_proxy.h
+++ b/weblayer/browser/download_callback_proxy.h
@@ -28,6 +28,14 @@
                          const std::string& content_disposition,
                          const std::string& mime_type,
                          int64_t content_length) override;
+  void AllowDownload(const GURL& url,
+                     const std::string& request_method,
+                     base::Optional<url::Origin> request_initiator,
+                     AllowDownloadCallback callback) override;
+  void DownloadStarted(Download* download) override;
+  void DownloadProgressChanged(Download* download) override;
+  void DownloadCompleted(Download* download) override;
+  void DownloadFailed(Download* download) override;
 
  private:
   Tab* tab_;
diff --git a/weblayer/browser/download_impl.cc b/weblayer/browser/download_impl.cc
new file mode 100644
index 0000000..cbef4c6f
--- /dev/null
+++ b/weblayer/browser/download_impl.cc
@@ -0,0 +1,162 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "weblayer/browser/download_impl.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/download/public/common/download_item.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_string.h"
+#include "weblayer/browser/java/jni/DownloadImpl_jni.h"
+#endif
+
+namespace weblayer {
+
+namespace {
+const char kDownloadImplKeyName[] = "weblayer_download_impl";
+}
+
+void DownloadImpl::Create(download::DownloadItem* item) {
+  item->SetUserData(kDownloadImplKeyName,
+                    base::WrapUnique(new DownloadImpl(item)));
+}
+
+DownloadImpl* DownloadImpl::Get(download::DownloadItem* item) {
+  return static_cast<DownloadImpl*>(item->GetUserData(kDownloadImplKeyName));
+}
+
+DownloadImpl::DownloadImpl(download::DownloadItem* item) : item_(item) {}
+
+DownloadImpl::~DownloadImpl() {
+#if defined(OS_ANDROID)
+  if (java_download_) {
+    Java_DownloadImpl_onNativeDestroyed(base::android::AttachCurrentThread(),
+                                        java_download_);
+  }
+#endif
+}
+
+#if defined(OS_ANDROID)
+void DownloadImpl::SetJavaDownload(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& java_download) {
+  java_download_.Reset(env, java_download);
+}
+
+base::android::ScopedJavaLocalRef<jstring> DownloadImpl::GetLocation(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  return base::android::ScopedJavaLocalRef<jstring>(
+      base::android::ConvertUTF8ToJavaString(env, GetLocation().value()));
+}
+#endif
+
+DownloadState DownloadImpl::GetState() {
+  if (item_->GetState() == download::DownloadItem::COMPLETE)
+    return DownloadState::kComplete;
+
+  if (cancel_pending_ || item_->GetState() == download::DownloadItem::CANCELLED)
+    return DownloadState::kCancelled;
+
+  if (pause_pending_ || (item_->IsPaused() && !resume_pending_))
+    return DownloadState::kPaused;
+
+  if (item_->GetState() == download::DownloadItem::IN_PROGRESS)
+    return DownloadState::kInProgress;
+
+  return DownloadState::kFailed;
+}
+
+int64_t DownloadImpl::GetTotalBytes() {
+  return item_->GetTotalBytes();
+}
+
+int64_t DownloadImpl::GetReceivedBytes() {
+  return item_->GetReceivedBytes();
+}
+
+void DownloadImpl::Pause() {
+  // The Pause/Resume/Cancel methods need to be called in a PostTask because we
+  // may be in a callback from the download subsystem and it doesn't handle
+  // nested calls.
+  resume_pending_ = false;
+  pause_pending_ = true;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&DownloadImpl::PauseInternal,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DownloadImpl::Resume() {
+  pause_pending_ = false;
+  resume_pending_ = true;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&DownloadImpl::ResumeInternal,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DownloadImpl::Cancel() {
+  cancel_pending_ = true;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&DownloadImpl::CancelInternal,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+base::FilePath DownloadImpl::GetLocation() {
+  return item_->GetTargetFilePath();
+}
+
+DownloadError DownloadImpl::GetError() {
+  auto reason = item_->GetLastReason();
+  if (reason == download::DOWNLOAD_INTERRUPT_REASON_NONE)
+    return DownloadError::kNoError;
+
+  if (reason == download::DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM)
+    return DownloadError::kSSLError;
+
+  if (reason >= download::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED &&
+      reason <=
+          download::DOWNLOAD_INTERRUPT_REASON_SERVER_CROSS_ORIGIN_REDIRECT)
+    return DownloadError::kServerError;
+
+  if (reason >= download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED &&
+      reason <= download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST)
+    return DownloadError::kConnectivityError;
+
+  if (reason == download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE)
+    return DownloadError::kNoSpace;
+
+  if (reason >= download::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED &&
+      reason <= download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE)
+    return DownloadError::kFileError;
+
+  if (reason == download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED)
+    return DownloadError::kCancelled;
+
+  return DownloadError::kOtherError;
+}
+
+void DownloadImpl::PauseInternal() {
+  if (pause_pending_) {
+    pause_pending_ = false;
+    item_->Pause();
+  }
+}
+
+void DownloadImpl::ResumeInternal() {
+  if (resume_pending_) {
+    resume_pending_ = false;
+    item_->Resume(true);
+  }
+}
+
+void DownloadImpl::CancelInternal() {
+  cancel_pending_ = false;
+  item_->Cancel(true);
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/download_impl.h b/weblayer/browser/download_impl.h
new file mode 100644
index 0000000..09ee2fe
--- /dev/null
+++ b/weblayer/browser/download_impl.h
@@ -0,0 +1,100 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBLAYER_BROWSER_DOWNLOAD_IMPL_H_
+#define WEBLAYER_BROWSER_DOWNLOAD_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "build/build_config.h"
+#include "weblayer/public/download.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/scoped_java_ref.h"
+#endif
+
+namespace download {
+class DownloadItem;
+}
+
+namespace weblayer {
+
+class DownloadImpl : public Download, public base::SupportsUserData::Data {
+ public:
+  ~DownloadImpl() override;
+  DownloadImpl(const DownloadImpl&) = delete;
+  DownloadImpl& operator=(const DownloadImpl&) = delete;
+
+  static void Create(download::DownloadItem* item);
+  static DownloadImpl* Get(download::DownloadItem* item);
+
+#if defined(OS_ANDROID)
+  void SetJavaDownload(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& java_download);
+  int GetState(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
+    return static_cast<int>(GetState());
+  }
+  jlong GetTotalBytes(JNIEnv* env,
+                      const base::android::JavaParamRef<jobject>& obj) {
+    return GetTotalBytes();
+  }
+  jlong GetReceivedBytes(JNIEnv* env,
+                         const base::android::JavaParamRef<jobject>& obj) {
+    return GetReceivedBytes();
+  }
+  void Pause(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
+    Pause();
+  }
+  void Resume(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
+    Resume();
+  }
+  void Cancel(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
+    Cancel();
+  }
+  base::android::ScopedJavaLocalRef<jstring> GetLocation(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+  int GetError(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
+    return static_cast<int>(GetError());
+  }
+
+  base::android::ScopedJavaGlobalRef<jobject> java_download() {
+    return java_download_;
+  }
+#endif
+
+  // Download implementation:
+  DownloadState GetState() override;
+  int64_t GetTotalBytes() override;
+  int64_t GetReceivedBytes() override;
+  void Pause() override;
+  void Resume() override;
+  void Cancel() override;
+  base::FilePath GetLocation() override;
+  DownloadError GetError() override;
+
+ private:
+  explicit DownloadImpl(download::DownloadItem* item);
+
+  void PauseInternal();
+  void ResumeInternal();
+  void CancelInternal();
+
+  download::DownloadItem* item_;
+  bool pause_pending_ = false;
+  bool resume_pending_ = false;
+  bool cancel_pending_ = false;
+
+#if defined(OS_ANDROID)
+  base::android::ScopedJavaGlobalRef<jobject> java_download_;
+#endif
+
+  base::WeakPtrFactory<DownloadImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_DOWNLOAD_IMPL_H_
diff --git a/weblayer/browser/download_manager_delegate_impl.cc b/weblayer/browser/download_manager_delegate_impl.cc
index ddde768..daa3126 100644
--- a/weblayer/browser/download_manager_delegate_impl.cc
+++ b/weblayer/browser/download_manager_delegate_impl.cc
@@ -4,13 +4,87 @@
 
 #include "weblayer/browser/download_manager_delegate_impl.h"
 
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/download/public/common/download_item.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/download_item_utils.h"
+#include "content/public/browser/download_manager.h"
+#include "net/base/filename_util.h"
+#include "weblayer/browser/browser_context_impl.h"
+#include "weblayer/browser/download_impl.h"
+#include "weblayer/browser/download_manager_delegate_impl.h"
+#include "weblayer/browser/profile_impl.h"
 #include "weblayer/browser/tab_impl.h"
 #include "weblayer/public/download_delegate.h"
 
 namespace weblayer {
 
-DownloadManagerDelegateImpl::DownloadManagerDelegateImpl() = default;
-DownloadManagerDelegateImpl::~DownloadManagerDelegateImpl() = default;
+namespace {
+
+void GenerateFilename(
+    const GURL& url,
+    const std::string& content_disposition,
+    const std::string& suggested_filename,
+    const std::string& mime_type,
+    const base::FilePath& suggested_directory,
+    base::OnceCallback<void(const base::FilePath&)> callback) {
+  base::FilePath generated_name =
+      net::GenerateFileName(url, content_disposition, std::string(),
+                            suggested_filename, mime_type, "download");
+
+  if (!base::PathExists(suggested_directory))
+    base::CreateDirectory(suggested_directory);
+
+  base::FilePath suggested_path(suggested_directory.Append(generated_name));
+  base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+                 base::BindOnce(std::move(callback), suggested_path));
+}
+
+}  // namespace
+
+DownloadManagerDelegateImpl::DownloadManagerDelegateImpl(
+    content::DownloadManager* download_manager)
+    : download_manager_(download_manager) {
+  download_manager_->AddObserver(this);
+}
+
+DownloadManagerDelegateImpl::~DownloadManagerDelegateImpl() {
+  download_manager_->RemoveObserver(this);
+}
+
+bool DownloadManagerDelegateImpl::DetermineDownloadTarget(
+    download::DownloadItem* item,
+    content::DownloadTargetCallback* callback) {
+  if (!item->GetForcedFilePath().empty()) {
+    std::move(*callback).Run(
+        item->GetForcedFilePath(),
+        download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+        download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, item->GetForcedFilePath(),
+        download::DOWNLOAD_INTERRUPT_REASON_NONE);
+    return true;
+  }
+
+  auto filename_determined_callback = base::BindOnce(
+      &DownloadManagerDelegateImpl::OnDownloadPathGenerated,
+      weak_ptr_factory_.GetWeakPtr(), item->GetId(), std::move(*callback));
+
+  auto* browser_context = content::DownloadItemUtils::GetBrowserContext(item);
+  base::FilePath default_download_path;
+  GetSaveDir(browser_context, nullptr, &default_download_path);
+
+  base::PostTask(FROM_HERE,
+                 {base::ThreadPool(), base::MayBlock(),
+                  base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+                  base::TaskPriority::USER_VISIBLE},
+                 base::BindOnce(GenerateFilename, item->GetURL(),
+                                item->GetContentDisposition(),
+                                item->GetSuggestedFilename(),
+                                item->GetMimeType(), default_download_path,
+                                std::move(filename_determined_callback)));
+  return true;
+}
 
 bool DownloadManagerDelegateImpl::InterceptDownloadIfApplicable(
     const GURL& url,
@@ -22,11 +96,7 @@
     bool is_transient,
     content::WebContents* web_contents) {
   // If there's no DownloadDelegate, the download is simply dropped.
-  auto* tab = TabImpl::FromWebContents(web_contents);
-  if (!tab)
-    return true;
-
-  DownloadDelegate* delegate = tab->download_delegate();
+  auto* delegate = GetDelegate(web_contents);
   if (!delegate)
     return true;
 
@@ -34,4 +104,112 @@
                                      mime_type, content_length);
 }
 
+void DownloadManagerDelegateImpl::GetSaveDir(
+    content::BrowserContext* browser_context,
+    base::FilePath* website_save_dir,
+    base::FilePath* download_save_dir) {
+  auto* browser_context_impl =
+      static_cast<BrowserContextImpl*>(browser_context);
+  auto* profile = browser_context_impl->profile_impl();
+  if (!profile->download_directory().empty())
+    *download_save_dir = profile->download_directory();
+}
+
+void DownloadManagerDelegateImpl::CheckDownloadAllowed(
+    const content::WebContents::Getter& web_contents_getter,
+    const GURL& url,
+    const std::string& request_method,
+    base::Optional<url::Origin> request_initiator,
+    bool from_download_cross_origin_redirect,
+    content::CheckDownloadAllowedCallback check_download_allowed_cb) {
+  // If there's no DownloadDelegate, the download is simply dropped.
+  auto* delegate = GetDelegate(web_contents_getter.Run());
+  if (!delegate) {
+    std::move(check_download_allowed_cb).Run(false);
+    return;
+  }
+
+  delegate->AllowDownload(url, request_method, request_initiator,
+                          std::move(check_download_allowed_cb));
+}
+
+void DownloadManagerDelegateImpl::OnDownloadCreated(
+    content::DownloadManager* manager,
+    download::DownloadItem* item) {
+  item->AddObserver(this);
+  // Create a DownloadImpl which will be owned by |item|.
+  DownloadImpl::Create(item);
+
+  auto* delegate = GetDelegate(item);
+  if (delegate)
+    delegate->DownloadStarted(DownloadImpl::Get(item));
+}
+
+void DownloadManagerDelegateImpl::OnDownloadDropped(
+    content::DownloadManager* manager) {
+  if (download_dropped_callback_)
+    download_dropped_callback_.Run();
+}
+
+void DownloadManagerDelegateImpl::OnDownloadUpdated(
+    download::DownloadItem* item) {
+  auto* delegate = GetDelegate(item);
+  if (item->GetState() == download::DownloadItem::COMPLETE ||
+      item->GetState() == download::DownloadItem::CANCELLED ||
+      item->GetState() == download::DownloadItem::INTERRUPTED) {
+    // Stop observing now to ensure we only send one complete/fail notification.
+    item->RemoveObserver(this);
+
+    if (item->GetState() == download::DownloadItem::COMPLETE)
+      delegate->DownloadCompleted(DownloadImpl::Get(item));
+    else
+      delegate->DownloadFailed(DownloadImpl::Get(item));
+
+    // Needs to happen asynchronously to avoid nested observer calls.
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&DownloadManagerDelegateImpl::RemoveItem,
+                       weak_ptr_factory_.GetWeakPtr(), item->GetGuid()));
+    return;
+  }
+
+  if (delegate)
+    delegate->DownloadProgressChanged(DownloadImpl::Get(item));
+}
+
+void DownloadManagerDelegateImpl::OnDownloadPathGenerated(
+    uint32_t download_id,
+    content::DownloadTargetCallback callback,
+    const base::FilePath& suggested_path) {
+  std::move(callback).Run(
+      suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+      download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+      suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")),
+      download::DOWNLOAD_INTERRUPT_REASON_NONE);
+}
+
+void DownloadManagerDelegateImpl::RemoveItem(const std::string& guid) {
+  auto* item = download_manager_->GetDownloadByGuid(guid);
+  if (item)
+    item->Remove();
+}
+
+DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate(
+    content::WebContents* web_contents) {
+  if (!web_contents)
+    return nullptr;
+
+  auto* tab = TabImpl::FromWebContents(web_contents);
+  if (!tab)
+    return nullptr;
+
+  return tab->download_delegate();
+}
+
+DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate(
+    download::DownloadItem* item) {
+  auto* web_contents = content::DownloadItemUtils::GetWebContents(item);
+  return GetDelegate(web_contents);
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/download_manager_delegate_impl.h b/weblayer/browser/download_manager_delegate_impl.h
index 6ed60d4..20eef4c 100644
--- a/weblayer/browser/download_manager_delegate_impl.h
+++ b/weblayer/browser/download_manager_delegate_impl.h
@@ -5,16 +5,33 @@
 #ifndef WEBLAYER_BROWSER_DOWNLOAD_MANAGER_DELEGATE_IMPL_H_
 #define WEBLAYER_BROWSER_DOWNLOAD_MANAGER_DELEGATE_IMPL_H_
 
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "components/download/public/common/download_item.h"
+#include "content/public/browser/download_manager.h"
 #include "content/public/browser/download_manager_delegate.h"
 
 namespace weblayer {
+class DownloadDelegate;
 
-class DownloadManagerDelegateImpl : public content::DownloadManagerDelegate {
+class DownloadManagerDelegateImpl : public content::DownloadManagerDelegate,
+                                    public content::DownloadManager::Observer,
+                                    public download::DownloadItem::Observer {
  public:
-  DownloadManagerDelegateImpl();
+  explicit DownloadManagerDelegateImpl(
+      content::DownloadManager* download_manager);
   ~DownloadManagerDelegateImpl() override;
 
+  void set_download_dropped_closure_for_testing(
+      const base::RepeatingClosure& callback) {
+    download_dropped_callback_ = callback;
+  }
+
+ private:
   // content::DownloadManagerDelegate implementation:
+  bool DetermineDownloadTarget(
+      download::DownloadItem* item,
+      content::DownloadTargetCallback* callback) override;
   bool InterceptDownloadIfApplicable(
       const GURL& url,
       const std::string& user_agent,
@@ -24,8 +41,38 @@
       int64_t content_length,
       bool is_transient,
       content::WebContents* web_contents) override;
+  void GetSaveDir(content::BrowserContext* browser_context,
+                  base::FilePath* website_save_dir,
+                  base::FilePath* download_save_dir) override;
+  void CheckDownloadAllowed(
+      const content::WebContents::Getter& web_contents_getter,
+      const GURL& url,
+      const std::string& request_method,
+      base::Optional<url::Origin> request_initiator,
+      bool from_download_cross_origin_redirect,
+      content::CheckDownloadAllowedCallback check_download_allowed_cb) override;
 
- private:
+  // content::DownloadManager::Observer implementation:
+  void OnDownloadCreated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override;
+  void OnDownloadDropped(content::DownloadManager* manager) override;
+
+  // download::DownloadItem::Observer implementation:
+  void OnDownloadUpdated(download::DownloadItem* item) override;
+
+  void OnDownloadPathGenerated(uint32_t download_id,
+                               content::DownloadTargetCallback callback,
+                               const base::FilePath& suggested_path);
+  void RemoveItem(const std::string& guid);
+
+  // Helper methods to get a DownloadDelegate.
+  DownloadDelegate* GetDelegate(content::WebContents* web_contents);
+  DownloadDelegate* GetDelegate(download::DownloadItem* item);
+
+  content::DownloadManager* download_manager_;
+  base::RepeatingClosure download_dropped_callback_;
+  base::WeakPtrFactory<DownloadManagerDelegateImpl> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(DownloadManagerDelegateImpl);
 };
 
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index 8b265bfe..58e253f 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -17,6 +17,7 @@
 
 java_cpp_enum("generated_enums") {
   sources = [
+    "//weblayer/public/download.h",
     "//weblayer/public/navigation.h",
     "//weblayer/public/new_tab_delegate.h",
     "//weblayer/public/profile.h",
@@ -34,6 +35,7 @@
     "org/chromium/weblayer_private/ContentViewRenderView.java",
     "org/chromium/weblayer_private/CrashReporterControllerImpl.java",
     "org/chromium/weblayer_private/DownloadCallbackProxy.java",
+    "org/chromium/weblayer_private/DownloadImpl.java",
     "org/chromium/weblayer_private/ErrorPageCallbackProxy.java",
     "org/chromium/weblayer_private/ExternalNavigationHandler.java",
     "org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java",
@@ -66,6 +68,7 @@
     "//components/autofill/android:provider_java",
     "//components/crash/android:handler_java",
     "//components/crash/android:java",
+    "//components/download/internal/common:internal_java",
     "//components/embedder_support/android:application_java",
     "//components/embedder_support/android:web_contents_delegate_java",
     "//components/minidump_uploader:minidump_uploader_java",
@@ -99,6 +102,7 @@
   sources = [
     "org/chromium/weblayer_private/ContentViewRenderView.java",
     "org/chromium/weblayer_private/DownloadCallbackProxy.java",
+    "org/chromium/weblayer_private/DownloadImpl.java",
     "org/chromium/weblayer_private/ErrorPageCallbackProxy.java",
     "org/chromium/weblayer_private/ExternalNavigationHandler.java",
     "org/chromium/weblayer_private/FullscreenCallbackProxy.java",
@@ -120,6 +124,8 @@
     "org/chromium/weblayer_private/interfaces/APICallException.java",
     "org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java",
     "org/chromium/weblayer_private/interfaces/BrowsingDataType.java",
+    "org/chromium/weblayer_private/interfaces/DownloadError.java",
+    "org/chromium/weblayer_private/interfaces/DownloadState.java",
     "org/chromium/weblayer_private/interfaces/LoadError.java",
     "org/chromium/weblayer_private/interfaces/NavigationState.java",
     "org/chromium/weblayer_private/interfaces/NewTabType.java",
@@ -161,9 +167,11 @@
     "org/chromium/weblayer_private/interfaces/IBrowserClient.aidl",
     "org/chromium/weblayer_private/interfaces/IBrowserFragment.aidl",
     "org/chromium/weblayer_private/interfaces/IChildProcessService.aidl",
+    "org/chromium/weblayer_private/interfaces/IClientDownload.aidl",
     "org/chromium/weblayer_private/interfaces/IClientNavigation.aidl",
     "org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl",
     "org/chromium/weblayer_private/interfaces/ICrashReporterControllerClient.aidl",
+    "org/chromium/weblayer_private/interfaces/IDownload.aidl",
     "org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl",
     "org/chromium/weblayer_private/interfaces/IErrorPageCallbackClient.aidl",
     "org/chromium/weblayer_private/interfaces/IFullscreenCallbackClient.aidl",
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java b/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java
index ad33b66..4a2a31a6 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java
@@ -5,11 +5,13 @@
 package org.chromium.weblayer_private;
 
 import android.os.RemoteException;
+import android.webkit.ValueCallback;
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
+import org.chromium.weblayer_private.interfaces.ObjectWrapper;
 
 /**
  * Owns the c++ DownloadCallbackProxy class, which is responsible for forwarding all
@@ -45,9 +47,56 @@
                 url, userAgent, contentDisposition, mimetype, contentLength);
     }
 
+    @CalledByNative
+    private void allowDownload(String url, String requestMethod, String requestInitiator,
+            long callbackId) throws RemoteException {
+        if (WebLayerFactoryImpl.getClientMajorVersion() < 81) {
+            DownloadCallbackProxyJni.get().allowDownload(callbackId, true);
+            return;
+        }
+
+        ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
+            @Override
+            public void onReceiveValue(Boolean result) {
+                if (mNativeDownloadCallbackProxy == 0) {
+                    throw new IllegalStateException("Called after destroy()");
+                }
+                DownloadCallbackProxyJni.get().allowDownload(callbackId, result);
+            }
+        };
+
+        mClient.allowDownload(url, requestMethod, requestInitiator, ObjectWrapper.wrap(callback));
+    }
+
+    @CalledByNative
+    private DownloadImpl createDownload(long nativeDownloadImpl) {
+        return new DownloadImpl(mClient, nativeDownloadImpl);
+    }
+
+    @CalledByNative
+    private void downloadStarted(DownloadImpl download) throws RemoteException {
+        mClient.downloadStarted(download.getClientDownload());
+    }
+
+    @CalledByNative
+    private void downloadProgressChanged(DownloadImpl download) throws RemoteException {
+        mClient.downloadProgressChanged(download.getClientDownload());
+    }
+
+    @CalledByNative
+    private void downloadCompleted(DownloadImpl download) throws RemoteException {
+        mClient.downloadCompleted(download.getClientDownload());
+    }
+
+    @CalledByNative
+    private void downloadFailed(DownloadImpl download) throws RemoteException {
+        mClient.downloadFailed(download.getClientDownload());
+    }
+
     @NativeMethods
     interface Natives {
         long createDownloadCallbackProxy(DownloadCallbackProxy proxy, long tab);
         void deleteDownloadCallbackProxy(long proxy);
+        void allowDownload(long callbackId, boolean allow);
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
new file mode 100644
index 0000000..2fcc0b2
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
@@ -0,0 +1,169 @@
+// Copyright 2020 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_private;
+
+import android.os.RemoteException;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.weblayer_private.interfaces.APICallException;
+import org.chromium.weblayer_private.interfaces.DownloadError;
+import org.chromium.weblayer_private.interfaces.DownloadState;
+import org.chromium.weblayer_private.interfaces.IClientDownload;
+import org.chromium.weblayer_private.interfaces.IDownload;
+import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
+import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
+
+/**
+ * Implementation of IDownload.
+ */
+@JNINamespace("weblayer")
+public final class DownloadImpl extends IDownload.Stub {
+    private final IClientDownload mClientDownload;
+    // WARNING: DownloadImpl may outlive the native side, in which case this member is set to 0.
+    private long mNativeDownloadImpl;
+
+    public DownloadImpl(IDownloadCallbackClient client, long nativeDownloadImpl) {
+        mNativeDownloadImpl = nativeDownloadImpl;
+        try {
+            mClientDownload = client.createClientDownload(this);
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+        DownloadImplJni.get().setJavaDownload(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    public IClientDownload getClientDownload() {
+        return mClientDownload;
+    }
+
+    @DownloadState
+    private static int implStateToJavaType(@ImplDownloadState int type) {
+        switch (type) {
+            case ImplDownloadState.IN_PROGRESS:
+                return DownloadState.IN_PROGRESS;
+            case ImplDownloadState.COMPLETE:
+                return DownloadState.COMPLETE;
+            case ImplDownloadState.PAUSED:
+                return DownloadState.PAUSED;
+            case ImplDownloadState.CANCELLED:
+                return DownloadState.CANCELLED;
+            case ImplDownloadState.FAILED:
+                return DownloadState.FAILED;
+        }
+        assert false;
+        return DownloadState.FAILED;
+    }
+
+    @DownloadError
+    private static int implErrorToJavaType(@ImplDownloadError int type) {
+        switch (type) {
+            case ImplDownloadError.NO_ERROR:
+                return DownloadError.NO_ERROR;
+            case ImplDownloadError.SERVER_ERROR:
+                return DownloadError.SERVER_ERROR;
+            case ImplDownloadError.SSL_ERROR:
+                return DownloadError.SSL_ERROR;
+            case ImplDownloadError.CONNECTIVITY_ERROR:
+                return DownloadError.CONNECTIVITY_ERROR;
+            case ImplDownloadError.NO_SPACE:
+                return DownloadError.NO_SPACE;
+            case ImplDownloadError.FILE_ERROR:
+                return DownloadError.FILE_ERROR;
+            case ImplDownloadError.CANCELLED:
+                return DownloadError.CANCELLED;
+            case ImplDownloadError.OTHER_ERROR:
+                return DownloadError.OTHER_ERROR;
+        }
+        assert false;
+        return DownloadError.OTHER_ERROR;
+    }
+
+    @Override
+    @DownloadState
+    public int getState() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return implStateToJavaType(
+                DownloadImplJni.get().getState(mNativeDownloadImpl, DownloadImpl.this));
+    }
+
+    @Override
+    public long getTotalBytes() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return DownloadImplJni.get().getTotalBytes(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    public long getReceivedBytes() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return DownloadImplJni.get().getReceivedBytes(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    public void pause() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        DownloadImplJni.get().pause(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    public void resume() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        DownloadImplJni.get().resume(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    public void cancel() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        DownloadImplJni.get().cancel(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    public String getLocation() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return DownloadImplJni.get().getLocation(mNativeDownloadImpl, DownloadImpl.this);
+    }
+
+    @Override
+    @DownloadError
+    public int getError() {
+        StrictModeWorkaround.apply();
+        throwIfNativeDestroyed();
+        return implErrorToJavaType(
+                DownloadImplJni.get().getError(mNativeDownloadImpl, DownloadImpl.this));
+    }
+
+    private void throwIfNativeDestroyed() {
+        if (mNativeDownloadImpl == 0) {
+            throw new IllegalStateException("Using Download after native destroyed");
+        }
+    }
+
+    @CalledByNative
+    private void onNativeDestroyed() {
+        mNativeDownloadImpl = 0;
+        // TODO: this should likely notify delegate in some way.
+    }
+
+    @NativeMethods
+    interface Natives {
+        void setJavaDownload(long nativeDownloadImpl, DownloadImpl caller);
+        int getState(long nativeDownloadImpl, DownloadImpl caller);
+        long getTotalBytes(long nativeDownloadImpl, DownloadImpl caller);
+        long getReceivedBytes(long nativeDownloadImpl, DownloadImpl caller);
+        void pause(long nativeDownloadImpl, DownloadImpl caller);
+        void resume(long nativeDownloadImpl, DownloadImpl caller);
+        void cancel(long nativeDownloadImpl, DownloadImpl caller);
+        String getLocation(long nativeDownloadImpl, DownloadImpl caller);
+        int getError(long nativeDownloadImpl, DownloadImpl caller);
+    }
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
index 7fc36e3..fadd825 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java
@@ -60,6 +60,12 @@
                 mNativeProfile, mapBrowsingDataTypes(dataTypes), fromMillis, toMillis, callback);
     }
 
+    @Override
+    public void setDownloadDirectory(String directory) {
+        StrictModeWorkaround.apply();
+        ProfileImplJni.get().setDownloadDirectory(mNativeProfile, directory);
+    }
+
     private static @ImplBrowsingDataType int[] mapBrowsingDataTypes(
             @NonNull @BrowsingDataType int[] dataTypes) {
         // Convert data types coming from aidl to the ones accepted by C++ (ImplBrowsingDataType is
@@ -90,5 +96,6 @@
         void deleteProfile(long profile);
         void clearBrowsingData(long nativeProfileImpl, @ImplBrowsingDataType int[] dataTypes,
                 long fromMillis, long toMillis, Runnable callback);
+        void setDownloadDirectory(long nativeProfileImpl, String directory);
     }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadError.java b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadError.java
new file mode 100644
index 0000000..1a07c71
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadError.java
@@ -0,0 +1,25 @@
+// Copyright 2020 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_private.interfaces;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef({DownloadError.NO_ERROR, DownloadError.SERVER_ERROR, DownloadError.SSL_ERROR,
+        DownloadError.CONNECTIVITY_ERROR, DownloadError.NO_SPACE, DownloadError.FILE_ERROR,
+        DownloadError.CANCELLED, DownloadError.OTHER_ERROR})
+@Retention(RetentionPolicy.SOURCE)
+public @interface DownloadError {
+    int NO_ERROR = 0;
+    int SERVER_ERROR = 1;
+    int SSL_ERROR = 2;
+    int CONNECTIVITY_ERROR = 3;
+    int NO_SPACE = 4;
+    int FILE_ERROR = 5;
+    int CANCELLED = 6;
+    int OTHER_ERROR = 7;
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadState.java b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadState.java
new file mode 100644
index 0000000..e3104bd
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/DownloadState.java
@@ -0,0 +1,21 @@
+// Copyright 2020 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_private.interfaces;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef({DownloadState.IN_PROGRESS, DownloadState.COMPLETE, DownloadState.PAUSED,
+        DownloadState.CANCELLED, DownloadState.FAILED})
+@Retention(RetentionPolicy.SOURCE)
+public @interface DownloadState {
+    int IN_PROGRESS = 0;
+    int COMPLETE = 1;
+    int PAUSED = 2;
+    int CANCELLED = 3;
+    int FAILED = 4;
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IClientDownload.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IClientDownload.aidl
new file mode 100644
index 0000000..42eed35
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IClientDownload.aidl
@@ -0,0 +1,11 @@
+// Copyright 2020 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_private.interfaces;
+
+/**
+ * Represents a download on the *client* side.
+ */
+interface IClientDownload {
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownload.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownload.aidl
new file mode 100644
index 0000000..e2d2249
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownload.aidl
@@ -0,0 +1,19 @@
+// Copyright 2020 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_private.interfaces;
+
+/**
+ * Contains information about a single download that's in progress.
+ */
+interface IDownload {
+  int getState() = 0;
+  long getTotalBytes() = 1;
+  long getReceivedBytes() = 2;
+  void pause() = 3;
+  void resume() = 4;
+  void cancel() = 5;
+  String getLocation() = 6;
+  int getError() = 7;
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl
index 830f709b..c3e049f 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl
@@ -4,9 +4,19 @@
 
 package org.chromium.weblayer_private.interfaces;
 
+import org.chromium.weblayer_private.interfaces.IClientDownload;
+import org.chromium.weblayer_private.interfaces.IDownload;
+import org.chromium.weblayer_private.interfaces.IObjectWrapper;
+
 /**
  * Used to forward download requests to the client.
  */
 interface IDownloadCallbackClient {
   boolean interceptDownload(in String uriString, in String userAgent, in String contentDisposition, in String mimetype, long contentLength) = 0;
+  void allowDownload(in String uriString, in String requestMethod, in String requestInitiatorString, in IObjectWrapper valueCallback) = 1;
+  IClientDownload createClientDownload(in IDownload impl) = 2;
+  void downloadStarted(IClientDownload download) = 3;
+  void downloadProgressChanged(IClientDownload download) = 4;
+  void downloadCompleted(IClientDownload download) = 5;
+  void downloadFailed(IClientDownload download) = 6;
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
index ef92f74..af80048 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl
@@ -13,4 +13,6 @@
           in IObjectWrapper completionCallback) = 1;
 
   String getName() = 2;
+
+  void setDownloadDirectory(String directory) = 3;
 }
diff --git a/weblayer/browser/profile_impl.cc b/weblayer/browser/profile_impl.cc
index 675eda9..2d383de9 100644
--- a/weblayer/browser/profile_impl.cc
+++ b/weblayer/browser/profile_impl.cc
@@ -14,6 +14,8 @@
 #include "components/web_cache/browser/web_cache_manager.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/device_service.h"
+#include "content/public/browser/download_manager.h"
 #include "content/public/browser/storage_partition.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "weblayer/browser/browser_context_impl.h"
@@ -101,7 +103,9 @@
 #endif
 }
 
-ProfileImpl::ProfileImpl(const std::string& name) : name_(name) {
+ProfileImpl::ProfileImpl(const std::string& name)
+    : name_(name),
+      download_directory_(BrowserContextImpl::GetDefaultDownloadDirectory()) {
   if (!name.empty()) {
     CHECK(IsNameValid(name));
     {
@@ -164,6 +168,10 @@
   clearer->ClearData(remove_mask, from_time, to_time);
 }
 
+void ProfileImpl::SetDownloadDirectory(const base::FilePath& directory) {
+  download_directory_ = directory;
+}
+
 void ProfileImpl::ClearRendererCache() {
   for (content::RenderProcessHost::iterator iter =
            content::RenderProcessHost::AllHostsIterator();
@@ -228,6 +236,16 @@
       base::BindOnce(base::android::RunRunnableAndroid,
                      base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
 }
+
+void ProfileImpl::SetDownloadDirectory(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jstring>& directory) {
+  base::FilePath directory_path(
+      base::android::ConvertJavaStringToUTF8(directory));
+
+  SetDownloadDirectory(directory_path);
+}
+
 #endif  // OS_ANDROID
 
 }  // namespace weblayer
diff --git a/weblayer/browser/profile_impl.h b/weblayer/browser/profile_impl.h
index 1af0ba57..ebf1276 100644
--- a/weblayer/browser/profile_impl.h
+++ b/weblayer/browser/profile_impl.h
@@ -43,6 +43,7 @@
                          base::Time from_time,
                          base::Time to_time,
                          base::OnceClosure callback) override;
+  void SetDownloadDirectory(const base::FilePath& directory) override;
 
 #if defined(OS_ANDROID)
   ProfileImpl(JNIEnv* env, const base::android::JavaParamRef<jstring>& path);
@@ -53,8 +54,13 @@
       const jlong j_from_time_millis,
       const jlong j_to_time_millis,
       const base::android::JavaRef<jobject>& j_callback);
+  void SetDownloadDirectory(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jstring>& directory);
 #endif
 
+  const base::FilePath& download_directory() { return download_directory_; }
+
  private:
   class DataClearer;
 
@@ -68,6 +74,8 @@
 
   std::unique_ptr<BrowserContextImpl> browser_context_;
 
+  base::FilePath download_directory_;
+
   std::unique_ptr<i18n::LocaleChangeSubscription> locale_change_subscription_;
 
   DISALLOW_COPY_AND_ASSIGN(ProfileImpl);
diff --git a/weblayer/public/download.h b/weblayer/public/download.h
new file mode 100644
index 0000000..e20eeaf
--- /dev/null
+++ b/weblayer/public/download.h
@@ -0,0 +1,87 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBLAYER_PUBLIC_DOWNLOAD_H_
+#define WEBLAYER_PUBLIC_DOWNLOAD_H_
+
+namespace base {
+class FilePath;
+}
+
+namespace weblayer {
+
+// These types are sent over IPC and across different versions. Never remove
+// or change the order.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private
+// GENERATED_JAVA_CLASS_NAME_OVERRIDE: ImplDownloadState
+enum class DownloadState {
+  // Download is actively progressing.
+  kInProgress = 0,
+  // Download is completely finished.
+  kComplete = 1,
+  // Download is paused by the user.
+  kPaused = 2,
+  // Download has been cancelled by the user.
+  kCancelled = 3,
+  // Download has failed (e.g. server or connection problem).
+  kFailed = 4,
+};
+
+// These types are sent over IPC and across different versions. Never remove
+// or change the order.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private
+// GENERATED_JAVA_CLASS_NAME_OVERRIDE: ImplDownloadError
+enum class DownloadError {
+  kNoError = 0,            // Download completed successfully.
+  kServerError = 1,        // Server failed, e.g. unauthorized or forbidden,
+                           // server unreachable,
+  kSSLError = 2,           // Certificate error.
+  kConnectivityError = 3,  // A network error occur. e.g. disconnected, timed
+                           // out, invalid request.
+  kNoSpace = 4,            // There isn't enough room in the storage location.
+  kFileError = 5,          // Various errors related to file access. e.g.
+                           // access denied, directory or filename too long,
+                           // file is too large for file system, file in use,
+                           // too many files open at once etc...
+  kCancelled = 6,          // The user cancelled the download.
+  kOtherError = 7,         // An error not listed above occurred.
+};
+
+// Contains information about a single download that's in progress.
+class Download {
+ public:
+  virtual ~Download() {}
+
+  virtual DownloadState GetState() = 0;
+
+  // Returns the total number of expected bytes. Returns -1 if the total size is
+  // not known.
+  virtual int64_t GetTotalBytes() = 0;
+
+  // Total number of bytes that have been received and written to the download
+  // file.
+  virtual int64_t GetReceivedBytes() = 0;
+
+  // Pauses the download.
+  virtual void Pause() = 0;
+
+  // Resumes the download.
+  virtual void Resume() = 0;
+
+  // Cancels the download.
+  virtual void Cancel() = 0;
+
+  // Returns the location of the downloaded file. This may be empty if the
+  // target path hasn't been determined yet. The file it points to won't be
+  // available until the download completes successfully.
+  virtual base::FilePath GetLocation() = 0;
+
+  // Return information about the error, if any, that was encountered during the
+  // download.
+  virtual DownloadError GetError() = 0;
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_PUBLIC_DOWNLOAD_H_
diff --git a/weblayer/public/download_delegate.h b/weblayer/public/download_delegate.h
index 6f311e2..80344be1 100644
--- a/weblayer/public/download_delegate.h
+++ b/weblayer/public/download_delegate.h
@@ -7,27 +7,56 @@
 
 #include <string>
 
+#include "base/callback_forward.h"
+#include "base/optional.h"
+#include "url/origin.h"
+
 class GURL;
 
 namespace weblayer {
+class Download;
+
+using AllowDownloadCallback = base::OnceCallback<void(bool /*allow*/)>;
 
 // An interface that allows clients to handle download requests originating in
-// the browser.
+// the browser. The object is safe to hold on to until DownloadCompleted or
+// DownloadFailed are called.
 class DownloadDelegate {
  public:
+  // Gives the embedder the opportunity to asynchronously allow or disallow the
+  // given download. The download is paused until the callback is run. It's safe
+  // to run |callback| synchronously.
+  virtual void AllowDownload(const GURL& url,
+                             const std::string& request_method,
+                             base::Optional<url::Origin> request_initiator,
+                             AllowDownloadCallback callback) = 0;
+
   // A download of |url| has been requested with the specified details. If
   // it returns true the download will be considered intercepted and WebLayer
   // won't proceed with it. Note that there are many corner cases where the
   // embedder downloading it won't work (e.g. POSTs, one-time URLs, requests
-  // that depend on cookies or auth state). If the method returns false, then
-  // currently WebLayer won't download it but in the future this will be hooked
-  // up with new callbacks in this interface.
+  // that depend on cookies or auth state). This is called after AllowDownload.
   virtual bool InterceptDownload(const GURL& url,
                                  const std::string& user_agent,
                                  const std::string& content_disposition,
                                  const std::string& mime_type,
                                  int64_t content_length) = 0;
 
+  // A download has started. There will be 0..n calls to
+  // DownloadProgressChanged, then either a call to DownloadCompleted or
+  // DownloadFailed.
+  virtual void DownloadStarted(Download* download) {}
+
+  // The progress percentage of a download has changed.
+  virtual void DownloadProgressChanged(Download* download) {}
+
+  // A download has completed successfully.
+  virtual void DownloadCompleted(Download* download) {}
+
+  // A download has failed because the user cancelled it or because of a server
+  // or network error.
+  virtual void DownloadFailed(Download* download) {}
+
  protected:
   virtual ~DownloadDelegate() {}
 };
diff --git a/weblayer/public/java/BUILD.gn b/weblayer/public/java/BUILD.gn
index 88105fdd..b23c4208 100644
--- a/weblayer/public/java/BUILD.gn
+++ b/weblayer/public/java/BUILD.gn
@@ -32,7 +32,10 @@
     "org/chromium/weblayer/ChildProcessService.java",
     "org/chromium/weblayer/CrashReporterCallback.java",
     "org/chromium/weblayer/CrashReporterController.java",
+    "org/chromium/weblayer/Download.java",
     "org/chromium/weblayer/DownloadCallback.java",
+    "org/chromium/weblayer/DownloadError.java",
+    "org/chromium/weblayer/DownloadState.java",
     "org/chromium/weblayer/ErrorPageCallback.java",
     "org/chromium/weblayer/FullscreenCallback.java",
     "org/chromium/weblayer/LoadError.java",
diff --git a/weblayer/public/java/org/chromium/weblayer/Download.java b/weblayer/public/java/org/chromium/weblayer/Download.java
new file mode 100644
index 0000000..1e8b75a1
--- /dev/null
+++ b/weblayer/public/java/org/chromium/weblayer/Download.java
@@ -0,0 +1,126 @@
+// Copyright 2020 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;
+
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.weblayer_private.interfaces.APICallException;
+import org.chromium.weblayer_private.interfaces.IClientDownload;
+import org.chromium.weblayer_private.interfaces.IDownload;
+
+import java.io.File;
+
+/**
+ * Contains information about a single download that's in progress.
+ *
+ * @since 81
+ */
+public final class Download extends IClientDownload.Stub {
+    private final IDownload mDownloadImpl;
+
+    Download(IDownload impl) {
+        mDownloadImpl = impl;
+    }
+
+    @DownloadState
+    public int getState() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mDownloadImpl.getState();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Returns the total number of expected bytes. Returns -1 if the total size is not known.
+     */
+    public long getTotalBytes() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mDownloadImpl.getTotalBytes();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Total number of bytes that have been received and written to the download file.
+     */
+    public long getReceivedBytes() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mDownloadImpl.getReceivedBytes();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Pauses the download.
+     */
+    public void pause() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            mDownloadImpl.pause();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Resumes the download.
+     */
+    public void resume() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            mDownloadImpl.resume();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Cancels the download.
+     */
+    public void cancel() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            mDownloadImpl.cancel();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Returns the location of the downloaded file. This may be empty if the target path hasn't been
+     * determined yet. The file it points to won't be available until the download completes
+     * successfully.
+     */
+    @NonNull
+    public File getLocation() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return new File(mDownloadImpl.getLocation());
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+
+    /**
+     * Return information about the error, if any, that was encountered during the download.
+     */
+    @DownloadError
+    public int getError() {
+        ThreadCheck.ensureOnUiThread();
+        try {
+            return mDownloadImpl.getError();
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
+}
diff --git a/weblayer/public/java/org/chromium/weblayer/DownloadCallback.java b/weblayer/public/java/org/chromium/weblayer/DownloadCallback.java
index 2a7536ba..0cd59f3 100644
--- a/weblayer/public/java/org/chromium/weblayer/DownloadCallback.java
+++ b/weblayer/public/java/org/chromium/weblayer/DownloadCallback.java
@@ -5,8 +5,10 @@
 package org.chromium.weblayer;
 
 import android.net.Uri;
+import android.webkit.ValueCallback;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * An interface that allows clients to handle download requests originating in the browser.
@@ -16,9 +18,7 @@
      * A download of has been requested with the specified details. If it returns true the download
      * will be considered intercepted and WebLayer won't proceed with it. Note that there are many
      * corner cases where the embedder downloading it won't work (e.g. POSTs, one-time URLs,
-     * requests that depend on cookies or auth state). If the method returns false, then currently
-     * WebLayer won't download it but in the future this will be hooked up with new callbacks in
-     * this interface.
+     * requests that depend on cookies or auth state). This is called after AllowDownload.
      *
      * @param uri the target that should be downloaded
      * @param userAgent the user agent to be used for the download
@@ -28,4 +28,65 @@
      */
     public abstract boolean onInterceptDownload(@NonNull Uri uri, @NonNull String userAgent,
             @NonNull String contentDisposition, @NonNull String mimetype, long contentLength);
+
+    /**
+     * Gives the embedder the opportunity to asynchronously allow or disallow the
+     * given download. It's safe to run |callback| synchronously.
+     *
+     * @param uri the target that is being downloaded
+     * @param requestMethod the method (GET/POST etc...) of the download
+     * @param requestInitiator the initiating Uri, if present
+     * @param callback a callback to allow or disallow the download. must be called to avoid leaks
+     *
+     * @since 81
+     */
+    public abstract void allowDownload(@NonNull Uri uri, @NonNull String requestMethod,
+            @Nullable Uri requestInitiator, @NonNull ValueCallback<Boolean> callback);
+
+    /**
+     * A download has started. There will be 0..n calls to DownloadProgressChanged, then either a
+     * call to DownloadCompleted or DownloadFailed. The same |download| will be provided on
+     * subsequent calls to those methods when related to this download. Observers should clear any
+     * references to |download| in onDownloadCompleted or onDownloadFailed, just before it is
+     * destroyed.
+     *
+     * @param download the unique object for this download.
+     *
+     * @since 81
+     */
+    public void onDownloadStarted(@NonNull Download download) {}
+
+    /**
+     * The download progress has changed.
+     *
+     * @param download the unique object for this download.
+     *
+     * @since 81
+     */
+    public void onDownloadProgressChanged(@NonNull Download download) {}
+
+    /**
+     * The download has completed successfully.
+     *
+     * Note that |download| will be destroyed at the end of this call, so do not keep a reference
+     * to it afterward.
+     *
+     * @param download the unique object for this download.
+     *
+     * @since 81
+     */
+    public void onDownloadCompleted(@NonNull Download download) {}
+
+    /**
+     * The download has failed because the user cancelled it or because of a server or network
+     * error.
+     *
+     * Note that |download| will be destroyed at the end of this call, so do not keep a reference
+     * to it afterward.
+     *
+     * @param download the unique object for this download.
+     *
+     * @since 81
+     */
+    public void onDownloadFailed(@NonNull Download download) {}
 }
diff --git a/weblayer/public/java/org/chromium/weblayer/DownloadError.java b/weblayer/public/java/org/chromium/weblayer/DownloadError.java
new file mode 100644
index 0000000..bae43c60
--- /dev/null
+++ b/weblayer/public/java/org/chromium/weblayer/DownloadError.java
@@ -0,0 +1,29 @@
+// Copyright 2020 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;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+@IntDef({DownloadError.NO_ERROR, DownloadError.SERVER_ERROR, DownloadError.SSL_ERROR,
+        DownloadError.CONNECTIVITY_ERROR, DownloadError.NO_SPACE, DownloadError.FILE_ERROR,
+        DownloadError.CANCELLED, DownloadError.OTHER_ERROR})
+@Retention(RetentionPolicy.SOURCE)
+public @interface DownloadError {
+    int NO_ERROR = org.chromium.weblayer_private.interfaces.DownloadError.NO_ERROR;
+    int SERVER_ERROR = org.chromium.weblayer_private.interfaces.DownloadError.SERVER_ERROR;
+    int SSL_ERROR = org.chromium.weblayer_private.interfaces.DownloadError.SSL_ERROR;
+    int CONNECTIVITY_ERROR =
+            org.chromium.weblayer_private.interfaces.DownloadError.CONNECTIVITY_ERROR;
+    int NO_SPACE = org.chromium.weblayer_private.interfaces.DownloadError.NO_SPACE;
+    int FILE_ERROR = org.chromium.weblayer_private.interfaces.DownloadError.FILE_ERROR;
+    int CANCELLED = org.chromium.weblayer_private.interfaces.DownloadError.CANCELLED;
+    int OTHER_ERROR = org.chromium.weblayer_private.interfaces.DownloadError.OTHER_ERROR;
+}
diff --git a/weblayer/public/java/org/chromium/weblayer/DownloadState.java b/weblayer/public/java/org/chromium/weblayer/DownloadState.java
new file mode 100644
index 0000000..18ce2f6c
--- /dev/null
+++ b/weblayer/public/java/org/chromium/weblayer/DownloadState.java
@@ -0,0 +1,24 @@
+// Copyright 2020 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;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+@IntDef({DownloadState.IN_PROGRESS, DownloadState.COMPLETE, DownloadState.PAUSED,
+        DownloadState.CANCELLED, DownloadState.FAILED})
+@Retention(RetentionPolicy.SOURCE)
+public @interface DownloadState {
+    int IN_PROGRESS = org.chromium.weblayer_private.interfaces.DownloadState.IN_PROGRESS;
+    int COMPLETE = org.chromium.weblayer_private.interfaces.DownloadState.COMPLETE;
+    int PAUSED = org.chromium.weblayer_private.interfaces.DownloadState.PAUSED;
+    int CANCELLED = org.chromium.weblayer_private.interfaces.DownloadState.CANCELLED;
+    int FAILED = org.chromium.weblayer_private.interfaces.DownloadState.FAILED;
+}
diff --git a/weblayer/public/java/org/chromium/weblayer/Profile.java b/weblayer/public/java/org/chromium/weblayer/Profile.java
index 7afeb06..eab782f 100644
--- a/weblayer/public/java/org/chromium/weblayer/Profile.java
+++ b/weblayer/public/java/org/chromium/weblayer/Profile.java
@@ -12,6 +12,7 @@
 import org.chromium.weblayer_private.interfaces.IProfile;
 import org.chromium.weblayer_private.interfaces.ObjectWrapper;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -101,4 +102,25 @@
         mImpl = null;
         sProfiles.remove(mName);
     }
+
+    /**
+     * Allows embedders to override the default download directory. By default this is the system
+     * download directory.
+     *
+     * @param directory the directory to place downloads in.
+     *
+     * @since 81
+     */
+    public void setDownloadDirectory(@NonNull File directory) {
+        ThreadCheck.ensureOnUiThread();
+        if (WebLayer.getSupportedMajorVersionInternal() < 81) {
+            throw new UnsupportedOperationException();
+        }
+
+        try {
+            mImpl.setDownloadDirectory(directory.toString());
+        } catch (RemoteException e) {
+            throw new APICallException(e);
+        }
+    }
 }
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index d301427..9e91a15 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -15,6 +15,8 @@
 import org.json.JSONObject;
 
 import org.chromium.weblayer_private.interfaces.APICallException;
+import org.chromium.weblayer_private.interfaces.IClientDownload;
+import org.chromium.weblayer_private.interfaces.IDownload;
 import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
 import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
 import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient;
@@ -281,6 +283,51 @@
             return mCallback.onInterceptDownload(
                     Uri.parse(uriString), userAgent, contentDisposition, mimetype, contentLength);
         }
+
+        @Override
+        public void allowDownload(String uriString, String requestMethod,
+                String requestInitiatorString, IObjectWrapper valueCallback) {
+            StrictModeWorkaround.apply();
+            Uri requestInitiator;
+            if (requestInitiatorString != null) {
+                requestInitiator = Uri.parse(requestInitiatorString);
+            } else {
+                requestInitiator = Uri.EMPTY;
+            }
+            mCallback.allowDownload(Uri.parse(uriString), requestMethod, requestInitiator,
+                    (ValueCallback<Boolean>) ObjectWrapper.unwrap(
+                            valueCallback, ValueCallback.class));
+        }
+
+        @Override
+        public IClientDownload createClientDownload(IDownload downloadImpl) {
+            StrictModeWorkaround.apply();
+            return new Download(downloadImpl);
+        }
+
+        @Override
+        public void downloadStarted(IClientDownload download) {
+            StrictModeWorkaround.apply();
+            mCallback.onDownloadStarted((Download) download);
+        }
+
+        @Override
+        public void downloadProgressChanged(IClientDownload download) {
+            StrictModeWorkaround.apply();
+            mCallback.onDownloadProgressChanged((Download) download);
+        }
+
+        @Override
+        public void downloadCompleted(IClientDownload download) {
+            StrictModeWorkaround.apply();
+            mCallback.onDownloadCompleted((Download) download);
+        }
+
+        @Override
+        public void downloadFailed(IClientDownload download) {
+            StrictModeWorkaround.apply();
+            mCallback.onDownloadFailed((Download) download);
+        }
     }
 
     private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub {
diff --git a/weblayer/public/profile.h b/weblayer/public/profile.h
index a4bb342..438c200 100644
--- a/weblayer/public/profile.h
+++ b/weblayer/public/profile.h
@@ -8,6 +8,10 @@
 #include <algorithm>
 #include <string>
 
+namespace base {
+class FilePath;
+}
+
 namespace weblayer {
 
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private
@@ -31,6 +35,11 @@
       base::Time from_time,
       base::Time to_time,
       base::OnceClosure callback) = 0;
+
+  // Allows embedders to override the default download directory, which is the
+  // system download directory on Android and on other platforms it's in the
+  // home directory.
+  virtual void SetDownloadDirectory(const base::FilePath& directory) = 0;
 };
 
 }  // namespace weblayer
diff --git a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
index 31b0cce2..fee6e30 100644
--- a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
+++ b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
@@ -22,6 +22,7 @@
 import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.webkit.ValueCallback;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
@@ -249,6 +250,12 @@
                 getSystemService(DownloadManager.class).enqueue(request);
                 return true;
             }
+
+            @Override
+            public void allowDownload(Uri uri, String requestMethod, Uri requestInitiator,
+                    ValueCallback<Boolean> callback) {
+                callback.onReceiveValue(true);
+            }
         });
         tab.setErrorPageCallback(new ErrorPageCallback() {
             @Override
diff --git a/weblayer/shell/browser/shell.cc b/weblayer/shell/browser/shell.cc
index 295bbeb..7177a4f5 100644
--- a/weblayer/shell/browser/shell.cc
+++ b/weblayer/shell/browser/shell.cc
@@ -35,6 +35,13 @@
   if (tab_) {
     tab_->AddObserver(this);
     tab_->GetNavigationController()->AddObserver(this);
+#if !defined(OS_ANDROID)  // Android does this in Java.
+
+    // TODO: how will tests work with this on android? can we get to the
+    // concrete type?
+
+    tab_->SetDownloadDelegate(this);
+#endif
   }
 }
 
@@ -128,6 +135,21 @@
   PlatformSetLoadProgress(progress);
 }
 
+bool Shell::InterceptDownload(const GURL& url,
+                              const std::string& user_agent,
+                              const std::string& content_disposition,
+                              const std::string& mime_type,
+                              int64_t content_length) {
+  return false;
+}
+
+void Shell::AllowDownload(const GURL& url,
+                          const std::string& request_method,
+                          base::Optional<url::Origin> request_initiator,
+                          AllowDownloadCallback callback) {
+  std::move(callback).Run(true);
+}
+
 gfx::Size Shell::AdjustWindowSize(const gfx::Size& initial_size) {
   if (!initial_size.IsEmpty())
     return initial_size;
diff --git a/weblayer/shell/browser/shell.h b/weblayer/shell/browser/shell.h
index 7ceb436d..3e1a51b 100644
--- a/weblayer/shell/browser/shell.h
+++ b/weblayer/shell/browser/shell.h
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
+#include "weblayer/public/download_delegate.h"
 #include "weblayer/public/navigation_observer.h"
 #include "weblayer/public/tab_observer.h"
 
@@ -39,7 +40,9 @@
 
 // This represents one window of the Web Shell, i.e. all the UI including
 // buttons and url bar, as well as the web content area.
-class Shell : public TabObserver, public NavigationObserver {
+class Shell : public TabObserver,
+              public NavigationObserver,
+              public DownloadDelegate {
  public:
   ~Shell() override;
 
@@ -86,6 +89,17 @@
   void LoadStateChanged(bool is_loading, bool to_different_document) override;
   void LoadProgressChanged(double progress) override;
 
+  // DownloadDelegate implementation:
+  bool InterceptDownload(const GURL& url,
+                         const std::string& user_agent,
+                         const std::string& content_disposition,
+                         const std::string& mime_type,
+                         int64_t content_length) override;
+  void AllowDownload(const GURL& url,
+                     const std::string& request_method,
+                     base::Optional<url::Origin> request_initiator,
+                     AllowDownloadCallback callback) override;
+
   // Helper to create a new Shell.
   static Shell* CreateShell(std::unique_ptr<Tab> tab,
                             const gfx::Size& initial_size);
diff --git a/weblayer/test/BUILD.gn b/weblayer/test/BUILD.gn
index e30e5f6..163f5e6 100644
--- a/weblayer/test/BUILD.gn
+++ b/weblayer/test/BUILD.gn
@@ -92,6 +92,7 @@
 
   sources = [
     "../browser/autofill_browsertest.cc",
+    "../browser/download_browsertest.cc",
     "../browser/errorpage_browsertest.cc",
     "../browser/navigation_browsertest.cc",
     "../browser/ssl_browsertest.cc",
diff --git a/weblayer/test/data/content-disposition.html b/weblayer/test/data/content-disposition.html
new file mode 100644
index 0000000..30d74d2
--- /dev/null
+++ b/weblayer/test/data/content-disposition.html
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/weblayer/test/data/content-disposition.html.mock-http-headers b/weblayer/test/data/content-disposition.html.mock-http-headers
new file mode 100644
index 0000000..edd7629
--- /dev/null
+++ b/weblayer/test/data/content-disposition.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.0 200 OK
+Content-Disposition: attachment; filename=test.html
diff --git a/weblayer/test/weblayer_browser_test.cc b/weblayer/test/weblayer_browser_test.cc
index 04cbfa78..bd4876fe 100644
--- a/weblayer/test/weblayer_browser_test.cc
+++ b/weblayer/test/weblayer_browser_test.cc
@@ -5,7 +5,10 @@
 #include "weblayer/test/weblayer_browser_test.h"
 
 #include "base/base_paths.h"
-#include "base/command_line.h"
+#include "weblayer/browser/browser_context_impl.h"
+#include "weblayer/browser/profile_impl.h"
+#include "weblayer/browser/tab_impl.h"
+#include "weblayer/public/common/switches.h"
 #include "weblayer/shell/browser/shell.h"
 #include "weblayer/shell/common/shell_switches.h"
 
@@ -27,6 +30,15 @@
 void WebLayerBrowserTest::PreRunTestOnMainThread() {
   ASSERT_EQ(Shell::windows().size(), 1u);
   shell_ = Shell::windows()[0];
+
+  // Don't fill machine's download directory from tests; instead place downloads
+  // in the temporary user-data-dir for this test.
+  auto* tab_impl = static_cast<TabImpl*>(shell_->tab());
+  auto* browser_context = tab_impl->web_contents()->GetBrowserContext();
+  auto* browser_context_impl =
+      static_cast<BrowserContextImpl*>(browser_context);
+  browser_context_impl->profile_impl()->SetDownloadDirectory(
+      browser_context->GetPath());
 }
 
 void WebLayerBrowserTest::PostRunTestOnMainThread() {