diff --git a/DEPS b/DEPS
index 72c8215d..d4bd6c1 100644
--- a/DEPS
+++ b/DEPS
@@ -105,7 +105,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '656cefe65d620f9aa7f689e412fa7720fe01c447',
+  'skia_revision': 'f88f49d2a52e2a28eaea0f5476fa4cc3ebd2a820',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -213,7 +213,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '32381e30ef56f5a15207679a55c73a206483c1c4',
+  'spv_tools_revision': 'ddc705933d3f32024ea0e320a2c9d91925b78d9c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -595,7 +595,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '82b404766db2865f290fb939577b2a0a931508eb',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '42db842648806b83ba74c765083b7f4e5211dd0d',
       'condition': 'checkout_linux',
   },
 
@@ -1137,7 +1137,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@928526013073be9b73b8ca5140d37030a66bc0a2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cd9671dd3ecf68876fb6cee9d1d07d1d4c4d614e',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index c818caf0..25b1a8a 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1514,10 +1514,8 @@
     },
     'tab_alert_indicators': {
       'filepath': 'content/browser/media/audio_stream_monitor'\
-        '|chrome/browser/ui/cocoa/tabs/alert_indicator_button'\
-        '|chrome/browser/ui/cocoa/tabs/tab_controller\.mm'\
         '|chrome/browser/ui/tabs/tab_utils'\
-        '|chrome/browser/ui/views/tabs/alert_indicator_button'\
+        '|chrome/browser/ui/views/tabs/alert_indicator'\
         '|chrome/browser/ui/views/tabs/tab\.cc'\
         '|chrome/browser/ui/views/tabs/tab_renderer_data'\
         '|media/audio/audio_(output_controller|power_monitor)',
diff --git a/android_webview/browser/aw_quota_manager_bridge.cc b/android_webview/browser/aw_quota_manager_bridge.cc
index ca988bb4..4d47700 100644
--- a/android_webview/browser/aw_quota_manager_bridge.cc
+++ b/android_webview/browser/aw_quota_manager_bridge.cc
@@ -21,6 +21,7 @@
 #include "storage/browser/quota/quota_manager.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 using base::android::AttachCurrentThread;
 using base::android::JavaParamRef;
@@ -49,10 +50,10 @@
   friend class base::RefCountedThreadSafe<GetOriginsTask>;
   ~GetOriginsTask();
 
-  void OnOriginsObtained(const std::set<GURL>& origins,
+  void OnOriginsObtained(const std::set<url::Origin>& origins,
                          blink::mojom::StorageType type);
 
-  void OnUsageAndQuotaObtained(const GURL& origin,
+  void OnUsageAndQuotaObtained(const url::Origin& origin,
                                blink::mojom::QuotaStatusCode status_code,
                                int64_t usage,
                                int64_t quota);
@@ -92,31 +93,29 @@
                      base::BindOnce(&GetOriginsTask::OnOriginsObtained, this)));
 }
 
-void GetOriginsTask::OnOriginsObtained(const std::set<GURL>& origins,
+void GetOriginsTask::OnOriginsObtained(const std::set<url::Origin>& origins,
                                        blink::mojom::StorageType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   num_callbacks_to_wait_ = origins.size();
   num_callbacks_received_ = 0u;
 
-  for (std::set<GURL>::const_iterator origin = origins.begin();
-       origin != origins.end(); ++origin) {
+  for (const url::Origin& origin : origins) {
     quota_manager_->GetUsageAndQuota(
-        *origin, type,
-        base::BindOnce(&GetOriginsTask::OnUsageAndQuotaObtained, this,
-                       *origin));
+        origin, type,
+        base::BindOnce(&GetOriginsTask::OnUsageAndQuotaObtained, this, origin));
   }
 
   CheckDone();
 }
 
 void GetOriginsTask::OnUsageAndQuotaObtained(
-    const GURL& origin,
+    const url::Origin& origin,
     blink::mojom::QuotaStatusCode status_code,
     int64_t usage,
     int64_t quota) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (status_code == blink::mojom::QuotaStatusCode::kOk) {
-    origin_.push_back(origin.spec());
+    origin_.push_back(origin.GetURL().spec());
     usage_.push_back(usage);
     quota_.push_back(quota);
   }
@@ -317,10 +316,12 @@
       base::BindOnce(&AwQuotaManagerBridge::QuotaUsageCallbackImpl,
                      weak_factory_.GetWeakPtr(), callback_id, is_quota);
 
+  // TODO(crbug.com/889590): Use helper for url::Origin creation from string.
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(
-          &QuotaManager::GetUsageAndQuota, GetQuotaManager(), GURL(origin),
+          &QuotaManager::GetUsageAndQuota, GetQuotaManager(),
+          url::Origin::Create(GURL(origin)),
           blink::mojom::StorageType::kTemporary,
           base::BindOnce(&OnUsageAndQuotaObtained, std::move(ui_callback))));
 }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 90dd255..7ce5e92 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -34,7 +34,6 @@
     "accelerators/accelerator_controller.h",
     "accessibility/accessibility_controller.h",
     "accessibility/accessibility_delegate.h",
-    "accessibility/ax_host_service.h",
     "accessibility/focus_ring_controller.h",
     "app_list/app_list_controller_impl.h",
     "detachable_base/detachable_base_handler.h",
@@ -132,7 +131,6 @@
     "accessibility/accessibility_observer.h",
     "accessibility/accessibility_panel_layout_manager.cc",
     "accessibility/accessibility_panel_layout_manager.h",
-    "accessibility/ax_host_service.cc",
     "accessibility/default_accessibility_delegate.cc",
     "accessibility/default_accessibility_delegate.h",
     "accessibility/focus_ring_controller.cc",
@@ -1689,7 +1687,6 @@
     "accessibility/accessibility_focus_ring_group_unittest.cc",
     "accessibility/accessibility_highlight_controller_unittest.cc",
     "accessibility/accessibility_panel_layout_manager_unittest.cc",
-    "accessibility/ax_host_service_unittest.cc",
     "accessibility/key_accessibility_enabler_unittest.cc",
     "accessibility/touch_accessibility_enabler_unittest.cc",
     "accessibility/touch_exploration_controller_unittest.cc",
diff --git a/ash/accessibility/accessibility_delegate.h b/ash/accessibility/accessibility_delegate.h
index 0a33e6d3..c59d0e6 100644
--- a/ash/accessibility/accessibility_delegate.h
+++ b/ash/accessibility/accessibility_delegate.h
@@ -5,15 +5,9 @@
 #ifndef ASH_ACCESSIBILITY_ACCESSIBILITY_DELEGATE_H_
 #define ASH_ACCESSIBILITY_ACCESSIBILITY_DELEGATE_H_
 
-#include <vector>
-
 #include "ash/ash_export.h"
-#include "ui/accessibility/ax_tree_id.h"
-#include "ui/accessibility/ax_tree_update.h"
-
-namespace ui {
-struct AXEvent;
-}
+#include "base/time/time.h"
+#include "ui/accessibility/ax_enums.mojom.h"
 
 namespace ash {
 
@@ -42,14 +36,6 @@
   // is not saved, return a negative value.
   virtual double GetSavedScreenMagnifierScale() = 0;
 
-  // Automation API support for remote mojo apps.
-  // TODO(jamescook): Convert to mojo API.
-  virtual void DispatchAccessibilityEvent(
-      const ui::AXTreeID& tree_id,
-      const std::vector<ui::AXTreeUpdate>& updates,
-      const ui::AXEvent& event) = 0;
-  virtual void DispatchTreeDestroyedEvent(const ui::AXTreeID& tree_id) = 0;
-
   // NOTE: Prefer adding methods to AccessibilityController, see class comment.
 };
 
diff --git a/ash/accessibility/default_accessibility_delegate.cc b/ash/accessibility/default_accessibility_delegate.cc
index f4b5ec3..f57af83 100644
--- a/ash/accessibility/default_accessibility_delegate.cc
+++ b/ash/accessibility/default_accessibility_delegate.cc
@@ -40,12 +40,4 @@
   return std::numeric_limits<double>::min();
 }
 
-void DefaultAccessibilityDelegate::DispatchAccessibilityEvent(
-    const ui::AXTreeID& tree_id,
-    const std::vector<ui::AXTreeUpdate>& updates,
-    const ui::AXEvent& event) {}
-
-void DefaultAccessibilityDelegate::DispatchTreeDestroyedEvent(
-    const ui::AXTreeID& tree_id) {}
-
 }  // namespace ash
diff --git a/ash/accessibility/default_accessibility_delegate.h b/ash/accessibility/default_accessibility_delegate.h
index 2f09148..4287df6 100644
--- a/ash/accessibility/default_accessibility_delegate.h
+++ b/ash/accessibility/default_accessibility_delegate.h
@@ -11,7 +11,6 @@
 
 namespace ash {
 
-// Used in tests and non-production code like ash_shell_with_content.
 class ASH_EXPORT DefaultAccessibilityDelegate : public AccessibilityDelegate {
  public:
   DefaultAccessibilityDelegate();
@@ -22,10 +21,6 @@
   bool ShouldShowAccessibilityMenu() const override;
   void SaveScreenMagnifierScale(double scale) override;
   double GetSavedScreenMagnifierScale() override;
-  void DispatchAccessibilityEvent(const ui::AXTreeID& tree_id,
-                                  const std::vector<ui::AXTreeUpdate>& updates,
-                                  const ui::AXEvent& event) override;
-  void DispatchTreeDestroyedEvent(const ui::AXTreeID& tree_id) override;
 
  private:
   bool screen_magnifier_enabled_ = false;
diff --git a/ash/ash_service.cc b/ash/ash_service.cc
index 399948e4..5ec00b29 100644
--- a/ash/ash_service.cc
+++ b/ash/ash_service.cc
@@ -203,7 +203,6 @@
     service_manager::mojom::ServiceRequest service,
     const std::string& name,
     service_manager::mojom::PIDReceiverPtr pid_receiver) {
-  // TODO(jamescook): Create the AXHostService here under mash.
   DCHECK_EQ(name, ws::mojom::kServiceName);
   Shell::Get()->window_service_owner()->BindWindowService(std::move(service));
   if (base::FeatureList::IsEnabled(features::kMash)) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 4709c96..350f866 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1533,10 +1533,10 @@
         <ph name="DEVICE_NAME">$1<ex>Pixel XL</ex></ph> added
       </message>
       <message name="IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_HOST_SWITCHED_MESSAGE" desc="Message shown as part of the notification shown to a user that has already completed the MultiDevice setup flow that a new phone is replacing the phone they currently have set up to connect to their Chromebook.">
-        Better Together switched to a new phone
+        Chromebook connected to a new phone
       </message>
       <message name="IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_NEW_CHROMEBOOK_ADDED_TITLE" desc="Title of the notification shown to a user that has already completed the MultiDevice setup flow logging into a new Chromebook that all their Chromebooks are MultiDevice enabled.">
-        Better Together is on
+        Connected to <ph name="DEVICE_NAME">$1<ex>Pixel XL</ex></ph>
       </message>
       <message name="IDS_ASH_MULTI_DEVICE_SETUP_EXISTING_USER_NEW_CHROMEBOOK_ADDED_MESSAGE" desc="Message shown as part of the notification shown to a user that has already completed the MultiDevice setup flow logging into a new Chromebook that all their Chromebooks are MultiDevice enabled.">
         This Chromebook and your phone will connect automatically
diff --git a/ash/assistant/assistant_controller.cc b/ash/assistant/assistant_controller.cc
index 5e801e7..7a873eb 100644
--- a/ash/assistant/assistant_controller.cc
+++ b/ash/assistant/assistant_controller.cc
@@ -303,7 +303,10 @@
     return;
   }
 
-  Shell::Get()->new_window_controller()->NewTabWithUrl(url);
+  // The new tab should be opened with a user activation since the user
+  // interacted with the assistant to open the url.
+  Shell::Get()->new_window_controller()->NewTabWithUrl(
+      url, true /* from_user_interaction */);
   NotifyUrlOpened(url);
 }
 
diff --git a/ash/dbus/url_handler_service_provider.cc b/ash/dbus/url_handler_service_provider.cc
index 6ee4f79..9af2baa 100644
--- a/ash/dbus/url_handler_service_provider.cc
+++ b/ash/dbus/url_handler_service_provider.cc
@@ -69,7 +69,8 @@
     return;
   }
 
-  ash::Shell::Get()->new_window_controller()->NewTabWithUrl(gurl);
+  ash::Shell::Get()->new_window_controller()->NewTabWithUrl(
+      gurl, false /* from_user_interaction */);
   response_sender.Run(dbus::Response::FromMethodCall(method_call));
 }
 
diff --git a/ash/new_window_controller.cc b/ash/new_window_controller.cc
index 9d307ea7..a47fb17 100644
--- a/ash/new_window_controller.cc
+++ b/ash/new_window_controller.cc
@@ -29,9 +29,10 @@
     client_->ShowKeyboardOverlay();
 }
 
-void NewWindowController::NewTabWithUrl(const GURL& url) {
+void NewWindowController::NewTabWithUrl(const GURL& url,
+                                        bool from_user_interaction) {
   if (client_)
-    client_->NewTabWithUrl(url);
+    client_->NewTabWithUrl(url, from_user_interaction);
 }
 
 void NewWindowController::NewTab() {
diff --git a/ash/new_window_controller.h b/ash/new_window_controller.h
index 186b407a..c28d221c 100644
--- a/ash/new_window_controller.h
+++ b/ash/new_window_controller.h
@@ -29,7 +29,7 @@
   void ShowKeyboardOverlay() override;
 
   // Pass throughs for methods of the same name on |client_|.
-  void NewTabWithUrl(const GURL& url);
+  void NewTabWithUrl(const GURL& url, bool from_user_interaction);
   void NewTab();
   void NewWindow(bool incognito);
   void OpenFileManager();
diff --git a/ash/public/interfaces/new_window.mojom b/ash/public/interfaces/new_window.mojom
index 98efe46..2c1231a 100644
--- a/ash/public/interfaces/new_window.mojom
+++ b/ash/public/interfaces/new_window.mojom
@@ -24,8 +24,10 @@
   // Invoked when the user uses Ctrl+T to open a new tab.
   NewTab();
 
-  // Opens a new tab with the specified URL.
-  NewTabWithUrl(url.mojom.Url url);
+  // Opens a new tab with the specified URL. If the |from_user_interaction|
+  // is true then the page will load with a user activation. This means the
+  // page will be able to autoplay media without restriction.
+  NewTabWithUrl(url.mojom.Url url, bool from_user_interaction);
 
   // Invoked when the user uses Ctrl-N or Ctrl-Shift-N to open a new window.
   NewWindow(bool incognito);
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index 783e2e7e..dd1c79a 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -30,6 +30,7 @@
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/power_policy_controller.h"
 #include "chromeos/network/network_handler.h"
+#include "chromeos/system/fake_statistics_provider.h"
 #include "components/discardable_memory/public/interfaces/discardable_shared_memory_manager.mojom.h"
 #include "components/prefs/testing_pref_service.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
@@ -232,6 +233,9 @@
         switches::kAshDisableSmoothScreenRotation);
   }
 
+  statistics_provider_ =
+      std::make_unique<chromeos::system::ScopedFakeStatisticsProvider>();
+
   ui::test::EventGeneratorDelegate::SetFactoryFunction(base::BindRepeating(
       &aura::test::EventGeneratorDelegateAura::Create, nullptr));
 
@@ -383,6 +387,8 @@
 
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       ui::test::EventGeneratorDelegate::FactoryFunction());
+
+  statistics_provider_.reset();
 }
 
 void AshTestHelper::SetRunningOutsideAsh() {
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index a9bc8e5f..b8653a7 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -23,6 +23,12 @@
 }
 }
 
+namespace chromeos {
+namespace system {
+class ScopedFakeStatisticsProvider;
+}  // namespace system
+}  // namespace chromeos
+
 namespace display {
 class Display;
 }
@@ -113,6 +119,9 @@
   // Called when running in ash to create Shell.
   void CreateShell();
 
+  std::unique_ptr<chromeos::system::ScopedFakeStatisticsProvider>
+      statistics_provider_;
+
   std::unique_ptr<aura::test::EnvWindowTreeClientSetter>
       env_window_tree_client_setter_;
   AshTestEnvironment* ash_test_environment_;  // Not owned.
diff --git a/ash/wm/non_client_frame_controller_unittest.cc b/ash/wm/non_client_frame_controller_unittest.cc
index 20b61c5..df54544 100644
--- a/ash/wm/non_client_frame_controller_unittest.cc
+++ b/ash/wm/non_client_frame_controller_unittest.cc
@@ -226,7 +226,7 @@
 }
 
 TEST_F(NonClientFrameControllerTest, ExposesChildTreeIdToAccessibility) {
-  const ui::AXTreeID ax_tree_id("123");
+  const ui::AXTreeID ax_tree_id = ui::AXTreeID::FromString("123");
   Shell::Get()->accessibility_controller()->set_remote_ax_tree_id(ax_tree_id);
   std::unique_ptr<aura::Window> window = CreateTestWindow();
   NonClientFrameController* non_client_frame_controller =
@@ -234,8 +234,9 @@
   views::View* contents_view = non_client_frame_controller->GetContentsView();
   ui::AXNodeData ax_node_data;
   contents_view->GetAccessibleNodeData(&ax_node_data);
-  EXPECT_EQ(ax_tree_id, ax_node_data.GetStringAttribute(
-                            ax::mojom::StringAttribute::kChildTreeId));
+  EXPECT_EQ(ax_tree_id,
+            ui::AXTreeID::FromString(ax_node_data.GetStringAttribute(
+                ax::mojom::StringAttribute::kChildTreeId)));
   EXPECT_EQ(ax::mojom::Role::kClient, ax_node_data.role);
 }
 
diff --git a/base/allocator/partition_allocator/partition_alloc.h b/base/allocator/partition_allocator/partition_alloc.h
index 95611a5..571d1de 100644
--- a/base/allocator/partition_allocator/partition_alloc.h
+++ b/base/allocator/partition_allocator/partition_alloc.h
@@ -348,9 +348,9 @@
                                                size_t size,
                                                const char* type_name) {
   DCHECK_LT(flags, PartitionAllocLastFlag << 1);
-  const bool zero_fill = flags & PartitionAllocZeroFill;
 
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
+  const bool zero_fill = flags & PartitionAllocZeroFill;
   void* result = zero_fill ? calloc(1, size) : malloc(size);
   CHECK(result || flags & PartitionAllocReturnNull);
   return result;
@@ -366,13 +366,6 @@
   }
   PartitionAllocHooks::AllocationHookIfEnabled(ret, requested_size, type_name);
 
-  // TODO(crbug.com/864462): This is suboptimal. Change `AllocFromBucket` such
-  // that it tells callers if the allocation was satisfied with a fresh mapping
-  // from the OS, so that we can skip this step and save some time.
-  if (ret && zero_fill) {
-    memset(ret, 0, requested_size);
-  }
-
   return ret;
 #endif
 }
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 82f4fa61..c5c08ba 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -11,8 +11,11 @@
 #include <vector>
 
 #include "base/allocator/partition_allocator/address_space_randomization.h"
+#include "base/allocator/partition_allocator/partition_alloc.h"
 #include "base/bit_cast.h"
 #include "base/bits.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
 #include "base/sys_info.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -86,6 +89,35 @@
 #endif
 }
 
+const size_t kTestSizes[] = {
+    1,
+    17,
+    100,
+    base::kSystemPageSize,
+    base::kSystemPageSize + 1,
+    base::internal::PartitionBucket::get_direct_map_size(100),
+    1 << 20,
+    1 << 21,
+};
+constexpr size_t kTestSizesCount = base::size(kTestSizes);
+
+void AllocateRandomly(base::PartitionRootGeneric* root,
+                      size_t count,
+                      int flags) {
+  std::vector<void*> allocations(count, nullptr);
+  for (size_t i = 0; i < count; ++i) {
+    const size_t size = kTestSizes[base::RandGenerator(kTestSizesCount)];
+    allocations[i] = PartitionAllocGenericFlags(root, flags, size, nullptr);
+    EXPECT_NE(nullptr, allocations[i]) << " size: " << size << " i: " << i;
+  }
+
+  for (size_t i = 0; i < count; ++i) {
+    if (allocations[i]) {
+      base::PartitionFree(allocations[i]);
+    }
+  }
+}
+
 }  // namespace
 
 namespace base {
@@ -2156,20 +2188,9 @@
 }
 
 TEST_F(PartitionAllocTest, ZeroFill) {
-  const size_t test_sizes[] = {
-      1,
-      17,
-      100,
-      kSystemPageSize,
-      kSystemPageSize + 1,
-      internal::PartitionBucket::get_direct_map_size(100),
-      1 << 20,
-      1 << 21,
-  };
-
   constexpr static size_t kAllZerosSentinel =
       std::numeric_limits<size_t>::max();
-  for (size_t size : test_sizes) {
+  for (size_t size : kTestSizes) {
     char* p = static_cast<char*>(PartitionAllocGenericFlags(
         generic_allocator.root(), PartitionAllocZeroFill, size, nullptr));
     size_t non_zero_position = kAllZerosSentinel;
@@ -2183,6 +2204,11 @@
         << "test allocation size: " << size;
     PartitionFree(p);
   }
+
+  for (int i = 0; i < 10; ++i) {
+    SCOPED_TRACE(i);
+    AllocateRandomly(generic_allocator.root(), 1000, PartitionAllocZeroFill);
+  }
 }
 
 }  // namespace internal
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 6751889..79d2dad 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -448,11 +448,13 @@
 
 void* PartitionBucket::SlowPathAlloc(PartitionRootBase* root,
                                      int flags,
-                                     size_t size) {
+                                     size_t size,
+                                     bool* is_already_zeroed) {
   // The slow path is called when the freelist is empty.
   DCHECK(!this->active_pages_head->freelist_head);
 
   PartitionPage* new_page = nullptr;
+  *is_already_zeroed = false;
 
   // For the PartitionRootGeneric::Alloc() API, we have a bunch of buckets
   // marked as special cases. We bounce them through to the slow path so that
@@ -474,6 +476,7 @@
       PartitionExcessiveAllocationSize();
     }
     new_page = PartitionDirectMap(root, flags, size);
+    *is_already_zeroed = true;
   } else if (LIKELY(this->SetNewActivePage())) {
     // First, did we find an active page in the active pages list?
     new_page = this->active_pages_head;
@@ -505,6 +508,7 @@
       void* addr = PartitionPage::ToPointer(new_page);
       root->RecommitSystemPages(addr, new_page->bucket->get_bytes_per_span());
       new_page->Reset();
+      *is_already_zeroed = true;
     }
     DCHECK(new_page);
   } else {
@@ -514,6 +518,7 @@
     if (LIKELY(raw_pages != nullptr)) {
       new_page = PartitionPage::FromPointerNoAlignmentCheck(raw_pages);
       InitializeSlotSpan(new_page);
+      *is_already_zeroed = true;
     }
   }
 
diff --git a/base/allocator/partition_allocator/partition_bucket.h b/base/allocator/partition_allocator/partition_bucket.h
index a626dfa8..a30b5694 100644
--- a/base/allocator/partition_allocator/partition_bucket.h
+++ b/base/allocator/partition_allocator/partition_bucket.h
@@ -31,10 +31,17 @@
   // Public API.
   void Init(uint32_t new_slot_size);
 
+  // Sets |is_already_zeroed| to true if the allocation was satisfied by
+  // requesting (a) new page(s) from the operating system, or false otherwise.
+  // This enables an optimization for when callers use |PartitionAllocZeroFill|:
+  // there is no need to call memset on fresh pages; the OS has already zeroed
+  // them. (See |PartitionRootBase::AllocFromBucket|.)
+  //
   // Note the matching Free() functions are in PartitionPage.
   BASE_EXPORT NOINLINE void* SlowPathAlloc(PartitionRootBase* root,
                                            int flags,
-                                           size_t size);
+                                           size_t size,
+                                           bool* is_already_zeroed);
 
   ALWAYS_INLINE bool is_direct_mapped() const {
     return !num_system_pages_per_slot_span;
diff --git a/base/allocator/partition_allocator/partition_root_base.h b/base/allocator/partition_allocator/partition_root_base.h
index e20990e4..0d22c09 100644
--- a/base/allocator/partition_allocator/partition_root_base.h
+++ b/base/allocator/partition_allocator/partition_root_base.h
@@ -86,16 +86,20 @@
 ALWAYS_INLINE void* PartitionRootBase::AllocFromBucket(PartitionBucket* bucket,
                                                        int flags,
                                                        size_t size) {
+  bool zero_fill = flags & PartitionAllocZeroFill;
+  bool is_already_zeroed = false;
+
   PartitionPage* page = bucket->active_pages_head;
   // Check that this page is neither full nor freed.
   DCHECK(page->num_allocated_slots >= 0);
   void* ret = page->freelist_head;
   if (LIKELY(ret != 0)) {
-    // If these DCHECKs fire, you probably corrupted memory.
-    // TODO(palmer): See if we can afford to make this a CHECK.
+    // If these DCHECKs fire, you probably corrupted memory. TODO(palmer): See
+    // if we can afford to make these CHECKs.
     DCHECK(PartitionRootBase::IsValidPage(page));
-    // All large allocations must go through the slow path to correctly
-    // update the size metadata.
+
+    // All large allocations must go through the slow path to correctly update
+    // the size metadata.
     DCHECK(page->get_raw_size() == 0);
     internal::PartitionFreelistEntry* new_head =
         internal::PartitionFreelistEntry::Transform(
@@ -103,15 +107,17 @@
     page->freelist_head = new_head;
     page->num_allocated_slots++;
   } else {
-    ret = bucket->SlowPathAlloc(this, flags, size);
+    ret = bucket->SlowPathAlloc(this, flags, size, &is_already_zeroed);
     // TODO(palmer): See if we can afford to make this a CHECK.
     DCHECK(!ret ||
            PartitionRootBase::IsValidPage(PartitionPage::FromPointer(ret)));
   }
+
 #if DCHECK_IS_ON()
-  if (!ret)
-    return 0;
-  // Fill the uninitialized pattern, and write the cookies.
+  if (!ret) {
+    return nullptr;
+  }
+
   page = PartitionPage::FromPointer(ret);
   // TODO(ajwong): Can |page->bucket| ever not be |this|? If not, can this just
   // be bucket->slot_size?
@@ -126,11 +132,20 @@
   // The value given to the application is actually just after the cookie.
   ret = char_ret + kCookieSize;
 
-  // Debug fill region kUninitializedByte and surround it with 2 cookies.
+  // Fill the region kUninitializedByte or 0, and surround it with 2 cookies.
   PartitionCookieWriteValue(char_ret);
-  memset(ret, kUninitializedByte, no_cookie_size);
+  if (!zero_fill) {
+    memset(ret, kUninitializedByte, no_cookie_size);
+  } else if (!is_already_zeroed) {
+    memset(ret, 0, no_cookie_size);
+  }
   PartitionCookieWriteValue(char_ret + kCookieSize + no_cookie_size);
+#else
+  if (ret && zero_fill && !is_already_zeroed) {
+    memset(ret, 0, size);
+  }
 #endif
+
   return ret;
 }
 
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index 22195837..c1d4a1a4 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -346,55 +346,14 @@
 
 #if defined(OS_WIN)
 
-void SubPumpFunc() {
-  MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
+void SubPumpFunc(OnceClosure on_done) {
+  MessageLoopCurrent::ScopedNestableTaskAllower allow_nestable_tasks;
   MSG msg;
-  while (GetMessage(&msg, NULL, 0, 0)) {
-    TranslateMessage(&msg);
-    DispatchMessage(&msg);
+  while (::GetMessage(&msg, NULL, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
   }
-  RunLoop::QuitCurrentWhenIdleDeprecated();
-}
-
-void RunTest_PostDelayedTask_SharedTimer_SubPump() {
-  MessageLoop message_loop(MessageLoop::TYPE_UI);
-
-  // Test that the interval of the timer, used to run the next delayed task, is
-  // set to a value corresponding to when the next delayed task should run.
-
-  // By setting num_tasks to 1, we ensure that the first task to run causes the
-  // run loop to exit.
-  int num_tasks = 1;
-  TimeTicks run_time;
-
-  message_loop.task_runner()->PostTask(FROM_HERE, BindOnce(&SubPumpFunc));
-
-  // This very delayed task should never run.
-  message_loop.task_runner()->PostDelayedTask(
-      FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time, &num_tasks),
-      TimeDelta::FromSeconds(1000));
-
-  // This slightly delayed task should run from within SubPumpFunc.
-  message_loop.task_runner()->PostDelayedTask(FROM_HERE,
-                                              BindOnce(&PostQuitMessage, 0),
-                                              TimeDelta::FromMilliseconds(10));
-
-  Time start_time = Time::Now();
-
-  RunLoop().Run();
-  EXPECT_EQ(1, num_tasks);
-
-  // Ensure that we ran in far less time than the slower timer.
-  TimeDelta total_time = Time::Now() - start_time;
-  EXPECT_GT(5000, total_time.InMilliseconds());
-
-  // In case both timers somehow run at nearly the same time, sleep a little
-  // and then run all pending to force them both to have run.  This is just
-  // encouraging flakiness if there is any.
-  PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
-  RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(run_time.is_null());
+  std::move(on_done).Run();
 }
 
 const wchar_t kMessageBoxTitle[] = L"MessageLoop Unit Test";
@@ -465,93 +424,6 @@
   }
 }
 
-// TODO(darin): These tests need to be ported since they test critical
-// message loop functionality.
-
-// A side effect of this test is the generation a beep. Sorry.
-void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) {
-  MessageLoop loop(message_loop_type);
-
-  Thread worker("RecursiveDenial2_worker");
-  Thread::Options options;
-  options.message_loop_type = message_loop_type;
-  ASSERT_EQ(true, worker.StartWithOptions(options));
-  TaskList order;
-  win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
-  worker.task_runner()->PostTask(
-      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
-                          event.Get(), true, &order, false));
-  // Let the other thread execute.
-  WaitForSingleObject(event.Get(), INFINITE);
-  RunLoop().Run();
-
-  ASSERT_EQ(17u, order.Size());
-  EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
-  EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
-  EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false));
-  EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false));
-  // When EndDialogFunc is processed, the window is already dismissed, hence no
-  // "end" entry.
-  EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true));
-  EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true));
-  EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false));
-  EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false));
-  EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false));
-  EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false));
-  EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false));
-}
-
-// A side effect of this test is the generation a beep. Sorry.  This test also
-// needs to process windows messages on the current thread.
-void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) {
-  MessageLoop loop(message_loop_type);
-
-  Thread worker("RecursiveSupport2_worker");
-  Thread::Options options;
-  options.message_loop_type = message_loop_type;
-  ASSERT_EQ(true, worker.StartWithOptions(options));
-  TaskList order;
-  win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
-  worker.task_runner()->PostTask(
-      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
-                          event.Get(), false, &order, true));
-  // Let the other thread execute.
-  WaitForSingleObject(event.Get(), INFINITE);
-  RunLoop().Run();
-
-  ASSERT_EQ(18u, order.Size());
-  EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
-  EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
-  // Note that this executes in the MessageBox modal loop.
-  EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, false));
-  EXPECT_EQ(order.Get(5), TaskItem(ENDDIALOG, 4, true));
-  EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, false));
-  EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false));
-  /* The order can subtly change here. The reason is that when RecursiveFunc(1)
-     is called in the main thread, if it is faster than getting to the
-     PostTask(FROM_HERE, BindOnce(&QuitFunc) execution, the order of task
-     execution can change. We don't care anyway that the order isn't correct.
-  EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true));
-  EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, false));
-  EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
-  */
-  EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 3, false));
-  EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, true));
-  EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 1, false));
-  EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, true));
-  EXPECT_EQ(order.Get(17), TaskItem(RECURSIVE, 3, false));
-}
-
 #endif  // defined(OS_WIN)
 
 void PostNTasksThenQuit(int posts_remaining) {
@@ -1734,9 +1606,10 @@
                         MessageLoopTypedTest::ParamInfoToString);
 
 #if defined(OS_WIN)
+
 // Verifies that the MessageLoop ignores WM_QUIT, rather than quitting.
 // Users of MessageLoop typically expect to control when their RunLoops stop
-// Run()ning explicitly, via QuitClosure() etc (see https://crbug.com/720078)
+// Run()ning explicitly, via QuitClosure() etc (see https://crbug.com/720078).
 TEST_F(MessageLoopTest, WmQuitIsIgnored) {
   MessageLoop loop(MessageLoop::TYPE_UI);
 
@@ -1786,9 +1659,213 @@
 }
 
 TEST_F(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) {
-  RunTest_PostDelayedTask_SharedTimer_SubPump();
+  MessageLoop message_loop(MessageLoop::TYPE_UI);
+
+  // Test that the interval of the timer, used to run the next delayed task, is
+  // set to a value corresponding to when the next delayed task should run.
+
+  // By setting num_tasks to 1, we ensure that the first task to run causes the
+  // run loop to exit.
+  int num_tasks = 1;
+  TimeTicks run_time;
+
+  RunLoop run_loop;
+
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, run_loop.QuitClosure()));
+
+  // This very delayed task should never run.
+  message_loop.task_runner()->PostDelayedTask(
+      FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time, &num_tasks),
+      TimeDelta::FromSeconds(1000));
+
+  // This slightly delayed task should run from within SubPumpFunc.
+  message_loop.task_runner()->PostDelayedTask(FROM_HERE,
+                                              BindOnce(&::PostQuitMessage, 0),
+                                              TimeDelta::FromMilliseconds(10));
+
+  Time start_time = Time::Now();
+
+  run_loop.Run();
+  EXPECT_EQ(1, num_tasks);
+
+  // Ensure that we ran in far less time than the slower timer.
+  TimeDelta total_time = Time::Now() - start_time;
+  EXPECT_GT(5000, total_time.InMilliseconds());
+
+  // In case both timers somehow run at nearly the same time, sleep a little
+  // and then run all pending to force them both to have run.  This is just
+  // encouraging flakiness if there is any.
+  PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(run_time.is_null());
 }
 
+TEST_F(MessageLoopTest, WmQuitIsVisibleToSubPump) {
+  MessageLoop message_loop(MessageLoop::TYPE_UI);
+
+  // Regression test for https://crbug.com/888559. When processing a
+  // kMsgHaveWork we peek and remove the next message and dispatch that ourself,
+  // to minimize impact of these messages on message-queue processing. If we
+  // received kMsgHaveWork dispatched by a nested pump (e.g. ::GetMessage()
+  // loop) then there is a risk that the next message is that loop's WM_QUIT
+  // message, which must be processed directly by ::GetMessage() for the loop to
+  // actually quit. This test verifies that WM_QUIT exits works as expected even
+  // if it happens to immediately follow a kMsgHaveWork in the queue.
+
+  RunLoop run_loop;
+
+  // This application task will enter the subpump.
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, run_loop.QuitClosure()));
+
+  // This application task will post a native WM_QUIT.
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+
+  // The presence of this application task means that the pump will see a
+  // non-empty queue after processing the previous application task (which
+  // posted the WM_QUIT) and hence will repost a kMsgHaveWork message in the
+  // native event queue. Without the fix to https://crbug.com/888559, this would
+  // previously result in the subpump processing kMsgHaveWork and it stealing
+  // the WM_QUIT message, leaving the test hung in the subpump.
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+
+  // Test success is determined by not hanging in this Run() call.
+  run_loop.Run();
+}
+
+TEST_F(MessageLoopTest, RepostingWmQuitDoesntStarveUpcomingNativeLoop) {
+  MessageLoop message_loop(MessageLoop::TYPE_UI);
+
+  // This test ensures that application tasks are being processed by the native
+  // subpump despite the kMsgHaveWork event having already been consumed by the
+  // time the subpump is entered. This is subtly enforced by
+  // MessageLoopCurrent::ScopedNestableTaskAllower which will ScheduleWork()
+  // upon construction (and if it's absent, the MessageLoop shouldn't process
+  // application tasks so kMsgHaveWork is irrelevant).
+  // Note: This test also fails prior to the fix for https://crbug.com/888559
+  // (in fact, the last two tasks are sufficient as a regression test), probably
+  // because of a dangling kMsgHaveWork recreating the effect from
+  // MessageLoopTest.NativeMsgProcessingDoesntStealWmQuit.
+
+  RunLoop run_loop;
+
+  // This application task will post a native WM_QUIT which will be ignored
+  // by the main message pump.
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+
+  // Make sure the pump does a few extra cycles and processes (ignores) the
+  // WM_QUIT.
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+
+  // This application task will enter the subpump.
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, run_loop.QuitClosure()));
+
+  // Post an application task that will post WM_QUIT to the nested loop. The
+  // test will hang if the subpump doesn't process application tasks as it
+  // should.
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+
+  // Test success is determined by not hanging in this Run() call.
+  run_loop.Run();
+}
+
+// TODO(https://crbug.com/890016): Enable once multiple layers of nested loops
+// works.
+TEST_F(MessageLoopTest,
+       DISABLED_UnwindingMultipleSubPumpsDoesntStarveApplicationTasks) {
+  MessageLoop message_loop(MessageLoop::TYPE_UI);
+
+  // Regression test for https://crbug.com/890016.
+  // Tests that the subpump is still processing application tasks after
+  // unwinding from nested subpumps (i.e. that they didn't consume the last
+  // kMsgHaveWork).
+
+  RunLoop run_loop;
+
+  // Enter multiple levels of nested subpumps.
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, run_loop.QuitClosure()));
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, DoNothing::Once()));
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&SubPumpFunc, DoNothing::Once()));
+
+  // Quit two layers (with tasks in between to allow each quit to be handled
+  // before continuing -- ::PostQuitMessage() sets a bit, it's not a real queued
+  // message :
+  // https://blogs.msdn.microsoft.com/oldnewthing/20051104-33/?p=33453).
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+  message_loop.task_runner()->PostTask(FROM_HERE, DoNothing());
+
+  bool last_task_ran = false;
+  message_loop.task_runner()->PostTask(
+      FROM_HERE, BindOnce([](bool* to_set) { *to_set = true; },
+                          Unretained(&last_task_ran)));
+
+  message_loop.task_runner()->PostTask(FROM_HERE,
+                                       BindOnce(&::PostQuitMessage, 0));
+
+  run_loop.Run();
+
+  EXPECT_TRUE(last_task_ran);
+}
+
+namespace {
+
+// A side effect of this test is the generation a beep. Sorry.
+void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) {
+  MessageLoop loop(message_loop_type);
+
+  Thread worker("RecursiveDenial2_worker");
+  Thread::Options options;
+  options.message_loop_type = message_loop_type;
+  ASSERT_EQ(true, worker.StartWithOptions(options));
+  TaskList order;
+  win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
+  worker.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
+                          event.Get(), true, &order, false));
+  // Let the other thread execute.
+  WaitForSingleObject(event.Get(), INFINITE);
+  RunLoop().Run();
+
+  ASSERT_EQ(17u, order.Size());
+  EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+  EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
+  EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false));
+  EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false));
+  // When EndDialogFunc is processed, the window is already dismissed, hence no
+  // "end" entry.
+  EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true));
+  EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true));
+  EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false));
+  EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false));
+  EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false));
+  EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false));
+  EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false));
+}
+
+}  // namespace
+
 // This test occasionally hangs. See http://crbug.com/44567.
 TEST_F(MessageLoopTest, DISABLED_RecursiveDenial2) {
   RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT);
@@ -1796,10 +1873,51 @@
   RunTest_RecursiveDenial2(MessageLoop::TYPE_IO);
 }
 
+// A side effect of this test is the generation a beep. Sorry.  This test also
+// needs to process windows messages on the current thread.
 TEST_F(MessageLoopTest, RecursiveSupport2) {
-  // This test requires a UI loop.
-  RunTest_RecursiveSupport2(MessageLoop::TYPE_UI);
+  MessageLoop loop(MessageLoop::TYPE_UI);
+
+  Thread worker("RecursiveSupport2_worker");
+  Thread::Options options;
+  options.message_loop_type = MessageLoop::TYPE_UI;
+  ASSERT_EQ(true, worker.StartWithOptions(options));
+  TaskList order;
+  win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
+  worker.task_runner()->PostTask(
+      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
+                          event.Get(), false, &order, true));
+  // Let the other thread execute.
+  WaitForSingleObject(event.Get(), INFINITE);
+  RunLoop().Run();
+
+  ASSERT_EQ(18u, order.Size());
+  EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+  EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
+  // Note that this executes in the MessageBox modal loop.
+  EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, false));
+  EXPECT_EQ(order.Get(5), TaskItem(ENDDIALOG, 4, true));
+  EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, false));
+  EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false));
+  /* The order can subtly change here. The reason is that when RecursiveFunc(1)
+     is called in the main thread, if it is faster than getting to the
+     PostTask(FROM_HERE, BindOnce(&QuitFunc) execution, the order of task
+     execution can change. We don't care anyway that the order isn't correct.
+  EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true));
+  EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, false));
+  EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+  */
+  EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 3, false));
+  EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, true));
+  EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 1, false));
+  EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, true));
+  EXPECT_EQ(order.Get(17), TaskItem(RECURSIVE, 3, false));
 }
+
 #endif  // defined(OS_WIN)
 
 TEST_F(MessageLoopTest, TaskObserver) {
diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc
index bba71fa7..1d6748e0 100644
--- a/base/message_loop/message_pump_win.cc
+++ b/base/message_loop/message_pump_win.cc
@@ -237,9 +237,9 @@
       // current thread.
       MSG msg = {0};
       bool has_pending_sent_message =
-          (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+          (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
       if (has_pending_sent_message ||
-          PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
+          ::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
         return;
       }
 
@@ -341,12 +341,12 @@
   // case to ensure that the message loop peeks again instead of calling
   // MsgWaitForMultipleObjectsEx again.
   bool sent_messages_in_queue = false;
-  DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE);
+  DWORD queue_status = ::GetQueueStatus(QS_SENDMESSAGE);
   if (HIWORD(queue_status) & QS_SENDMESSAGE)
     sent_messages_in_queue = true;
 
   MSG msg;
-  if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE)
+  if (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE)
     return ProcessMessageHelper(msg);
 
   return sent_messages_in_queue;
@@ -356,17 +356,14 @@
   TRACE_EVENT1("base,toplevel", "MessagePumpForUI::ProcessMessageHelper",
                "message", msg.message);
   if (WM_QUIT == msg.message) {
+    // WM_QUIT is the standard way to exit a ::GetMessage() loop. Our
+    // MessageLoop has its own quit mechanism, so WM_QUIT should only terminate
+    // it if |enable_wm_quit_| is explicitly set (and is generally unexpected
+    // otherwise).
     if (enable_wm_quit_) {
-      // Repost the QUIT message so that it will be retrieved by the primary
-      // GetMessage() loop.
       state_->should_quit = true;
-      PostQuitMessage(static_cast<int>(msg.wParam));
       return false;
     }
-
-    // WM_QUIT is the standard way to exit a GetMessage() loop. Our MessageLoop
-    // has its own quit mechanism, so WM_QUIT is unexpected and should be
-    // ignored when |enable_wm_quit_| is set to false.
     UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem",
                               RECEIVED_WM_QUIT_ERROR, MESSAGE_LOOP_PROBLEM_MAX);
     return true;
@@ -378,8 +375,8 @@
 
   for (Observer& observer : observers_)
     observer.WillDispatchMSG(msg);
-  TranslateMessage(&msg);
-  DispatchMessage(&msg);
+  ::TranslateMessage(&msg);
+  ::DispatchMessage(&msg);
   for (Observer& observer : observers_)
     observer.DidDispatchMSG(msg);
 
@@ -398,7 +395,7 @@
 
   MSG msg;
   const bool have_message =
-      PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE;
+      ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE;
 
   // Expect no message or a message different than kMsgHaveWork.
   DCHECK(!have_message || kMsgHaveWork != msg.message ||
@@ -412,8 +409,29 @@
   if (!have_message)
     return false;
 
+  if (WM_QUIT == msg.message) {
+    // If we're in a nested ::GetMessage() loop then we must let that loop see
+    // the WM_QUIT in order for it to exit. If we're in DoRunLoop then the re-
+    // posted WM_QUIT will be either ignored, or handled, by
+    // ProcessMessageHelper() called directly from ProcessNextWindowsMessage().
+    ::PostQuitMessage(static_cast<int>(msg.wParam));
+    // Note: we *must not* ScheduleWork() here as WM_QUIT is a low-priority
+    // message on Windows (it is only returned by ::PeekMessage() when idle) :
+    // https://blogs.msdn.microsoft.com/oldnewthing/20051104-33/?p=33453. As
+    // such posting a kMsgHaveWork message via ScheduleWork() would cause an
+    // infinite loop (kMsgHaveWork message handled first means we end up here
+    // again and repost WM_QUIT+ScheduleWork() again, etc.). Not leaving a
+    // kMsgHaveWork message behind however is also problematic as unwinding
+    // multiple layers of nested ::GetMessage() loops can result in starving
+    // application tasks. TODO(https://crbug.com/890016) : Fix this.
+
+    // The return value is mostly irrelevant but return true like we would after
+    // processing a QuitClosure() task.
+    return true;
+  }
+
   // Guarantee we'll get another time slice in the case where we go into native
-  // windows code.   This ScheduleWork() may hurt performance a tiny bit when
+  // windows code. This ScheduleWork() may hurt performance a tiny bit when
   // tasks appear very infrequently, but when the event queue is busy, the
   // kMsgHaveWork events get (percentage wise) rarer and rarer.
   ScheduleWork();
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.cc b/base/trace_event/heap_profiler_allocation_context_tracker.cc
index 556719e..98f5d11 100644
--- a/base/trace_event/heap_profiler_allocation_context_tracker.cc
+++ b/base/trace_event/heap_profiler_allocation_context_tracker.cc
@@ -259,13 +259,7 @@
 
   ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames);
 
-  // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension
-  // (component name) in the heap profiler and not piggy back on the type name.
-  if (!task_contexts_.empty()) {
-    ctx->type_name = task_contexts_.back();
-  } else {
-    ctx->type_name = nullptr;
-  }
+  ctx->type_name = TaskContext();
 
   return true;
 }
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.h b/base/trace_event/heap_profiler_allocation_context_tracker.h
index da03b7f6..a117ea0 100644
--- a/base/trace_event/heap_profiler_allocation_context_tracker.h
+++ b/base/trace_event/heap_profiler_allocation_context_tracker.h
@@ -108,6 +108,11 @@
   void PushCurrentTaskContext(const char* context);
   void PopCurrentTaskContext(const char* context);
 
+  // Returns most recent task context added by ScopedTaskExecutionTracker.
+  const char* TaskContext() const {
+    return task_contexts_.empty() ? nullptr : task_contexts_.back();
+  }
+
   // Fills a snapshot of the current thread-local context. Doesn't fill and
   // returns false if allocations are being ignored.
   bool GetContextSnapshot(AllocationContext* snapshot);
diff --git a/build/android/gyp/jar.py b/build/android/gyp/jar.py
index fcfb7a5..3e85c3b 100755
--- a/build/android/gyp/jar.py
+++ b/build/android/gyp/jar.py
@@ -63,12 +63,12 @@
 
 def JarDirectory(classes_dir, jar_path, manifest_file=None, predicate=None,
                  provider_configurations=None, additional_files=None):
-  all_files = sorted(build_utils.FindInDirectory(classes_dir, '*'))
+  all_classes = sorted(build_utils.FindInDirectory(classes_dir, '*.class'))
   if predicate:
-    all_files = [
-        f for f in all_files if predicate(os.path.relpath(f, classes_dir))]
+    all_classes = [
+        f for f in all_classes if predicate(os.path.relpath(f, classes_dir))]
 
-  Jar(all_files, classes_dir, jar_path, manifest_file=manifest_file,
+  Jar(all_classes, classes_dir, jar_path, manifest_file=manifest_file,
       provider_configurations=provider_configurations,
       additional_files=additional_files)
 
diff --git a/build/android/gyp/javac.py b/build/android/gyp/javac.py
index e23ac3c..3a611b4 100755
--- a/build/android/gyp/javac.py
+++ b/build/android/gyp/javac.py
@@ -5,6 +5,7 @@
 # found in the LICENSE file.
 
 import distutils.spawn
+import itertools
 import optparse
 import os
 import shutil
@@ -194,16 +195,6 @@
   return new_args
 
 
-def _FixTempPathsInIncrementalMetadata(pdb_path, temp_dir):
-  # The .pdb records absolute paths. Fix up paths within /tmp (srcjars).
-  if os.path.exists(pdb_path):
-    # Although its a binary file, search/replace still seems to work fine.
-    with open(pdb_path) as fileobj:
-      pdb_data = fileobj.read()
-    with open(pdb_path, 'w') as fileobj:
-      fileobj.write(re.sub(r'/tmp/[^/]*', temp_dir, pdb_data))
-
-
 def _ParsePackageAndClassNames(java_file):
   package_name = ''
   class_names = []
@@ -235,7 +226,7 @@
                     (java_file, expected_path_suffix))
 
 
-def _CreateInfoFile(java_files, options, srcjar_files):
+def _CreateInfoFile(java_files, options, srcjar_files, javac_generated_sources):
   """Writes a .jar.info file.
 
   This maps fully qualified names for classes to either the java file that they
@@ -245,7 +236,7 @@
   .jar.info files of its transitive dependencies.
   """
   info_data = dict()
-  for java_file in java_files:
+  for java_file in itertools.chain(java_files, javac_generated_sources):
     package_name, class_names = _ParsePackageAndClassNames(java_file)
     for class_name in class_names:
       fully_qualified_name = '{}.{}'.format(package_name, class_name)
@@ -258,6 +249,7 @@
         'Chromium java files must only have one class: {}'.format(source))
     if options.chromium_code:
       _CheckPathMatchesClassName(java_file, package_name, class_names[0])
+
   with build_utils.AtomicOutput(options.jar_path + '.info') as f:
     jar_info_utils.WriteJarInfoFile(f.name, info_data, srcjar_files)
 
@@ -299,25 +291,29 @@
       # sources are stale by having their .class files be missing entirely
       # (by not extracting them).
       javac_cmd = _ConvertToJMakeArgs(javac_cmd, pdb_path)
-      if srcjars:
-        _FixTempPathsInIncrementalMetadata(pdb_path, temp_dir)
 
-    srcjar_files = dict()
+    generated_java_dir = options.generated_dir
+    # Incremental means not all files will be extracted, so don't bother
+    # clearing out stale generated files.
+    if not incremental:
+      shutil.rmtree(generated_java_dir, True)
+
+    srcjar_files = {}
     if srcjars:
-      java_dir = os.path.join(temp_dir, 'java')
-      os.makedirs(java_dir)
+      build_utils.MakeDirectory(generated_java_dir)
+      jar_srcs = []
       for srcjar in options.java_srcjars:
         if changed_paths:
-          changed_paths.update(os.path.join(java_dir, f)
+          changed_paths.update(os.path.join(generated_java_dir, f)
                                for f in changes.IterChangedSubpaths(srcjar))
         extracted_files = build_utils.ExtractAll(
-            srcjar, path=java_dir, pattern='*.java')
+            srcjar, path=generated_java_dir, pattern='*.java')
         for path in extracted_files:
           # We want the path inside the srcjar so the viewer can have a tree
           # structure.
           srcjar_files[path] = '{}/{}'.format(
-              srcjar, os.path.relpath(path, java_dir))
-      jar_srcs = build_utils.FindInDirectory(java_dir, '*.java')
+              srcjar, os.path.relpath(path, generated_java_dir))
+        jar_srcs.extend(extracted_files)
       java_files.extend(jar_srcs)
       if changed_paths:
         # Set the mtime of all sources to 0 since we use the absence of .class
@@ -325,8 +321,6 @@
         for path in jar_srcs:
           os.utime(path, (0, 0))
 
-    _CreateInfoFile(java_files, options, srcjar_files)
-
     if java_files:
       if changed_paths:
         changed_java_files = [p for p in java_files if p in changed_paths]
@@ -380,6 +374,18 @@
         os.unlink(pdb_path)
         attempt_build()
 
+    # Move any Annotation Processor-generated .java files into $out/gen
+    # so that codesearch can find them.
+    javac_generated_sources = []
+    for src_path in build_utils.FindInDirectory(classes_dir, '*.java'):
+      dst_path = os.path.join(
+          generated_java_dir, os.path.relpath(src_path, classes_dir))
+      build_utils.MakeDirectory(os.path.dirname(dst_path))
+      shutil.move(src_path, dst_path)
+      javac_generated_sources.append(dst_path)
+
+    _CreateInfoFile(java_files, options, srcjar_files, javac_generated_sources)
+
     if options.incremental and (not java_files or not incremental):
       # Make sure output exists.
       build_utils.Touch(pdb_path)
@@ -408,6 +414,10 @@
       default=[],
       help='List of srcjars to include in compilation.')
   parser.add_option(
+      '--generated-dir',
+      help='Subdirectory within target_gen_dir to place extracted srcjars and '
+           'annotation processor output for codesearch to find.')
+  parser.add_option(
       '--bootclasspath',
       action='append',
       default=[],
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index f7d99bd5..7e885e0 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2464,6 +2464,7 @@
   # the variables properly.
   #
   # Variables:
+  #  main_target_name: Used when extracting srcjars for codesearch.
   #  java_files: Optional list of Java source file paths.
   #  srcjar_deps: Optional list of .srcjar dependencies (not file paths).
   #    The corresponding source files they contain will be compiled too.
@@ -2578,8 +2579,12 @@
           rebase_path(invoker.javac_jar_path, root_build_dir)
       _rebased_java_srcjars = rebase_path(_java_srcjars, root_build_dir)
       _rebased_depfile = rebase_path(depfile, root_build_dir)
+      _rebased_generated_dir = rebase_path(
+              "$target_gen_dir/${invoker.main_target_name}/generated_java",
+              root_build_dir)
       args = [
         "--depfile=$_rebased_depfile",
+        "--generated-dir=$_rebased_generated_dir",
         "--jar-path=$_rebased_javac_jar_path",
         "--java-srcjars=$_rebased_java_srcjars",
         "--java-version=1.8",
@@ -3081,6 +3086,7 @@
                                  "provider_configurations",
                                  "javac_args",
                                ])
+        main_target_name = _main_target_name
         build_config = _build_config
         java_files = _java_files
         if (_java_files != []) {
diff --git a/build/config/chromeos/rules.gni b/build/config/chromeos/rules.gni
index 047cab3..d5dbde7 100644
--- a/build/config/chromeos/rules.gni
+++ b/build/config/chromeos/rules.gni
@@ -159,7 +159,7 @@
     testonly = true
     tast_tests = invoker.tast_tests
     generated_script = "$root_build_dir/bin/run_${target_name}"
-    runtime_deps_file = "$root_out_dir/${target_name}/.runtime_deps"
+    runtime_deps_file = "$root_out_dir/${target_name}.runtime_deps"
     deploy_chrome = true
     data_deps = [
       "//:chromiumos_preflight",  # Builds the browser.
diff --git a/chrome/android/java/res/color/menu_item_tint.xml b/chrome/android/java/res/color/menu_item_tint.xml
new file mode 100644
index 0000000..33b9103b
--- /dev/null
+++ b/chrome/android/java/res/color/menu_item_tint.xml
@@ -0,0 +1,10 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/modern_blue_600_alpha_38_opaque" android:state_enabled="false" />
+    <item android:color="@android:color/white" />
+</selector>
diff --git a/chrome/android/java/res_download/layout/download_manager_section_header.xml b/chrome/android/java/res_download/layout/download_manager_section_header.xml
index 038be20..a914005 100644
--- a/chrome/android/java/res_download/layout/download_manager_section_header.xml
+++ b/chrome/android/java/res_download/layout/download_manager_section_header.xml
@@ -4,13 +4,28 @@
      found in the LICENSE file.
 -->
 
-<TextView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingTop="@dimen/download_manager_section_title_padding_top"
-    android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
-    android:paddingStart="@dimen/list_item_default_margin"
-    android:gravity="start|center_vertical"
-    android:textAppearance="@style/BlackHint2"
-    android:textAlignment="viewStart" />
\ No newline at end of file
+    android:orientation="horizontal">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="@dimen/download_manager_section_title_padding_top"
+        android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
+        android:paddingStart="@dimen/list_item_default_margin"
+        android:textAppearance="@style/BlackHint2"
+        android:textAlignment="viewStart"/>
+
+    <include layout="@layout/list_menu_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/chrome/android/java/res_download/menu/download_manager_menu.xml b/chrome/android/java/res_download/menu/download_manager_menu.xml
index 38ad545..a610a6f7 100644
--- a/chrome/android/java/res_download/menu/download_manager_menu.xml
+++ b/chrome/android/java/res_download/menu/download_manager_menu.xml
@@ -59,11 +59,13 @@
             android:id="@+id/selection_mode_share_menu_id"
             android:icon="@drawable/ic_share_white_24dp"
             android:title="@string/share"
-            app:showAsAction="ifRoom" />
+            app:showAsAction="ifRoom"
+            app:iconTint="@color/menu_item_tint" />
         <item
             android:id="@+id/selection_mode_delete_menu_id"
             android:icon="@drawable/ic_delete_white_24dp"
             android:title="@string/delete"
-            app:showAsAction="ifRoom" />
+            app:showAsAction="ifRoom"
+            app:iconTint="@color/menu_item_tint" />
      </group>
 </menu>
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 80774529..c7ebb10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -247,8 +247,10 @@
             "OfflinePagesDescriptivePendingStatus";
     public static final String OFFLINE_PAGES_LIVE_PAGE_SHARING = "OfflinePagesLivePageSharing";
     public static final String OFFLINE_PAGES_PREFETCHING = "OfflinePagesPrefetching";
-    public static final String OMNIBOX_HIDE_SCHEME_DOMAIN_IN_STEADY_STATE =
-            "OmniboxUIExperimentHideSteadyStateUrlSchemeAndSubdomains";
+    public static final String OMNIBOX_HIDE_SCHEME_IN_STEADY_STATE =
+            "OmniboxUIExperimentHideSteadyStateUrlScheme";
+    public static final String OMNIBOX_HIDE_TRIVIAL_SUBDOMAINS_IN_STEADY_STATE =
+            "OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains";
     public static final String OMNIBOX_SPARE_RENDERER = "OmniboxSpareRenderer";
     public static final String OMNIBOX_VOICE_SEARCH_ALWAYS_VISIBLE =
             "OmniboxVoiceSearchAlwaysVisible";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
index bb315bbe..18a12a24 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
@@ -126,8 +126,8 @@
             if (chip.enabled) visibleChips.add(chip);
         }
 
-        // Remove the none chip if no other chip is visible.
-        if (visibleChips.size() == 1) {
+        // If there is only one chip type or only NONE chips, remove the entire row of chips.
+        if (visibleChips.size() <= 2) {
             assert visibleChips.get(0).id == FilterType.NONE;
             visibleChips.clear();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
index f9a374a6..f73fe33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
@@ -6,6 +6,7 @@
 
 import android.content.Intent;
 import android.os.Handler;
+import android.support.annotation.Nullable;
 import android.support.v4.util.Pair;
 
 import org.chromium.base.CollectionUtil;
@@ -150,10 +151,11 @@
                 ListProperties.CALLBACK_RESUME, item -> mProvider.resumeDownload(item, true));
         mModel.getProperties().set(ListProperties.CALLBACK_CANCEL, mProvider::cancelDownload);
         mModel.getProperties().set(ListProperties.CALLBACK_SHARE, this ::onShareItem);
+        mModel.getProperties().set(ListProperties.CALLBACK_SHARE_ALL, this ::onShareItems);
         mModel.getProperties().set(ListProperties.CALLBACK_REMOVE, this ::onDeleteItem);
+        mModel.getProperties().set(ListProperties.CALLBACK_REMOVE_ALL, this ::onDeleteItems);
         mModel.getProperties().set(ListProperties.PROVIDER_VISUALS, this ::getVisuals);
-        mModel.getProperties().set(
-                ListProperties.CALLBACK_SELECTION, selectionDelegate::toggleSelectionForItem);
+        mModel.getProperties().set(ListProperties.CALLBACK_SELECTION, this ::onSelection);
     }
 
     /** Tears down this mediator. */
@@ -227,6 +229,14 @@
         return mTypeFilter;
     }
 
+    private void onSelection(@Nullable ListItem item) {
+        if (item == null) {
+            mSelectionDelegate.setSelectionModeEnabledForZeroItems(true);
+        } else {
+            mSelectionDelegate.toggleSelectionForItem(item);
+        }
+    }
+
     private void onDeleteItem(OfflineItem item) {
         onDeleteItems(CollectionUtil.newArrayList(item));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
index b032eea..dca0e42 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
@@ -146,6 +146,7 @@
                 if (!mHideSectionHeaders && !mHideAllHeaders) {
                     SectionHeaderListItem sectionHeaderItem =
                             new SectionHeaderListItem(filter, date.getTime());
+                    sectionHeaderItem.items = new ArrayList<>(section.items.values());
                     sectionHeaderItem.isFirstSectionOfDay = sectionIndex == 0;
                     listItems.add(sectionHeaderItem);
                 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
index 40015a1d..e79c150 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
@@ -11,6 +11,7 @@
 
 import java.util.Calendar;
 import java.util.Date;
+import java.util.List;
 
 /** An abstract class that represents a variety of possible list items to show in downloads home. */
 public abstract class ListItem {
@@ -74,6 +75,7 @@
     public static class SectionHeaderListItem extends DateListItem {
         public final int filter;
         public boolean isFirstSectionOfDay;
+        public List<OfflineItem> items;
 
         /**
          * Creates a {@link SectionHeaderListItem} instance for a given {@code filter} and
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
index c7de803..5aefcb4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
@@ -12,6 +12,8 @@
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
 import org.chromium.components.offline_items_collection.VisualsCallback;
 
+import java.util.List;
+
 /**
  * The properties required to build a {@link ListItem} which contain two types of properties for the
  * download manager: (1) A set of properties that act directly on the list view itself. (2) A set of
@@ -54,10 +56,18 @@
     WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_SHARE =
             new WritableObjectPropertyKey<>();
 
+    /** The callback for when a UI action should share all selected {@link OfflineItem}s. */
+    WritableObjectPropertyKey < Callback < List<OfflineItem>>> CALLBACK_SHARE_ALL =
+            new WritableObjectPropertyKey<>();
+
     /** The callback for when a UI action should remove a {@link OfflineItem}. */
     WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_REMOVE =
             new WritableObjectPropertyKey<>();
 
+    /** The callback for when a UI action should remove all selected {@link OfflineItem}s. */
+    WritableObjectPropertyKey < Callback < List<OfflineItem>>> CALLBACK_REMOVE_ALL =
+            new WritableObjectPropertyKey<>();
+
     /** The provider to retrieve expensive assets for a {@link OfflineItem}. */
     WritableObjectPropertyKey<VisualsProvider> PROVIDER_VISUALS = new WritableObjectPropertyKey<>();
 
@@ -69,6 +79,7 @@
     WritableBooleanPropertyKey SELECTION_MODE_ACTIVE = new WritableBooleanPropertyKey();
 
     PropertyKey[] ALL_KEYS = new PropertyKey[] {ENABLE_ITEM_ANIMATIONS, CALLBACK_OPEN,
-            CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_REMOVE,
-            PROVIDER_VISUALS, CALLBACK_SELECTION, SELECTION_MODE_ACTIVE};
-}
\ No newline at end of file
+            CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_SHARE_ALL,
+            CALLBACK_REMOVE, CALLBACK_REMOVE_ALL, PROVIDER_VISUALS, CALLBACK_SELECTION,
+            SELECTION_MODE_ACTIVE};
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
index ab664e2..2c90e23 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -11,15 +12,28 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.browser.download.home.list.ListItem;
+import org.chromium.chrome.browser.download.home.list.ListProperties;
 import org.chromium.chrome.browser.download.home.list.ListUtils;
 import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.widget.ListMenuButton;
 import org.chromium.chrome.download.R;
 import org.chromium.components.offline_items_collection.OfflineItemFilter;
 
 /**
  * A {@link ViewHolder} specifically meant to display a section header.
  */
-public class SectionTitleViewHolder extends ListItemViewHolder {
+public class SectionTitleViewHolder extends ListItemViewHolder implements ListMenuButton.Delegate {
+    private final TextView mTitle;
+    private final ListMenuButton mMore;
+
+    private Runnable mShareCallback;
+    private Runnable mDeleteCallback;
+    private Runnable mShareAllCallback;
+    private Runnable mDeleteAllCallback;
+    private Runnable mSelectCallback;
+
+    private boolean mListHasMultipleItems;
+
     /** Create a new {@link SectionTitleViewHolder} instance. */
     public static SectionTitleViewHolder create(ViewGroup parent) {
         View view = LayoutInflater.from(parent.getContext())
@@ -29,13 +43,16 @@
 
     private SectionTitleViewHolder(View view) {
         super(view);
+        mTitle = (TextView) view.findViewById(R.id.title);
+        mMore = (ListMenuButton) view.findViewById(R.id.more);
+        if (mMore != null) mMore.setDelegate(this);
     }
 
     // ListItemViewHolder implementation.
     @Override
     public void bind(PropertyModel properties, ListItem item) {
         ListItem.SectionHeaderListItem sectionItem = (ListItem.SectionHeaderListItem) item;
-        ((TextView) itemView).setText(ListUtils.getTextForSection(sectionItem.filter));
+        mTitle.setText(ListUtils.getTextForSection(sectionItem.filter));
 
         boolean isPhoto = sectionItem.filter == OfflineItemFilter.FILTER_IMAGE;
         Resources resources = itemView.getContext().getResources();
@@ -52,7 +69,62 @@
                     R.dimen.download_manager_section_title_padding_top_condensed);
         }
 
-        itemView.setPadding(
-                itemView.getPaddingLeft(), paddingTop, itemView.getPaddingRight(), paddingBottom);
+        mTitle.setPadding(
+                mTitle.getPaddingLeft(), paddingTop, mTitle.getPaddingRight(), paddingBottom);
+
+        if (mMore != null) mMore.setVisibility(isPhoto ? View.VISIBLE : View.GONE);
+
+        mListHasMultipleItems = sectionItem.items.size() > 1;
+
+        if (isPhoto && mMore != null) {
+            assert sectionItem.items.size() > 0;
+            mShareCallback = ()
+                    -> properties.get(ListProperties.CALLBACK_SHARE)
+                               .onResult(sectionItem.items.get(0));
+            mDeleteCallback = ()
+                    -> properties.get(ListProperties.CALLBACK_REMOVE)
+                               .onResult(sectionItem.items.get(0));
+
+            mShareAllCallback = ()
+                    -> properties.get(ListProperties.CALLBACK_SHARE_ALL)
+                               .onResult(sectionItem.items);
+            mDeleteAllCallback = ()
+                    -> properties.get(ListProperties.CALLBACK_REMOVE_ALL)
+                               .onResult(sectionItem.items);
+            mSelectCallback =
+                    () -> properties.get(ListProperties.CALLBACK_SELECTION).onResult(null);
+
+            mMore.setClickable(!properties.get(ListProperties.SELECTION_MODE_ACTIVE));
+        }
+    }
+
+    @Override
+    public ListMenuButton.Item[] getItems() {
+        Context context = itemView.getContext();
+        if (mListHasMultipleItems) {
+            return new ListMenuButton.Item[] {
+                    new ListMenuButton.Item(context, R.string.select, true),
+                    new ListMenuButton.Item(context, R.string.share_group, true),
+                    new ListMenuButton.Item(context, R.string.delete_group, true)};
+        } else {
+            return new ListMenuButton.Item[] {
+                    new ListMenuButton.Item(context, R.string.share, true),
+                    new ListMenuButton.Item(context, R.string.delete, true)};
+        }
+    }
+
+    @Override
+    public void onItemSelected(ListMenuButton.Item item) {
+        if (item.getTextId() == R.string.select) {
+            mSelectCallback.run();
+        } else if (item.getTextId() == R.string.share) {
+            mShareCallback.run();
+        } else if (item.getTextId() == R.string.delete) {
+            mDeleteCallback.run();
+        } else if (item.getTextId() == R.string.share_group) {
+            mShareAllCallback.run();
+        } else if (item.getTextId() == R.string.delete_group) {
+            mDeleteAllCallback.run();
+        }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
index e64097ed..e7b2519 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
@@ -115,6 +115,7 @@
     private static final int CONNECTIVITY_CHECK_MAX_DELAY_MS = 2 * 60 * 1000;
 
     private static boolean sSkipSystemCheckForTesting;
+    private static boolean sSkipHttpProbeForTesting;
     private static String sDefaultProbeUrl = DEFAULT_PROBE_URL;
     private static String sFallbackProbeUrl = FALLBACK_PROBE_URL;
     private static String sProbeMethod = PROBE_METHOD;
@@ -174,6 +175,11 @@
             return;
         }
 
+        if (sSkipHttpProbeForTesting) {
+            updateConnectionState(ConnectionState.VALIDATED);
+            return;
+        }
+
         // Do manual check via sending HTTP probes to server.
         mConnectivityCheckDelayMs = 0;
         mConnectivityCheckStartTimeMs = SystemClock.elapsedRealtime();
@@ -412,6 +418,11 @@
     }
 
     @VisibleForTesting
+    static void skipHttpProbeForTesting() {
+        sSkipHttpProbeForTesting = true;
+    }
+
+    @VisibleForTesting
     static void overrideDefaultProbeUrlForTesting(String url) {
         sDefaultProbeUrl = url;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
index e4b58482..2b6419e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
@@ -153,11 +153,6 @@
                 protected void onPostExecute(Void result) {
                     String renamedAccount = getNewSignedInAccountName();
                     if (renamedAccount == null) {
-                        // SigninManager.signOut() uses the same code path as a user-triggered
-                        // signout, which can be prohibited in some cases (e.g. child accounts).
-                        // Here we have to sign out though to ensure account consistency,
-                        // so override the flag.
-                        mSigninManager.prohibitSignout(false);
                         mSigninManager.signOut(SignoutReason.ACCOUNT_REMOVED_FROM_DEVICE);
                     } else {
                         validateAccountSettings(true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
index d32619b0..1f8cc50 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
@@ -568,10 +568,6 @@
         nativeClearLastSignedInUser(mNativeSigninManagerAndroid);
     }
 
-    public void prohibitSignout(boolean prohibitSignout) {
-        nativeProhibitSignout(mNativeSigninManagerAndroid, prohibitSignout);
-    }
-
     /**
      * Aborts the current sign in.
      *
@@ -751,6 +747,4 @@
     native void nativeLogInSignedInUser(long nativeSigninManagerAndroid);
     @VisibleForTesting
     native boolean nativeIsSignedInOnNative(long nativeSigninManagerAndroid);
-    @VisibleForTesting
-    native void nativeProhibitSignout(long nativeSigninManagerAndroid, boolean prohibitSignout);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarModel.java
index df7ac3e..935ea8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarModel.java
@@ -185,12 +185,9 @@
             return buildUrlBarData(url, searchTerms);
         }
 
-        if (ChromeFeatureList.isEnabled(
-                    ChromeFeatureList.OMNIBOX_HIDE_SCHEME_DOMAIN_IN_STEADY_STATE)) {
-            String urlForDisplay = getUrlForDisplay();
-            if (!urlForDisplay.equals(formattedUrl)) {
-                return buildUrlBarData(url, urlForDisplay, formattedUrl);
-            }
+        String urlForDisplay = getUrlForDisplay();
+        if (!urlForDisplay.equals(formattedUrl)) {
+            return buildUrlBarData(url, urlForDisplay, formattedUrl);
         }
 
         return buildUrlBarData(url, formattedUrl);
@@ -402,7 +399,9 @@
             list = AppCompatResources.getColorStateList(mContext, R.color.light_mode_tint);
         } else if (!hasTab() || isUsingBrandColor()
                 || ChromeFeatureList.isEnabled(
-                           ChromeFeatureList.OMNIBOX_HIDE_SCHEME_DOMAIN_IN_STEADY_STATE)) {
+                           ChromeFeatureList.OMNIBOX_HIDE_SCHEME_IN_STEADY_STATE)
+                || ChromeFeatureList.isEnabled(
+                           ChromeFeatureList.OMNIBOX_HIDE_TRIVIAL_SUBDOMAINS_IN_STEADY_STATE)) {
             // For theme colors which are not dark and are also not
             // light enough to warrant an opaque URL bar, use dark
             // icons.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/NumberRollView.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/NumberRollView.java
index a7ba9fb..bb579d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/NumberRollView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/NumberRollView.java
@@ -28,6 +28,7 @@
     private float mNumber;
     private Animator mLastRollAnimator;
     private int mStringId;
+    private int mStringIdForZero;
 
     /**
      * A Property wrapper around the <code>number</code> functionality handled by the
@@ -92,6 +93,14 @@
     }
 
     /**
+     * @param stringIdForZero The id of the string to use for the description when the number is
+     * zero.
+     */
+    public void setStringForZero(int stringIdForZero) {
+        mStringIdForZero = stringIdForZero;
+    }
+
+    /**
      * Gets the current number roll position.
      */
     private float getNumberRoll() {
@@ -109,7 +118,9 @@
         NumberFormat numberFormatter = NumberFormat.getIntegerInstance();
         String newString;
         if (mStringId != 0) {
-            newString = getResources().getQuantityString(mStringId, upNumber, upNumber);
+            newString = upNumber == 0 && mStringIdForZero != 0
+                    ? getResources().getString(mStringIdForZero)
+                    : getResources().getQuantityString(mStringId, upNumber, upNumber);
         } else {
             newString = numberFormatter.format(upNumber);
         }
@@ -118,7 +129,9 @@
         }
 
         if (mStringId != 0) {
-            newString = getResources().getQuantityString(mStringId, downNumber, downNumber);
+            newString = downNumber == 0 && mStringIdForZero != 0
+                    ? getResources().getString(mStringIdForZero)
+                    : getResources().getQuantityString(mStringId, downNumber, downNumber);
         } else {
             newString = numberFormatter.format(downNumber);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
index 0e9631d..4fd14ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java
@@ -312,6 +312,7 @@
         LayoutInflater.from(getContext()).inflate(R.layout.number_roll_view, this);
         mNumberRollView = (NumberRollView) findViewById(R.id.selection_mode_number);
         mNumberRollView.setString(R.plurals.selected_items);
+        mNumberRollView.setStringForZero(R.string.select_items);
     }
 
     @Override
@@ -598,6 +599,7 @@
     protected void showSelectionView(List<E> selectedItems, boolean wasSelectionEnabled) {
         getMenu().setGroupVisible(mNormalGroupResId, false);
         getMenu().setGroupVisible(mSelectedGroupResId, true);
+        getMenu().setGroupEnabled(mSelectedGroupResId, !selectedItems.isEmpty());
         if (mHasSearchView) mSearchView.setVisibility(View.GONE);
 
         setNavigationButton(NAVIGATION_BUTTON_SELECTION_BACK);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectionDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectionDelegate.java
index 267cd9d..8fd5e23 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectionDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectionDelegate.java
@@ -19,6 +19,10 @@
     // True if the SelectionDelegate should only support a single item being selected at a time.
     private boolean mIsSingleSelection;
 
+    // If true, we can enter the selection mode even though zero items are currently selected.
+    // When the number of items drops to zero again, this will automatically turn off.
+    private boolean mEnableSelectionForZeroItems;
+
     /**
      * Observer interface to be notified of selection changes.
      * @param <E> The type of the selectable items this delegate interacts with.
@@ -43,6 +47,15 @@
     }
 
     /**
+     * Enables selection mode even though there are zero items selected.
+     * @param enable True, for entering selection mode. False, to turn-off this mode.
+     */
+    public void setSelectionModeEnabledForZeroItems(boolean enable) {
+        mEnableSelectionForZeroItems = enable;
+        notifyObservers();
+    }
+
+    /**
      * Toggles the selected state for the given item.
      * @param item The item to toggle.
      * @return Whether the item is selected.
@@ -55,6 +68,8 @@
             mSelectedItems.add(item);
         }
 
+        if (mSelectedItems.isEmpty()) mEnableSelectionForZeroItems = false;
+
         notifyObservers();
 
         return isItemSelected(item);
@@ -82,13 +97,14 @@
      * @return Whether any items are selected.
      */
     public boolean isSelectionEnabled() {
-        return !mSelectedItems.isEmpty();
+        return !mSelectedItems.isEmpty() || mEnableSelectionForZeroItems;
     }
 
    /**
     * Clears all selected items.
     */
     public void clearSelection() {
+        mEnableSelectionForZeroItems = false;
         mSelectedItems.clear();
         notifyObservers();
     }
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index c266b7a..ee8a536 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -149,6 +149,9 @@
       <message name="IDS_DELETE" desc="Label for a delete button. Used in multiple contexts. [CHAR-LIMIT=20]">
         Delete
       </message>
+      <message name="IDS_DELETE_GROUP" desc="Label for button that deletes a group of items. [CHAR-LIMIT=20]">
+        Delete group
+      </message>
       <message name="IDS_REMOVE" desc="Label for a button to remove an item (e.g. a bookmark) from a list. [CHAR-LIMIT=20]">
         Remove
       </message>
@@ -215,6 +218,9 @@
       <message name="IDS_SHARE" desc="Content description for a button to share item(s). [CHAR-LIMIT=20]">
         Share
       </message>
+      <message name="IDS_SHARE_GROUP" desc="Content description for a button to share a group item(s). [CHAR-LIMIT=20]">
+        Share group
+      </message>
       <message name="IDS_SEARCH" desc="The label for a search button.">
         Search
       </message>
@@ -3276,7 +3282,9 @@
           =1 {1 selected}
           other {# selected}}
       </message>
-
+      <message name="IDS_SELECT_ITEMS" desc="Label shown on toolbar asking user to select items from the list.">
+        Select items
+      </message>
       <message name="IDS_MEDIA_NOTIFICATION_LINK_TEXT" desc="Url of the current tab. The notification will display this text for the user to identify the tab to return to.">
         Touch to return to <ph name="URL_OF_THE_CURRENT_TAB">%1$s<ex>https://apprtc.appspot.com</ex></ph>
       </message>
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
index ba4c907..d33cded 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorControllerTest.java
@@ -68,6 +68,7 @@
     @Before
     public void setUp() throws Exception {
         ConnectivityDetector.skipSystemCheckForTesting();
+        ConnectivityDetector.skipHttpProbeForTesting();
         mActivityTestRule.startMainActivityOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(() -> {
             if (!NetworkChangeNotifier.isInitialized()) {
@@ -75,6 +76,9 @@
             }
             NetworkChangeNotifier.forceConnectivityState(true);
             OfflineIndicatorController.initialize();
+            OfflineIndicatorController.getInstance()
+                    .getConnectivityDetectorForTesting()
+                    .updateConnectionState(ConnectivityDetector.ConnectionState.VALIDATED);
         });
     }
 
@@ -245,7 +249,7 @@
     public void testDoNotShowOfflineIndicatorOnPageLoadingWhenOffline() throws Exception {
         EmbeddedTestServer testServer =
                 EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
-        String testUrl = testServer.getURL(TEST_PAGE);
+        String testUrl = testServer.getURL("/slow?1");
 
         // Load a page without waiting it to finish.
         loadPageWithoutWaiting(testUrl, null);
@@ -303,6 +307,8 @@
     private void waitForPageLoaded(String pageUrl) throws Exception {
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         ChromeTabUtils.waitForTabPageLoaded(tab, pageUrl);
+        ChromeTabUtils.waitForInteractable(tab);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     private void savePage(String url) throws InterruptedException {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarModelTest.java
index 8185022..6625caa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarModelTest.java
@@ -20,7 +20,6 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.UrlConstants;
@@ -29,8 +28,6 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 
 /**
  * Tests for ToolbarModel.
@@ -68,8 +65,7 @@
 
     @Test
     @SmallTest
-    @DisableFeatures(ChromeFeatureList.OMNIBOX_HIDE_SCHEME_DOMAIN_IN_STEADY_STATE)
-    public void testDisplayAndEditText_DisabledExperiment() throws Exception {
+    public void testDisplayAndEditText() throws Exception {
         ThreadUtils.runOnUiThreadBlocking(() -> {
             TestToolbarModel model = new TestToolbarModel();
             model.mUrl = UrlConstants.NTP_URL;
@@ -81,25 +77,9 @@
             assertDisplayAndEditText(model, "chrome://about", "chrome://about");
 
             model.mUrl = "https://www.foo.com";
-            model.mDisplayUrl = "foo.com";
+            model.mDisplayUrl = "https://foo.com";
             model.mFullUrl = "https://foo.com";
             assertDisplayAndEditText(model, "https://foo.com", "https://foo.com");
-        });
-    }
-
-    @Test
-    @SmallTest
-    @EnableFeatures(ChromeFeatureList.OMNIBOX_HIDE_SCHEME_DOMAIN_IN_STEADY_STATE)
-    public void testDisplayAndEditText_EnabledExperiment() throws Exception {
-        ThreadUtils.runOnUiThreadBlocking(() -> {
-            TestToolbarModel model = new TestToolbarModel();
-            model.mUrl = UrlConstants.NTP_URL;
-            assertDisplayAndEditText(model, "", null);
-
-            model.mUrl = "chrome://about";
-            model.mDisplayUrl = "chrome://about";
-            model.mFullUrl = "chrome://about";
-            assertDisplayAndEditText(model, "chrome://about", "chrome://about");
 
             model.mUrl = "https://www.foo.com";
             model.mDisplayUrl = "foo.com";
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 296c62f..25edda30 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -142,14 +142,17 @@
     Try again
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_PASSWORD_PAGE_HEADER" desc="Header for the first page in the multi-device setup flow; the page displays a field in which the user must enter their password to continue.">
-    Set up Better Together
-  </message>
-  <message name="IDS_MULTIDEVICE_SETUP_PASSWORD_PAGE_ENTER_PASSWORD_LABEL" desc="Message displayed in the multi-device setup flow which asks the user to enter their password.">
     Enter your password
   </message>
+  <message name="IDS_MULTIDEVICE_SETUP_PASSWORD_PAGE_ENTER_PASSWORD_LABEL" desc="Default text in the field for user to enter their password.">
+    Password
+  </message>
   <message name="IDS_MULTIDEVICE_SETUP_PASSWORD_PAGE_WRONG_PASSWORD_LABEL" desc="Message displayed in the multi-device setup flow when the user has entered an incorrect password.">
     Wrong password
   </message>
+  <message name="IDS_MULTIDEVICE_SETUP_BACK_LABEL" desc="Label for button to navigate back in MultiDevice setup workflow.">
+    Back
+  </message>
   <message name="IDS_MULTIDEVICE_SETUP_SETUP_FAILED_PAGE_HEADER" desc="Header for failed setup page.">
     Better Together setup couldn't complete
   </message>
@@ -160,13 +163,13 @@
     All set!
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_SETUP_SUCCEEDED_PAGE_MESSAGE" desc="Message for successful setup page.">
-  Go to <ph name="LINK_BEGIN">&lt;a id="settings-link"&gt;</ph>Settings<ph name="LINK_END">&lt;/a&gt;</ph> to see your options for Better Together
+    Go to <ph name="LINK_BEGIN">&lt;a id="settings-link"&gt;</ph>Settings<ph name="LINK_END">&lt;/a&gt;</ph> to see options for your connected phone
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_HEADER" desc="Header for welcome page.">
-    Better Together
+    Connect to your phone
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_MESSAGE" desc="Summary to tell user that they can share their Android's capabilities with their Chromebook; includes details on specific features that can be used and a link to an information page">
-    Connect your Android phone and Chromebook to text from your computer, share your internet connection, and unlock your Chromebook with your phone.<ph name="FOOTNOTE_POINTER">$1<ex>*</ex></ph> <ph name="LINK_BEGIN">&lt;a href="$2<ex>https://support.google.com/chromebook/?p=multidevice</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
+    Your Chromebook and Android phone work better together. Connect them so you can text from your computer, share your internet connection, and unlock your Chromebook with your phone.<ph name="FOOTNOTE_POINTER">$1<ex>*</ex></ph> <ph name="LINK_BEGIN">&lt;a href="$2<ex>https://support.google.com/chromebook/?p=multidevice</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_FOOTNOTE" desc="Footnote for multi-device feature setup page, which indicates that the exact set of features available to users differs according to the device model used.">
     <ph name="FOOTNOTE_POINTER">$1<ex>*</ex></ph>Features vary by device
@@ -177,8 +180,8 @@
   <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_MULTIPLE_DEVICE_HEADER" desc="Label appearing over a list of Android phones which this Chromebook can connect to.">
     Select a device
   </message>
-  <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_FEATURE_LIST_HEADER" desc="Header to introduce a list of things Better Together can do.">
-    Turning on Better Together means that it can:
+  <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_FEATURE_LIST_HEADER" desc="Header to introduce a list of the features that the user is agreeing to in the multi-device setup.">
+    When you connect your devices, you agree that your Chromebook can:
   </message>
   <message name="IDS_MULTIDEVICE_SETUP_START_SETUP_PAGE_AWM_DESCRIPTION" desc="Description of a feature that shows text messages received by the user's phone as notification on their Chromebook and a link to an information page.">
     Send you notifications and default to remembering this computer for Messages. <ph name="LINK_BEGIN">&lt;a href="$1<ex>https://support.google.com/chromebook/?p=messages</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index cab8bd1..078f4f2a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4313,11 +4313,8 @@
         </message>
       </if>
 
-      <message name="IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE_MANY_ACCOUNTS" desc="The title of the account chooser dialog/infobar for more than one account.">
-        Choose your account saved with <ph name="PASSWORD_MANAGER_BRAND">$1<ex>Google Chrome</ex></ph> to sign in
-      </message>
-      <message name="IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE_ONE_ACCOUNT" desc="The title of the account chooser dialog/infobar for one account.">
-        Sign in with your account saved with <ph name="PASSWORD_MANAGER_BRAND">$1<ex>Google Chrome</ex></ph>
+      <message name="IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE" desc="The title of the account chooser dialog.">
+        Sign in as
       </message>
       <message name="IDS_PASSWORD_MANAGER_CONFIRM_SAVED_TITLE" desc="The title text used in the passwords bubble when the user has saved a password.">
         Password saved
@@ -4332,7 +4329,7 @@
         Save password?
       </message>
       <message name="IDS_SAVE_ACCOUNT" desc="The title of the save password bubble when a federated credential can be saved.">
-        Do you want <ph name="PASSWORD_MANAGER_BRAND">$1<ex>Google Chrome</ex></ph> to save your account for this site?
+        Save username?
       </message>
       <message name="IDS_UPDATE_PASSWORD" desc="The title of the save password bubble when a password can be updated.">
         Update password?
@@ -7963,9 +7960,6 @@
       <message name="IDS_AUTO_SIGNIN_FIRST_RUN_TITLE_LOCAL_DEVICE" desc="The title of the dialog during the autosign-in first run experience for the Chrome signed out users.">
         Sign in easily
       </message>
-      <message name="IDS_AUTO_SIGNIN_FIRST_RUN_SMART_LOCK_TEXT" desc="The text of the dialog during the autosign-in first run experience for the Chrome syncing users.">
-        <ph name="PASSWORD_MANAGER_BRAND">$1<ex>Google Smart Lock</ex></ph> automatically signs you in to eligible sites and apps with passwords you saved.
-      </message>
       <message name="IDS_AUTO_SIGNIN_FIRST_RUN_TEXT" desc="The text of the dialog during the autosign-in first run experience for the Chrome signed out users.">
         <ph name="PASSWORD_MANAGER_BRAND">$1<ex>Google Chrome</ex></ph> automatically signs you in to eligible sites with passwords you saved.
       </message>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 742c10d..a1cdf36a 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -4237,10 +4237,10 @@
       Connected devices
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_SETUP_ITEM_HEADING" desc="Heading for settings item that allows the user to connect their phone to their Chromebook.">
-      Better Together
+      Android phone
     </message>
-    <message name="IDS_SETTINGS_MULTIDEVICE_SETUP_SUMMARY" desc="Description of the idea that the user can get a better experience on their Chromebook by connecting it to their phone.">
-      Your Chromebook works even better with your phone. <ph name="LINK_BEGIN">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
+    <message name="IDS_SETTINGS_MULTIDEVICE_SETUP_SUMMARY" desc="Tells the user to connect their Chromebook to their phone.">
+      Connect your Chromebook with your phone. <ph name="LINK_BEGIN">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_NO_ELIGIBLE_HOSTS" desc="Tells the user that there is no phone with their account on it that can connect to their Chromebook.">
       No eligible devices. <ph name="LINK_BEGIN">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
@@ -4255,10 +4255,10 @@
       Verify
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_ENABLED" desc="Text to tell user multidevice features are turned on">
-      Better Together is on
+      Enabled
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_DISABLED" desc="Text to tell user multidevice features are turned off">
-      Better Together is off
+      Disabled
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_SMART_LOCK_SUMMARY" desc="Description of for the 'Smart Lock' setting. This feature automatically unlocks the user's Chromebook if their phone is nearby and unlocked.">
       Unlock your Chromebook with your phone. <ph name="LINK_BEGIN">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
@@ -4266,23 +4266,23 @@
     <message name="IDS_SETTINGS_MULTIDEVICE_INSTANT_TETHERING" desc="Name of a feature. This feature automatically offers the user to tether to their phone if their Chromebook is offline and their phone supports tethering.">
       Instant Tethering
     </message>
+    <message name="IDS_SETTINGS_MULTIDEVICE_INSTANT_TETHERING_SUMMARY" desc="Description of for the 'Instant Tethering' setting. This feature automatically offers the user to tether to their phone if their Chromebook is offline and their phone supports tethering.">
+      Connect to the internet through your phone
+    </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_ANDROID_MESSAGES" desc="Name of a feature. This feature lets the user read and reply to text messages from their Chromebook. New text messages will appear as notifications.">
       Messages
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_ANDROID_MESSAGES_SUMMARY" desc="Description of for the 'Android Messages' setting. This feature lets the user read and reply to text messages from their Chromebook. New text messages will appear as notifications.">
       Send and receive text messages from your Chromebook. <ph name="LINK_BEGIN">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
     </message>
-    <message name="IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE" desc="Header for a clickable menu item that makes the Chromebook forget the user's phone. This means they will no longer have access to multidevice features.">
-      Forget this device
+    <message name="IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE" desc="Header to tell the user an action will make their Chromebook forget their phone. This means they will no longer have access to multidevice features.">
+      Forget phone
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE_EXPLANATION" desc="Explanation on a clickable menu item that makes the Chromebook forget the user's phone. It tells the user that the menu item will cause their phone to stop acting as a partner for their Chromebook for multidevice features.">
-      Remove from Better Together
-    </message>
-    <message name="IDS_SETTINGS_MULTIDEVICE_FORGET_DEVICE_DIALOG_HEADING" desc="Heading for a dialog that lets the user choose if their Chromebook should forget their phone. This means they will no longer have access to multidevice features.">
-      Forget device
+      Disconnect your phone from your Chromebook
     </message>
     <message name="IDS_SETTINGS_MULTIDEVICE_FORGET_DEVICE_DIALOG_MESSAGE" desc="Text of a dialog that lets the user choose if their Chromebook should forget their phone. This means they will no longer have access to multidevice features.">
-      Remove your phone and disable Better Together
+      Disconnect your phone from your Chromebook. They will no longer connect automatically.
     </message>
   </if>
 
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_avatar.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_avatar.png
deleted file mode 100644
index 1626536f..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_avatar.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom.png
deleted file mode 100644
index 6e8e384..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_left.png
deleted file mode 100644
index e2ce4e4..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_right.png
deleted file mode 100644
index b048fb6..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_center.png
deleted file mode 100644
index 1c73ed20..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom.png
deleted file mode 100644
index 4f4322c..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_left.png
deleted file mode 100644
index 25b1522..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_right.png
deleted file mode 100644
index baeb7d99..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_center.png
deleted file mode 100644
index f1e5486..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_left.png
deleted file mode 100644
index 55f859a..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_right.png
deleted file mode 100644
index 8baf2ea..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top.png
deleted file mode 100644
index feb1f67..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_left.png
deleted file mode 100644
index e85f90ec..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_right.png
deleted file mode 100644
index 3379bb7c..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_hover_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_left.png
deleted file mode 100644
index be48aa2..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom.png
deleted file mode 100644
index ce126e3..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_left.png
deleted file mode 100644
index eda0cd7..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_right.png
deleted file mode 100644
index aa93eb3..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_center.png
deleted file mode 100644
index 05bfe96..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_left.png
deleted file mode 100644
index c7d5730..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_right.png
deleted file mode 100644
index 0b2bbc5..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top.png
deleted file mode 100644
index b54c05b7..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_left.png
deleted file mode 100644
index 39a5491..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_right.png
deleted file mode 100644
index 1366a76..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_pressed_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_right.png
deleted file mode 100644
index 0d25707a..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top.png
deleted file mode 100644
index 616055c5..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_left.png
deleted file mode 100644
index a43ee18..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_right.png
deleted file mode 100644
index c0a6e37..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/themed/sign_in_button_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_avatar.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_avatar.png
deleted file mode 100644
index ea0104a..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_avatar.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom.png
deleted file mode 100644
index ac2c17d..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_left.png
deleted file mode 100644
index e4eb6396..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_right.png
deleted file mode 100644
index 15f1fdbf..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_center.png
deleted file mode 100644
index cf69acd..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom.png
deleted file mode 100644
index b6a7b89..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_left.png
deleted file mode 100644
index ed60122f..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_right.png
deleted file mode 100644
index 74044de..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_center.png
deleted file mode 100644
index 52857c7..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_left.png
deleted file mode 100644
index 5773833..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_right.png
deleted file mode 100644
index 4b10e066..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top.png
deleted file mode 100644
index c829613..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_left.png
deleted file mode 100644
index 74afb04..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_right.png
deleted file mode 100644
index eb6a7b2..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_hover_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_left.png
deleted file mode 100644
index 5ea1114..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom.png
deleted file mode 100644
index 34c6f18..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_left.png
deleted file mode 100644
index d82f9c9..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_right.png
deleted file mode 100644
index 5630611..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_center.png
deleted file mode 100644
index b843c34..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_left.png
deleted file mode 100644
index aa344b1..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_right.png
deleted file mode 100644
index 91a6228..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top.png
deleted file mode 100644
index 31ffbd14..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_left.png
deleted file mode 100644
index 89f334a..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_right.png
deleted file mode 100644
index 192c9b1..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_pressed_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_right.png
deleted file mode 100644
index 318e77d..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top.png
deleted file mode 100644
index d91c581..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_left.png
deleted file mode 100644
index 2b18488..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_right.png
deleted file mode 100644
index ed03f644..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win7/sign_in_button_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_avatar.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_avatar.png
deleted file mode 100644
index 39cee7a..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_avatar.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom.png
deleted file mode 100644
index cd31098..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_left.png
deleted file mode 100644
index cd31098..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_right.png
deleted file mode 100644
index cd31098..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_center.png
deleted file mode 100644
index 7b6c31cc..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom.png
deleted file mode 100644
index 1e05a12..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_left.png
deleted file mode 100644
index 1e05a12..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_right.png
deleted file mode 100644
index 1e05a12..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_center.png
deleted file mode 100644
index 97e8d14..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_left.png
deleted file mode 100644
index 97e8d14..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_right.png
deleted file mode 100644
index 97e8d14..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top.png
deleted file mode 100644
index 74beec4..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_left.png
deleted file mode 100644
index 74beec4..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_right.png
deleted file mode 100644
index 74beec4..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_hover_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_left.png
deleted file mode 100644
index 7b6c31cc..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom.png
deleted file mode 100644
index 58cbeba..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_left.png
deleted file mode 100644
index 58cbeba..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_right.png
deleted file mode 100644
index 58cbeba..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_center.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_center.png
deleted file mode 100644
index b8616b0..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_center.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_left.png
deleted file mode 100644
index b8616b0..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_right.png
deleted file mode 100644
index b8616b0..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top.png
deleted file mode 100644
index 91c2828..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_left.png
deleted file mode 100644
index 91c2828..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_right.png
deleted file mode 100644
index 91c2828..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_pressed_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_right.png
deleted file mode 100644
index 7b6c31cc..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top.png
deleted file mode 100644
index e6fe999..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_left.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_left.png
deleted file mode 100644
index e6fe999..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_left.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_right.png b/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_right.png
deleted file mode 100644
index e6fe999..0000000
--- a/chrome/app/theme/default_100_percent/win/avatar_button/win8/sign_in_button_top_right.png
+++ /dev/null
Binary files differ
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 6c3c1012..9a1bd9be 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -251,98 +251,6 @@
       <structure type="chrome_scaled_image" name="IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE" file="common/profile_avatar_placeholder_large.png" />
       <structure type="chrome_scaled_image" name="IDR_PROFILES_DICE_TURN_ON_SYNC" file="common/turn_on_sync_illustration.png" />
 
-      <!-- New style avatar button -->
-      <if expr="is_win">
-        <!-- Windows, themed -->
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_AVATAR" file="win/avatar_button/themed/sign_in_button_avatar.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_BOTTOM" file="win/avatar_button/themed/sign_in_button_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_BOTTOM_LEFT" file="win/avatar_button/themed/sign_in_button_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_BOTTOM_RIGHT" file="win/avatar_button/themed/sign_in_button_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_CENTER" file="win/avatar_button/themed/sign_in_button_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_LEFT" file="win/avatar_button/themed/sign_in_button_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_RIGHT" file="win/avatar_button/themed/sign_in_button_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_TOP" file="win/avatar_button/themed/sign_in_button_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_TOP_LEFT" file="win/avatar_button/themed/sign_in_button_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_NORMAL_TOP_RIGHT" file="win/avatar_button/themed/sign_in_button_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_BOTTOM" file="win/avatar_button/themed/sign_in_button_hover_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_BOTTOM_LEFT" file="win/avatar_button/themed/sign_in_button_hover_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_BOTTOM_RIGHT" file="win/avatar_button/themed/sign_in_button_hover_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_CENTER" file="win/avatar_button/themed/sign_in_button_hover_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_LEFT" file="win/avatar_button/themed/sign_in_button_hover_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_RIGHT" file="win/avatar_button/themed/sign_in_button_hover_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_TOP" file="win/avatar_button/themed/sign_in_button_hover_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_TOP_LEFT" file="win/avatar_button/themed/sign_in_button_hover_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_HOVER_TOP_RIGHT" file="win/avatar_button/themed/sign_in_button_hover_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_BOTTOM" file="win/avatar_button/themed/sign_in_button_pressed_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_BOTTOM_LEFT" file="win/avatar_button/themed/sign_in_button_pressed_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_BOTTOM_RIGHT" file="win/avatar_button/themed/sign_in_button_pressed_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_CENTER" file="win/avatar_button/themed/sign_in_button_pressed_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_LEFT" file="win/avatar_button/themed/sign_in_button_pressed_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_RIGHT" file="win/avatar_button/themed/sign_in_button_pressed_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_TOP" file="win/avatar_button/themed/sign_in_button_pressed_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_TOP_LEFT" file="win/avatar_button/themed/sign_in_button_pressed_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_THEMED_BUTTON_PRESSED_TOP_RIGHT" file="win/avatar_button/themed/sign_in_button_pressed_top_right.png" />
-
-        <!-- Windows 7, Aero -->
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_AVATAR" file="win/avatar_button/win7/sign_in_button_avatar.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_BOTTOM" file="win/avatar_button/win7/sign_in_button_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_BOTTOM_LEFT" file="win/avatar_button/win7/sign_in_button_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_BOTTOM_RIGHT" file="win/avatar_button/win7/sign_in_button_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_CENTER" file="win/avatar_button/win7/sign_in_button_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_LEFT" file="win/avatar_button/win7/sign_in_button_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_RIGHT" file="win/avatar_button/win7/sign_in_button_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_TOP" file="win/avatar_button/win7/sign_in_button_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_TOP_LEFT" file="win/avatar_button/win7/sign_in_button_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_NORMAL_TOP_RIGHT" file="win/avatar_button/win7/sign_in_button_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_BOTTOM" file="win/avatar_button/win7/sign_in_button_hover_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_BOTTOM_LEFT" file="win/avatar_button/win7/sign_in_button_hover_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_BOTTOM_RIGHT" file="win/avatar_button/win7/sign_in_button_hover_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_CENTER" file="win/avatar_button/win7/sign_in_button_hover_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_LEFT" file="win/avatar_button/win7/sign_in_button_hover_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_RIGHT" file="win/avatar_button/win7/sign_in_button_hover_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_TOP" file="win/avatar_button/win7/sign_in_button_hover_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_TOP_LEFT" file="win/avatar_button/win7/sign_in_button_hover_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_HOVER_TOP_RIGHT" file="win/avatar_button/win7/sign_in_button_hover_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_BOTTOM" file="win/avatar_button/win7/sign_in_button_pressed_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_BOTTOM_LEFT" file="win/avatar_button/win7/sign_in_button_pressed_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_BOTTOM_RIGHT" file="win/avatar_button/win7/sign_in_button_pressed_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_CENTER" file="win/avatar_button/win7/sign_in_button_pressed_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_LEFT" file="win/avatar_button/win7/sign_in_button_pressed_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_RIGHT" file="win/avatar_button/win7/sign_in_button_pressed_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_TOP" file="win/avatar_button/win7/sign_in_button_pressed_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_TOP_LEFT" file="win/avatar_button/win7/sign_in_button_pressed_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_GLASS_BUTTON_PRESSED_TOP_RIGHT" file="win/avatar_button/win7/sign_in_button_pressed_top_right.png" />
-
-        <!-- Windows 8 and up -->
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_AVATAR" file="win/avatar_button/win8/sign_in_button_avatar.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_BOTTOM" file="win/avatar_button/win8/sign_in_button_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_BOTTOM_LEFT" file="win/avatar_button/win8/sign_in_button_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_BOTTOM_RIGHT" file="win/avatar_button/win8/sign_in_button_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_CENTER" file="win/avatar_button/win8/sign_in_button_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_LEFT" file="win/avatar_button/win8/sign_in_button_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_RIGHT" file="win/avatar_button/win8/sign_in_button_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_TOP" file="win/avatar_button/win8/sign_in_button_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_TOP_LEFT" file="win/avatar_button/win8/sign_in_button_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_NORMAL_TOP_RIGHT" file="win/avatar_button/win8/sign_in_button_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_BOTTOM" file="win/avatar_button/win8/sign_in_button_hover_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_BOTTOM_LEFT" file="win/avatar_button/win8/sign_in_button_hover_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_BOTTOM_RIGHT" file="win/avatar_button/win8/sign_in_button_hover_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_CENTER" file="win/avatar_button/win8/sign_in_button_hover_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_LEFT" file="win/avatar_button/win8/sign_in_button_hover_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_RIGHT" file="win/avatar_button/win8/sign_in_button_hover_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_TOP" file="win/avatar_button/win8/sign_in_button_hover_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_TOP_LEFT" file="win/avatar_button/win8/sign_in_button_hover_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_HOVER_TOP_RIGHT" file="win/avatar_button/win8/sign_in_button_hover_top_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_BOTTOM" file="win/avatar_button/win8/sign_in_button_pressed_bottom.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_BOTTOM_LEFT" file="win/avatar_button/win8/sign_in_button_pressed_bottom_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_BOTTOM_RIGHT" file="win/avatar_button/win8/sign_in_button_pressed_bottom_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_CENTER" file="win/avatar_button/win8/sign_in_button_pressed_center.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_LEFT" file="win/avatar_button/win8/sign_in_button_pressed_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_RIGHT" file="win/avatar_button/win8/sign_in_button_pressed_right.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_TOP" file="win/avatar_button/win8/sign_in_button_pressed_top.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_TOP_LEFT" file="win/avatar_button/win8/sign_in_button_pressed_top_left.png" />
-        <structure type="chrome_scaled_image" name="IDR_AVATAR_NATIVE_BUTTON_PRESSED_TOP_RIGHT" file="win/avatar_button/win8/sign_in_button_pressed_top_right.png" />
-      </if>
       <if expr="chromeos">
         <structure type="chrome_scaled_image" name="IDR_RESET_WARNING" file="cros/reset_warning.png" />
       </if>
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index ce408882..78c4bb4 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -12,7 +12,6 @@
     "account_box.icon",
     "account_child.icon",
     "account_child_circle.icon",
-    "account_circle.icon",
     "add.icon",
     "ads.icon",
     "apps.icon",
@@ -76,7 +75,6 @@
     "picture_in_picture_control_background.icon",
     "play_arrow.icon",
     "picture_in_picture_alt.icon",
-    "profile_switcher_outline.icon",
     "reload_touch.icon",
     "remove.icon",
     "remove_box.icon",
@@ -94,7 +92,6 @@
     "sync.icon",
     "sync_circle.icon",
     "sync_error_circle.icon",
-    "sync_paused.icon",
     "sync_paused_circle.icon",
     "sync_problem.icon",
     "sync_switch_account.icon",
diff --git a/chrome/app/vector_icons/account_circle.icon b/chrome/app/vector_icons/account_circle.icon
deleted file mode 100644
index 07bf0c78..0000000
--- a/chrome/app/vector_icons/account_circle.icon
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 24,
-MOVE_TO, 12, 2,
-CUBIC_TO, 6.48f, 2, 2, 6.48f, 2, 12,
-R_CUBIC_TO, 0, 5.52f, 4.48f, 10, 10, 10,
-R_CUBIC_TO, 5.52f, 0, 10, -4.48f, 10, -10,
-CUBIC_TO, 22, 6.48f, 17.52f, 2, 12, 2,
-CLOSE,
-R_MOVE_TO, 0, 3,
-R_CUBIC_TO, 1.66f, 0, 3, 1.34f, 3, 3,
-R_CUBIC_TO, 0, 1.66f, -1.34f, 3, -3, 3,
-R_CUBIC_TO, -1.66f, 0, -3, -1.34f, -3, -3,
-R_CUBIC_TO, 0, -1.66f, 1.34f, -3, 3, -3,
-CLOSE,
-R_MOVE_TO, 0, 14.2f,
-R_CUBIC_TO, -2.5f, 0, -4.71f, -1.28f, -6, -3.22f,
-R_CUBIC_TO, 0.03f, -1.99f, 4, -3.08f, 6, -3.08f,
-R_CUBIC_TO, 1.99f, 0, 5.97f, 1.09f, 6, 3.08f,
-R_CUBIC_TO, -1.29f, 1.94f, -3.5f, 3.22f, -6, 3.22f,
-CLOSE
diff --git a/chrome/app/vector_icons/profile_switcher_outline.icon b/chrome/app/vector_icons/profile_switcher_outline.icon
deleted file mode 100644
index f7efb07..0000000
--- a/chrome/app/vector_icons/profile_switcher_outline.icon
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This is a copy of account_circle.icon with an added path for an outline.
-// Outline:
-STROKE, 3,
-MOVE_TO, 24, 4,
-CUBIC_TO, 12.95f, 4, 4, 12.95f, 4, 24,
-R_CUBIC_TO, 0, 11.05f, 8.95f, 20, 20, 20,
-R_CUBIC_TO, 11.05f, 0, 20, -8.95f, 20, -20,
-CUBIC_TO_SHORTHAND, 35.05f, 4, 24, 4,
-CLOSE,
-R_MOVE_TO, 0, 6,
-R_CUBIC_TO, 3.31f, 0, 6, 2.69f, 6, 6,
-R_CUBIC_TO, 0, 3.32f, -2.69f, 6, -6, 6,
-R_CUBIC_TO, -3.31f, 0, -6, -2.68f, -6, -6,
-R_CUBIC_TO, 0, -3.31f, 2.69f, -6, 6, -6,
-CLOSE,
-R_MOVE_TO, 0, 28.4f,
-R_CUBIC_TO, -5.01f, 0, -9.41f, -2.56f, -12, -6.44f,
-R_CUBIC_TO, 0.05f, -3.97f, 8.01f, -6.16f, 12, -6.16f,
-R_CUBIC_TO, 3.99f, 0, 11.94f, 2.19f, 12, 6.16f,
-R_CUBIC_TO, -2.59f, 3.88f, -6.99f, 6.44f, -12, 6.44f,
-CLOSE,
-
-// Fill:
-NEW_PATH,
-PATH_COLOR_ARGB, 0xff, 0xff, 0xff, 0xff,
-MOVE_TO, 24, 4,
-CUBIC_TO, 12.95f, 4, 4, 12.95f, 4, 24,
-R_CUBIC_TO, 0, 11.05f, 8.95f, 20, 20, 20,
-R_CUBIC_TO, 11.05f, 0, 20, -8.95f, 20, -20,
-CUBIC_TO_SHORTHAND, 35.05f, 4, 24, 4,
-CLOSE,
-R_MOVE_TO, 0, 6,
-R_CUBIC_TO, 3.31f, 0, 6, 2.69f, 6, 6,
-R_CUBIC_TO, 0, 3.32f, -2.69f, 6, -6, 6,
-R_CUBIC_TO, -3.31f, 0, -6, -2.68f, -6, -6,
-R_CUBIC_TO, 0, -3.31f, 2.69f, -6, 6, -6,
-CLOSE,
-R_MOVE_TO, 0, 28.4f,
-R_CUBIC_TO, -5.01f, 0, -9.41f, -2.56f, -12, -6.44f,
-R_CUBIC_TO, 0.05f, -3.97f, 8.01f, -6.16f, 12, -6.16f,
-R_CUBIC_TO, 3.99f, 0, 11.94f, 2.19f, 12, 6.16f,
-R_CUBIC_TO, -2.59f, 3.88f, -6.99f, 6.44f, -12, 6.44f,
-CLOSE
diff --git a/chrome/app/vector_icons/sync_paused.icon b/chrome/app/vector_icons/sync_paused.icon
deleted file mode 100644
index 6eb251b..0000000
--- a/chrome/app/vector_icons/sync_paused.icon
+++ /dev/null
@@ -1,40 +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.
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 6.67f, 4.23f,
-V_LINE_TO, 2.84f,
-R_ARC_TO, 5.48f, 5.48f, 0, 0, 0, -1.49f, 0.64f,
-R_LINE_TO, 0.97f, 0.97f,
-R_CUBIC_TO, 0.17f, -0.08f, 0.33f, -0.16f, 0.51f, -0.22f,
-CLOSE,
-R_MOVE_TO, -4.76f, -0.63f,
-LINE_TO, 3.48f, 5.18f,
-R_ARC_TO, 5.28f, 5.28f, 0, 0, 0, 0.76f, 6.58f,
-R_LINE_TO, -1.57f, 1.57f,
-R_H_LINE_TO, 4,
-R_V_LINE_TO, -4,
-R_LINE_TO, -1.49f, 1.49f,
-ARC_TO, 4, 4, 0, 0, 1, 4, 8,
-R_CUBIC_TO, 0, -0.67f, 0.17f, -1.29f, 0.45f, -1.85f,
-LINE_TO, 9.84f, 11.54f,
-R_ARC_TO, 3.53f, 3.53f, 0, 0, 1, -0.51f, 0.23f,
-R_V_LINE_TO, 1.39f,
-R_ARC_TO, 5.48f, 5.48f, 0, 0, 0, 1.49f, -0.64f,
-R_LINE_TO, 1.57f, 1.57f,
-R_LINE_TO, 0.85f, -0.85f,
-LINE_TO, 2.76f, 2.76f,
-R_LINE_TO, -0.85f, 0.85f,
-CLOSE,
-R_MOVE_TO, 11.43f, -0.94f,
-R_H_LINE_TO, -4,
-R_V_LINE_TO, 4,
-R_LINE_TO, 1.49f, -1.49f,
-ARC_TO, 4, 4, 0, 0, 1, 12, 8,
-R_CUBIC_TO, 0, 0.67f, -0.17f, 1.29f, -0.45f, 1.85f,
-R_LINE_TO, 0.97f, 0.97f,
-R_ARC_TO, 5.28f, 5.28f, 0, 0, 0, -0.76f, -6.58f,
-R_LINE_TO, 1.57f, -1.57f,
-CLOSE
-
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 632368c..4adc6a2f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -554,8 +554,6 @@
     "google/did_run_updater_win.h",
     "google/google_brand.cc",
     "google/google_brand.h",
-    "google/google_brand_chromeos.cc",
-    "google/google_brand_chromeos.h",
     "google/google_search_domain_mixing_metrics_emitter.cc",
     "google/google_search_domain_mixing_metrics_emitter.h",
     "google/google_search_domain_mixing_metrics_emitter_factory.cc",
@@ -3076,6 +3074,10 @@
       "download/notification/download_notification_manager.h",
       "feedback/feedback_util_chromeos.cc",
       "feedback/feedback_util_chromeos.h",
+      "google/google_brand_chromeos.cc",
+      "google/google_brand_chromeos.h",
+      "google/google_brand_code_map_chromeos.cc",
+      "google/google_brand_code_map_chromeos.h",
       "media/chromeos_login_media_access_handler.cc",
       "media/chromeos_login_media_access_handler.h",
       "media/public_session_media_access_handler.cc",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 5ce5910..7793cb5d 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -215,8 +215,11 @@
 
 specific_include_rules = {
   "ash_service_registry\.cc": [
+    # The following are needed for classic mode to create the WindowService.
+    # Once Ash runs out of process no longer needed.
+    "+ash/shell.h",
+
     # Needed for classic mode.
-    "+ash/accessibility/ax_host_service.h",
     "+ash/ash_service.h",
   ],
   # TODO(mash): Fix. https://crbug.com/768439, https://crbug.com/768395.
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d770b45..a9aba7ea 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2191,9 +2191,6 @@
      flag_descriptions::kMemoryCoordinatorDescription,
      kOsAndroid | kOsCrOS | kOsLinux | kOsWin,
      FEATURE_VALUE_TYPE(features::kMemoryCoordinator)},
-    {"enable-tab-audio-muting", flag_descriptions::kTabAudioMutingName,
-     flag_descriptions::kTabAudioMutingDescription, kOsDesktop,
-     SINGLE_VALUE_TYPE(switches::kEnableTabAudioMuting)},
     {"reduced-referrer-granularity",
      flag_descriptions::kReducedReferrerGranularityName,
      flag_descriptions::kReducedReferrerGranularityDescription, kOsAll,
@@ -3375,13 +3372,18 @@
      kOsAll,
      FEATURE_VALUE_TYPE(omnibox::kUIExperimentElideSuggestionUrlAfterHost)},
 
-    {"omnibox-ui-hide-steady-state-url-scheme-and-subdomains",
-     flag_descriptions::kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsName,
+    {"omnibox-ui-hide-steady-state-url-scheme",
+     flag_descriptions::kOmniboxUIHideSteadyStateUrlSchemeName,
+     flag_descriptions::kOmniboxUIHideSteadyStateUrlSchemeDescription, kOsAll,
+     FEATURE_VALUE_TYPE(toolbar::features::kHideSteadyStateUrlScheme)},
+
+    {"omnibox-ui-hide-steady-state-url-trivial-subdomains",
+     flag_descriptions::kOmniboxUIHideSteadyStateUrlTrivialSubdomainsName,
      flag_descriptions::
-         kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsDescription,
+         kOmniboxUIHideSteadyStateUrlTrivialSubdomainsDescription,
      kOsAll,
      FEATURE_VALUE_TYPE(
-         omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains)},
+         toolbar::features::kHideSteadyStateUrlTrivialSubdomains)},
 
     {"omnibox-ui-jog-textfield-on-popup",
      flag_descriptions::kOmniboxUIJogTextfieldOnPopupName,
diff --git a/chrome/browser/accessibility/DEPS b/chrome/browser/accessibility/DEPS
deleted file mode 100644
index cdc3383..0000000
--- a/chrome/browser/accessibility/DEPS
+++ /dev/null
@@ -1,7 +0,0 @@
-specific_include_rules = {
-  "accessibility_extension_api\.cc": [
-    # TODO(mash): Replace with mojo API. http://crbug.com/594887
-    "+ash/accessibility/accessibility_focus_ring_controller.h",
-    "+ash/shell.h",
-  ],
-}
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index 2ffd012..cc84c72 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -38,12 +38,13 @@
 #include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/event_rewriter_controller.mojom.h"
-#include "ash/shell.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h"
-#include "ui/aura/window_tree_host.h"
+#include "services/ws/public/mojom/constants.mojom.h"
+#include "services/ws/public/mojom/event_injector.mojom.h"
 #include "ui/base/ui_base_features.h"
-#include "ui/events/event_sink.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
 #endif
 
 namespace accessibility_private = extensions::api::accessibility_private;
@@ -233,12 +234,6 @@
 
 ExtensionFunction::ResponseAction
 AccessibilityPrivateSendSyntheticKeyEventFunction::Run() {
-  // TODO(crbug.com/876043): Mash support.
-  if (features::IsUsingWindowService()) {
-    NOTIMPLEMENTED();
-    return RespondNow(NoArguments());
-  }
-
   std::unique_ptr<accessibility_private::SendSyntheticKeyEvent::Params> params =
       accessibility_private::SendSyntheticKeyEvent::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params);
@@ -256,19 +251,23 @@
       modifiers |= ui::EF_SHIFT_DOWN;
   }
 
-  ui::KeyEvent synthetic_key_event(
-      key_data->type ==
-              accessibility_private::SYNTHETIC_KEYBOARD_EVENT_TYPE_KEYUP
-          ? ui::ET_KEY_RELEASED
-          : ui::ET_KEY_PRESSED,
-      static_cast<ui::KeyboardCode>(key_data->key_code),
-      static_cast<ui::DomCode>(0), modifiers);
+  std::unique_ptr<ui::KeyEvent> synthetic_key_event =
+      std::make_unique<ui::KeyEvent>(
+          key_data->type ==
+                  accessibility_private::SYNTHETIC_KEYBOARD_EVENT_TYPE_KEYUP
+              ? ui::ET_KEY_RELEASED
+              : ui::ET_KEY_PRESSED,
+          static_cast<ui::KeyboardCode>(key_data->key_code),
+          static_cast<ui::DomCode>(0), modifiers);
 
-  // Only keyboard events, so dispatching to primary window suffices.
-  ui::EventSink* sink =
-      ash::Shell::GetPrimaryRootWindow()->GetHost()->event_sink();
-  if (sink->OnEventFromSource(&synthetic_key_event).dispatcher_destroyed)
-    return RespondNow(Error("Unable to dispatch key "));
+  ws::mojom::EventInjectorPtr event_injector_ptr;
+  content::ServiceManagerConnection* connection =
+      content::ServiceManagerConnection::GetForProcess();
+  connection->GetConnector()->BindInterface(ws::mojom::kServiceName,
+                                            &event_injector_ptr);
+  event_injector_ptr->InjectEventNoAck(
+      display::Screen::GetScreen()->GetPrimaryDisplay().id(),
+      std::move(synthetic_key_event));
 
   return RespondNow(NoArguments());
 }
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index b34f7078..ff87f10 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -26,6 +26,7 @@
 #include "components/payments/core/features.h"
 #include "components/safe_browsing/features.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
+#include "components/toolbar/toolbar_field_trial.h"
 #include "components/unified_consent/feature.h"
 #include "content/public/common/content_features.h"
 #include "jni/ChromeFeatureList_jni.h"
@@ -161,11 +162,12 @@
     &offline_pages::kOfflinePagesLivePageSharingFeature,
     &offline_pages::kPrefetchingOfflinePagesFeature,
     &omnibox::kQueryInOmnibox,
-    &omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains,
     &password_manager::features::kPasswordSearchMobile,
     &password_manager::features::kPasswordsKeyboardAccessory,
     &unified_consent::kUnifiedConsent,
     &subresource_filter::kSafeBrowsingSubresourceFilter,
+    &toolbar::features::kHideSteadyStateUrlScheme,
+    &toolbar::features::kHideSteadyStateUrlTrivialSubdomains,
 };
 
 const base::Feature* FindFeatureExposedToJava(const std::string& feature_name) {
diff --git a/chrome/browser/android/signin/signin_manager_android.cc b/chrome/browser/android/signin/signin_manager_android.cc
index 175e860..7d8823cc 100644
--- a/chrome/browser/android/signin/signin_manager_android.cc
+++ b/chrome/browser/android/signin/signin_manager_android.cc
@@ -326,13 +326,6 @@
   return SigninManagerFactory::GetForProfile(profile_)->IsAuthenticated();
 }
 
-void SigninManagerAndroid::ProhibitSignout(JNIEnv* env,
-                                           const JavaParamRef<jobject>& obj,
-                                           jboolean prohibit_signout) {
-  SigninManagerFactory::GetForProfile(profile_)->ProhibitSignout(
-      prohibit_signout);
-}
-
 void SigninManagerAndroid::GoogleSigninFailed(
     const GoogleServiceAuthError& error) {}
 
diff --git a/chrome/browser/android/signin/signin_manager_android.h b/chrome/browser/android/signin/signin_manager_android.h
index 2596442..4d711455 100644
--- a/chrome/browser/android/signin/signin_manager_android.h
+++ b/chrome/browser/android/signin/signin_manager_android.h
@@ -80,10 +80,6 @@
   jboolean IsSignedInOnNative(JNIEnv* env,
                               const base::android::JavaParamRef<jobject>& obj);
 
-  void ProhibitSignout(JNIEnv* env,
-                       const base::android::JavaParamRef<jobject>& obj,
-                       jboolean prohibit_signout);
-
   // SigninManagerBase::Observer implementation.
   void GoogleSigninFailed(const GoogleServiceAuthError& error) override;
   void GoogleSigninSucceeded(const std::string& account_id,
diff --git a/chrome/browser/ash_service_registry.cc b/chrome/browser/ash_service_registry.cc
index 25d15c5..0a694395 100644
--- a/chrome/browser/ash_service_registry.cc
+++ b/chrome/browser/ash_service_registry.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ash_service_registry.h"
 
-#include "ash/accessibility/ax_host_service.h"
 #include "ash/ash_service.h"
 #include "ash/components/quick_launch/public/mojom/constants.mojom.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
@@ -16,6 +15,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 #include "chrome/browser/chromeos/prefs/pref_connector_service.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/common/service_manager_connection.h"
@@ -84,20 +84,20 @@
     (*services)[ash::mojom::kPrefConnectorServiceName] = info;
   }
 
-  if (features::IsMultiProcessMash())
-    return;
-
   {
     // Register the accessibility host service.
     service_manager::EmbeddedServiceInfo info;
     info.task_runner = base::ThreadTaskRunnerHandle::Get();
     info.factory =
         base::BindRepeating([]() -> std::unique_ptr<service_manager::Service> {
-          return std::make_unique<ash::AXHostService>();
+          return std::make_unique<AXHostService>();
         });
     (*services)[ax::mojom::kAXHostServiceName] = info;
   }
 
+  if (features::IsMultiProcessMash())
+    return;
+
   (*services)[ash::mojom::kServiceName] =
       ash::AshService::CreateEmbeddedServiceInfo();
 }
diff --git a/chrome/browser/autofill/autofill_server_browsertest.cc b/chrome/browser/autofill/autofill_server_browsertest.cc
index a7a3139..56cae23b 100644
--- a/chrome/browser/autofill/autofill_server_browsertest.cc
+++ b/chrome/browser/autofill/autofill_server_browsertest.cc
@@ -189,6 +189,8 @@
   upload.set_action_signature(15724779818122431245U);
   upload.set_form_name("test_form");
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_HTML_FORM_SUBMISSION);
 
   test::FillUploadField(upload.add_field(), 2594484045U, "one", "text", nullptr,
                         2U);
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index feef0c9..1b84a87 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -71,6 +71,23 @@
         <structure name="IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_BROWSER_PROXY_JS" file="resources\welcome\onboarding_welcome\welcome_browser_proxy.js" type="chrome_html"/>
         <structure name="IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_CSS" file="resources\welcome\onboarding_welcome\welcome.css" type="chrome_html" preprocess="true"/>
         <structure name="IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_HTML" file="resources\welcome\onboarding_welcome\welcome.html" type="chrome_html" preprocess="true"/>
+         <!-- NUX files -->
+        <structure name="IDR_NUX_EMAIL_CHOOSER_HTML" file="resources\welcome\onboarding_welcome\email\email_chooser.html" type="chrome_html" />
+        <structure name="IDR_NUX_EMAIL_CHOOSER_JS" file="resources\welcome\onboarding_welcome\email\email_chooser.js" type="chrome_html" />
+        <structure name="IDR_NUX_EMAIL_HTML" file="resources\welcome\onboarding_welcome\email\nux_email.html" type="chrome_html" />
+        <structure name="IDR_NUX_EMAIL_JS" file="resources\welcome\onboarding_welcome\email\nux_email.js" type="chrome_html" />
+        <structure name="IDR_NUX_EMAIL_PROXY_HTML" file="resources\welcome\onboarding_welcome\email\nux_email_proxy.html" type="chrome_html" />
+        <structure name="IDR_NUX_EMAIL_PROXY_JS" file="resources\welcome\onboarding_welcome\email\nux_email_proxy.js" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_HTML" file="resources\welcome\onboarding_welcome\google_apps\nux_google_apps.html" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_JS" file="resources\welcome\onboarding_welcome\google_apps\nux_google_apps.js" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_PROXY_HTML" file="resources\welcome\onboarding_welcome\google_apps\nux_google_apps_proxy.html" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_PROXY_JS" file="resources\welcome\onboarding_welcome\google_apps\nux_google_apps_proxy.js" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_CHOOSER_HTML" file="resources\welcome\onboarding_welcome\google_apps\apps_chooser.html" type="chrome_html" />
+        <structure name="IDR_NUX_GOOGLE_APPS_CHOOSER_JS" file="resources\welcome\onboarding_welcome\google_apps\apps_chooser.js" type="chrome_html" />
+        <structure name="IDR_NUX_SET_AS_DEFAULT_HTML" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default.html" type="chrome_html" />
+        <structure name="IDR_NUX_SET_AS_DEFAULT_JS" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default.js" type="chrome_html" />
+        <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_HTML" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default_proxy.html" type="chrome_html" />
+        <structure name="IDR_NUX_SET_AS_DEFAULT_PROXY_JS" file="resources\welcome\onboarding_welcome\set_as_default\nux_set_as_default_proxy.js" type="chrome_html" />
       </if>
       <if expr="is_win">
         <structure name="IDR_WELCOME_WIN10_CSS" file="resources\welcome\welcome_win10.css" type="chrome_html" />
@@ -670,6 +687,30 @@
       <if expr="is_win">
         <include name="IDR_NACL_BROKER_MANIFEST" file="../../components/nacl/broker/nacl_broker_manifest.json" type="BINDATA" />
       </if>
+      <if expr="not is_android and not chromeos">
+        <include name="IDR_NUX_EMAIL_AOL_1X" file="resources\welcome\onboarding_welcome\images\aol_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_AOL_2X" file="resources\welcome\onboarding_welcome\images\aol_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_GMAIL_1X" file="resources\welcome\onboarding_welcome\images\gmail_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_GMAIL_2X" file="resources\welcome\onboarding_welcome\images\gmail_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_ICLOUD_1X" file="resources\welcome\onboarding_welcome\images\icloud_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_ICLOUD_2X" file="resources\welcome\onboarding_welcome\images\icloud_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_OUTLOOK_1X" file="resources\welcome\onboarding_welcome\images\outlook_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_OUTLOOK_2X" file="resources\welcome\onboarding_welcome\images\outlook_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_YAHOO_1X" file="resources\welcome\onboarding_welcome\images\yahoo_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_EMAIL_YAHOO_2X" file="resources\welcome\onboarding_welcome\images\yahoo_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_1X" file="resources\welcome\onboarding_welcome\images\chrome_store_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_2X" file="resources\welcome\onboarding_welcome\images\chrome_store_24dp_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_GMAIL_1X" file="resources\welcome\onboarding_welcome\images\gmail_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_GMAIL_2X" file="resources\welcome\onboarding_welcome\images\gmail_24dp_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_MAPS_1X" file="resources\welcome\onboarding_welcome\images\maps_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_MAPS_2X" file="resources\welcome\onboarding_welcome\images\maps_24dp_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_NEWS_1X" file="resources\welcome\onboarding_welcome\images\news_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_NEWS_2X" file="resources\welcome\onboarding_welcome\images\news_24dp_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_1X" file="resources\welcome\onboarding_welcome\images\translate_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_2X" file="resources\welcome\onboarding_welcome\images\translate_24dp_2x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_1X" file="resources\welcome\onboarding_welcome\images\youtube_24dp_1x.png" type="BINDATA" />
+        <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_2X" file="resources\welcome\onboarding_welcome\images\youtube_24dp_2x.png" type="BINDATA" />
+      </if>
       <if expr="is_win">
         <include name="IDR_WELCOME_WIN10_DEFAULT_WEBP" file="resources\welcome\default.webp" type="BINDATA" />
         <include name="IDR_WELCOME_WIN10_PIN_WEBP" file="resources\welcome\pin.webp" type="BINDATA" />
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
index 31ac756..b3aee798 100644
--- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
+++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
@@ -17,9 +17,9 @@
 #include "chrome/common/url_constants.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/storage_partition.h"
 #include "storage/browser/quota/quota_manager.h"
+#include "url/origin.h"
 
 using blink::mojom::StorageType;
 using content::BrowserThread;
@@ -84,15 +84,16 @@
   }
 }
 
-void BrowsingDataQuotaHelperImpl::GotOrigins(PendingHosts* pending_hosts,
-                                             base::OnceClosure completion,
-                                             const std::set<GURL>& origins,
-                                             StorageType type) {
+void BrowsingDataQuotaHelperImpl::GotOrigins(
+    PendingHosts* pending_hosts,
+    base::OnceClosure completion,
+    const std::set<url::Origin>& origins,
+    StorageType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  for (const GURL& url : origins) {
-    if (!BrowsingDataHelper::HasWebScheme(url))
+  for (const url::Origin& origin : origins) {
+    if (!BrowsingDataHelper::IsWebScheme(origin.scheme()))
       continue;  // Non-websafe state is not considered browsing data.
-    pending_hosts->insert(std::make_pair(url.host(), type));
+    pending_hosts->insert(std::make_pair(origin.host(), type));
   }
   std::move(completion).Run();
 }
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
index d88e744..d12f37aa 100644
--- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
+++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
@@ -19,12 +19,14 @@
 #include "chrome/browser/browsing_data/browsing_data_quota_helper.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
-class GURL;
-
 namespace storage {
 class QuotaManager;
 }
 
+namespace url {
+class Origin;
+}
+
 // Implementation of BrowsingDataQuotaHelper.  Since a client of
 // BrowsingDataQuotaHelper should live in UI thread and QuotaManager lives in
 // IO thread, we have to communicate over thread using PostTask.
@@ -47,7 +49,7 @@
   // Callback function for QuotaManager::GetOriginModifiedSince.
   void GotOrigins(PendingHosts* pending_hosts,
                   base::OnceClosure completion,
-                  const std::set<GURL>& origins,
+                  const std::set<url::Origin>& origins,
                   blink::mojom::StorageType type);
 
   // Calls QuotaManager::GetHostUsage for each (origin, type) pair.
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
index 8c2f5da5..a61bac42 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
@@ -24,6 +24,8 @@
 #include "ppapi/buildflags/buildflags.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "storage/browser/quota/quota_manager.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 using content::BrowserThread;
 
@@ -143,13 +145,17 @@
 }
 
 void SiteDataCountingHelper::GetQuotaOriginsCallback(
-    const std::set<GURL>& origin_set,
+    const std::set<url::Origin>& origins,
     blink::mojom::StorageType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  std::vector<GURL> origins(origin_set.begin(), origin_set.end());
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
-                           base::BindOnce(&SiteDataCountingHelper::Done,
-                                          base::Unretained(this), origins));
+  std::vector<GURL> urls;
+  urls.resize(origins.size());
+  for (const url::Origin& origin : origins)
+    urls.push_back(origin.GetURL());
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&SiteDataCountingHelper::Done, base::Unretained(this),
+                     std::move(urls)));
 }
 
 void SiteDataCountingHelper::GetLocalStorageUsageInfoCallback(
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.h b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
index 64700c6..ac1a2624 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.h
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
@@ -24,6 +24,10 @@
 struct SessionStorageUsageInfo;
 }
 
+namespace url {
+class Origin;
+}
+
 namespace storage {
 class SpecialStoragePolicy;
 }
@@ -52,7 +56,7 @@
       const scoped_refptr<storage::SpecialStoragePolicy>&
           special_storage_policy,
       const std::vector<content::LocalStorageUsageInfo>& infos);
-  void GetQuotaOriginsCallback(const std::set<GURL>& origin_set,
+  void GetQuotaOriginsCallback(const std::set<url::Origin>& origin_set,
                                blink::mojom::StorageType type);
   void SitesWithFlashDataCallback(const std::vector<std::string>& sites);
   void GetChannelIDsOnIOThread(
diff --git a/chrome/browser/chrome_content_renderer_manifest_overlay.json b/chrome/browser/chrome_content_renderer_manifest_overlay.json
index d7ec48b..2d817f18 100644
--- a/chrome/browser/chrome_content_renderer_manifest_overlay.json
+++ b/chrome/browser/chrome_content_renderer_manifest_overlay.json
@@ -28,7 +28,8 @@
           "extensions.mojom.AppWindow",
           "safe_browsing.mojom.ThreatReporter",
           "safe_browsing.mojom.PhishingDetector",
-          "spellcheck.mojom.SpellCheckPanel"
+          "spellcheck.mojom.SpellCheckPanel",
+          "subresource_filter.mojom.SubresourceFilterAgent"
         ]
       },
       "requires": {
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 6004ff7..f2f94ab1 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -265,6 +265,8 @@
     "accessibility/accessibility_manager.h",
     "accessibility/accessibility_panel.cc",
     "accessibility/accessibility_panel.h",
+    "accessibility/ax_host_service.cc",
+    "accessibility/ax_host_service.h",
     "accessibility/chromevox_panel.cc",
     "accessibility/chromevox_panel.h",
     "accessibility/dictation_chromeos.cc",
@@ -2021,6 +2023,7 @@
     "../policy/default_geolocation_policy_handler_unittest.cc",
     "../resources/chromeos/zip_archiver/test/char_coding_test.cc",
     "../ui/browser_finder_chromeos_unittest.cc",
+    "accessibility/ax_host_service_unittest.cc",
     "app_mode/startup_app_launcher_unittest.cc",
     "apps/intent_helper/apps_navigation_throttle_unittest.cc",
     "apps/intent_helper/page_transition_util_unittest.cc",
diff --git a/ash/accessibility/ax_host_service.cc b/chrome/browser/chromeos/accessibility/ax_host_service.cc
similarity index 68%
rename from ash/accessibility/ax_host_service.cc
rename to chrome/browser/chromeos/accessibility/ax_host_service.cc
index 00ac720c..18c0281e 100644
--- a/ash/accessibility/ax_host_service.cc
+++ b/chrome/browser/chromeos/accessibility/ax_host_service.cc
@@ -2,29 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/accessibility/ax_host_service.h"
+#include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 
 #include "ash/accessibility/accessibility_controller.h"
-#include "ash/accessibility/accessibility_delegate.h"
 #include "ash/shell.h"
 #include "base/bind.h"
+#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
+#include "chrome/common/extensions/chrome_extension_messages.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/service_context.h"
 #include "ui/accessibility/ax_event.h"
+#include "ui/aura/env.h"
 #include "ui/views/mus/ax_remote_host.h"
 
-namespace ash {
-
 AXHostService* AXHostService::instance_ = nullptr;
 
 bool AXHostService::automation_enabled_ = false;
 
 AXHostService::AXHostService() {
   // AX tree ID is automatically assigned.
-  DCHECK(!tree_id().empty());
+  DCHECK_NE(tree_id(), ui::AXTreeIDUnknown());
 
-  // TODO(jamescook): Eliminate this when multiple remote trees are supported.
-  Shell::Get()->accessibility_controller()->set_remote_ax_tree_id(tree_id());
+  // ash::Shell may not exist in tests.
+  if (ash::Shell::HasInstance()) {
+    // TODO(jamescook): Eliminate this when tree ID assignment is handed in ash.
+    ash::Shell::Get()->accessibility_controller()->set_remote_ax_tree_id(
+        tree_id());
+  }
 
   DCHECK(!instance_);
   instance_ = this;
@@ -63,16 +67,20 @@
 }
 
 void AXHostService::HandleAccessibilityEvent(
-    const std::string& tree_id,
+    const ui::AXTreeID& tree_id,
     const std::vector<ui::AXTreeUpdate>& updates,
     const ui::AXEvent& event) {
   CHECK_EQ(tree_id, this->tree_id());
-  // Use an in-process delegate back to Chrome for efficiency in classic ash.
-  // For mash we'll need to pass around a mojo interface pointer such that a
-  // remote app can talk directly to the browser process and not ping-pong
-  // through the ash process.
-  Shell::Get()->accessibility_delegate()->DispatchAccessibilityEvent(
-      tree_id, updates, event);
+  ExtensionMsg_AccessibilityEventBundleParams event_bundle;
+  event_bundle.tree_id = tree_id;
+  for (const ui::AXTreeUpdate& update : updates)
+    event_bundle.updates.push_back(update);
+  event_bundle.events.push_back(event);
+  event_bundle.mouse_location = aura::Env::GetInstance()->last_mouse_location();
+
+  // Forward the tree updates and the event to the accessibility extension.
+  extensions::AutomationEventRouter::GetInstance()->DispatchAccessibilityEvents(
+      event_bundle);
 }
 
 void AXHostService::PerformAction(const ui::AXActionData& data) {
@@ -96,7 +104,6 @@
 }
 
 void AXHostService::OnRemoteHostDisconnected() {
-  Shell::Get()->accessibility_delegate()->DispatchTreeDestroyedEvent(tree_id());
+  extensions::AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
+      tree_id(), nullptr /* browser_context */);
 }
-
-}  // namespace ash
diff --git a/ash/accessibility/ax_host_service.h b/chrome/browser/chromeos/accessibility/ax_host_service.h
similarity index 82%
rename from ash/accessibility/ax_host_service.h
rename to chrome/browser/chromeos/accessibility/ax_host_service.h
index b0809cde0..c3efe1738 100644
--- a/ash/accessibility/ax_host_service.h
+++ b/chrome/browser/chromeos/accessibility/ax_host_service.h
@@ -2,30 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_ACCESSIBILITY_AX_HOST_SERVICE_H_
-#define ASH_ACCESSIBILITY_AX_HOST_SERVICE_H_
+#ifndef CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_AX_HOST_SERVICE_H_
+#define CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_AX_HOST_SERVICE_H_
 
 #include <memory>
 
-#include "ash/ash_export.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/service.h"
 #include "ui/accessibility/ax_host_delegate.h"
-#include "ui/accessibility/ax_tree_id.h"
 #include "ui/accessibility/mojom/ax_host.mojom.h"
 
-namespace ash {
-
 // Forwards accessibility events from clients in other processes that use aura
 // and views (e.g. the Chrome OS keyboard shortcut_viewer) to accessibility
 // extensions. Renderers, PDF, etc. use a different path. Created when the first
 // client connects over mojo. Implements AXHostDelegate by routing actions over
 // mojo to the remote process.
-class ASH_EXPORT AXHostService : public service_manager::Service,
-                                 public ax::mojom::AXHost,
-                                 public ui::AXHostDelegate {
+class AXHostService : public service_manager::Service,
+                      public ax::mojom::AXHost,
+                      public ui::AXHostDelegate {
  public:
   AXHostService();
   ~AXHostService() override;
@@ -44,7 +40,7 @@
   // ax::mojom::AXHost:
   void SetRemoteHost(ax::mojom::AXRemoteHostPtr remote,
                      SetRemoteHostCallback cb) override;
-  void HandleAccessibilityEvent(const std::string& tree_id,
+  void HandleAccessibilityEvent(const ui::AXTreeID& tree_id,
                                 const std::vector<ui::AXTreeUpdate>& updates,
                                 const ui::AXEvent& event) override;
 
@@ -70,6 +66,4 @@
   DISALLOW_COPY_AND_ASSIGN(AXHostService);
 };
 
-}  // namespace ash
-
-#endif  // ASH_ACCESSIBILITY_AX_HOST_SERVICE_H_
+#endif  // CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_AX_HOST_SERVICE_H_
diff --git a/ash/accessibility/ax_host_service_unittest.cc b/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
similarity index 91%
rename from ash/accessibility/ax_host_service_unittest.cc
rename to chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
index 4ecc260b..43ac4b48 100644
--- a/ash/accessibility/ax_host_service_unittest.cc
+++ b/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/accessibility/ax_host_service.h"
+#include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 
-#include "ash/test/ash_test_base.h"
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/test/scoped_task_environment.h"
@@ -12,7 +11,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/mojom/ax_host.mojom.h"
 
-namespace ash {
 namespace {
 
 class TestAXRemoteHost : ax::mojom::AXRemoteHost {
@@ -53,7 +51,16 @@
   DISALLOW_COPY_AND_ASSIGN(TestAXRemoteHost);
 };
 
-using AXHostServiceTest = AshTestBase;
+class AXHostServiceTest : public testing::Test {
+ public:
+  AXHostServiceTest() = default;
+  ~AXHostServiceTest() override = default;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_enviroment_;
+
+  DISALLOW_COPY_AND_ASSIGN(AXHostServiceTest);
+};
 
 TEST_F(AXHostServiceTest, AddClientThenEnable) {
   AXHostService service;
@@ -113,4 +120,3 @@
 }
 
 }  // namespace
-}  // namespace ash
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
index d5b85cb..189b11f4 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
@@ -396,8 +396,8 @@
   if (out_data->role == ax::mojom::Role::kRootWebArea) {
     std::string package_name;
     if (GetProperty(node, AXStringProperty::PACKAGE_NAME, &package_name)) {
-      const std::string& url =
-          base::StringPrintf("%s/%s", package_name.c_str(), tree_id().c_str());
+      const std::string& url = base::StringPrintf("%s/%s", package_name.c_str(),
+                                                  tree_id().ToString().c_str());
       out_data->AddStringAttribute(ax::mojom::StringAttribute::kUrl, url);
     }
   }
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.cc b/chrome/browser/chromeos/smb_client/smb_file_system.cc
index d2c7302..714d43f 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.cc
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.cc
@@ -40,6 +40,10 @@
 // Maximum number of entries to send at a time for read directory,
 constexpr uint32_t kReadDirectoryMaxBatchSize = 2048;
 
+// Capacity of the task queue. Represents how many operations can be in-flight
+// over D-Bus concurrently.
+constexpr size_t kTaskQueueCapacity = 2;
+
 constexpr ProvidedFileSystemInterface::MetadataFieldMask kRequestableFields =
     ProvidedFileSystemInterface::MetadataField::METADATA_FIELD_IS_DIRECTORY |
     ProvidedFileSystemInterface::MetadataField::METADATA_FIELD_NAME |
@@ -80,6 +84,26 @@
   return (fields & kRequestableFields) == 0;
 }
 
+filesystem::mojom::FsFileType MapEntryType(bool is_directory) {
+  return is_directory ? filesystem::mojom::FsFileType::DIRECTORY
+                      : filesystem::mojom::FsFileType::REGULAR_FILE;
+}
+
+std::unique_ptr<smb_client::TempFileManager> CreateTempFileManager() {
+  return std::make_unique<smb_client::TempFileManager>();
+}
+
+void ConvertEntries(const smbprovider::DirectoryEntryListProto& entries_proto,
+                    storage::AsyncFileUtil::EntryList* out_entries) {
+  DCHECK(out_entries);
+
+  for (const smbprovider::DirectoryEntryProto& entry :
+       entries_proto.entries()) {
+    out_entries->emplace_back(base::FilePath(entry.name()),
+                              MapEntryType(entry.is_directory()));
+  }
+}
+
 // Metrics recording.
 void RecordReadDirectoryCount(int count) {
   UMA_HISTOGRAM_COUNTS_100000("NativeSmbFileShare.ReadDirectoryCount", count);
@@ -93,21 +117,6 @@
 
 namespace smb_client {
 
-namespace {
-
-filesystem::mojom::FsFileType MapEntryType(bool is_directory) {
-  return is_directory ? filesystem::mojom::FsFileType::DIRECTORY
-                      : filesystem::mojom::FsFileType::REGULAR_FILE;
-}
-
-constexpr size_t kTaskQueueCapacity = 2;
-
-std::unique_ptr<TempFileManager> CreateTempFileManager() {
-  return std::make_unique<TempFileManager>();
-}
-
-}  // namespace
-
 using file_system_provider::AbortCallback;
 
 SmbFileSystem::SmbFileSystem(
@@ -524,6 +533,40 @@
   EnqueueTask(std::move(task), operation_id);
 }
 
+void SmbFileSystem::StartReadDirectory(
+    const base::FilePath& directory_path,
+    OperationId operation_id,
+    storage::AsyncFileUtil::ReadDirectoryCallback callback) {
+  base::ElapsedTimer metrics_timer;
+
+  auto reply = base::BindOnce(&SmbFileSystem::HandleStartReadDirectoryCallback,
+                              AsWeakPtr(), std::move(callback), operation_id,
+                              std::move(metrics_timer));
+
+  SmbTask task = base::BindOnce(&SmbProviderClient::StartReadDirectory,
+                                GetWeakSmbProviderClient(), GetMountId(),
+                                directory_path, std::move(reply));
+
+  EnqueueTask(std::move(task), operation_id);
+}
+
+void SmbFileSystem::ContinueReadDirectory(
+    OperationId operation_id,
+    int32_t read_dir_token,
+    storage::AsyncFileUtil::ReadDirectoryCallback callback,
+    int entries_count,
+    base::ElapsedTimer metrics_timer) {
+  auto reply =
+      base::BindOnce(&SmbFileSystem::HandleContinueReadDirectoryCallback,
+                     AsWeakPtr(), std::move(callback), operation_id,
+                     read_dir_token, entries_count, std::move(metrics_timer));
+  SmbTask task = base::BindOnce(&SmbProviderClient::ContinueReadDirectory,
+                                GetWeakSmbProviderClient(), GetMountId(),
+                                read_dir_token, std::move(reply));
+
+  EnqueueTask(std::move(task), operation_id);
+}
+
 void SmbFileSystem::HandleRequestReadDirectoryCallback(
     storage::AsyncFileUtil::ReadDirectoryCallback callback,
     const base::ElapsedTimer& metrics_timer,
@@ -631,6 +674,71 @@
   std::move(callback).Run(TranslateToFileError(error));
 }
 
+void SmbFileSystem::HandleStartReadDirectoryCallback(
+    storage::AsyncFileUtil::ReadDirectoryCallback callback,
+    OperationId operation_id,
+    base::ElapsedTimer metrics_timer,
+    smbprovider::ErrorType error,
+    int32_t read_dir_token,
+    const smbprovider::DirectoryEntryListProto& entries) {
+  task_queue_.TaskFinished();
+
+  int entries_count = 0;
+  ProcessReadDirectoryResults(std::move(callback), operation_id, read_dir_token,
+                              error, entries, entries_count,
+                              std::move(metrics_timer));
+}
+
+void SmbFileSystem::HandleContinueReadDirectoryCallback(
+    storage::AsyncFileUtil::ReadDirectoryCallback callback,
+    OperationId operation_id,
+    int32_t read_dir_token,
+    int entries_count,
+    base::ElapsedTimer metrics_timer,
+    smbprovider::ErrorType error,
+    const smbprovider::DirectoryEntryListProto& entries) {
+  task_queue_.TaskFinished();
+
+  ProcessReadDirectoryResults(std::move(callback), operation_id, read_dir_token,
+                              error, entries, entries_count,
+                              std::move(metrics_timer));
+}
+
+void SmbFileSystem::ProcessReadDirectoryResults(
+    storage::AsyncFileUtil::ReadDirectoryCallback callback,
+    OperationId operation_id,
+    int32_t read_dir_token,
+    smbprovider::ErrorType error,
+    const smbprovider::DirectoryEntryListProto& entries,
+    int entries_count,
+    base::ElapsedTimer metrics_timer) {
+  storage::AsyncFileUtil::EntryList entry_list;
+
+  if (error != smbprovider::ERROR_OPERATION_PENDING &&
+      error != smbprovider::ERROR_OK) {
+    callback.Run(TranslateToFileError(error), entry_list, false /* has_more */);
+  }
+
+  ConvertEntries(entries, &entry_list);
+  entries_count += entry_list.size();
+
+  const bool has_more = (error == smbprovider::ERROR_OPERATION_PENDING);
+
+  // Run |callback| with the next batch of results;
+  callback.Run(base::File::FILE_OK, entry_list, has_more);
+
+  if (has_more) {
+    // There are more directory entries to read.
+    ContinueReadDirectory(operation_id, read_dir_token, callback, entries_count,
+                          std::move(metrics_timer));
+    return;
+  }
+
+  // Read Directory is complete, record metrics.
+  RecordReadDirectoryCount(entries_count);
+  RecordReadDirectoryDuration(metrics_timer.Elapsed());
+}
+
 AbortCallback SmbFileSystem::HandleSyncRedundantGetMetadata(
     ProvidedFileSystemInterface::MetadataFieldMask fields,
     ProvidedFileSystemInterface::GetMetadataCallback callback) {
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.h b/chrome/browser/chromeos/smb_client/smb_file_system.h
index 528c5df..23929cb 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.h
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.h
@@ -207,6 +207,23 @@
                     int32_t copy_token,
                     storage::AsyncFileUtil::StatusCallback callback);
 
+  // Starts a ReadDirectory operation for |directory_path| with the OperationId
+  // |operation_id|.
+  void StartReadDirectory(
+      const base::FilePath& directory_path,
+      OperationId operation_id,
+      storage::AsyncFileUtil::ReadDirectoryCallback callback);
+
+  // Continues a ReadDirectory corresponding to |operation_id| and
+  // |read_dir_token|. |entries_count| and |metrics_timer| are used for metrics
+  // recording.
+  void ContinueReadDirectory(
+      OperationId operation_id,
+      int32_t read_dir_token,
+      storage::AsyncFileUtil::ReadDirectoryCallback callback,
+      int entires_count,
+      base::ElapsedTimer metrics_timer);
+
   void HandleRequestUnmountCallback(
       storage::AsyncFileUtil::StatusCallback callback,
       smbprovider::ErrorType error);
@@ -267,6 +284,32 @@
       int32_t copy_token,
       smbprovider::ErrorType error);
 
+  void HandleStartReadDirectoryCallback(
+      storage::AsyncFileUtil::ReadDirectoryCallback callback,
+      OperationId operation_id,
+      base::ElapsedTimer metrics_timer,
+      smbprovider::ErrorType error,
+      int32_t read_dir_token,
+      const smbprovider::DirectoryEntryListProto& entries);
+
+  void HandleContinueReadDirectoryCallback(
+      storage::AsyncFileUtil::ReadDirectoryCallback callback,
+      OperationId operation_id,
+      int32_t read_dir_token,
+      int entries_count,
+      base::ElapsedTimer metrics_timer,
+      smbprovider::ErrorType error,
+      const smbprovider::DirectoryEntryListProto& entries);
+
+  void ProcessReadDirectoryResults(
+      storage::AsyncFileUtil::ReadDirectoryCallback callback,
+      OperationId operation_id,
+      int32_t read_dir_token,
+      smbprovider::ErrorType error,
+      const smbprovider::DirectoryEntryListProto& entries,
+      int entries_count,
+      base::ElapsedTimer metrics_timer);
+
   int32_t GetMountId() const;
 
   SmbProviderClient* GetSmbProviderClient() const;
diff --git a/chrome/browser/content_settings/sound_content_setting_observer.cc b/chrome/browser/content_settings/sound_content_setting_observer.cc
index b42f03a..1cab9e1 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer.cc
@@ -138,10 +138,6 @@
   if (!mute && reason == TabMutedReason::MEDIA_CAPTURE)
     return;
 
-  // Do not unmute if we're muted due to audio indicator.
-  if (!mute && reason == TabMutedReason::AUDIO_INDICATOR)
-    return;
-
   // Do not override the decisions of an extension.
   if (reason == TabMutedReason::EXTENSION)
     return;
diff --git a/chrome/browser/content_settings/sound_content_setting_observer_unittest.cc b/chrome/browser/content_settings/sound_content_setting_observer_unittest.cc
index eb74d3a1..3ef95ad 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer_unittest.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer_unittest.cc
@@ -11,7 +11,6 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/recently_audible_helper.h"
-#include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/ukm/content/source_url_recorder.h"
@@ -211,19 +210,6 @@
   EXPECT_TRUE(web_contents()->IsAudioMuted());
 }
 
-TEST_F(SoundContentSettingObserverTest, DontUnmuteWhenMutedByAudioIndicator) {
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      switches::kEnableTabAudioMuting);
-  EXPECT_FALSE(web_contents()->IsAudioMuted());
-
-  SetMuteStateForReason(true, TabMutedReason::AUDIO_INDICATOR);
-  EXPECT_TRUE(web_contents()->IsAudioMuted());
-
-  // Navigating to a new URL should not unmute the tab muted by audio indicator.
-  NavigateAndCommit(GURL(kURL2));
-  EXPECT_TRUE(web_contents()->IsAudioMuted());
-}
-
 TEST_F(SoundContentSettingObserverTest, DontUnmuteChromeTabWhenMuted) {
   NavigateAndCommit(GURL(kChromeURL));
   EXPECT_FALSE(web_contents()->IsAudioMuted());
diff --git a/chrome/browser/devtools/devtools_eye_dropper.cc b/chrome/browser/devtools/devtools_eye_dropper.cc
index 3bb3aa8..3a72b4b 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.cc
+++ b/chrome/browser/devtools/devtools_eye_dropper.cc
@@ -23,9 +23,9 @@
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkColorSpaceXform.h"
 #include "third_party/skia/include/core/SkPaint.h"
 #include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkPixmap.h"
 #include "ui/gfx/geometry/size_conversions.h"
 
 DevToolsEyeDropper::DevToolsEyeDropper(content::WebContents* web_contents,
@@ -128,13 +128,9 @@
     }
 
     SkColor sk_color = frame_.getColor(last_cursor_x_, last_cursor_y_);
-    uint8_t rgba_color[4] = {
-        SkColorGetR(sk_color), SkColorGetG(sk_color), SkColorGetB(sk_color),
-        SkColorGetA(sk_color),
-    };
 
-    // The picked colors are expected to be sRGB. Create a color transform from
-    // |frame_|'s color space to sRGB.
+    // The picked colors are expected to be sRGB. Convert from |frame_|'s color
+    // space to sRGB.
     // TODO(ccameron): We don't actually know |frame_|'s color space, so just
     // use |host_|'s current display's color space. This will almost always be
     // the right color space, but is sloppy.
@@ -142,16 +138,18 @@
     content::ScreenInfo screen_info;
     host_->GetScreenInfo(&screen_info);
     gfx::ColorSpace frame_color_space = screen_info.color_space;
-    std::unique_ptr<SkColorSpaceXform> frame_color_space_to_srgb_xform =
-        SkColorSpaceXform::New(frame_color_space.ToSkColorSpace().get(),
-                               SkColorSpace::MakeSRGB().get());
-    if (frame_color_space_to_srgb_xform) {
-      bool xform_apply_result = frame_color_space_to_srgb_xform->apply(
-          SkColorSpaceXform::kRGBA_8888_ColorFormat, rgba_color,
-          SkColorSpaceXform::kRGBA_8888_ColorFormat, rgba_color, 1,
-          kUnpremul_SkAlphaType);
-      DCHECK(xform_apply_result);
-    }
+
+    SkPixmap pm(
+        SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType,
+                          frame_color_space.ToSkColorSpace()),
+        &sk_color, sizeof(sk_color));
+
+    uint8_t rgba_color[4];
+    bool ok = pm.readPixels(
+        SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType,
+                          SkColorSpace::MakeSRGB()),
+        rgba_color, sizeof(rgba_color));
+    DCHECK(ok);
 
     callback_.Run(rgba_color[0], rgba_color[1], rgba_color[2], rgba_color[3]);
   }
diff --git a/chrome/browser/download/download_history.cc b/chrome/browser/download/download_history.cc
index 3b5f2fa..3fe7a1b 100644
--- a/chrome/browser/download/download_history.cc
+++ b/chrome/browser/download/download_history.cc
@@ -35,6 +35,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/post_task.h"
+#include "build/build_config.h"
 #include "chrome/browser/download/download_crx_util.h"
 #include "components/download/public/common/download_features.h"
 #include "components/download/public/common/download_item.h"
@@ -313,11 +314,20 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(notifier_.GetManager());
 
+  int cancelled_download_cleared_from_history = 0;
   for (InfoVector::const_iterator it = infos->begin();
        it != infos->end(); ++it) {
     loading_id_ = history::ToContentDownloadId(it->id);
     download::DownloadItem::DownloadState history_download_state =
         history::ToContentDownloadState(it->state);
+#if defined(OS_ANDROID)
+    // Clean up cancelled download on Android.
+    if (history_download_state == download::DownloadItem::CANCELLED) {
+      ScheduleRemoveDownload(it->id);
+      ++cancelled_download_cleared_from_history;
+      continue;
+    }
+#endif  // defined(OS_ANDROID)
     download::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
         it->guid, loading_id_, it->current_path, it->target_path, it->url_chain,
         it->referrer_url, it->site_url, it->tab_url, it->tab_referrer_url,
@@ -351,6 +361,12 @@
     ++history_size_;
   }
 
+  if (cancelled_download_cleared_from_history > 0) {
+    UMA_HISTOGRAM_COUNTS_1000(
+        "MobileDownload.CancelledDownloadRemovedFromHistory",
+        cancelled_download_cleared_from_history);
+  }
+
   // Indicate that the history db is initialized.
   notifier_.GetManager()->PostInitialization(
       content::DownloadManager::DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB);
diff --git a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
index 19d5c616..08c2626a 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
@@ -303,8 +303,8 @@
   std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->tree_id));
   if (!rfh)
     return RespondNow(Error("unable to load tab"));
 
@@ -324,7 +324,7 @@
 AutomationInternalPerformActionFunction::ConvertToAXActionData(
     api::automation_internal::PerformAction::Params* params,
     ui::AXActionData* action) {
-  action->target_tree_id = params->args.tree_id;
+  action->target_tree_id = ui::AXTreeID::FromString(params->args.tree_id);
   action->source_extension_id = extension_id();
   action->target_node_id = params->args.automation_node_id;
   int* request_id = params->args.request_id.get();
@@ -477,7 +477,7 @@
   EXTENSION_FUNCTION_VALIDATE(params.get());
   ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
   ui::AXHostDelegate* delegate =
-      registry->GetHostDelegate(params->args.tree_id);
+      registry->GetHostDelegate(ui::AXTreeID::FromString(params->args.tree_id));
   if (delegate) {
 #if defined(USE_AURA)
     ui::AXActionData data;
@@ -491,8 +491,8 @@
                             " platform does not support desktop automation"));
 #endif  // defined(USE_AURA)
   }
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->args.tree_id));
   if (!rfh)
     return RespondNow(Error("Ignoring action on destroyed node"));
 
@@ -563,8 +563,8 @@
   std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->args.tree_id));
   if (!rfh) {
     return RespondNow(
         Error("domQuerySelector query sent on non-web or destroyed tree."));
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
index a36c624..8d1c434 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
@@ -151,6 +151,14 @@
   std::unique_ptr<cryptotoken_private::CanAppIdGetAttestation::Params> params =
       cryptotoken_private::CanAppIdGetAttestation::Params::Create(*args_);
   EXTENSION_FUNCTION_VALIDATE(params);
+
+  const GURL origin_url(params->options.origin);
+  if (!origin_url.is_valid()) {
+    return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage(
+        "Security origin * is not a valid URL", params->options.origin)));
+  }
+  const url::Origin origin(url::Origin::Create(origin_url));
+
   const std::string& app_id = params->options.app_id;
 
   // If the appId is permitted by the enterprise policy then no permission
@@ -194,7 +202,6 @@
   }
 
   // The created AttestationPermissionRequest deletes itself once complete.
-  const url::Origin origin(url::Origin::Create(app_id_url));
   permission_request_manager->AddRequest(NewAttestationPermissionRequest(
       origin,
       base::BindOnce(
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
index 519623a..0500f7e 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
@@ -208,6 +208,7 @@
     base::Value::DictStorage dict;
     dict.emplace("appId", std::make_unique<base::Value>(app_id));
     dict.emplace("tabId", std::make_unique<base::Value>(tab_id_));
+    dict.emplace("origin", std::make_unique<base::Value>(app_id));
     auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
     args->GetList().emplace_back(std::move(dict));
     auto args_list = base::ListValue::From(std::move(args));
diff --git a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc
index 2905518c..089c9f2 100644
--- a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc
+++ b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc
@@ -30,6 +30,7 @@
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/common/fileapi/file_system_types.h"
 #include "storage/common/fileapi/file_system_util.h"
+#include "url/origin.h"
 
 using content::BrowserContext;
 using content::BrowserThread;
@@ -335,7 +336,7 @@
       FROM_HERE, {BrowserThread::IO},
       BindOnce(
           &storage::QuotaManager::GetUsageAndQuotaForWebApps, quota_manager,
-          source_url().GetOrigin(),
+          url::Origin::Create(source_url()),
           storage::FileSystemTypeToQuotaStorageType(file_system_url.type()),
           Bind(&SyncFileSystemGetUsageAndQuotaFunction::DidGetUsageAndQuota,
                this)));
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 3929059b..f75bb02 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -54,7 +54,6 @@
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/window_sizer/window_sizer.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
-#include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/tabs.h"
 #include "chrome/common/extensions/api/windows.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -1313,23 +1312,11 @@
     tab_index = tab_strip->GetIndexOfWebContents(contents);
   }
 
-  if (params->update_properties.muted.get()) {
-    TabMutedResult tab_muted_result = chrome::SetTabAudioMuted(
-        contents, *params->update_properties.muted,
-        TabMutedReason::EXTENSION, extension()->id());
-
-    switch (tab_muted_result) {
-      case TabMutedResult::SUCCESS:
-        break;
-      case TabMutedResult::FAIL_NOT_ENABLED:
-        return RespondNow(Error(ErrorUtils::FormatErrorMessage(
-            tabs_constants::kCannotUpdateMuteDisabled,
-            base::IntToString(tab_id), ::switches::kEnableTabAudioMuting)));
-      case TabMutedResult::FAIL_TABCAPTURE:
-        return RespondNow(Error(ErrorUtils::FormatErrorMessage(
-            tabs_constants::kCannotUpdateMuteCaptured,
-            base::IntToString(tab_id))));
-    }
+  if (params->update_properties.muted.get() &&
+      !chrome::SetTabAudioMuted(contents, *params->update_properties.muted,
+                                TabMutedReason::EXTENSION, extension()->id())) {
+    return RespondNow(Error(ErrorUtils::FormatErrorMessage(
+        tabs_constants::kCannotUpdateMuteCaptured, base::IntToString(tab_id))));
   }
 
   if (params->update_properties.opener_tab_id.get()) {
diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.cc b/chrome/browser/extensions/api/tabs/tabs_constants.cc
index d4861be..a68b6ee0 100644
--- a/chrome/browser/extensions/api/tabs/tabs_constants.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_constants.cc
@@ -104,8 +104,6 @@
 const char kInvalidWindowTypeError[] = "Invalid value for type";
 const char kInvalidWindowStateError[] = "Invalid value for state";
 const char kScreenshotsDisabled[] = "Taking screenshots has been disabled";
-const char kCannotUpdateMuteDisabled[] =
-    "Failed to update mute state for tab *, --* must be enabled";
 const char kCannotUpdateMuteCaptured[] =
     "Cannot update mute state for tab *, tab has audio or video currently "
     "being captured";
diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.h b/chrome/browser/extensions/api/tabs/tabs_constants.h
index 101362e7..bfb4386 100644
--- a/chrome/browser/extensions/api/tabs/tabs_constants.h
+++ b/chrome/browser/extensions/api/tabs/tabs_constants.h
@@ -101,7 +101,6 @@
 extern const char kInvalidWindowTypeError[];
 extern const char kInvalidWindowStateError[];
 extern const char kScreenshotsDisabled[];
-extern const char kCannotUpdateMuteDisabled[];
 extern const char kCannotUpdateMuteCaptured[];
 extern const char kCannotDetermineLanguageOfUnloadedTab[];
 extern const char kMissingLockWindowFullscreenPrivatePermission[];
diff --git a/chrome/browser/extensions/extension_special_storage_policy.cc b/chrome/browser/extensions/extension_special_storage_policy.cc
index 579e4ad..419b80a14 100644
--- a/chrome/browser/extensions/extension_special_storage_policy.cc
+++ b/chrome/browser/extensions/extension_special_storage_policy.cc
@@ -35,6 +35,7 @@
 #include "extensions/common/permissions/permissions_data.h"
 #include "storage/browser/quota/quota_manager.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
+#include "url/origin.h"
 
 using content::BrowserThread;
 using extensions::APIPermission;
@@ -73,7 +74,8 @@
         FROM_HERE,
         base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO}),
         base::BindOnce(&storage::QuotaManager::GetUsageAndQuotaForWebApps,
-                       partition->GetQuotaManager(), launch_url,
+                       partition->GetQuotaManager(),
+                       url::Origin::Create(launch_url),
                        blink::mojom::StorageType::kPersistent,
                        base::Bind(&ReportQuotaUsage)));
   }
diff --git a/chrome/browser/extensions/extension_storage_monitor.cc b/chrome/browser/extensions/extension_storage_monitor.cc
index 7aa193d..4015427 100644
--- a/chrome/browser/extensions/extension_storage_monitor.cc
+++ b/chrome/browser/extensions/extension_storage_monitor.cc
@@ -41,6 +41,8 @@
 #include "ui/message_center/public/cpp/message_center_constants.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 using content::BrowserThread;
 
@@ -113,7 +115,7 @@
       ExtensionStorageMonitorIOHelper* io_helper,
       const std::string& extension_id,
       scoped_refptr<storage::QuotaManager> quota_manager,
-      const GURL& origin,
+      const url::Origin& origin,
       int64_t next_threshold,
       base::TimeDelta rate,
       bool should_uma)
@@ -181,7 +183,7 @@
   void StartObservingForExtension(
       scoped_refptr<storage::QuotaManager> quota_manager,
       const std::string& extension_id,
-      const GURL& site_url,
+      const url::Origin& site_origin,
       int64_t next_threshold,
       const base::TimeDelta& rate,
       bool should_uma) {
@@ -192,7 +194,7 @@
 
     storage_observers_[extension_id] =
         std::make_unique<SingleExtensionStorageObserver>(
-            this, extension_id, std::move(quota_manager), site_url.GetOrigin(),
+            this, extension_id, std::move(quota_manager), site_origin,
             next_threshold, rate, should_uma);
   }
 
@@ -498,9 +500,11 @@
   scoped_refptr<storage::QuotaManager> quota_manager(
       storage_partition->GetQuotaManager());
 
-  GURL storage_origin(site_url.GetOrigin());
-  if (extension->is_hosted_app())
-    storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
+  url::Origin storage_origin = url::Origin::Create(site_url);
+  if (extension->is_hosted_app()) {
+    storage_origin =
+        url::Origin::Create(AppLaunchInfo::GetLaunchWebURL(extension));
+  }
 
   // Don't give a threshold if we're not enforcing.
   int next_threshold =
diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index 887f2cad..4a307f1 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -476,12 +476,11 @@
 std::unique_ptr<api::tabs::MutedInfo> ExtensionTabUtil::CreateMutedInfo(
     content::WebContents* contents) {
   DCHECK(contents);
-  std::unique_ptr<api::tabs::MutedInfo> info(new api::tabs::MutedInfo);
+  auto info = std::make_unique<api::tabs::MutedInfo>();
   info->muted = contents->IsAudioMuted();
   switch (chrome::GetTabAudioMutedReason(contents)) {
     case TabMutedReason::NONE:
       break;
-    case TabMutedReason::AUDIO_INDICATOR:
     case TabMutedReason::CONTENT_SETTING:
     case TabMutedReason::CONTENT_SETTING_CHROME:
     case TabMutedReason::CONTEXT_MENU:
@@ -492,8 +491,9 @@
       break;
     case TabMutedReason::EXTENSION:
       info->reason = api::tabs::MUTED_INFO_REASON_EXTENSION;
-      info->extension_id.reset(
-          new std::string(chrome::GetExtensionIdForMutedTab(contents)));
+      info->extension_id = std::make_unique<std::string>(
+          LastMuteMetadata::FromWebContents(contents)->extension_id);
+      DCHECK(!info->extension_id->empty());
       break;
   }
   return info;
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc
index bde6240..b6f311fc 100644
--- a/chrome/browser/extensions/installed_loader.cc
+++ b/chrome/browser/extensions/installed_loader.cc
@@ -20,11 +20,13 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/load_error_reporter.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/chrome_manifest_url_handlers.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
+#include "content/public/common/url_constants.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
@@ -334,6 +336,10 @@
   RecordExtensionsMetrics();
 }
 
+void InstalledLoader::RecordExtensionsMetricsForTesting() {
+  RecordExtensionsMetrics();
+}
+
 void InstalledLoader::RecordExtensionsMetrics() {
   Profile* profile = extension_service_->profile();
 
@@ -538,6 +544,38 @@
 
     if (!ManifestURL::UpdatesFromGallery(extension))
       ++off_store_item_count;
+
+    ScriptingPermissionsModifier scripting_modifier(profile, extension);
+    // NOTE: CanAffectExtension() returns false in all cases when the
+    // RuntimeHostPermissions feature is disabled.
+    if (scripting_modifier.CanAffectExtension()) {
+      bool extension_has_withheld_hosts =
+          scripting_modifier.HasWithheldHostPermissions();
+      UMA_HISTOGRAM_BOOLEAN(
+          "Extensions.RuntimeHostPermissions.ExtensionHasWithheldHosts",
+          extension_has_withheld_hosts);
+      if (extension_has_withheld_hosts) {
+        // Record the number of granted hosts if and only if the extension
+        // has withheld host permissions. This lets us equate "0" granted
+        // hosts to "on click only".
+        size_t num_granted_hosts = 0;
+        for (const auto& pattern : extension->permissions_data()
+                                       ->active_permissions()
+                                       .effective_hosts()) {
+          // Ignore chrome:-scheme patterns (like chrome://favicon); these
+          // aren't withheld, and thus shouldn't be considered "granted".
+          if (pattern.scheme() != content::kChromeUIScheme)
+            ++num_granted_hosts;
+        }
+        // TODO(devlin): This only takes into account the granted hosts that
+        // were also requested by the extension (because it looks at the active
+        // permissions). We could potentially also record the granted hosts that
+        // were explicitly not requested.
+        UMA_HISTOGRAM_COUNTS_100(
+            "Extensions.RuntimeHostPermissions.GrantedHostCount",
+            num_granted_hosts);
+      }
+    }
   }
 
   const ExtensionSet& disabled_extensions =
diff --git a/chrome/browser/extensions/installed_loader.h b/chrome/browser/extensions/installed_loader.h
index 755e325..6d2714f 100644
--- a/chrome/browser/extensions/installed_loader.h
+++ b/chrome/browser/extensions/installed_loader.h
@@ -28,6 +28,10 @@
   // Loads all installed extensions (used by startup and testing code).
   void LoadAllExtensions();
 
+  // Allows tests to verify metrics without needing to go through
+  // LoadAllExtensions().
+  void RecordExtensionsMetricsForTesting();
+
  private:
   // Returns the flags that should be used with Extension::Create() for an
   // extension that is already installed.
diff --git a/chrome/browser/extensions/installed_loader_unittest.cc b/chrome/browser/extensions/installed_loader_unittest.cc
new file mode 100644
index 0000000..c4a31d8
--- /dev/null
+++ b/chrome/browser/extensions/installed_loader_unittest.cc
@@ -0,0 +1,146 @@
+// 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 "chrome/browser/extensions/installed_loader.h"
+
+#include "base/macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_service_test_base.h"
+#include "chrome/browser/extensions/permissions_updater.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
+#include "chrome/browser/profiles/profile.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_features.h"
+
+namespace extensions {
+
+namespace {
+
+constexpr const char kHasWithheldHostsHistogram[] =
+    "Extensions.RuntimeHostPermissions.ExtensionHasWithheldHosts";
+constexpr const char kGrantedHostCountHistogram[] =
+    "Extensions.RuntimeHostPermissions.GrantedHostCount";
+
+}  // namespace
+
+class InstalledLoaderUnitTest : public ExtensionServiceTestBase {
+ public:
+  InstalledLoaderUnitTest() {}
+  ~InstalledLoaderUnitTest() override = default;
+
+  void SetUp() override {
+    ExtensionServiceTestBase::SetUp();
+    InitializeEmptyExtensionService();
+  }
+
+  const Extension* AddExtension();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InstalledLoaderUnitTest);
+};
+
+const Extension* InstalledLoaderUnitTest::AddExtension() {
+  // Metrics aren't recorded for unpacked extensions, so we need to make sure
+  // the extension has an INTERNAL location.
+  constexpr Manifest::Location kManifestLocation = Manifest::INTERNAL;
+  scoped_refptr<const Extension> extension = ExtensionBuilder("test")
+                                                 .AddPermissions({"<all_urls>"})
+                                                 .SetLocation(kManifestLocation)
+                                                 .Build();
+  PermissionsUpdater updater(profile());
+  updater.InitializePermissions(extension.get());
+  updater.GrantActivePermissions(extension.get());
+  service()->AddExtension(extension.get());
+
+  return extension.get();
+}
+
+TEST_F(InstalledLoaderUnitTest,
+       RuntimeHostPermissions_Metrics_FeatureDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  AddExtension();
+
+  base::HistogramTester histograms;
+  InstalledLoader loader(service());
+  loader.RecordExtensionsMetricsForTesting();
+
+  // No metrics should be recorded when the feature is disabled.
+  histograms.ExpectTotalCount(kHasWithheldHostsHistogram, 0);
+  histograms.ExpectTotalCount(kGrantedHostCountHistogram, 0);
+}
+
+TEST_F(InstalledLoaderUnitTest,
+       RuntimeHostPermissions_Metrics_HasWithheldHosts_False) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  AddExtension();
+
+  base::HistogramTester histograms;
+  InstalledLoader loader(service());
+  loader.RecordExtensionsMetricsForTesting();
+
+  // The extension didn't have withheld hosts, so a single `false` record
+  // should be present.
+  histograms.ExpectUniqueSample(kHasWithheldHostsHistogram, false, 1);
+  // Granted host counts should only be recorded if the extension had withheld
+  // hosts.
+  histograms.ExpectTotalCount(kGrantedHostCountHistogram, 0);
+}
+
+TEST_F(InstalledLoaderUnitTest,
+       RuntimeHostPermissions_Metrics_HasWithheldHosts_True) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  const Extension* extension = AddExtension();
+  ScriptingPermissionsModifier(profile(), extension)
+      .SetWithholdHostPermissions(true);
+
+  base::HistogramTester histograms;
+  InstalledLoader loader(service());
+  loader.RecordExtensionsMetricsForTesting();
+
+  // The extension had withheld hosts, so a single `true` record should be
+  // present.
+  histograms.ExpectUniqueSample(kHasWithheldHostsHistogram, true, 1);
+  // There were no granted hosts, so a single `0` record should be present.
+  constexpr int kGrantedHostCount = 0;
+  constexpr int kEmitCount = 1;
+  histograms.ExpectUniqueSample(kGrantedHostCountHistogram, kGrantedHostCount,
+                                kEmitCount);
+}
+
+TEST_F(InstalledLoaderUnitTest,
+       RuntimeHostPermissions_Metrics_GrantedHostCount) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  const Extension* extension = AddExtension();
+  ScriptingPermissionsModifier modifier(profile(), extension);
+  modifier.SetWithholdHostPermissions(true);
+  modifier.GrantHostPermission(GURL("https://example.com/"));
+  modifier.GrantHostPermission(GURL("https://chromium.org/"));
+
+  base::HistogramTester histograms;
+  InstalledLoader loader(service());
+  loader.RecordExtensionsMetricsForTesting();
+
+  histograms.ExpectUniqueSample(kHasWithheldHostsHistogram, true, 1);
+  // The extension had granted hosts, so a single `2` record should be present.
+  constexpr int kGrantedHostCount = 2;
+  constexpr int kEmitCount = 1;
+  histograms.ExpectUniqueSample(kGrantedHostCountHistogram, kGrantedHostCount,
+                                kEmitCount);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index a787ba6b..0b6ae8f 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1276,12 +1276,19 @@
     "Elides the path, query, and ref of suggested URLs in the Omnibox "
     "dropdown.";
 
-const char kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsName[] =
-    "Omnibox UI Hide Steady-State URL Scheme and Trivial Subdomains";
-const char kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsDescription[] =
-    "In the Omnibox, hide the scheme and trivial subdomains from steady state "
-    "displayed URLs. Hidden portions are restored during editing. For Mac, "
-    "this flag will have no effect unless MacViews is enabled.";
+const char kOmniboxUIHideSteadyStateUrlSchemeName[] =
+    "Omnibox UI Hide Steady-State URL Scheme";
+const char kOmniboxUIHideSteadyStateUrlSchemeDescription[] =
+    "In the omnibox, hide the scheme from steady state displayed URLs. It is "
+    "restored during editing. For Mac, this flag will have no effect unless "
+    "MacViews is enabled.";
+
+const char kOmniboxUIHideSteadyStateUrlTrivialSubdomainsName[] =
+    "Omnibox UI Hide Steady-State URL Trivial Subdomains";
+const char kOmniboxUIHideSteadyStateUrlTrivialSubdomainsDescription[] =
+    "In the omnibox, hide trivial subdomains from steady state displayed URLs. "
+    "Hidden portions are restored during editing. For Mac, this flag will have "
+    "no effect unless MacViews is enabled.";
 
 const char kOmniboxUIJogTextfieldOnPopupName[] =
     "Omnibox UI Jog Textfield on Popup";
@@ -1803,12 +1810,6 @@
     "keyboard shortcuts and have the events routed directly to the website "
     "when in fullscreen mode.";
 
-const char kTabAudioMutingName[] = "Tab audio muting UI control";
-const char kTabAudioMutingDescription[] =
-    "When enabled, the audio indicators in the tab strip double as tab audio "
-    "mute controls. This also adds commands in the tab context menu for "
-    "quickly muting multiple selected tabs.";
-
 const char kTabsInCbdName[] = "Enable tabs for the Clear Browsing Data dialog.";
 const char kTabsInCbdDescription[] =
     "Enables a basic and an advanced tab for the Clear Browsing Data dialog.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 3474b4a..1ee435a 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -795,8 +795,11 @@
 extern const char kOmniboxUIElideSuggestionUrlAfterHostName[];
 extern const char kOmniboxUIElideSuggestionUrlAfterHostDescription[];
 
-extern const char kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsName[];
-extern const char kOmniboxUIHideSteadyStateUrlSchemeAndSubdomainsDescription[];
+extern const char kOmniboxUIHideSteadyStateUrlSchemeName[];
+extern const char kOmniboxUIHideSteadyStateUrlSchemeDescription[];
+
+extern const char kOmniboxUIHideSteadyStateUrlTrivialSubdomainsName[];
+extern const char kOmniboxUIHideSteadyStateUrlTrivialSubdomainsDescription[];
 
 extern const char kOmniboxUIJogTextfieldOnPopupName[];
 extern const char kOmniboxUIJogTextfieldOnPopupDescription[];
@@ -1099,9 +1102,6 @@
 extern const char kSysInternalsName[];
 extern const char kSysInternalsDescription[];
 
-extern const char kTabAudioMutingName[];
-extern const char kTabAudioMutingDescription[];
-
 extern const char kTabModalJsDialogName[];
 extern const char kTabModalJsDialogDescription[];
 
diff --git a/chrome/browser/google/google_brand.cc b/chrome/browser/google/google_brand.cc
index f5399e7..611ab30 100644
--- a/chrome/browser/google/google_brand.cc
+++ b/chrome/browser/google/google_brand.cc
@@ -93,6 +93,15 @@
 
 #endif
 
+bool GetRlzBrand(std::string* brand) {
+#if defined(OS_CHROMEOS)
+  brand->assign(google_brand::chromeos::GetRlzBrand());
+  return true;
+#else
+  return GetBrand(brand);
+#endif
+}
+
 bool IsOrganic(const std::string& brand) {
 #if defined(OS_MACOSX)
   if (brand.empty()) {
diff --git a/chrome/browser/google/google_brand.h b/chrome/browser/google/google_brand.h
index 6b37a80..d1dbe8e 100644
--- a/chrome/browser/google/google_brand.h
+++ b/chrome/browser/google/google_brand.h
@@ -26,6 +26,11 @@
 // install. Returns false if the information is not available.
 bool GetReactivationBrand(std::string* brand);
 
+// The same as GetBrand() on non-ChromeOS platforms. On ChromeOS, returns a
+// variation of the brand code based on enrollment type.
+// TODO(crbug.com/888725): Rename this to GetBrand and replace the current one.
+bool GetRlzBrand(std::string* brand);
+
 // True if a build is strictly organic, according to its brand code.
 bool IsOrganic(const std::string& brand);
 
diff --git a/chrome/browser/google/google_brand_chromeos.cc b/chrome/browser/google/google_brand_chromeos.cc
index c42aab9..3e81fb92 100644
--- a/chrome/browser/google/google_brand_chromeos.cc
+++ b/chrome/browser/google/google_brand_chromeos.cc
@@ -9,8 +9,11 @@
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/google/google_brand_code_map_chromeos.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/system/statistics_provider.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 
@@ -63,6 +66,18 @@
   return g_browser_process->local_state()->GetString(prefs::kRLZBrand);
 }
 
+std::string GetRlzBrand() {
+  policy::BrowserPolicyConnectorChromeOS* connector =
+      g_browser_process->platform_part()->browser_policy_connector_chromeos();
+  base::Optional<policy::MarketSegment> market_segment;
+  if (connector->IsEnterpriseManaged())
+    market_segment = connector->GetEnterpriseMarketSegment();
+  // The rlz brand code may change over time (e.g. when device goes from
+  // unenrolled to enrolled status in OOBE). Prefer not to save it in pref to
+  // avoid using outdated value.
+  return GetRlzBrandCode(GetBrand(), market_segment);
+}
+
 void InitBrand(const base::Closure& callback) {
   ::chromeos::system::StatisticsProvider* provider =
       ::chromeos::system::StatisticsProvider::GetInstance();
diff --git a/chrome/browser/google/google_brand_chromeos.h b/chrome/browser/google/google_brand_chromeos.h
index 51e1afd..beb2146 100644
--- a/chrome/browser/google/google_brand_chromeos.h
+++ b/chrome/browser/google/google_brand_chromeos.h
@@ -16,6 +16,10 @@
 // partner. Returns empty string if the information is not available.
 std::string GetBrand();
 
+// Returns a variation of the brand code based on enrollment type.
+// TODO(crbug.com/888725): Rename this to GetBrand and replace the current one.
+std::string GetRlzBrand();
+
 // Clears brand code for the current session (not persisted through browser
 // restart). Future calls to GetBrand() will return an empty string.
 void ClearBrandForCurrentSession();
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.cc b/chrome/browser/google/google_brand_code_map_chromeos.cc
new file mode 100644
index 0000000..18ff316
--- /dev/null
+++ b/chrome/browser/google/google_brand_code_map_chromeos.cc
@@ -0,0 +1,48 @@
+// 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 "chrome/browser/google/google_brand_code_map_chromeos.h"
+
+#include "base/containers/flat_map.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
+
+namespace google_brand {
+namespace chromeos {
+
+std::string GetRlzBrandCode(
+    const std::string& static_brand_code,
+    base::Optional<policy::MarketSegment> market_segment) {
+  struct BrandCodeValueEntry {
+    const char* unenrolled_brand_code;
+    const char* education_enrolled_brand_code;
+    const char* enterprise_enrolled_brand_code;
+  };
+  static const base::NoDestructor<
+      base::flat_map<std::string, BrandCodeValueEntry>>
+      kBrandCodeMap({{"NPEC", {"BMGD", "YETH", "XAWJ"}}});
+
+  const auto it = kBrandCodeMap->find(static_brand_code);
+  if (it == kBrandCodeMap->end())
+    return static_brand_code;
+  const auto& entry = it->second;
+  // An empty value indicates the device is not enrolled.
+  if (!market_segment.has_value())
+    return entry.unenrolled_brand_code;
+
+  switch (market_segment.value()) {
+    case policy::MarketSegment::EDUCATION:
+      return entry.education_enrolled_brand_code;
+    case policy::MarketSegment::ENTERPRISE:
+    case policy::MarketSegment::UNKNOWN:
+      // If the device is enrolled but market segment is unknown, it's fine to
+      // treat it as enterprise enrolled.
+      return entry.enterprise_enrolled_brand_code;
+  }
+  NOTREACHED();
+  return static_brand_code;
+}
+
+}  // namespace chromeos
+}  // namespace google_brand
\ No newline at end of file
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.h b/chrome/browser/google/google_brand_code_map_chromeos.h
new file mode 100644
index 0000000..32fbc0a
--- /dev/null
+++ b/chrome/browser/google/google_brand_code_map_chromeos.h
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_GOOGLE_GOOGLE_BRAND_CODE_MAP_CHROMEOS_H_
+#define CHROME_BROWSER_GOOGLE_GOOGLE_BRAND_CODE_MAP_CHROMEOS_H_
+
+#include <string>
+
+#include "base/optional.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+
+namespace google_brand {
+namespace chromeos {
+
+// Returns |static_brand_code| if it is not found in the map. Otherwise, returns
+// a variation of the brand code based on |market_segment| (an empty value
+// indicates the device is not enrolled).
+std::string GetRlzBrandCode(
+    const std::string& static_brand_code,
+    base::Optional<policy::MarketSegment> market_segment);
+
+}  // namespace chromeos
+}  // namespace google_brand
+
+#endif  // CHROME_BROWSER_GOOGLE_GOOGLE_BRAND_CODE_MAP_CHROMEOS_H_
\ No newline at end of file
diff --git a/chrome/browser/google/google_brand_code_map_chromeos_unittest.cc b/chrome/browser/google/google_brand_code_map_chromeos_unittest.cc
new file mode 100644
index 0000000..ec9bf4d
--- /dev/null
+++ b/chrome/browser/google/google_brand_code_map_chromeos_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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 "chrome/browser/google/google_brand_code_map_chromeos.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using google_brand::chromeos::GetRlzBrandCode;
+
+TEST(GoogleBrandCodeMapTest, GetRlzBrandCode) {
+  // If the static brand code is in the map, |GetRlzBrandCode| returns a
+  // variation based on the enrollment status and market segment.
+  EXPECT_EQ("BMGD", GetRlzBrandCode("NPEC", base::nullopt));
+  EXPECT_EQ("YETH", GetRlzBrandCode("NPEC", policy::MarketSegment::EDUCATION));
+  EXPECT_EQ("XAWJ", GetRlzBrandCode("NPEC", policy::MarketSegment::ENTERPRISE));
+  EXPECT_EQ("XAWJ", GetRlzBrandCode("NPEC", policy::MarketSegment::UNKNOWN));
+
+  // If the static brand code is not in the map, |GetRlzBrandCode| always
+  // returns the static brand code.
+  EXPECT_EQ("AAAA", GetRlzBrandCode("AAAA", base::nullopt));
+  EXPECT_EQ("AAAA", GetRlzBrandCode("AAAA", policy::MarketSegment::UNKNOWN));
+  EXPECT_EQ("AAAA", GetRlzBrandCode("AAAA", policy::MarketSegment::EDUCATION));
+  EXPECT_EQ("AAAA", GetRlzBrandCode("AAAA", policy::MarketSegment::ENTERPRISE));
+}
\ No newline at end of file
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 682d549b..aca8d59 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -20,6 +20,7 @@
 #include "chrome/common/chrome_content_client.h"
 #include "chrome/common/chrome_paths_internal.h"
 #include "chrome/common/pref_names.h"
+#include "components/certificate_transparency/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -38,6 +39,19 @@
 #include "extensions/common/constants.h"
 #endif
 
+namespace {
+
+std::vector<std::string> TranslateStringArray(const base::ListValue* list) {
+  std::vector<std::string> strings;
+  for (const base::Value& value : *list) {
+    DCHECK(value.is_string());
+    strings.push_back(value.GetString());
+  }
+  return strings;
+}
+
+}  // namespace
+
 ProfileNetworkContextService::ProfileNetworkContextService(Profile* profile)
     : profile_(profile), proxy_config_monitor_(profile) {
   PrefService* profile_prefs = profile->GetPrefs();
@@ -62,6 +76,27 @@
 
   // Observe content settings so they can be synced to the network service.
   HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this);
+
+  pref_change_registrar_.Init(profile_prefs);
+
+  // When any of the following CT preferences change, we schedule an update
+  // to aggregate the actual update using a |ct_policy_update_timer_|.
+  pref_change_registrar_.Add(
+      certificate_transparency::prefs::kCTRequiredHosts,
+      base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+                          base::Unretained(this)));
+  pref_change_registrar_.Add(
+      certificate_transparency::prefs::kCTExcludedHosts,
+      base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+                          base::Unretained(this)));
+  pref_change_registrar_.Add(
+      certificate_transparency::prefs::kCTExcludedSPKIs,
+      base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+                          base::Unretained(this)));
+  pref_change_registrar_.Add(
+      certificate_transparency::prefs::kCTExcludedLegacySPKIs,
+      base::BindRepeating(&ProfileNetworkContextService::ScheduleUpdateCTPolicy,
+                          base::Unretained(this)));
 }
 
 ProfileNetworkContextService::~ProfileNetworkContextService() {}
@@ -73,7 +108,11 @@
   network::mojom::NetworkContextPtr network_context;
   PartitionInfo partition_info(in_memory, relative_partition_path);
 
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    content::GetNetworkService()->CreateNetworkContext(
+        MakeRequest(&network_context),
+        CreateNetworkContextParams(in_memory, relative_partition_path));
+  } else {
     // The corresponding |profile_io_data_network_contexts_| may already be
     // initialized if SetUpProfileIODataNetworkContext was called first.
     auto iter = profile_io_data_network_contexts_.find(partition_info);
@@ -91,12 +130,11 @@
       // and NetworkContexts can't be destroyed without destroying the profile.
       profile_io_data_network_contexts_.erase(iter);
     }
-    return network_context;
   }
 
-  content::GetNetworkService()->CreateNetworkContext(
-      MakeRequest(&network_context),
-      CreateNetworkContextParams(in_memory, relative_partition_path));
+  std::vector<network::mojom::NetworkContext*> contexts{network_context.get()};
+  UpdateCTPolicyForContexts(contexts);
+
   return network_context;
 }
 
@@ -193,6 +231,51 @@
           enable_referrers_.GetValue()));
 }
 
+void ProfileNetworkContextService::UpdateCTPolicyForContexts(
+    const std::vector<network::mojom::NetworkContext*>& contexts) {
+  auto* prefs = profile_->GetPrefs();
+  const base::ListValue* ct_required =
+      prefs->GetList(certificate_transparency::prefs::kCTRequiredHosts);
+  const base::ListValue* ct_excluded =
+      prefs->GetList(certificate_transparency::prefs::kCTExcludedHosts);
+  const base::ListValue* ct_excluded_spkis =
+      prefs->GetList(certificate_transparency::prefs::kCTExcludedSPKIs);
+  const base::ListValue* ct_excluded_legacy_spkis =
+      prefs->GetList(certificate_transparency::prefs::kCTExcludedLegacySPKIs);
+
+  std::vector<std::string> required(TranslateStringArray(ct_required));
+  std::vector<std::string> excluded(TranslateStringArray(ct_excluded));
+  std::vector<std::string> excluded_spkis(
+      TranslateStringArray(ct_excluded_spkis));
+  std::vector<std::string> excluded_legacy_spkis(
+      TranslateStringArray(ct_excluded_legacy_spkis));
+
+  for (auto* context : contexts) {
+    context->SetCTPolicy(required, excluded, excluded_spkis,
+                         excluded_legacy_spkis);
+  }
+}
+
+void ProfileNetworkContextService::UpdateCTPolicy() {
+  std::vector<network::mojom::NetworkContext*> contexts;
+  content::BrowserContext::ForEachStoragePartition(
+      profile_,
+      base::BindRepeating(
+          [](std::vector<network::mojom::NetworkContext*>* contexts_ptr,
+             content::StoragePartition* storage_partition) {
+            contexts_ptr->push_back(storage_partition->GetNetworkContext());
+          },
+          &contexts));
+
+  UpdateCTPolicyForContexts(contexts);
+}
+
+void ProfileNetworkContextService::ScheduleUpdateCTPolicy() {
+  ct_policy_update_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(0),
+                                this,
+                                &ProfileNetworkContextService::UpdateCTPolicy);
+}
+
 void ProfileNetworkContextService::FlushProxyConfigMonitorForTesting() {
   proxy_config_monitor_.FlushForTesting();
 }
diff --git a/chrome/browser/net/profile_network_context_service.h b/chrome/browser/net/profile_network_context_service.h
index b56a3c42..4656c25f 100644
--- a/chrome/browser/net/profile_network_context_service.h
+++ b/chrome/browser/net/profile_network_context_service.h
@@ -9,9 +9,11 @@
 
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/net/proxy_config_monitor.h"
 #include "components/content_settings/core/browser/content_settings_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_member.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 
@@ -86,6 +88,15 @@
 
   void UpdateReferrersEnabled();
 
+  // Update the CTPolicy for the given NetworkContexts.
+  void UpdateCTPolicyForContexts(
+      const std::vector<network::mojom::NetworkContext*>& contexts);
+
+  // Update the CTPolicy for the all of profiles_'s NetworkContexts.
+  void UpdateCTPolicy();
+
+  void ScheduleUpdateCTPolicy();
+
   // Creates parameters for the NetworkContext. Use |in_memory| instead of
   // |profile_->IsOffTheRecord()| because sometimes normal profiles want off the
   // record partitions (e.g. for webview tag).
@@ -123,8 +134,11 @@
   BooleanPrefMember quic_allowed_;
   StringPrefMember pref_accept_language_;
   BooleanPrefMember block_third_party_cookies_;
-
   BooleanPrefMember enable_referrers_;
+  PrefChangeRegistrar pref_change_registrar_;
+
+  // Used to post schedule CT policy updates
+  base::OneShotTimer ct_policy_update_timer_;
 
   DISALLOW_COPY_AND_ASSIGN(ProfileNetworkContextService);
 };
diff --git a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
index b310a40..a54eead 100644
--- a/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
+#include "content/public/common/process_type.h"
 #include "net/http/http_response_headers.h"
 #include "ui/base/page_transition_types.h"
 
@@ -83,6 +84,9 @@
     "PageLoad.PaintTiming.NavigationToFirstContentfulPaint";
 const char kBackgroundHistogramFirstContentfulPaint[] =
     "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.Background";
+const char kHistogramFirstContenfulPaintInitiatingProcess[] =
+    "PageLoad.Internal.PaintTiming.NavigationToFirstContenfulPaint."
+    "InitiatingProcess";
 const char kHistogramFirstMeaningfulPaint[] =
     "PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint";
 const char kHistogramTimeToInteractive[] =
@@ -391,6 +395,12 @@
     PAGE_LOAD_HISTOGRAM(internal::kHistogramParseStartToFirstContentfulPaint,
                         timing.paint_timing->first_contentful_paint.value() -
                             timing.parse_timing->parse_start.value());
+    UMA_HISTOGRAM_ENUMERATION(
+        internal::kHistogramFirstContenfulPaintInitiatingProcess,
+        info.user_initiated_info.browser_initiated
+            ? content::PROCESS_TYPE_BROWSER
+            : content::PROCESS_TYPE_RENDERER,
+        content::PROCESS_TYPE_CONTENT_END);
 
     if (was_no_store_main_resource_) {
       PAGE_LOAD_HISTOGRAM(internal::kHistogramFirstContentfulPaintNoStore,
diff --git a/chrome/browser/password_manager/account_chooser_dialog_android.cc b/chrome/browser/password_manager/account_chooser_dialog_android.cc
index 8a164bc..9fcf08b 100644
--- a/chrome/browser/password_manager/account_chooser_dialog_android.cc
+++ b/chrome/browser/password_manager/account_chooser_dialog_android.cc
@@ -121,15 +121,9 @@
 
 void AccountChooserDialogAndroid::ShowDialog() {
   JNIEnv* env = AttachCurrentThread();
-  bool is_smartlock_branding_enabled =
-      password_bubble_experiment::IsSmartLockUser(
-          ProfileSyncServiceFactory::GetForProfile(
-              Profile::FromBrowserContext(web_contents_->GetBrowserContext())));
-  base::string16 title;
-  gfx::Range title_link_range = gfx::Range();
-  GetAccountChooserDialogTitleTextAndLinkRange(
-      is_smartlock_branding_enabled, local_credentials_forms().size() > 1,
-      &title, &title_link_range);
+  base::string16 title =
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE);
+  ;
   gfx::NativeWindow native_window = web_contents_->GetTopLevelNativeWindow();
   ScopedJavaLocalRef<jobjectArray> java_credentials_array =
       CreateNativeCredentialArray(env, local_credentials_forms().size());
@@ -145,8 +139,7 @@
   dialog_jobject_.Reset(Java_AccountChooserDialog_createAndShowAccountChooser(
       env, native_window->GetJavaObject(), reinterpret_cast<intptr_t>(this),
       java_credentials_array,
-      base::android::ConvertUTF16ToJavaString(env, title),
-      title_link_range.start(), title_link_range.end(),
+      base::android::ConvertUTF16ToJavaString(env, title), 0, 0,
       base::android::ConvertUTF8ToJavaString(env, origin),
       base::android::ConvertUTF16ToJavaString(env, signin_button)));
   network::mojom::URLLoaderFactory* loader_factory =
diff --git a/chrome/browser/password_manager/auto_signin_first_run_dialog_android.cc b/chrome/browser/password_manager/auto_signin_first_run_dialog_android.cc
index 2ef8700..cbae41c 100644
--- a/chrome/browser/password_manager/auto_signin_first_run_dialog_android.cc
+++ b/chrome/browser/password_manager/auto_signin_first_run_dialog_android.cc
@@ -10,8 +10,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
+#include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/browser_sync/profile_sync_service.h"
 #include "components/password_manager/core/browser/password_bubble_experiment.h"
 #include "components/password_manager/core/browser/password_manager_constants.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
@@ -45,16 +45,9 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
 
-  bool is_smartlock_branding_enabled =
-      password_bubble_experiment::IsSmartLockUser(
-          ProfileSyncServiceFactory::GetForProfile(profile));
-  base::string16 explanation;
-  gfx::Range explanation_link_range = gfx::Range();
-  GetBrandedTextAndLinkRange(
-      is_smartlock_branding_enabled,
-      IDS_AUTO_SIGNIN_FIRST_RUN_SMART_LOCK_TEXT,
-      IDS_AUTO_SIGNIN_FIRST_RUN_TEXT, &explanation,
-      &explanation_link_range);
+  base::string16 explanation = l10n_util::GetStringFUTF16(
+      IDS_AUTO_SIGNIN_FIRST_RUN_TEXT,
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_TITLE_BRAND));
   gfx::NativeWindow native_window = web_contents_->GetTopLevelNativeWindow();
   base::android::ScopedJavaGlobalRef<jobject> java_dialog_global;
   base::string16 message = l10n_util::GetStringUTF16(
@@ -69,8 +62,7 @@
   dialog_jobject_.Reset(Java_AutoSigninFirstRunDialog_createAndShowDialog(
       env, native_window->GetJavaObject(), reinterpret_cast<intptr_t>(this),
       base::android::ConvertUTF16ToJavaString(env, message),
-      base::android::ConvertUTF16ToJavaString(env, explanation),
-      explanation_link_range.start(), explanation_link_range.end(),
+      base::android::ConvertUTF16ToJavaString(env, explanation), 0, 0,
       base::android::ConvertUTF16ToJavaString(env, ok_button_text),
       base::android::ConvertUTF16ToJavaString(env, turn_off_button_text)));
 }
diff --git a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
index 16ffbc9..d723390 100644
--- a/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
+++ b/chrome/browser/password_manager/save_password_infobar_delegate_android.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/ui/android/infobars/save_password_infobar.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
+#include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/infobars/core/infobar.h"
@@ -55,16 +56,14 @@
       form_to_save_(std::move(form_to_save)),
       infobar_response_(password_manager::metrics_util::NO_DIRECT_INTERACTION) {
   base::string16 message;
-  gfx::Range message_link_range = gfx::Range();
   PasswordTitleType type =
       form_to_save_->GetPendingCredentials().federation_origin.opaque()
           ? PasswordTitleType::SAVE_PASSWORD
           : PasswordTitleType::SAVE_ACCOUNT;
-  GetSavePasswordDialogTitleTextAndLinkRange(
-      web_contents->GetVisibleURL(), form_to_save_->GetOrigin(),
-      is_smartlock_branding_enabled, type, &message, &message_link_range);
+  GetSavePasswordDialogTitleTextAndLinkRange(web_contents->GetVisibleURL(),
+                                             form_to_save_->GetOrigin(), type,
+                                             &message);
   SetMessage(message);
-  SetMessageLinkRange(message_link_range);
 
   if (type == PasswordTitleType::SAVE_PASSWORD &&
       is_smartlock_branding_enabled) {
diff --git a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
index 8778124..b7bf90a 100644
--- a/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
+++ b/chrome/browser/password_manager/update_password_infobar_delegate_android.cc
@@ -70,13 +70,10 @@
     : infobar_response_(password_manager::metrics_util::NO_DIRECT_INTERACTION),
       is_smartlock_branding_enabled_(is_smartlock_branding_enabled) {
   base::string16 message;
-  gfx::Range message_link_range = gfx::Range();
   GetSavePasswordDialogTitleTextAndLinkRange(
       web_contents->GetVisibleURL(), form_to_update->GetOrigin(),
-      is_smartlock_branding_enabled, PasswordTitleType::UPDATE_PASSWORD,
-      &message, &message_link_range);
+      PasswordTitleType::UPDATE_PASSWORD, &message);
   SetMessage(message);
-  SetMessageLinkRange(message_link_range);
   if (is_smartlock_branding_enabled)
     SetDetailsMessage(l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD_FOOTER));
 
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index 2276f1c..0da36c88 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
+#include "chrome/browser/signin/signin_util.h"
 #include "components/account_id/account_id.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
@@ -190,7 +191,8 @@
   UserCloudPolicyManager* manager = policy_manager();
   // Allow the user to signout again.
   if (manager)
-    signin_manager()->ProhibitSignout(false);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, true);
+
   UserPolicySigninServiceBase::ShutdownUserCloudPolicyManager();
 }
 
@@ -245,7 +247,7 @@
 void UserPolicySigninService::ProhibitSignoutIfNeeded() {
   if (policy_manager()->IsClientRegistered()) {
     DVLOG(1) << "User is registered for policy - prohibiting signout";
-    signin_manager()->ProhibitSignout(true);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, false);
   }
 }
 
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
index eedad53..0d64cfc 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/signin/fake_signin_manager_builder.h"
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/signin/signin_util.h"
 #include "chrome/browser/signin/test_signin_client_builder.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
@@ -846,7 +847,7 @@
 
   EXPECT_TRUE(manager_->IsClientRegistered());
 #if !defined(OS_ANDROID)
-  EXPECT_TRUE(signin_manager_->IsSignoutProhibited());
+  EXPECT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile_.get()));
 #endif
 
   // Kick off another policy fetch.
@@ -868,7 +869,7 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(manager_->IsClientRegistered());
 #if !defined(OS_ANDROID)
-  EXPECT_FALSE(signin_manager_->IsSignoutProhibited());
+  EXPECT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile_.get()));
 #endif
 }
 
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index a86a1f23..4898978 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -188,6 +188,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_constants.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/content_paths.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/result_codes.h"
@@ -4587,6 +4588,28 @@
   EXPECT_FALSE(IsShowingInterstitial(tab));
   EXPECT_EQ(base::UTF8ToUTF16("OK"),
             browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
+
+  // Now ensure that this setting still works after a network process crash.
+  if (!base::FeatureList::IsEnabled(network::features::kNetworkService) ||
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kSingleProcess) ||
+      base::FeatureList::IsEnabled(features::kNetworkServiceInProcess)) {
+    return;
+  }
+
+  ui_test_utils::NavigateToURL(browser(),
+                               https_server_ok.GetURL("/title1.html"));
+
+  SimulateNetworkServiceCrash();
+  SetShouldRequireCTForTesting(&required);
+
+  ui_test_utils::NavigateToURL(browser(),
+                               https_server_ok.GetURL("/simple.html"));
+
+  // There should be no interstitial after the page loads.
+  EXPECT_FALSE(IsShowingInterstitial(tab));
+  EXPECT_EQ(base::UTF8ToUTF16("OK"),
+            browser()->tab_strip_model()->GetActiveWebContents()->GetTitle());
 }
 
 IN_PROC_BROWSER_TEST_F(PolicyTest,
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 5012debc..730a4ef 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -101,7 +101,6 @@
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/chromium_strings.h"
 #include "components/bookmarks/browser/bookmark_model.h"
-#include "components/certificate_transparency/pref_names.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
@@ -616,21 +615,6 @@
       base::Bind(&ProfileImpl::UpdateIsEphemeralInStorage,
                  base::Unretained(this)));
 
-  // When any of the following CT preferences change, we schedule an update
-  // to aggregate the actual update using a |ct_policy_update_timer_|.
-  pref_change_registrar_.Add(
-      certificate_transparency::prefs::kCTRequiredHosts,
-      base::Bind(&ProfileImpl::ScheduleUpdateCTPolicy, base::Unretained(this)));
-  pref_change_registrar_.Add(
-      certificate_transparency::prefs::kCTExcludedHosts,
-      base::Bind(&ProfileImpl::ScheduleUpdateCTPolicy, base::Unretained(this)));
-  pref_change_registrar_.Add(
-      certificate_transparency::prefs::kCTExcludedSPKIs,
-      base::Bind(&ProfileImpl::ScheduleUpdateCTPolicy, base::Unretained(this)));
-  pref_change_registrar_.Add(
-      certificate_transparency::prefs::kCTExcludedLegacySPKIs,
-      base::Bind(&ProfileImpl::ScheduleUpdateCTPolicy, base::Unretained(this)));
-
   media_device_id_salt_ = new MediaDeviceIDSalt(prefs_.get());
 
   // It would be nice to use PathService for fetching this directory, but
@@ -744,8 +728,6 @@
 
   content::URLDataSource::Add(this,
                               std::make_unique<PrefsInternalsSource>(this));
-
-  ScheduleUpdateCTPolicy();
 }
 
 base::FilePath ProfileImpl::last_selected_directory() {
@@ -1459,36 +1441,6 @@
   }
 }
 
-std::vector<std::string> TranslateStringArray(const base::ListValue* list) {
-  std::vector<std::string> strings;
-  for (const base::Value& value : *list) {
-    DCHECK(value.is_string());
-    strings.push_back(value.GetString());
-  }
-  return strings;
-}
-
-void ProfileImpl::ScheduleUpdateCTPolicy() {
-  ct_policy_update_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(0),
-                                this, &ProfileImpl::UpdateCTPolicy);
-}
-
-void ProfileImpl::UpdateCTPolicy() {
-  const base::ListValue* ct_required =
-      prefs_->GetList(certificate_transparency::prefs::kCTRequiredHosts);
-  const base::ListValue* ct_excluded =
-      prefs_->GetList(certificate_transparency::prefs::kCTExcludedHosts);
-  const base::ListValue* ct_excluded_spkis =
-      prefs_->GetList(certificate_transparency::prefs::kCTExcludedSPKIs);
-  const base::ListValue* ct_excluded_legacy_spkis =
-      prefs_->GetList(certificate_transparency::prefs::kCTExcludedLegacySPKIs);
-
-  GetDefaultStoragePartition(this)->GetNetworkContext()->SetCTPolicy(
-      TranslateStringArray(ct_required), TranslateStringArray(ct_excluded),
-      TranslateStringArray(ct_excluded_spkis),
-      TranslateStringArray(ct_excluded_legacy_spkis));
-}
-
 // Gets the media cache parameters from the command line. |cache_path| will be
 // set to the user provided path, or will not be touched if there is not an
 // argument. |max_size| will be the user provided value or zero by default.
diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h
index b7cbdf1..ca54ebc 100644
--- a/chrome/browser/profiles/profile_impl.h
+++ b/chrome/browser/profiles/profile_impl.h
@@ -14,7 +14,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "chrome/browser/net/reporting_permissions_checker.h"
 #include "chrome/browser/profiles/profile.h"
@@ -191,9 +190,6 @@
   void UpdateNameInStorage();
   void UpdateAvatarInStorage();
   void UpdateIsEphemeralInStorage();
-  void UpdateCTPolicy();
-
-  void ScheduleUpdateCTPolicy();
 
   void GetMediaCacheParameters(base::FilePath* cache_path, int* max_size);
 
@@ -290,9 +286,6 @@
 
   ReportingPermissionsCheckerFactory reporting_permissions_checker_factory_;
 
-  // Used to post schedule CT policy updates
-  base::OneShotTimer ct_policy_update_timer_;
-
   DISALLOW_COPY_AND_ASSIGN(ProfileImpl);
 };
 
diff --git a/chrome/browser/profiles/profiles_state.cc b/chrome/browser/profiles/profiles_state.cc
index 8122aca..b58cc4af 100644
--- a/chrome/browser/profiles/profiles_state.cc
+++ b/chrome/browser/profiles/profiles_state.cc
@@ -36,7 +36,6 @@
 #include "chrome/browser/signin/signin_error_controller_factory.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
-#include "ui/gfx/text_elider.h"
 #endif
 
 namespace profiles {
@@ -113,19 +112,6 @@
 }
 
 #if !defined(OS_CHROMEOS)
-base::string16 GetAvatarButtonTextForProfile(Profile* profile) {
-  const int kMaxCharactersToDisplay = 15;
-  base::string16 name = GetAvatarNameForProfile(profile->GetPath());
-  name = gfx::TruncateString(name,
-                             kMaxCharactersToDisplay,
-                             gfx::CHARACTER_BREAK);
-  if (profile->IsLegacySupervised()) {
-    name = l10n_util::GetStringFUTF16(
-        IDS_LEGACY_SUPERVISED_USER_NEW_AVATAR_LABEL, name);
-  }
-  return name;
-}
-
 base::string16 GetProfileSwitcherTextForItem(const AvatarMenu::Item& item) {
   if (item.legacy_supervised) {
     return l10n_util::GetStringFUTF16(
diff --git a/chrome/browser/profiles/profiles_state.h b/chrome/browser/profiles/profiles_state.h
index bc66518..89be5774 100644
--- a/chrome/browser/profiles/profiles_state.h
+++ b/chrome/browser/profiles/profiles_state.h
@@ -53,11 +53,6 @@
 base::string16 GetAvatarNameForProfile(const base::FilePath& profile_path);
 
 #if !defined(OS_CHROMEOS)
-// Returns the string to use in the avatar button for the specified profile.
-// This is essentially the name returned by GetAvatarNameForProfile, but it
-// may be elided and contain an indicator for supervised users.
-base::string16 GetAvatarButtonTextForProfile(Profile* profile);
-
 // Returns the string to use in the fast user switcher menu for the specified
 // menu item. Adds a supervision indicator to the profile name if appropriate.
 base::string16 GetProfileSwitcherTextForItem(const AvatarMenu::Item& item);
diff --git a/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc b/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
index 600e49e..ad7437e5 100644
--- a/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
+++ b/chrome/browser/renderer_host/render_process_host_chrome_browsertest.cc
@@ -201,7 +201,8 @@
     else
       EXPECT_EQ(tab2->GetMainFrame()->GetProcess(), rph2);
 
-    // Create another WebUI tab.  It should share the process with omnibox.
+    // Create another WebUI tab.  Each WebUI tab should get a separate process
+    // because of origin locking.
     // Note: intentionally create this tab after the normal tabs to exercise bug
     // 43448 where extension and WebUI tabs could get combined into normal
     // renderers.
@@ -211,11 +212,12 @@
     ::ShowSingletonTab(browser(), history);
     observer3.Wait();
     tab_count++;
+    host_count++;
     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
     tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
     EXPECT_EQ(tab2->GetURL(), GURL(history));
     EXPECT_EQ(host_count, RenderProcessHostCount());
-    EXPECT_EQ(tab2->GetMainFrame()->GetProcess(), rph1);
+    EXPECT_NE(tab2->GetMainFrame()->GetProcess(), rph1);
 
     // Create an extension tab.  It should be in its own process.
     GURL extension_url("chrome-extension://" + extension->id());
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index 20c5b59..034f556 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -922,11 +922,15 @@
 }
 
 IN_PROC_BROWSER_TEST_F(TabManagerTest, ProactiveFastShutdownSharedTabProcess) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
   // Set max renderers to 1 before opening tabs to force running out of
   // processes and for both these tabs to share a renderer.
   content::RenderProcessHost::SetMaxRendererProcessCount(1);
-  OpenTwoTabs(GURL(chrome::kChromeUIAboutURL),
-              GURL(chrome::kChromeUICreditsURL));
+  OpenTwoTabs(embedded_test_server()->GetURL("a.com", "/title1.html"),
+              embedded_test_server()->GetURL("a.com", "/title2.html"));
+  EXPECT_EQ(tsm()->GetWebContentsAt(0)->GetMainFrame()->GetProcess(),
+            tsm()->GetWebContentsAt(1)->GetMainFrame()->GetProcess());
 
   // The Tab Manager will not be able to fast-kill either of the tabs since they
   // share the same process regardless of the discard reason. No unsafe attempts
@@ -941,12 +945,15 @@
 }
 
 IN_PROC_BROWSER_TEST_F(TabManagerTest, UrgentFastShutdownSharedTabProcess) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
   // Set max renderers to 1 before opening tabs to force running out of
   // processes and for both these tabs to share a renderer.
   content::RenderProcessHost::SetMaxRendererProcessCount(1);
-  // Disable the protection of recent tabs.
-  OpenTwoTabs(GURL(chrome::kChromeUIAboutURL),
-              GURL(chrome::kChromeUICreditsURL));
+  OpenTwoTabs(embedded_test_server()->GetURL("a.com", "/title1.html"),
+              embedded_test_server()->GetURL("a.com", "/title2.html"));
+  EXPECT_EQ(tsm()->GetWebContentsAt(0)->GetMainFrame()->GetProcess(),
+            tsm()->GetWebContentsAt(1)->GetMainFrame()->GetProcess());
 
   // Advance time so everything is urgent discardable.
   test_clock_.Advance(kBackgroundUrgentProtectionTime);
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index a4fdaaa..a16a24e 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -1761,3 +1761,18 @@
     });
   });
 });
+
+TEST_F('BackgroundTest', 'ListName', function() {
+  var mockFeedback = this.createMockFeedback();
+  this.runWithLoadedTree(function() {/*
+    <div id="_md-chips-wrapper-76" tabindex="-1" class="md-chips md-readonly" aria-setsize="4" aria-label="Favorite Sports" role="list" aria-describedby="chipsNote">
+      <div role="listitem">Baseball</div>
+      <div role="listitem">Hockey</div>
+      <div role="listitem">Lacrosse</div>
+      <div role="listitem">Football</div>
+    </div>
+  */}, function(root) {
+    mockFeedback.expectSpeech('Favorite Sports')
+        .replay();
+  });
+});
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index 8f7ca2e..26aa071 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -459,8 +459,8 @@
     },
     list: {
       enter: `$role @@list_with_items($countChildren(listItem))`,
-      speak: `$descendants $role @@list_with_items($countChildren(listItem))
-          $description $state`
+      speak: `$nameFromNode $descendants $role
+          @@list_with_items($countChildren(listItem)) $description $state`
     },
     listBox: {
       enter: `$nameFromNode
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/BUILD.gn b/chrome/browser/resources/chromeos/multidevice_setup/BUILD.gn
index cac0a67..0b624c9 100644
--- a/chrome/browser/resources/chromeos/multidevice_setup/BUILD.gn
+++ b/chrome/browser/resources/chromeos/multidevice_setup/BUILD.gn
@@ -6,14 +6,34 @@
 
 js_type_check("closure_compile") {
   deps = [
-    ":multidevice_setup_dialog",
+    ":multidevice_setup_post_oobe",
+    ":post_oobe_delegate",
   ]
 }
 
-js_library("multidevice_setup_dialog") {
+js_library("multidevice_setup_post_oobe") {
   deps = [
-    "//ui/webui/resources/cr_components/chromeos/multidevice_setup:multidevice_setup",
-    "//ui/webui/resources/cr_components/chromeos/multidevice_setup:ui_mode",
-    "//ui/webui/resources/js:util",
+    ":post_oobe_delegate",
+  ]
+}
+
+js_library("post_oobe_delegate") {
+  deps = [
+    "//ui/webui/resources/cr_components/chromeos/multidevice_setup:mojo_api",
+    "//ui/webui/resources/cr_components/chromeos/multidevice_setup:multidevice_setup_delegate",
+    "//ui/webui/resources/js:cr",
+  ]
+
+  extra_deps = [
+    "//chromeos/services/device_sync/public/mojom:mojom_js",
+    "//chromeos/services/multidevice_setup/public/mojom:mojom_js",
+    "//mojo/public/mojom/base:base_js",
+  ]
+
+  externs_list = [
+    "$root_gen_dir/chromeos/services/device_sync/public/mojom/device_sync.mojom.externs.js",
+    "$root_gen_dir/chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.externs.js",
+    "$root_gen_dir/mojo/public/mojom/base/time.mojom.externs.js",
+    "$externs_path/mojo.js",
   ]
 }
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.html b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.html
index 3ba9f86..59fd688c 100644
--- a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.html
+++ b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.html
@@ -16,13 +16,11 @@
   </style>
 </head>
 <body>
-  <multidevice-setup id="main-element"></multidevice-setup>
   <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="import" href="chrome://resources/html/util.html">
   <link rel="import" href="i18n_setup.html">
-  <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html">
-  <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_mode.html">
+  <link rel="import" href="multidevice_setup_post_oobe.html">
+  <multidevice-setup-post-oobe id="main-element"></multidevice-setup-post-oobe>
 </body>
-<script src="multidevice_setup_dialog.js"></script>
 </html>
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.js b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.js
deleted file mode 100644
index d0c68e8..0000000
--- a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_dialog.js
+++ /dev/null
@@ -1,11 +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.
-
-function closeUi() {
-  console.log('Closing Unified MultiDevice Setup WebUI');
-  chrome.send('dialogClose');
-}
-
-$('main-element').uiMode = multidevice_setup.UiMode.POST_OOBE;
-$('main-element').addEventListener('setup-exited', () => closeUi());
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html
new file mode 100644
index 0000000..92081b2
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html
@@ -0,0 +1,37 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
+<link rel="import" href="post_oobe_delegate.html">
+
+<dom-module id="multidevice-setup-post-oobe">
+  <template>
+    <style include="multidevice-setup-shared">
+      #backward-button,
+      #forward-button {
+        align-items: center;
+        display: flex;
+        justify-content: center;
+        text-transform: none;
+      }
+    </style>
+    <multidevice-setup delegate="[[delegate_]]"
+        on-setup-exited="onExitRequested_"
+        forward-button-text="{{forwardButtonText_}}"
+        forward-button-disabled="{{forwardButtonDisabled_}}"
+        backward-button-text="{{backwardButtonText_}}">
+      <paper-button id="backward-button"
+          slot="backward-button" class="cancel-button">
+        [[backwardButtonText_]]
+      </paper-button>
+      <paper-button id="forward-button"
+          slot="forward-button" class="action-button"
+          disabled$="[[forwardButtonDisabled_]]">
+        [[forwardButtonText_]]
+      </paper-button>
+    </multidevice-setup>
+  </template>
+  <script src="multidevice_setup_post_oobe.js">
+  </script>
+</dom-module>
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.js b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.js
new file mode 100644
index 0000000..3af2027
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.js
@@ -0,0 +1,52 @@
+// 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.
+
+/**
+ * MultiDevice setup flow which is shown after OOBE has completed.
+ */
+Polymer({
+  is: 'multidevice-setup-post-oobe',
+
+  properties: {
+    /** @private {!multidevice_setup.MultiDeviceSetupDelegate} */
+    delegate_: Object,
+
+    /**
+     * Text to be shown on the forward navigation button.
+     * @private {string|undefined}
+     */
+    forwardButtonText: {
+      type: String,
+      value: '',
+    },
+
+    /**
+     * Whether the forward button should be disabled.
+     * @private
+     */
+    forwardButtonDisabled_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /**
+     * Text to be shown on the backward navigation button.
+     * @private {string|undefined}
+     */
+    backwardButtonText_: {
+      type: String,
+      value: '',
+    },
+  },
+
+  /** @override */
+  attached: function() {
+    this.delegate_ = new multidevice_setup.PostOobeDelegate();
+  },
+
+  /** @private */
+  onExitRequested_: function() {
+    chrome.send('dialogClose');
+  },
+});
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd
index 191cce3..81ae952 100644
--- a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd
+++ b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd
@@ -20,8 +20,17 @@
                  flattenhtml="true"
                  allowexternalscript="true"
                  type="chrome_html" />
-      <structure name="IDR_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_DIALOG_JS"
-                 file="multidevice_setup_dialog.js"
+      <structure name="IDR_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_POST_OOBE_HTML"
+                 file="multidevice_setup_post_oobe.html"
+                 type="chrome_html" />
+      <structure name="IDR_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_POST_OOBE_JS"
+                 file="multidevice_setup_post_oobe.js"
+                 type="chrome_html" />
+      <structure name="IDR_MULTIDEVICE_SETUP_POST_OOBE_DELEGATE_HTML"
+                 file="post_oobe_delegate.html"
+                 type="chrome_html" />
+      <structure name="IDR_MULTIDEVICE_SETUP_POST_OOBE_DELEGATE_JS"
+                 file="post_oobe_delegate.js"
                  type="chrome_html" />
     </structures>
   </release>
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.html b/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.html
new file mode 100644
index 0000000..1fc839f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.html
@@ -0,0 +1,3 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/mojo_api.html">
+<script src="post_oobe_delegate.js"></script>
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.js b/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.js
new file mode 100644
index 0000000..2bb55720
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_setup/post_oobe_delegate.js
@@ -0,0 +1,37 @@
+// 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.
+
+cr.define('multidevice_setup', function() {
+  /** @implements {multidevice_setup.MultiDeviceSetupDelegate} */
+  class PostOobeDelegate {
+    /** @override */
+    isPasswordRequiredToSetHost() {
+      return true;
+    }
+
+    /** @override */
+    setHostDevice(hostDeviceId, opt_authToken) {
+      // An authentication token is required to set the host device post-OOBE.
+      assert(!!opt_authToken);
+
+      // Note: A cast is needed here because currently all Mojo functions which
+      // return a promise are typed only as {Promise}. The setHostDevice()
+      // function always returns a {!Promise<{success: boolean}>} (see
+      // multidevice_setup.mojom).
+      return /** @type {!Promise<{success: boolean}>} */ (
+          multidevice_setup.MojoInterfaceProviderImpl.getInstance()
+              .getInterfacePtr()
+              .setHostDevice(hostDeviceId, opt_authToken));
+    }
+
+    /** @override */
+    shouldExitSetupFlowAfterSettingHost() {
+      return false;
+    }
+  }
+
+  return {
+    PostOobeDelegate: PostOobeDelegate,
+  };
+});
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
index a90080e..a5d7bae8 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
@@ -409,6 +409,7 @@
 .v2 .dialog-body {
   display: flex;
   height: 100%;
+  min-height: 0;
 }
 
 .v2 .dialog-topbar {
diff --git a/chrome/browser/resources/cryptotoken/enroller.js b/chrome/browser/resources/cryptotoken/enroller.js
index 4bc7bb0..3721acc 100644
--- a/chrome/browser/resources/cryptotoken/enroller.js
+++ b/chrome/browser/resources/cryptotoken/enroller.js
@@ -388,6 +388,12 @@
     sendResponseOnce(sentResponse, closeable, response, sendResponse);
   }
 
+  var sender = createSenderFromMessageSender(messageSender);
+  if (!sender) {
+    sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
+    return null;
+  }
+
   async function getRegistrationData(
       appId, enrollChallenge, registrationData, opt_clientData) {
     var isDirect = true;
@@ -397,7 +403,10 @@
     } else if (chrome.cryptotokenPrivate != null) {
       isDirect = await(new Promise((resolve, reject) => {
         chrome.cryptotokenPrivate.canAppIdGetAttestation(
-            {'appId': appId, 'tabId': messageSender.tab.id}, resolve);
+            {'appId': appId,
+             'tabId': messageSender.tab.id,
+             'origin': sender.origin,
+            }, resolve);
       }));
     }
 
@@ -452,11 +461,6 @@
     sendErrorResponse({errorCode: ErrorCodes.TIMEOUT});
   }
 
-  var sender = createSenderFromMessageSender(messageSender);
-  if (!sender) {
-    sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
-    return null;
-  }
   if (sender.origin.indexOf('http://') == 0 && !HTTP_ORIGINS_ALLOWED) {
     sendErrorResponse({errorCode: ErrorCodes.BAD_REQUEST});
     return null;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 8ac4660..762ae75 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -288,11 +288,13 @@
  * Returns a timeout that can be executed early.
  * @param {!Function} timeout The timeout function.
  * @param {number} delay The timeout delay.
+ * @param {Object} previousContainer The pre-existing notification container.
  * @return {Object}
  */
-function createExecutableTimeout(timeout, delay) {
+function createExecutableTimeout(timeout, delay, previousContainer) {
   let timeoutId = window.setTimeout(timeout, delay);
   return {
+    previousContainer: previousContainer,
     clear: () => {
       window.clearTimeout(timeoutId);
     },
@@ -700,9 +702,13 @@
  * @param {!Element} notificationContainer The notification container element.
  */
 function floatUpNotification(notification, notificationContainer) {
-  // Hide any pre-existing notification.
+  // Hide pre-existing notification if it was different type. Clear timeout and
+  // replace it with the new timeout and new message if it was the same type.
   if (delayedHideNotification) {
-    delayedHideNotification.trigger();
+    if (delayedHideNotification.previousContainer === notificationContainer)
+      delayedHideNotification.clear();
+    else
+      delayedHideNotification.trigger();
     delayedHideNotification = null;
   }
 
@@ -716,7 +722,7 @@
   // Automatically hide the notification after a period of time.
   delayedHideNotification = createExecutableTimeout(() => {
     floatDownNotification(notification, notificationContainer);
-  }, NOTIFICATION_TIMEOUT);
+  }, NOTIFICATION_TIMEOUT, notificationContainer);
 }
 
 
diff --git a/chrome/browser/resources/settings/a11y_page/BUILD.gn b/chrome/browser/resources/settings/a11y_page/BUILD.gn
index 7759dba..a5a8c33 100644
--- a/chrome/browser/resources/settings/a11y_page/BUILD.gn
+++ b/chrome/browser/resources/settings/a11y_page/BUILD.gn
@@ -34,7 +34,7 @@
   deps = [
     ":externs",
     "..:route",
-    "../device_page:display_size_slider",
+    "../controls:settings_slider",
     "../languages_page:languages_browser_proxy",
     "../settings_page:settings_animated_pages",
     "//ui/webui/resources/js:i18n_behavior",
diff --git a/chrome/browser/resources/settings/a11y_page/tts_subpage.html b/chrome/browser/resources/settings/a11y_page/tts_subpage.html
index 47e2e5ab..2eb88372 100644
--- a/chrome/browser/resources/settings/a11y_page/tts_subpage.html
+++ b/chrome/browser/resources/settings/a11y_page/tts_subpage.html
@@ -6,7 +6,7 @@
 <link rel="import" href="chrome://resources/html/md_select_css.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-<link rel="import" href="../device_page/display_size_slider.html">
+<link rel="import" href="../controls/settings_slider.html">
 <link rel="import" href="../i18n_setup.html">
 <link rel="import" href="../languages_page/languages_browser_proxy.html">
 <link rel="import" href="../settings_shared_css.html">
@@ -39,33 +39,33 @@
     <h2>$i18n{textToSpeechProperties}</h2>
     <div class="settings-box first">
       <div class="start" id="rate">$i18n{textToSpeechRate}</div>
-      <display-size-slider
+      <settings-slider show-markers
           pref="{{prefs.settings.tts.speech_rate}}"
           ticks="[[speechRateTicks_()]]"
-          min-label="$i18n{textToSpeechRateMinimumLabel}"
-          max-label="$i18n{textToSpeechRateMaximumLabel}"
+          label-min="$i18n{textToSpeechRateMinimumLabel}"
+          label-max="$i18n{textToSpeechRateMaximumLabel}"
           aria-describedby="rate">
-      </display-size-slider>
+      </settings-slider>
     </div>
     <div class="settings-box continuation">
       <div class="start" id="pitch">$i18n{textToSpeechPitch}</div>
-      <display-size-slider
+      <settings-slider show-markers
           pref="{{prefs.settings.tts.speech_pitch}}"
           ticks="[[speechPitchTicks_()]]"
-          min-label="$i18n{textToSpeechPitchMinimumLabel}"
-          max-label="$i18n{textToSpeechPitchMaximumLabel}"
+          label-min="$i18n{textToSpeechPitchMinimumLabel}"
+          label-max="$i18n{textToSpeechPitchMaximumLabel}"
           aria-describedby="pitch">
-      </display-size-slider>
+      </settings-slider>
     </div>
     <div class="settings-box continuation">
       <div class="start" id="volume">$i18n{textToSpeechVolume}</div>
-      <display-size-slider
+      <settings-slider show-markers
           pref="{{prefs.settings.tts.speech_volume}}"
           ticks="[[speechVolumeTicks_()]]"
-          min-label="$i18n{textToSpeechVolumeMinimumLabel}"
-          max-label="$i18n{textToSpeechVolumeMaximumLabel}"
+          label-min="$i18n{textToSpeechVolumeMinimumLabel}"
+          label-max="$i18n{textToSpeechVolumeMaximumLabel}"
           aria-describedby="volume">
-      </display-size-slider>
+      </settings-slider>
     </div>
 
     <h2>$i18n{textToSpeechPreviewHeading}</h2>
diff --git a/chrome/browser/resources/settings/a11y_page/tts_subpage.js b/chrome/browser/resources/settings/a11y_page/tts_subpage.js
index 0e27875..d2377f1 100644
--- a/chrome/browser/resources/settings/a11y_page/tts_subpage.js
+++ b/chrome/browser/resources/settings/a11y_page/tts_subpage.js
@@ -75,7 +75,7 @@
   /**
    * Ticks for the Speech Rate slider. Non-linear as we expect people
    * to want more control near 1.0.
-   * @return Array<SliderTick>
+   * @return Array<cr_slider.SliderTick>
    * @private
    */
   speechRateTicks_: function() {
@@ -91,7 +91,7 @@
   /**
    * Ticks for the Speech Pitch slider. Valid pitches are between 0 and 2,
    * exclusive of 0.
-   * @return Array<SliderTick>
+   * @return Array<cr_slider.SliderTick>
    * @private
    */
   speechPitchTicks_: function() {
@@ -104,7 +104,7 @@
    * Ticks for the Speech Volume slider. Valid volumes are between 0 and
    * 1 (100%), but volumes lower than .2 are excluded as being too quiet.
    * The values are linear between .2 and 1.0.
-   * @return Array<SliderTick>
+   * @return Array<cr_slider.SliderTick>
    * @private
    */
   speechVolumeTicks_: function() {
@@ -116,7 +116,7 @@
   /**
    * Initializes i18n labels for ticks arrays.
    * @param {number} tick The value to make a tick for.
-   * @return {SliderTick}
+   * @return {cr_slider.SliderTick}
    * @private
    */
   initTick_: function(tick) {
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
index f335b74..3953ab06 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
+++ b/chrome/browser/resources/settings/appearance_page/appearance_fonts_page.html
@@ -16,7 +16,7 @@
       <div class="start">$i18n{fontSize}</div>
       <settings-slider id="sizeSlider"
           pref="{{prefs.webkit.webprefs.default_font_size}}"
-          tick-values="[[fontSizeRange_]]"
+          ticks="[[fontSizeRange_]]"
           label-min="$i18n{tiny}" label-max="$i18n{huge}">
       </settings-slider>
     </div>
@@ -33,7 +33,7 @@
       </div>
       <settings-slider id="minimumSizeSlider"
           pref="{{prefs.webkit.webprefs.minimum_font_size}}"
-          tick-values="[[minimumFontSizeRange_]]"
+          ticks="[[minimumFontSizeRange_]]"
           label-min="$i18n{tiny}" label-max="$i18n{huge}">
       </settings-slider>
     </div>
diff --git a/chrome/browser/resources/settings/controls/settings_slider.html b/chrome/browser/resources/settings/controls/settings_slider.html
index f58423c9..d6046aa 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.html
+++ b/chrome/browser/resources/settings/controls/settings_slider.html
@@ -8,11 +8,8 @@
   <template>
     <style>
       :host {
-        --calculated-paper-slider-height: var(--paper-slider-height, 2px);
         display: inline-flex;
-        /* Counteract the margin on #sliderContainer in paper-slider.html. */
-        margin-inline-end:
-            calc(-15px - var(--calculated-paper-slider-height) / 2);
+        margin-inline-end: -16px;
       }
 
       cr-policy-pref-indicator {
@@ -55,9 +52,8 @@
       <cr-policy-pref-indicator pref="[[pref]]"></cr-policy-pref-indicator>
     </template>
     <div class="outer">
-      <cr-slider id="slider" disabled$="[[disableSlider_]]" snaps
-          on-immediate-value-changed="onSliderChanged_" max="[[max]]"
-          min="[[min]]">
+      <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
+          on-value-changed="onSliderChanged_" max="[[max]]" min="[[min]]">
       </cr-slider>
       <div id="labels" disabled$="[[disableSlider_]]">
         <div id="label-begin">[[labelMin]]</div>
diff --git a/chrome/browser/resources/settings/controls/settings_slider.js b/chrome/browser/resources/settings/controls/settings_slider.js
index 5131b2d..d9f79075 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.js
+++ b/chrome/browser/resources/settings/controls/settings_slider.js
@@ -7,9 +7,6 @@
  * settings-slider wraps a paper-slider. It maps the slider's values from a
  * linear UI range to a range of real values.  When |value| does not map exactly
  * to a tick mark, it interpolates to the nearest tick.
- *
- * Unlike paper-slider, there is no distinction between value and
- * immediateValue; when either changes, the |value| property is updated.
  */
 Polymer({
   is: 'settings-slider',
@@ -20,16 +17,19 @@
     /** @type {!chrome.settingsPrivate.PrefObject} */
     pref: Object,
 
-    /** @type {!Array<number>} Values corresponding to each tick. */
-    tickValues: {
+    /**
+     * Values corresponding to each tick.
+     * @type {!Array<cr_slider.SliderTick>|!Array<number>}
+     */
+    ticks: {
       type: Array,
       value: () => [],
     },
 
     /**
      * A scale factor used to support fractional pref values since paper-slider
-     * only supports integers. This is not compatible with |tickValues|,
-     * i.e. if |scale| is not 1 then |tickValues| must be empty.
+     * only supports integers. This is not compatible with |ticks|,
+     * i.e. if |scale| is not 1 then |ticks| must be empty.
      */
     scale: {
       type: Number,
@@ -46,30 +46,55 @@
 
     disabled: Boolean,
 
+    showMarkers: Boolean,
+
     /** @private */
     disableSlider_: {
-      computed: 'computeDisableSlider_(pref.*, disabled)',
+      computed: 'computeDisableSlider_(pref.*, disabled, ticks.*)',
       type: Boolean,
     },
+
+    loaded_: Boolean,
   },
 
   observers: [
-    'valueChanged_(pref.*, tickValues.*)',
+    'valueChanged_(pref.*, ticks.*, loaded_)',
   ],
 
+  attached: function() {
+    this.loaded_ = true;
+  },
+
+  /**
+   * @param {number|cr_slider.SliderTick} tick
+   * @return {number|undefined}
+   */
+  getTickValue_: function(tick) {
+    return typeof tick == 'object' ? tick.value : tick;
+  },
+
+  /**
+   * @param {number} index
+   * @return {number|undefined}
+   * @private
+   */
+  getTickValueAtIndex_: function(index) {
+    return this.getTickValue_(this.ticks[index]);
+  },
+
   /**
    * Sets the |pref.value| property to the value corresponding to the knob
    * position after a user action.
    * @private
    */
   onSliderChanged_: function() {
-    const sliderValue = isNaN(this.$.slider.immediateValue) ?
-        this.$.slider.value :
-        this.$.slider.immediateValue;
+    if (!this.loaded_)
+      return;
+    const sliderValue = this.$.slider.value;
 
     let newValue;
-    if (this.tickValues && this.tickValues.length > 0)
-      newValue = this.tickValues[sliderValue];
+    if (this.ticks && this.ticks.length > 0)
+      newValue = this.getTickValueAtIndex_(sliderValue);
     else
       newValue = sliderValue / this.scale;
 
@@ -88,70 +113,48 @@
    * @private
    */
   valueChanged_: function() {
-    if (this.pref == undefined)
+    if (this.pref == undefined || !this.loaded_)
       return;
 
-    // If |tickValues| is empty, simply set current value to the slider.
-    if (this.tickValues.length == 0) {
-      this.$.slider.value =
-          /** @type {number} */ (this.pref.value) * this.scale;
+    // First update the slider settings if |ticks| was set.
+    const numTicks = this.ticks.length;
+    if (numTicks == 1) {
+      this.$.slider.disabled = true;
       return;
     }
-    assert(this.scale == 1);
 
-    // First update the slider settings if |tickValues| was set.
-    const numTicks = Math.max(1, this.tickValues.length);
-    this.$.slider.max = numTicks - 1;
+    const prefValue = /** @type {number} */ (this.pref.value);
+    // If |ticks| is empty, simply set current value to the slider.
+    if (numTicks == 0) {
+      this.$.slider.value = prefValue * this.scale;
+      return;
+    }
+
+    assert(this.scale == 1);
     // Limit the number of ticks to 10 to keep the slider from looking too busy.
     const MAX_TICKS = 10;
-    this.$.slider.snaps = numTicks < MAX_TICKS;
-    this.$.slider.maxMarkers = numTicks < MAX_TICKS ? numTicks : 0;
+    this.$.slider.markerCount =
+        (this.showMarkers || numTicks <= MAX_TICKS) ? numTicks : 0;
 
-    if (this.$.slider.dragging && this.tickValues.length > 0 &&
-        this.pref.value != this.tickValues[this.$.slider.immediateValue]) {
+    const tickValue = this.getTickValueAtIndex_(this.$.slider.value);
+    if (this.$.slider.dragging && this.pref.value != tickValue) {
       // The value changed outside settings-slider but we're still holding the
       // knob, so set the value back to where the knob was.
       // Async so we don't confuse Polymer's data binding.
-      this.async(function() {
-        const newValue = this.tickValues[this.$.slider.immediateValue];
-        this.set('pref.value', newValue);
+      this.async(() => {
+        this.set('pref.value', tickValue);
       });
       return;
     }
 
     // Convert from the public |value| to the slider index (where the knob
     // should be positioned on the slider).
-    let sliderIndex = this.tickValues.length > 0 ?
-        this.tickValues.indexOf(/** @type {number} */ (this.pref.value)) :
-        0;
-    if (sliderIndex == -1) {
-      // No exact match.
-      sliderIndex = this.findNearestIndex_(
-          this.tickValues,
-          /** @type {number} */ (this.pref.value));
-    }
-    this.$.slider.value = sliderIndex;
-  },
-
-  /**
-   * Returns the index of the item in |arr| closest to |value|.
-   * @param {!Array<number>} arr
-   * @param {number} value
-   * @return {number}
-   * @private
-   */
-  findNearestIndex_: function(arr, value) {
-    let closestIndex;
-    let minDifference = Number.MAX_VALUE;
-    for (let i = 0; i < arr.length; i++) {
-      const difference = Math.abs(arr[i] - value);
-      if (difference < minDifference) {
-        closestIndex = i;
-        minDifference = difference;
-      }
-    }
-
-    assert(typeof closestIndex != 'undefined');
-    return closestIndex;
+    this.$.slider.value =
+        this.ticks.map(tick => Math.abs(this.getTickValue_(tick) - prefValue))
+            .reduce(
+                (acc, diff, index) => diff < acc.diff ? {index, diff} : acc,
+                {index: -1, diff: Number.MAX_VALUE})
+            .index;
+    assert(this.$.slider.value != -1);
   },
 });
diff --git a/chrome/browser/resources/settings/device_page/BUILD.gn b/chrome/browser/resources/settings/device_page/BUILD.gn
index ddcc588..2bb5b80 100644
--- a/chrome/browser/resources/settings/device_page/BUILD.gn
+++ b/chrome/browser/resources/settings/device_page/BUILD.gn
@@ -11,7 +11,6 @@
     ":display",
     ":display_layout",
     ":display_overscan_dialog",
-    ":display_size_slider",
     ":drag_behavior",
     ":drive_cache_dialog",
     ":keyboard",
@@ -72,8 +71,8 @@
 js_library("display") {
   deps = [
     ":display_layout",
-    ":display_size_slider",
     "../controls:settings_dropdown_menu",
+    "../controls:settings_slider",
     "../prefs:prefs_behavior",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
@@ -124,24 +123,11 @@
 js_library("night_light_slider") {
   deps = [
     "../prefs:prefs_behavior",
-    "//third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior-extracted",
     "//third_party/polymer/v1_0/components-chromium/iron-resizable-behavior:iron-resizable-behavior-extracted",
     "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-inky-focus-behavior-extracted",
   ]
 }
 
-js_library("display_size_slider") {
-  deps = [
-    "../prefs:prefs_behavior",
-    "//third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior-extracted",
-    "//third_party/polymer/v1_0/components-chromium/iron-range-behavior:iron-range-behavior-extracted",
-    "//third_party/polymer/v1_0/components-chromium/iron-resizable-behavior:iron-resizable-behavior-extracted",
-    "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-inky-focus-behavior-extracted",
-    "//third_party/polymer/v1_0/components-chromium/paper-progress:paper-progress-extracted",
-    "//ui/webui/resources/cr_elements/policy:cr_policy_pref_behavior",
-  ]
-}
-
 js_library("power") {
   deps = [
     ":device_page_browser_proxy",
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html
index f271dcdc..8c7b7425 100644
--- a/chrome/browser/resources/settings/device_page/display.html
+++ b/chrome/browser/resources/settings/device_page/display.html
@@ -14,7 +14,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-tabs/paper-tabs.html">
 <link rel="import" href="display_layout.html">
 <link rel="import" href="display_overscan_dialog.html">
-<link rel="import" href="display_size_slider.html">
 <link rel="import" href="night_light_slider.html">
 <link rel="import" href="../controls/settings_dropdown_menu.html">
 <link rel="import" href="../controls/settings_slider.html">
@@ -147,12 +146,12 @@
             [[logicalResolutionText_]]
           </div>
         </div>
-        <display-size-slider id="displaySizeSlider"
+        <settings-slider id="displaySizeSlider"
             ticks="[[zoomValues_]]" pref="{{selectedZoomPref_}}"
-            min-label="$i18n{displaySizeSliderMinLabel}"
-            max-label="$i18n{displaySizeSliderMaxLabel}"
-            on-immediate-value-changed="onDisplaySizeSliderDrag_">
-        </display-size-slider>
+            label-min="$i18n{displaySizeSliderMinLabel}"
+            label-max="$i18n{displaySizeSliderMaxLabel}"
+            on-value-changed="onDisplaySizeSliderDrag_">
+        </settings-slider>
       </div>
 
       <!-- Drop down select menu for resolution -->
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index cde11ae..e3c2898 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -102,7 +102,9 @@
     /** @private {!Array<number>} Mode index values for slider. */
     modeValues_: Array,
 
-    /** @private {SliderTicks} Display zoom slider tick values. */
+    /**
+     * @private {!Array<cr_slider.SliderTick>} Display zoom slider tick values.
+     */
     zoomValues_: Array,
 
     /** @private {!DropdownMenuOptionList} */
@@ -331,19 +333,17 @@
    * Given the display with the current display mode, this function lists all
    * the display zoom values and their labels to be used by the slider.
    * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {SliderTicks}
+   * @return {!Array<cr_slider.SliderTick>}
    */
   getZoomValues_: function(selectedDisplay) {
-    /** @type {SliderTicks} */
-    let zoomValues = [];
-    for (let i = 0; i < selectedDisplay.availableDisplayZoomFactors.length;
-         i++) {
-      const value = selectedDisplay.availableDisplayZoomFactors[i];
+    return selectedDisplay.availableDisplayZoomFactors.map(value => {
       const ariaValue = Math.round(value * 100);
-      const label = this.i18n('displayZoomValue', ariaValue.toString());
-      zoomValues.push({value: value, label: label, ariaValue: ariaValue});
-    }
-    return zoomValues;
+      return {
+        value,
+        ariaValue,
+        label: this.i18n('displayZoomValue', ariaValue.toString())
+      };
+    });
   },
 
   /**
@@ -621,6 +621,8 @@
    * @private
    */
   onDisplaySizeSliderDrag_: function(e) {
+    if (!this.selectedDisplay)
+      return;
     this.updateLogicalResolutionText_(/** @type {number} */ (e.detail.value));
   },
 
diff --git a/chrome/browser/resources/settings/device_page/display_size_slider.html b/chrome/browser/resources/settings/device_page/display_size_slider.html
deleted file mode 100644
index 93757c4e..0000000
--- a/chrome/browser/resources/settings/device_page/display_size_slider.html
+++ /dev/null
@@ -1,230 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-inky-focus-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-range-behavior/iron-range-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="../prefs/prefs_behavior.html">
-
-<dom-module id="display-size-slider">
-  <template>
-    <style>
-      :host {
-        cursor: default;
-        display: inline-flex;
-        font-weight: 500;
-
-        /* Counteract the margin on #sliderContainer and match the margin from
-           settings-slider.html */
-        margin-inline-end: -16px;
-
-        min-width: 200px;
-        text-align: center;
-        user-select: none;
-      }
-
-      /* focus shows the ripple */
-      :host(:focus) {
-        outline: none;
-      }
-
-      :host-context([dir='rtl']) #sliderContainer {
-        transform: scaleX(-1);
-      }
-
-      /* We dont want the text to be flipped in rtl */
-      :host-context([dir='rtl']) #labelText,
-      :host-context([dir='rtl']) #subLabelContainer {
-        transform: scaleX(-1);
-      }
-
-      #sliderContainer {
-        display: inline-table;
-        height: 32px;
-        margin-left: 16px;
-        margin-right: 16px;
-        position: relative;
-        width: 100%;
-      }
-
-      #sliderContainer:focus {
-        outline: 0;
-      }
-
-      #labelContainer {
-        bottom: 36px;
-        display: none;
-        height: 1.75em;
-        position: absolute;
-        width: inherit;
-        z-index: 10;
-      }
-
-      #sliderContainer:hover #labelContainer,
-      .hover #labelContainer,
-      :host([hold-down_]) #labelContainer {
-        display: block;
-      }
-
-      .label {
-        background: var(--google-blue-600);
-        border-radius: 14px;
-        color: white;
-        font-size: 12px;
-        left: 0;
-        line-height: 1.5em;
-        padding: 0 8px;
-        position: absolute;
-        text-align: center;
-        transform: translateX(-50%);
-        transition: margin-top 200ms cubic-bezier(0, 0, 0.2, 1);
-        vertical-align: middle;
-        white-space: nowrap;
-        width: auto;
-      }
-
-      .bar-container {
-        bottom: 0;
-        left: 0;
-        overflow: hidden;
-        position: absolute;
-        right: 0;
-        top: 0;
-      }
-
-      .slider-markers {
-        box-sizing: border-box;
-        height: 2px;
-        left: 0;
-        pointer-events: none;
-        position: absolute;
-        right: -1px;
-        top: 15px;
-        @apply --layout-horizontal;
-      }
-
-      .slider-marker {
-        @apply --layout-flex;
-      }
-      .slider-markers::after,
-      .slider-marker::after {
-        background-color: rgba(255, 255, 255, 0.54);
-        border-radius: 50%;
-        content: '';
-        display: block;
-        height: 2px;
-        margin-left: -1px;
-        width: 2px;
-      }
-
-      #sliderBar {
-        --paper-progress-height: 2px;
-        background-color: transparent;
-        padding: 15px 0;
-        width: 100%;
-      }
-
-      .slider-knob {
-        height: 32px;
-        left: 0;
-        margin-left: -16px;
-        position: absolute;
-        top: 0;
-        width: 32px;
-      }
-
-      .slider-knob:focus {
-        outline: none;
-      }
-
-      .slider-knob-inner {
-        background-color: var(--google-blue-600);
-        border: 0;
-        border-radius: 50%;
-        box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
-        box-sizing: content-box;
-        height: 10px;
-        margin: 11px;
-        width: 10px;
-      }
-
-      paper-ripple {
-        color: var(--google-blue-600);
-      }
-
-      #subLabelContainer {
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        padding-top: 24px;
-        pointer-events: none;
-      }
-
-      #subLabelContainer > div {
-        font-size: 12px;
-        font-weight: normal;
-      }
-
-      paper-progress {
-        --paper-progress-active-color: var(--google-blue-600);
-        --paper-progress-container-color: var(--google-blue-600-opacity-24);
-        --paper-progress-disabled-active-color: var(--google-grey-600);
-      }
-
-      paper-progress[disabled] {
-        --paper-progress-container-color: var(--google-grey-600-opacity-24);
-      }
-
-      /* Styles for disabled state */
-      #sliderContainer.disabled {
-        pointer-events: none;
-      }
-
-      #subLabelContainer[disabled] {
-        color: var(--google-grey-600);
-      }
-
-      .slider-knob-inner[disabled] {
-        background-color: var(--google-grey-600);
-        border: 2px solid white;
-        box-shadow: unset;
-        margin: 9px;
-      }
-    </style>
-    <div id="sliderContainer" class$="[[getClassNames_(disabled, dragging)]]">
-      <div id="labelContainer" aria-label="[[getLabelForIndex_(ticks, index)]]">
-        <div id="label" class="label">
-          <div id="labelText">
-            [[getLabelForIndex_(ticks, index)]]
-          </div>
-        </div>
-      </div>
-      <div class="bar-container" aria-hidden="true">
-        <paper-progress id="sliderBar" disabled$="[[disabled]]"
-            on-down="onBarDown_" on-up="onBarUp_" on-track="knobTrack_"
-            value="[[index]]" min="[[min]]" max="[[max]]">
-        </paper-progress>
-      </div>
-
-
-      <div class="slider-markers">
-        <template is="dom-repeat" items="[[markers]]">
-          <div class="slider-marker"></div>
-        </template>
-      </div>
-
-      <div id="sliderKnob" class="slider-knob" on-track="knobTrack_">
-        <div class="slider-knob-inner" disabled$="[[disabled]]"></div>
-      </div>
-      <div id="subLabelContainer" disabled$="[[disabled]]">
-        <div id="subLabelMin">[[minLabel]]</div>
-        <div id="subLabelMax">[[maxLabel]]</div>
-      </div>
-    </div>
-  </template>
-  <script src="display_size_slider.js"></script>
-</dom-module>
diff --git a/chrome/browser/resources/settings/device_page/display_size_slider.js b/chrome/browser/resources/settings/device_page/display_size_slider.js
deleted file mode 100644
index 7dd8f904a..0000000
--- a/chrome/browser/resources/settings/device_page/display_size_slider.js
+++ /dev/null
@@ -1,538 +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.
-
-/**
- * @fileoverview
- * display-size-slider is used to change the value of a pref via a slider
- * control. This specific slider is used instead of the settings-slider due to
- * its implementation of the tool tip that displays the current slider value.
- * This component fires a |immediate-value-changed| event while the dragging is
- * active. This event includes the immediate value of the slider.
- *
- * TODO (crbug.com/858882): this control should be replaced with a common
- * control like settings-slider or cr-slider.
- */
-
-/**
- * The |value| is the corresponding value that the current slider tick is
- * assocated with. The string |label| is shown in the ui as the label for the
- * current slider value. The |ariaValue| number is used for aria-valuemin,
- * aria-valuemax, and aria-valuenow, and is optional. If missing, |value| will
- * be used instead.
- * @typedef {{
- *   value: !number,
- *   label: !string,
- *   ariaValue: (number|undefined),
- * }}
- */
-let SliderTick;
-
-/**
- * @typedef {!Array<SliderTick>}
- */
-let SliderTicks;
-
-(function() {
-
-Polymer({
-  is: 'display-size-slider',
-
-  behaviors: [
-    CrPolicyPrefBehavior,
-    Polymer.IronA11yKeysBehavior,
-    Polymer.IronRangeBehavior,
-    Polymer.IronResizableBehavior,
-    Polymer.PaperInkyFocusBehavior,
-    PrefsBehavior,
-  ],
-
-  properties: {
-    /** The slider is disabled if true. */
-    disabled: {type: Boolean, value: false, readOnly: true},
-
-    /** True when the user is dragging the slider knob. */
-    dragging: {type: Boolean, value: false, readOnly: true},
-
-    /** @type {number} The index for the current slider value in |ticks|. */
-    index: {
-      type: Number,
-      value: 0,
-      readOnly: true,
-      observer: 'onIndexChanged_',
-    },
-
-    /** @type {string} The label for the minimum slider value */
-    minLabel: {type: String, value: ''},
-
-    /** @type {string} The label for the maximum slider value */
-    maxLabel: {type: String, value: ''},
-
-    /**
-     * Each item in the array represents a UI element corresponding to a tick
-     * value.
-     * @type {!Array<number>}
-     */
-    markers: {
-      type: Array,
-      readOnly: true,
-      value: function() {
-        return [];
-      }
-    },
-
-    /** @type {!chrome.settingsPrivate.PrefObject} */
-    pref: Object,
-
-    /**
-     * The data associated with each tick on the slider. Each element in the
-     * array contains a value and the label corresponding to that value.
-     * @type {SliderTicks}
-     */
-    ticks: {
-      type: Array,
-      value: () => [],
-    },
-
-    /** @private */
-    holdDown_: {
-      type: Boolean,
-      value: false,
-      reflectToAttribute: true,
-    },
-  },
-
-  listeners: {
-    'blur': 'onBlur_',
-    'focus': 'onFocus_',
-    'keydown': 'onKeyDown_',
-    'pointerdown': 'onPointerDown_',
-    'pointerup': 'onPointerUp_',
-  },
-
-  observers: [
-    'updateIndex_(pref.value)',
-    'updateSliderParams_(ticks)',
-    'updateMarkers_(min, max)',
-    'updateDisabled_(ticks, pref.*)',
-  ],
-
-  hostAttributes: {role: 'slider', tabindex: 0},
-
-  keyBindings: {
-    'left': 'leftKeyPress_',
-    'right': 'rightKeyPress_',
-    'up': 'increment_',
-    'down': 'decrement_',
-    'pagedown home': 'resetToMinIndex_',
-    'pageup end': 'resetToMaxIndex_',
-  },
-
-  /** @override */
-  ready: function() {
-    chrome.settingsPrivate.onPrefsChanged.addListener((prefs) => {
-      prefs.forEach((pref) => {
-        if (pref.key == this.pref.key && this.pref.value != pref.value)
-          this.pref.value = pref.value;
-      });
-    });
-  },
-
-  /** @private {boolean} */
-  usedMouse_: false,
-
-  get _isRTL() {
-    if (this.__isRTL === undefined) {
-      this.__isRTL = window.getComputedStyle(this)['direction'] === 'rtl';
-    }
-    return this.__isRTL;
-  },
-
-  /** @private */
-  setRippleHoldDown_: function(holdDown) {
-    this.ensureRipple();
-    this._ripple.holdDown = holdDown;
-    this.holdDown_ = holdDown;
-  },
-
-  /** @private */
-  onFocus_: function() {
-    this.setRippleHoldDown_(true);
-  },
-
-  /** @private */
-  onBlur_: function() {
-    this.setRippleHoldDown_(false);
-  },
-
-  /** @private */
-  onChange_: function() {
-    this.setRippleHoldDown_(!this.usedMouse_);
-    this.usedMouse_ = false;
-  },
-
-  /** @private */
-  onKeyDown_: function() {
-    this.usedMouse_ = false;
-    if (!this.disabled)
-      this.onFocus_();
-  },
-
-  /**
-   * @param {!MouseEvent} event
-   * @private
-   */
-  onPointerDown_: function(event) {
-    if (this.disabled || event.button != 0) {
-      event.preventDefault();
-      return;
-    }
-    this.usedMouse_ = true;
-    this.setRippleHoldDown_(false);
-    setTimeout(() => {
-      this.setRippleHoldDown_(true);
-    });
-  },
-
-  /**
-   * @param {!MouseEvent} event
-   * @private
-   */
-  onPointerUp_: function(event) {
-    if (event.button != 0)
-      return;
-    this.setRippleHoldDown_(false);
-  },
-
-  /**
-   * Clamps the value of |newIndex| to IronRangeBehavior's max/min bounds and
-   * updates the value for |index|.
-   * @param {number} newIndex The new value for index that needs to be set.
-   * @private
-   */
-  clampAndSetIndex_: function(newIndex) {
-    newIndex = this.clampToRange_(newIndex, this.min, this.max);
-    if (newIndex != this.index) {
-      this._setIndex(newIndex);
-      this.setAttribute(
-          'aria-valuenow', this.getAriaValueForIndex_(this.ticks, this.index));
-      this.setAttribute(
-          'aria-valuetext', this.getLabelForIndex_(this.ticks, this.index));
-      if (this.dragging) {
-        this.fire(
-            'immediate-value-changed', {value: this.ticks[newIndex].value});
-      }
-    }
-  },
-
-  /**
-   * Clamps the value of |newIndex| to IronRangeBehavior's max/min bounds and
-   * updates the value of |pref| to this clamped value.
-   * @param {number} newIndex The new value for index that needs to be set.
-   * @private
-   */
-  clampIndexAndUpdatePref_: function(newIndex) {
-    newIndex = this.clampToRange_(newIndex, this.min, this.max);
-    if (this.ticks[newIndex].value != this.pref.value)
-      this.set('pref.value', this.ticks[newIndex].value);
-    this.onChange_();
-  },
-
-  /**
-   * Clamps and returns the given |value| within the |min| and |max| range.
-   * @param {number} value The number to clamp.
-   * @param {number} min The minimum value in range. Any value below this will
-   *     be clamped to |min|.
-   * @param {number} max The maximum value in range. Any value above this will
-   *     be clamped to |max|.
-   * @return {number}
-   * @private
-   */
-  clampToRange_: function(value, min, max) {
-    return Math.max(Math.min(value, max), min);
-  },
-
-  /**
-   * Overrides _createRipple from PaperInkyFocusBehavior to set the ripple
-   * container as the slider knob before creating the ripple animation. Without
-   * this the PaperInkyFocusBehavior does not know where to create the ripple
-   * animation.
-   * @protected
-   */
-  _createRipple: function() {
-    this._rippleContainer = this.$.sliderKnob;
-    return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this);
-  },
-
-  /** @private Safely decrements the slider index value by 1 and updates pref */
-  decrement_: function() {
-    this.clampIndexAndUpdatePref_(this.index - 1);
-  },
-
-  /**
-   * Returns a string concatenated list of class names based on whether the
-   * corresponding properties are set.
-   * @return {string}
-   */
-  getClassNames_: function() {
-    return this.mergeClasses_({
-      disabled: this.disabled,
-      dragging: this.dragging,
-    });
-  },
-
-  /**
-   * Returns the current label for the selected slider index.
-   * @param {SliderTicks} ticks Slider label and corresponding value for each
-   *    tick.
-   * @param {number} index Index of the slider tick with the desired label.
-   * @return {string}
-   */
-  getLabelForIndex_: function(ticks, index) {
-    if (!ticks || ticks.length == 0 || index >= ticks.length)
-      return '';
-    return ticks[index].label;
-  },
-
-  /**
-   * Returns the aria value for a selected slider index. aria-valuenow,
-   * aria-valuemin and aria-valuemax are expected to be a numbers, so sliders
-   * which use strings for labels should populate the ariaValue with a number
-   * as well.
-   * @param {SliderTicks} ticks Slider label and corresponding value for each
-   *    tick.
-   * @param {number} index Index of the slider tick with the desired label.
-   * @return {number|string} Returns the empty string if there is not tick at
-   *    the given index.
-   */
-  getAriaValueForIndex_: function(ticks, index) {
-    if (!ticks || ticks.length == 0 || index >= ticks.length)
-      return '';
-    // ariaValue factored out for closure compilation.
-    let ariaValue = ticks[index].ariaValue;
-    return ariaValue !== undefined ? ariaValue : ticks[index].value;
-  },
-
-  /** @private Safely increments the slider index value by 1 and updates pref */
-  increment_: function() {
-    this.clampIndexAndUpdatePref_(this.index + 1);
-  },
-
-  /**
-   * Handles the mouse drag and drop event for slider knob from start to end.
-   * @param {Event} event
-   * @private
-   */
-  knobTrack_: function(event) {
-    switch (event.detail.state) {
-      case 'start':
-        this.knobTrackStart_();
-        break;
-      case 'track':
-        this.knobTrackDrag_(event);
-        break;
-      case 'end':
-        this.knobTrackEnd_(event);
-        break;
-    }
-  },
-
-  /**
-   * Handles the drag event for the slider knob.
-   * @param {!Event} event
-   * @private
-   */
-  knobTrackDrag_: function(event) {
-    // Distance travelled during mouse drag.
-    const dx = event.detail.dx;
-
-    // Total width of the progress bar in CSS pixels.
-    const totalWidth = this.$.sliderBar.offsetWidth;
-
-    // Distance between 2 consecutive tick markers.
-    const tickWidth = totalWidth / (this.ticks.length - 1);
-
-    // Number of ticks covered by |dx|.
-    let dxInTicks = Math.round(dx / tickWidth);
-
-    if (this._isRTL)
-      dxInTicks *= -1;
-
-    let nextIndex = this.startIndex_ + dxInTicks;
-
-    this.clampAndSetIndex_(nextIndex);
-  },
-
-  /**
-   * Handles the end of slider knob drag event.
-   * @param {!Event} event
-   * @private
-   */
-  knobTrackEnd_: function(event) {
-    this._setDragging(false);
-
-    // Update the pref once user stops dragging and releases mouse.
-    if (this.index != this.startIndex_)
-      this.clampIndexAndUpdatePref_(this.index);
-  },
-
-  /**
-   * Handles the start of the slider knob drag event.
-   * @private
-   */
-  knobTrackStart_: function() {
-    this.startIndex_ = this.index;
-    this._setDragging(true);
-  },
-
-  /** @private Handles the 'left' key press. */
-  leftKeyPress_: function() {
-    this._isRTL ? this.increment_() : this.decrement_();
-  },
-
-  /**
-   * Returns a concatenated string of classnames based on the boolean table
-   * |classes|.
-   * @param {!Object<string, boolean>} classes An object mapping between
-   *     classnames and boolean. The boolean for the corresponding classname is
-   *     true if that classname needs to be present in the returned string.
-   * @return {string}
-   * @private
-   */
-  mergeClasses_: function(classes) {
-    return Object.keys(classes)
-        .filter(function(className) {
-          return classes[className];
-        })
-        .join(' ');
-  },
-
-  /**
-   * Handles the event where the user clicks or taps on the slider bar directly.
-   * @param {!Event} event
-   * @private
-   */
-  onBarDown_: function(event) {
-    const barWidth = this.$.sliderBar.offsetWidth;
-    const barOriginX = this.$.sliderBar.getBoundingClientRect().left;
-    let eventOffsetFromOriginX = event.detail.x - barOriginX;
-    if (this._isRTL)
-      eventOffsetFromOriginX = barWidth - eventOffsetFromOriginX;
-    const tickWidth = barWidth / (this.ticks.length - 1);
-    let newTickIndex = Math.round(eventOffsetFromOriginX / tickWidth);
-    this._setDragging(true);
-    this.startIndex_ = this.index;
-
-    // Update the index but dont update the pref until mouse is released.
-    this.clampAndSetIndex_(newTickIndex);
-  },
-
-  /**
-   * Handles the event of mouse up from the slider bar.
-   * @private
-   */
-  onBarUp_: function() {
-    if (this.dragging)
-      this._setDragging(false);
-    if (this.startIndex_ != this.index)
-      this.clampIndexAndUpdatePref_(this.index);
-  },
-
-  /** @private Handles the 'right' key press. */
-  rightKeyPress_: function() {
-    this._isRTL ? this.decrement_() : this.increment_();
-  },
-
-  /** @private Handles the 'end' and 'page up' key press. */
-  resetToMaxIndex_: function() {
-    this.clampIndexAndUpdatePref_(this.max);
-  },
-
-  /** @private Handles the 'home' and 'page down' key press. */
-  resetToMinIndex_: function() {
-    this.clampIndexAndUpdatePref_(this.min);
-  },
-
-  /**
-   * Sets the disabled property if there are no tick values or if the pref
-   * cannot be modified by the user.
-   * @private
-   */
-  updateDisabled_: function() {
-    let disabled = false;
-
-    // Disabled if no tick values are set for the slider.
-    if (!this.ticks || this.ticks.length <= 1)
-      disabled |= true;
-
-
-    // Disabled if the pref cannot be modified.
-    disabled |= this.isPrefEnforced();
-
-    this._setDisabled(!!disabled);
-  },
-
-  /**
-   * Updates the value for |index| based on the current set value of |pref|.
-   * @private
-   */
-  updateIndex_: function() {
-    if (!this.ticks || this.ticks.length == 0)
-      return;
-    if (!this.pref || typeof(this.pref.value) != 'number')
-      return;
-    let resolvedTick = this.ticks.length - 1;
-    for (let i = 0; i < this.ticks.length; i++) {
-      if (this.ticks[i].value >= this.pref.value) {
-        resolvedTick = i;
-        break;
-      }
-    }
-    this._setIndex(resolvedTick);
-    this.setAttribute(
-        'aria-valuenow', this.getAriaValueForIndex_(this.ticks, this.index));
-    this.setAttribute(
-        'aria-valuetext', this.getLabelForIndex_(this.ticks, this.index));
-  },
-
-  /**
-   * Updates the knob position based on the the value of progress indicator.
-   * @private
-   */
-  onIndexChanged_: function() {
-    this._setRatio(this._calcRatio(this.index) * 100);
-
-    this.$.sliderKnob.style.left = this.ratio + '%';
-    this.$.label.style.left = this.ratio + '%';
-  },
-
-  /**
-   * Initializes the |markers| array based on the number of ticks.
-   * @private
-   */
-  updateMarkers_: function(min, max) {
-    let steps = Math.round((max - min) / this.step);
-    if (steps < 0 || !isFinite(steps))
-      steps = 0;
-    this._setMarkers(new Array(steps));
-  },
-
-  /**
-   * Updates the min and max possible values for the slider.
-   * @private
-   */
-  updateSliderParams_: function() {
-    this.min = 0;
-    if (this.ticks.length == 0) {
-      this.max = 0;
-      return;
-    }
-    this.max = this.ticks.length - 1;
-    this.setAttribute(
-        'aria-valuemin', this.getAriaValueForIndex_(this.ticks, this.min));
-    this.setAttribute(
-        'aria-valuemax', this.getAriaValueForIndex_(this.ticks, this.max));
-    this.updateIndex_();
-  },
-});
-})();
diff --git a/chrome/browser/resources/settings/device_page/keyboard.html b/chrome/browser/resources/settings/device_page/keyboard.html
index 415d4d6..9771d71 100644
--- a/chrome/browser/resources/settings/device_page/keyboard.html
+++ b/chrome/browser/resources/settings/device_page/keyboard.html
@@ -100,7 +100,7 @@
         <div class="start" id="repeatDelayLabel">$i18n{keyRepeatDelay}</div>
         <settings-slider id="delaySlider"
             pref="{{prefs.settings.language.xkb_auto_repeat_delay_r2}}"
-            tick-values="[[autoRepeatDelays_]]"
+            ticks="[[autoRepeatDelays_]]"
             disabled="[[
                 !prefs.settings.language.xkb_auto_repeat_enabled_r2.value]]"
             aria-labelledby="repeatDelayLabel"
@@ -113,7 +113,7 @@
         <settings-slider id="repeatRateSlider"
             pref="{{
                 prefs.settings.language.xkb_auto_repeat_interval_r2}}"
-            tick-values="[[autoRepeatIntervals_]]"
+            ticks="[[autoRepeatIntervals_]]"
             disabled="[[
                 !prefs.settings.language.xkb_auto_repeat_enabled_r2.value]]"
             aria-labelledby="repeatRateLabel"
diff --git a/chrome/browser/resources/settings/device_page/night_light_slider.html b/chrome/browser/resources/settings/device_page/night_light_slider.html
index 809bec10..ceec80b 100644
--- a/chrome/browser/resources/settings/device_page/night_light_slider.html
+++ b/chrome/browser/resources/settings/device_page/night_light_slider.html
@@ -1,7 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-inky-focus-behavior.html">
 <link rel="import" href="../prefs/prefs_behavior.html">
@@ -105,7 +104,8 @@
         width: 100%;
       }
 
-      .markers {
+      .active-marker,
+      .inactive-marker {
         background-color: rgba(255, 255, 255, 0.54);
         border-radius: 50%;
         display: block;
@@ -117,6 +117,10 @@
         z-index: 2;
       }
 
+      .inactive-marker {
+        background-color: rgba(26, 115, 232, 0.54);
+      }
+
       #legendContainer {
         height: 10px;
         position: relative;
@@ -159,13 +163,11 @@
         <div id="markersContainer">
         </div>
         <div id="startKnob" class="knob" tabindex="1" on-down="startDrag_"
-            on-up="endDrag_" on-track="continueDrag_" on-focus="onFocus_"
-            on-blur="onBlur_">
+            on-up="endDrag_" on-track="continueDrag_">
           <div class="knob-inner" tabindex="-1"></div>
         </div>
         <div id="endKnob" class="knob" tabindex="2" on-down="startDrag_"
-            on-up="endDrag_" on-track="continueDrag_" on-focus="onFocus_"
-            on-blur="onBlur_">
+            on-up="endDrag_" on-track="continueDrag_">
           <div class="knob-inner" tabindex="-1"></div>
         </div>
       </div>
diff --git a/chrome/browser/resources/settings/device_page/night_light_slider.js b/chrome/browser/resources/settings/device_page/night_light_slider.js
index 7003fd0..7164374 100644
--- a/chrome/browser/resources/settings/device_page/night_light_slider.js
+++ b/chrome/browser/resources/settings/device_page/night_light_slider.js
@@ -16,12 +16,32 @@
 const OFFSET_MINUTES_6PM = 18 * 60;
 const TOTAL_MINUTES_PER_DAY = 24 * 60;
 
+/**
+ * % is the javascript remainder operator that satisfies the following for the
+ * resultant z given the operands x and y as in (z = x % y):
+ *   1. x = k * y + z
+ *   2. k is an integer.
+ *   3. |z| < |y|
+ *   4. z has the same sign as x.
+ *
+ * It is more convenient to have z be the same sign as y. In most cases y
+ * is a positive integer, and it is more intuitive to have z also be a positive
+ * integer (0 <= z < y).
+ *
+ * For example (-1 % 24) equals -1 whereas modulo(-1, 24) equals 23.
+ * @param {number} x
+ * @param {number} y
+ * @return {number}
+ */
+function modulo(x, y) {
+  return ((x % y) + y) % y;
+}
+
 Polymer({
   is: 'night-light-slider',
 
   behaviors: [
     PrefsBehavior,
-    Polymer.IronA11yKeysBehavior,
     Polymer.IronResizableBehavior,
     Polymer.PaperInkyFocusBehavior,
   ],
@@ -49,38 +69,28 @@
 
   listeners: {
     'iron-resize': 'onResize_',
+    focus: 'onFocus_',
+    blur: 'onBlur_',
+    keydown: 'onKeyDown_',
   },
 
   observers: [
     'updateKnobs_(prefs.ash.night_light.custom_start_time.*, ' +
         'prefs.ash.night_light.custom_end_time.*, isRTL_, isReady_)',
     'hourFormatChanged_(prefs.settings.clock.use_24hour_clock.*)',
+    'updateMarkers_(prefs.ash.night_light.custom_start_time.*, ' +
+        'prefs.ash.night_light.custom_end_time.*, isRTL_, isReady_)',
   ],
 
-  keyBindings: {
-    'left': 'onLeftKey_',
-    'right': 'onRightKey_',
-  },
-
   /**
    * The object currently being dragged. Either the start or end knobs.
-   * @type {?Object}
+   * @type {Element}
    * @private
    */
   dragObject_: null,
 
   /** @override */
   attached: function() {
-    // Build the legend markers.
-    const markersContainer = this.$.markersContainer;
-    const width = markersContainer.offsetWidth;
-    for (let i = 0; i <= HOURS_PER_DAY; ++i) {
-      const marker = document.createElement('div');
-      marker.className = 'markers';
-      markersContainer.appendChild(marker);
-      marker.style.left = (i * 100 / HOURS_PER_DAY) + '%';
-    }
-
     this.isRTL_ = window.getComputedStyle(this).direction == 'rtl';
 
     this.$.sliderContainer.addEventListener('contextmenu', function(e) {
@@ -100,6 +110,51 @@
   },
 
   /**
+   * @return {boolean}
+   * @private
+   */
+  prefsAvailable: function() {
+    return ['custom_start_time', 'custom_end_time']
+        .map(key => `prefs.ash.night_light.${key}.value`)
+        .every(path => this.get(path) != undefined);
+  },
+
+  /** @private */
+  updateMarkers_: function() {
+    if (!this.isReady_ || !this.prefsAvailable())
+      return;
+
+    const startHour =
+        /** @type {number} */ (
+            this.getPref('ash.night_light.custom_start_time').value) /
+        60.0;
+    const endHour = /** @type {number} */ (
+                        this.getPref('ash.night_light.custom_end_time').value) /
+        60.0;
+
+    const markersContainer = this.$.markersContainer;
+    markersContainer.innerHTML = '';
+    for (let i = 0; i <= HOURS_PER_DAY; ++i) {
+      const marker = document.createElement('div');
+
+      const hourIndex = this.isRTL_ ? 24 - i : i;
+      // Rotate around clock by 18 hours for the 6pm start.
+      const hour = (hourIndex + 18) % 24;
+      if (startHour < endHour) {
+        marker.className = hour > startHour && hour < endHour ?
+            'active-marker' :
+            'inactive-marker';
+      } else {
+        marker.className = hour > endHour && hour < startHour ?
+            'inactive-marker' :
+            'active-marker';
+      }
+      markersContainer.appendChild(marker);
+      marker.style.left = (i * 100 / HOURS_PER_DAY) + '%';
+    }
+  },
+
+  /**
    * Invoked when the element is resized and the knobs positions need to be
    * updated.
    * @private
@@ -131,24 +186,6 @@
   },
 
   /**
-   * Expands or un-expands the knob being dragged along with its corresponding
-   * label bubble.
-   * @param {boolean} expand True to expand, and false to un-expand.
-   * @private
-   */
-  setExpanded_: function(expand) {
-    let knob = this.$.startKnob;
-    let label = this.$.startLabel;
-    if (this.dragObject_ == this.$.endKnob) {
-      knob = this.$.endKnob;
-      label = this.$.endLabel;
-    }
-
-    knob.classList.toggle('expanded-knob', expand);
-    label.classList.toggle('expanded-knob', expand);
-  },
-
-  /**
    * If one of the two knobs is focused, this function blurs it.
    * @private
    */
@@ -160,6 +197,7 @@
 
   /**
    * Start dragging the target knob.
+   * @param {!Event} event
    * @private
    */
   startDrag_: function(event) {
@@ -177,19 +215,14 @@
       return;
     }
 
-    this.setExpanded_(true);
+    this.handleKnobEvent_(event, this.dragObject_);
 
-    // Focus is only given to the knobs by means of keyboard tab navigations.
-    // When we start dragging, we don't want to see any focus halos around any
-    // knob.
-    this.blurAnyFocusedKnob_();
-
-    // However, our night-light-slider element must get the focus.
-    this.focus();
+    this.valueAtDragStart_ = this.getPrefValue_(this.dragObject_);
   },
 
   /**
    * Continues dragging the selected knob if any.
+   * @param {!Event} event
    * @private
    */
   continueDrag_: function(event) {
@@ -211,40 +244,47 @@
   },
 
   /**
+   * Converts horizontal pixels into number of minutes.
+   * @param {number} deltaX
+   * @return {number}
+   * @private
+   */
+  getDeltaMinutes_: function(deltaX) {
+    return (this.isRTL_ ? -1 : 1) *
+        Math.floor(
+            TOTAL_MINUTES_PER_DAY * deltaX / this.$.sliderBar.offsetWidth);
+  },
+
+  /**
    * Updates the knob's corresponding pref value in response to dragging, which
    * will in turn update the location of the knob and its corresponding label
    * bubble and its text contents.
+   * @param {!Event} event
    * @private
    */
   doKnobTracking_: function(event) {
-    const deltaRatio =
-        Math.abs(event.detail.ddx) / this.$.sliderBar.offsetWidth;
-    const deltaMinutes = Math.floor(deltaRatio * TOTAL_MINUTES_PER_DAY);
-    if (deltaMinutes <= 0)
+    const lastDeltaMinutes = this.getDeltaMinutes_(event.detail.ddx);
+    if (Math.abs(lastDeltaMinutes) < 1)
       return;
 
-    const knobPref = this.dragObject_ == this.$.startKnob ?
-        'ash.night_light.custom_start_time' :
-        'ash.night_light.custom_end_time';
-
-    const ddx = this.isRTL_ ? event.detail.ddx * -1 : event.detail.ddx;
-    if (ddx > 0) {
-      // Increment the knob's pref by the amount of deltaMinutes.
-      this.incrementPref_(knobPref, deltaMinutes);
-    } else {
-      // Decrement the knob's pref by the amount of deltaMinutes.
-      this.decrementPref_(knobPref, deltaMinutes);
-    }
+    // Using |ddx| to compute the delta minutes and adding that to the current
+    // value will result in a rounding error for every update. The cursor will
+    // drift away from the knob. Storing the original value and calculating the
+    // delta minutes from |dx| will provide a stable update that will not lose
+    // pixel movement due to rounding.
+    this.updatePref_(
+        this.valueAtDragStart_ + this.getDeltaMinutes_(event.detail.dx), true);
   },
 
   /**
    * Ends the dragging.
+   * @param {!Event} event
    * @private
    */
   endDrag_: function(event) {
     event.preventDefault();
-    this.setExpanded_(false);
     this.dragObject_ = null;
+    this.removeRipple_();
   },
 
   /**
@@ -300,6 +340,8 @@
    * @private
    */
   updateKnobs_: function() {
+    if (!this.isReady_ || !this.prefsAvailable())
+      return;
     const startOffsetMinutes = /** @type {number} */ (
         this.getPref('ash.night_light.custom_start_time').value);
     this.updateKnobLeft_(this.$.startKnob, startOffsetMinutes);
@@ -414,69 +456,71 @@
   },
 
   /**
-   * Increments the value of the pref whose path is given by |prefPath| by the
-   * amount given in |increment|.
-   * @param {string} prefPath
-   * @param {number} increment
+   * Updates the value of the pref and wraps around if necessary.
+   *
+   * When the |updatedValue| would put the start and end times closer than the
+   * minimum distance, the |updatedValue| is changed to maintain the minimum
+   * distance.
+   *
+   * When |fromUserGesture| is true the update source is from a pointer such as
+   * a mouse, touch or pen. When the knobs are close, the dragging knob will
+   * stay on the same side with respect to the other knob. For example, when the
+   * minimum distance is 1 hour, the start knob is at 8:30 am, and the end knob
+   * is at 7:00, let's examine what happens if the start knob is dragged past
+   * the end knob. At first the start knob values will change past 8:20 and
+   * 8:10, all the way up to 8:00. Further movements in the same direction will
+   * not change the start knob value until the pointer crosses past the end knob
+   * (modulo the bar width). At that point, the start knob value will be updated
+   * to 6:00 and remain at 6:00 until the pointer passes the 6:00 location.
+   *
+   * When |fromUserGesture| is false, the input is coming from a key event. As
+   * soon as the |updatedValue| is closer than the minimum distance, the knob
+   * is moved to the other side of the other knob. For example, with a minimum
+   * distance of 1 hour, the start knob is at 8:00 am, and the end knob is at
+   * 7:00, if the start knob value is decreased, then the start knob will be
+   * updated to 6:00.
+   * @param {number} updatedValue
+   * @param {boolean} fromUserGesture
    * @private
    */
-  incrementPref_: function(prefPath, increment) {
-    let value = this.getPref(prefPath).value + increment;
-
+  updatePref_: function(updatedValue, fromUserGesture) {
+    const prefPath = assert(this.getFocusedKnobPrefPathIfAny_());
     const otherValue = this.getOtherKnobPrefValue_(prefPath);
-    if (otherValue > value &&
-        ((otherValue - value) < MIN_KNOBS_DISTANCE_MINUTES)) {
-      // We are incrementing the minutes offset moving towards the other knob.
-      // We have a minimum 60 minutes overlap threshold. Move this knob to the
-      // other side of the other knob.
-      //
-      // Was:
-      // ------ (+) --- 59 MIN --- + ------->>
-      //
-      // Now:
-      // ------ + --- 60 MIN --- (+) ------->>
-      //
-      // (+) ==> Knob being moved.
-      value = otherValue + MIN_KNOBS_DISTANCE_MINUTES;
-    }
+
+    const totalMinutes = TOTAL_MINUTES_PER_DAY;
+    const minDistance = MIN_KNOBS_DISTANCE_MINUTES;
+    if (modulo(otherValue - updatedValue, totalMinutes) < minDistance)
+      updatedValue = otherValue + (fromUserGesture ? -1 : 1) * minDistance;
+    else if (modulo(updatedValue - otherValue, totalMinutes) < minDistance)
+      updatedValue = otherValue + (fromUserGesture ? 1 : -1) * minDistance;
 
     // The knobs are allowed to wrap around.
-    this.setPrefValue(prefPath, (value % TOTAL_MINUTES_PER_DAY));
+    this.setPrefValue(prefPath, modulo(updatedValue, TOTAL_MINUTES_PER_DAY));
   },
 
   /**
-   * Decrements the value of the pref whose path is given by |prefPath| by the
-   * amount given in |decrement|.
-   * @param {string} prefPath
-   * @param {number} decrement
+   * @param {Element} knob
+   * @returns {?string}
    * @private
    */
-  decrementPref_: function(prefPath, decrement) {
-    let value =
-        /** @type {number} */ (this.getPref(prefPath).value) - decrement;
+  getPrefPath_: function(knob) {
+    if (knob == this.$.startKnob)
+      return 'ash.night_light.custom_start_time';
 
-    const otherValue = this.getOtherKnobPrefValue_(prefPath);
-    if (value > otherValue &&
-        ((value - otherValue) < MIN_KNOBS_DISTANCE_MINUTES)) {
-      // We are decrementing the minutes offset moving towards the other knob.
-      // We have a minimum 60 minutes overlap threshold. Move this knob to the
-      // other side of the other knob.
-      //
-      // Was:
-      // <<------ + --- 59 MIN --- (+) -------
-      //
-      // Now:
-      // <<------ (+) --- 60 MIN --- + ------
-      //
-      // (+) ==> Knob being moved.
-      value = otherValue - MIN_KNOBS_DISTANCE_MINUTES;
-    }
+    if (knob == this.$.endKnob)
+      return 'ash.night_light.custom_end_time';
 
-    // The knobs are allowed to wrap around.
-    if (value < 0)
-      value += TOTAL_MINUTES_PER_DAY;
+    return null;
+  },
 
-    this.setPrefValue(prefPath, Math.abs(value) % TOTAL_MINUTES_PER_DAY);
+  /**
+   * @param {Element} knob
+   * @returns {?number}
+   * @private
+   */
+  getPrefValue_: function(knob) {
+    const path = this.getPrefPath_(knob);
+    return path ? /** @type {number} */ (this.getPref(path).value) : null;
   },
 
   /**
@@ -486,46 +530,7 @@
    * @private
    */
   getFocusedKnobPrefPathIfAny_: function() {
-    const focusedElement = this.shadowRoot.activeElement;
-    if (focusedElement == this.$.startKnob)
-      return 'ash.night_light.custom_start_time';
-
-    if (focusedElement == this.$.endKnob)
-      return 'ash.night_light.custom_end_time';
-
-    return null;
-  },
-
-  /**
-   * Handles the 'left' key event.
-   * @private
-   */
-  onLeftKey_: function(e) {
-    e.preventDefault();
-    const knobPref = this.getFocusedKnobPrefPathIfAny_();
-    if (!knobPref)
-      return;
-
-    if (this.isRTL_)
-      this.incrementPref_(knobPref, 1);
-    else
-      this.decrementPref_(knobPref, 1);
-  },
-
-  /**
-   * Handles the 'right' key event.
-   * @private
-   */
-  onRightKey_: function(e) {
-    e.preventDefault();
-    const knobPref = this.getFocusedKnobPrefPathIfAny_();
-    if (!knobPref)
-      return;
-
-    if (this.isRTL_)
-      this.decrementPref_(knobPref, 1);
-    else
-      this.incrementPref_(knobPref, 1);
+    return this.getPrefPath_(this.shadowRoot.activeElement);
   },
 
   /**
@@ -558,10 +563,34 @@
   },
 
   /**
-   * Handles focus events on the start and end knobs.
+   * @param {!Event} event
    * @private
    */
-  onFocus_: function() {
+  onFocus_: function(event) {
+    this.handleKnobEvent_(event);
+  },
+
+  /**
+   * Handles focus, drag and key events on the start and end knobs.
+   * If |overrideElement| is provided, it will be the knob that gains focus and
+   * and the ripple. Otherwise, the knob is determined from the |event|.
+   * @param {!Event} event
+   * @param {Element=} overrideElement
+   * @private
+   */
+  handleKnobEvent_: function(event, overrideElement) {
+    const knob = overrideElement ||
+        event.path.find(el => el.classList && el.classList.contains('knob'));
+    if (!knob) {
+      event.preventDefault();
+      return;
+    }
+
+    if (this._rippleContainer != knob) {
+      this.removeRipple_();
+      knob.focus();
+    }
+
     this.ensureRipple();
 
     if (this.hasRipple()) {
@@ -575,12 +604,65 @@
    * @private
    */
   onBlur_: function() {
+    this.removeRipple_();
+  },
+
+  /**
+   * Removes ripple if one exists.
+   * @private
+   */
+  removeRipple_: function() {
     if (this.hasRipple()) {
       this._ripple.remove();
       this._ripple = null;
     }
   },
 
+  /**
+   * @param {!Event} event
+   * @private
+   */
+  onKeyDown_: function(event) {
+    const activeElement = this.shadowRoot.activeElement;
+    if (event.key == 'Tab') {
+      if (event.shiftKey && this.$.endKnob == activeElement) {
+        event.preventDefault();
+        this.handleKnobEvent_(event, this.$.startKnob);
+        return;
+      }
+
+      if (!event.shiftKey && this.$.startKnob == activeElement) {
+        event.preventDefault();
+        this.handleKnobEvent_(event, this.$.endKnob);
+      }
+      return;
+    }
+
+    if (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey)
+      return;
+
+    const deltaKeyMap = {
+      ArrowDown: -1,
+      ArrowLeft: this.isRTL_ ? 1 : -1,
+      ArrowRight: this.isRTL_ ? -1 : 1,
+      ArrowUp: 1,
+      PageDown: -15,
+      PageUp: 15,
+    };
+
+    if (event.key in deltaKeyMap) {
+      this.handleKnobEvent_(event);
+
+      event.preventDefault();
+      const value = this.getPrefValue_(activeElement);
+      if (value == null)
+        return;
+
+      const delta = deltaKeyMap[event.key];
+      this.updatePref_(value + delta, false);
+    }
+  },
+
   /** @private */
   _focusedChanged: function(receivedFocusFromKeyboard) {
     // Overrides the _focusedChanged() from the PaperInkyFocusBehavior so that
diff --git a/chrome/browser/resources/settings/device_page/pointers.html b/chrome/browser/resources/settings/device_page/pointers.html
index 0ae8891..3208555 100644
--- a/chrome/browser/resources/settings/device_page/pointers.html
+++ b/chrome/browser/resources/settings/device_page/pointers.html
@@ -45,7 +45,7 @@
         <div class="settings-box">
           <div class="start" id="mouseSpeedLabel">$i18n{mouseSpeed}</div>
           <settings-slider pref="{{prefs.settings.mouse.sensitivity2}}"
-              tick-values="[[sensitivityValues_]]"
+              ticks="[[sensitivityValues_]]"
               aria-labelledby="mouseSpeedLabel"
               label-min="$i18n{pointerSlow}"
               label-max="$i18n{pointerFast}">
@@ -69,7 +69,7 @@
           <div class="start" id="touchpadSpeedLabel">$i18n{touchpadSpeed}</div>
             <settings-slider id="touchpadSensitivity"
                 pref="{{prefs.settings.touchpad.sensitivity2}}"
-                tick-values="[[sensitivityValues_]]"
+                ticks="[[sensitivityValues_]]"
                 aria-labelledby="touchpadSpeedLabel"
                 label-min="$i18n{pointerSlow}"
                 label-max="$i18n{pointerFast}">
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
index 8c0310d..383a1b23 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
@@ -113,7 +113,7 @@
       </div>
     </div>
     <cr-dialog id="forgetDeviceDialog">
-      <div slot="title">$i18n{multideviceForgetDeviceDialogHeading}</div>
+      <div slot="title">$i18n{multideviceForgetDevice}</div>
       <div slot="body">
         <div class="settings-box first">
           $i18n{multideviceForgetDeviceDialogMessage}
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 4dd02ed..98b783f 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -562,13 +562,6 @@
         <structure name="IDR_SETTINGS_DEVICE_NIGHT_LIGHT_SLIDER_JS"
                    file="device_page/night_light_slider.js"
                    type="chrome_html" />
-        <structure name="IDR_SETTINGS_DEVICE_DISPLAY_SIZE_SLIDER_HTML"
-                   file="device_page/display_size_slider.html"
-                   type="chrome_html" />
-        <structure name="IDR_SETTINGS_DEVICE_DISPLAY_SIZE_SLIDER_JS"
-                   file="device_page/display_size_slider.js"
-                   type="chrome_html" />
-
       </if>
       <structure name="IDR_SETTINGS_DOWNLOADS_BROWSER_PROXY_HTML"
                  file="downloads_page/downloads_browser_proxy.html"
diff --git a/components/nux/OWNERS b/chrome/browser/resources/welcome/onboarding_welcome/OWNERS
similarity index 100%
rename from components/nux/OWNERS
rename to chrome/browser/resources/welcome/onboarding_welcome/OWNERS
diff --git a/components/nux/email/resources/BUILD.gn b/chrome/browser/resources/welcome/onboarding_welcome/email/BUILD.gn
similarity index 100%
rename from components/nux/email/resources/BUILD.gn
rename to chrome/browser/resources/welcome/onboarding_welcome/email/BUILD.gn
diff --git a/components/nux/email/resources/email_chooser.html b/chrome/browser/resources/welcome/onboarding_welcome/email/email_chooser.html
similarity index 100%
rename from components/nux/email/resources/email_chooser.html
rename to chrome/browser/resources/welcome/onboarding_welcome/email/email_chooser.html
diff --git a/components/nux/email/resources/email_chooser.js b/chrome/browser/resources/welcome/onboarding_welcome/email/email_chooser.js
similarity index 92%
rename from components/nux/email/resources/email_chooser.js
rename to chrome/browser/resources/welcome/onboarding_welcome/email/email_chooser.js
index d246358..277633a 100644
--- a/components/nux/email/resources/email_chooser.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/email/email_chooser.js
@@ -5,7 +5,7 @@
 /**
  * @const
  */
-var nux_email = nux_email || {};
+var nuxEmail = nuxEmail || {};
 
 /**
  * @typedef {{
@@ -15,7 +15,7 @@
  *    bookmarkId: {string|undefined},
  * }}
  */
-nux_email.EmailProviderModel;
+nuxEmail.EmailProviderModel;
 
 Polymer({
   is: 'email-chooser',
@@ -34,7 +34,7 @@
     /** @private */
     finalized_: Boolean,
 
-    /** @private {nux_email.EmailProviderModel} */
+    /** @private {nuxEmail.EmailProviderModel} */
     selectedEmailProvider_: {
       type: Object,
       value: () => null,
@@ -75,7 +75,7 @@
 
   /**
    * Handle toggling the email selected.
-   * @param {!{model: {item: !nux_email.EmailProviderModel}}} e
+   * @param {!{model: {item: !nuxEmail.EmailProviderModel}}} e
    * @private
    */
   onEmailClick_: function(e) {
@@ -110,7 +110,7 @@
   },
 
   /**
-   * @param {nux_email.EmailProviderModel=} newEmail
+   * @param {nuxEmail.EmailProviderModel=} newEmail
    * @private
    */
   revertBookmark_: function(emailProvider) {
@@ -121,8 +121,8 @@
   },
 
   /**
-   * @param {nux_email.EmailProviderModel} newEmail
-   * @param {nux_email.EmailProviderModel} prevEmail
+   * @param {nuxEmail.EmailProviderModel} newEmail
+   * @param {nuxEmail.EmailProviderModel} prevEmail
    * @private
    */
   onSelectedEmailProviderChange_: function(newEmail, prevEmail) {
diff --git a/components/nux/email/resources/nux_email.html b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
similarity index 100%
rename from components/nux/email/resources/nux_email.html
rename to chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.html
diff --git a/components/nux/email/resources/nux_email.js b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.js
similarity index 100%
rename from components/nux/email/resources/nux_email.js
rename to chrome/browser/resources/welcome/onboarding_welcome/email/nux_email.js
diff --git a/components/nux/email/resources/nux_email_proxy.html b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html
similarity index 100%
rename from components/nux/email/resources/nux_email_proxy.html
rename to chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.html
diff --git a/components/nux/email/resources/nux_email_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.js
similarity index 99%
rename from components/nux/email/resources/nux_email_proxy.js
rename to chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.js
index 2c49bbdd..75d2b553 100644
--- a/components/nux/email/resources/nux_email_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/email/nux_email_proxy.js
@@ -102,7 +102,7 @@
           name: loadTimeData.getString(`email_name_${i}`),
           icon: loadTimeData.getString(`email_name_${i}`).toLowerCase(),
           url: loadTimeData.getString(`email_url_${i}`)
-        })
+        });
       }
       return emailList;
     }
diff --git a/components/nux/google_apps/resources/BUILD.gn b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/BUILD.gn
similarity index 100%
rename from components/nux/google_apps/resources/BUILD.gn
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/BUILD.gn
diff --git a/components/nux/google_apps/resources/apps_chooser.html b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/apps_chooser.html
similarity index 100%
rename from components/nux/google_apps/resources/apps_chooser.html
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/apps_chooser.html
diff --git a/components/nux/google_apps/resources/apps_chooser.js b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/apps_chooser.js
similarity index 90%
rename from components/nux/google_apps/resources/apps_chooser.js
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/apps_chooser.js
index 87c50fe..688ffb79 100644
--- a/components/nux/google_apps/resources/apps_chooser.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/apps_chooser.js
@@ -5,7 +5,7 @@
 /**
  * @const
  */
-var nux_google_apps = nux_google_apps || {};
+var nuxGoogleApps = nuxGoogleApps || {};
 
 /**
  * @typedef {{
@@ -17,7 +17,7 @@
  *   set: function(string, boolean):void
  * }}
  */
-nux_google_apps.AppsArrayModel;
+nuxGoogleApps.AppsArrayModel;
 
 Polymer({
   is: 'apps-chooser',
@@ -73,12 +73,12 @@
    * @return {Array<boolean>}
    */
   getSelectedAppList() {
-    return this.appList.map(a => a.selected)
+    return this.appList.map(a => a.selected);
   },
 
   /**
    * Handle toggling the apps selected.
-   * @param {!{model: !nux_google_apps.AppsArrayModel}} e
+   * @param {!{model: !nuxGoogleApps.AppsArrayModel}} e
    * @private
    */
   onAppClick_: function(e) {
diff --git a/components/nux/google_apps/resources/nux_google_apps.html b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html
similarity index 100%
rename from components/nux/google_apps/resources/nux_google_apps.html
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.html
diff --git a/components/nux/google_apps/resources/nux_google_apps.js b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js
similarity index 100%
rename from components/nux/google_apps/resources/nux_google_apps.js
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js
diff --git a/components/nux/google_apps/resources/nux_google_apps_proxy.html b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps_proxy.html
similarity index 100%
rename from components/nux/google_apps/resources/nux_google_apps_proxy.html
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps_proxy.html
diff --git a/components/nux/google_apps/resources/nux_google_apps_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps_proxy.js
similarity index 100%
rename from components/nux/google_apps/resources/nux_google_apps_proxy.js
rename to chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps_proxy.js
diff --git a/components/nux/email/resources/aol_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/aol_1x.png
similarity index 100%
rename from components/nux/email/resources/aol_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/aol_1x.png
Binary files differ
diff --git a/components/nux/email/resources/aol_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/aol_2x.png
similarity index 100%
rename from components/nux/email/resources/aol_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/aol_2x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/chrome_store_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/chrome_store_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/chrome_store_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/chrome_store_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/chrome_store_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/chrome_store_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/chrome_store_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/chrome_store_24dp_2x.png
Binary files differ
diff --git a/components/nux/email/resources/gmail_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/gmail_1x.png
similarity index 100%
rename from components/nux/email/resources/gmail_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/gmail_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/gmail_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/gmail_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/gmail_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/gmail_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/gmail_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/gmail_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/gmail_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/gmail_24dp_2x.png
Binary files differ
diff --git a/components/nux/email/resources/gmail_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/gmail_2x.png
similarity index 100%
rename from components/nux/email/resources/gmail_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/gmail_2x.png
Binary files differ
diff --git a/components/nux/email/resources/icloud_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/icloud_1x.png
similarity index 100%
rename from components/nux/email/resources/icloud_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/icloud_1x.png
Binary files differ
diff --git a/components/nux/email/resources/icloud_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/icloud_2x.png
similarity index 100%
rename from components/nux/email/resources/icloud_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/icloud_2x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/maps_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/maps_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/maps_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/maps_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/maps_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/maps_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/maps_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/maps_24dp_2x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/news_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/news_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/news_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/news_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/news_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/news_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/news_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/news_24dp_2x.png
Binary files differ
diff --git a/components/nux/email/resources/outlook_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/outlook_1x.png
similarity index 100%
rename from components/nux/email/resources/outlook_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/outlook_1x.png
Binary files differ
diff --git a/components/nux/email/resources/outlook_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/outlook_2x.png
similarity index 100%
rename from components/nux/email/resources/outlook_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/outlook_2x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/translate_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/translate_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/translate_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/translate_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/translate_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/translate_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/translate_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/translate_24dp_2x.png
Binary files differ
diff --git a/components/nux/email/resources/yahoo_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/yahoo_1x.png
similarity index 100%
rename from components/nux/email/resources/yahoo_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/yahoo_1x.png
Binary files differ
diff --git a/components/nux/email/resources/yahoo_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/yahoo_2x.png
similarity index 100%
rename from components/nux/email/resources/yahoo_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/yahoo_2x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/youtube_24dp_1x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/youtube_24dp_1x.png
similarity index 100%
rename from components/nux/google_apps/resources/youtube_24dp_1x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/youtube_24dp_1x.png
Binary files differ
diff --git a/components/nux/google_apps/resources/youtube_24dp_2x.png b/chrome/browser/resources/welcome/onboarding_welcome/images/youtube_24dp_2x.png
similarity index 100%
rename from components/nux/google_apps/resources/youtube_24dp_2x.png
rename to chrome/browser/resources/welcome/onboarding_welcome/images/youtube_24dp_2x.png
Binary files differ
diff --git a/components/nux/set_as_default/resources/BUILD.gn b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/BUILD.gn
similarity index 100%
rename from components/nux/set_as_default/resources/BUILD.gn
rename to chrome/browser/resources/welcome/onboarding_welcome/set_as_default/BUILD.gn
diff --git a/components/nux/set_as_default/resources/nux_set_as_default.html b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
similarity index 100%
rename from components/nux/set_as_default/resources/nux_set_as_default.html
rename to chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
diff --git a/components/nux/set_as_default/resources/nux_set_as_default.js b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
similarity index 100%
rename from components/nux/set_as_default/resources/nux_set_as_default.js
rename to chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
diff --git a/components/nux/set_as_default/resources/nux_set_as_default_proxy.html b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.html
similarity index 100%
rename from components/nux/set_as_default/resources/nux_set_as_default_proxy.html
rename to chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.html
diff --git a/components/nux/set_as_default/resources/nux_set_as_default_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
similarity index 85%
rename from components/nux/set_as_default/resources/nux_set_as_default_proxy.js
rename to chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
index f33fe32..19be0e40 100644
--- a/components/nux/set_as_default/resources/nux_set_as_default_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
@@ -4,12 +4,10 @@
 
 cr.define('nux', function() {
   /** @interface */
-  class NuxSetAsDefaultProxy {
-  }
+  class NuxSetAsDefaultProxy {}
 
   /** @implements {NuxSetAsDefaultProxy} */
-  class NuxSetAsDefaultProxyImpl {
-  }
+  class NuxSetAsDefaultProxyImpl {}
 
   cr.addSingletonGetter(NuxSetAsDefaultProxyImpl);
 
diff --git a/chrome/browser/rlz/chrome_rlz_tracker_delegate.cc b/chrome/browser/rlz/chrome_rlz_tracker_delegate.cc
index e0c0610..346c93a 100644
--- a/chrome/browser/rlz/chrome_rlz_tracker_delegate.cc
+++ b/chrome/browser/rlz/chrome_rlz_tracker_delegate.cc
@@ -122,7 +122,7 @@
 }
 
 bool ChromeRLZTrackerDelegate::GetBrand(std::string* brand) {
-  return google_brand::GetBrand(brand);
+  return google_brand::GetRlzBrand(brand);
 }
 
 bool ChromeRLZTrackerDelegate::IsBrandOrganic(const std::string& brand) {
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index d0831ee..3367067 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -458,8 +458,8 @@
       break;
     }
     case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
-      BuildThemeInfo();
-      NotifyAboutThemeInfo();
+      theme_info_ = nullptr;
+      UpdateThemeInfo();
       break;
     default:
       NOTREACHED() << "Unexpected notification type in InstantService.";
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index 226b739..834b2fa5 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -65,6 +65,25 @@
 #include "chrome/browser/ui/user_manager.h"
 #endif
 
+namespace {
+SigninClient::SignoutDecision IsSignoutAllowed(
+    Profile* profile,
+    const signin_metrics::ProfileSignout signout_source_metric) {
+  // TODO(chcunningham): This logic should be reworked to only prohibit user-
+  // initiated sign-out. For now signin_util::IsUserSignoutAllowedForProfile()
+  // prohibits ALL sign-outs with the exception of ACCOUNT_REMOVED_FROM_DEVICE
+  // because this preserves the original behavior. A follow-up CL will make the
+  // slightly riskier change described above.
+  if (signin_util::IsUserSignoutAllowedForProfile(profile) ||
+      signout_source_metric ==
+          signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE) {
+    return SigninClient::SignoutDecision::ALLOW_SIGNOUT;
+  }
+
+  return SigninClient::SignoutDecision::DISALLOW_SIGNOUT;
+}
+}  // namespace
+
 ChromeSigninClient::ChromeSigninClient(
     Profile* profile,
     SigninErrorController* signin_error_controller)
@@ -186,8 +205,12 @@
 }
 
 void ChromeSigninClient::PreSignOut(
-    const base::Callback<void()>& sign_out,
+    base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
     signin_metrics::ProfileSignout signout_source_metric) {
+  DCHECK(on_signout_decision_reached);
+  DCHECK(!on_signout_decision_reached_) << "SignOut already in-progress!";
+  on_signout_decision_reached_ = std::move(on_signout_decision_reached);
+
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 
   // These sign out won't remove the policy cache, keep the window opened.
@@ -206,13 +229,12 @@
       // Call OnCloseBrowsersSuccess to continue sign out and show UserManager
       // afterwards.
       should_display_user_manager_ = false;  // Don't show UserManager twice.
-      OnCloseBrowsersSuccess(sign_out, signout_source_metric,
-                             profile_->GetPath());
+      OnCloseBrowsersSuccess(signout_source_metric, profile_->GetPath());
     } else {
       BrowserList::CloseAllBrowsersWithProfile(
           profile_,
           base::Bind(&ChromeSigninClient::OnCloseBrowsersSuccess,
-                     base::Unretained(this), sign_out, signout_source_metric),
+                     base::Unretained(this), signout_source_metric),
           base::Bind(&ChromeSigninClient::OnCloseBrowsersAborted,
                      base::Unretained(this)),
           signout_source_metric == signin_metrics::ABORT_SIGNIN ||
@@ -224,7 +246,8 @@
 #else
   {
 #endif
-    SigninClient::PreSignOut(sign_out, signout_source_metric);
+    std::move(on_signout_decision_reached_)
+        .Run(IsSignoutAllowed(profile_, signout_source_metric));
   }
 }
 
@@ -390,7 +413,6 @@
 }
 
 void ChromeSigninClient::OnCloseBrowsersSuccess(
-    const base::Callback<void()>& sign_out,
     const signin_metrics::ProfileSignout signout_source_metric,
     const base::FilePath& profile_path) {
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
@@ -398,7 +420,9 @@
     force_signin_verifier_->Cancel();
   }
 #endif
-  SigninClient::PreSignOut(sign_out, signout_source_metric);
+
+  std::move(on_signout_decision_reached_)
+      .Run(IsSignoutAllowed(profile_, signout_source_metric));
 
   LockForceSigninProfile(profile_path);
   // After sign out, lock the profile and show UserManager if necessary.
@@ -412,6 +436,10 @@
 void ChromeSigninClient::OnCloseBrowsersAborted(
     const base::FilePath& profile_path) {
   should_display_user_manager_ = true;
+
+  // Disallow sign-out (aborted).
+  std::move(on_signout_decision_reached_)
+      .Run(SignoutDecision::DISALLOW_SIGNOUT);
 }
 
 void ChromeSigninClient::LockForceSigninProfile(
diff --git a/chrome/browser/signin/chrome_signin_client.h b/chrome/browser/signin/chrome_signin_client.h
index f6e2643..d80f6f87 100644
--- a/chrome/browser/signin/chrome_signin_client.h
+++ b/chrome/browser/signin/chrome_signin_client.h
@@ -46,6 +46,9 @@
 
   // SigninClient implementation.
   PrefService* GetPrefs() override;
+  void PreSignOut(
+      base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
+      signin_metrics::ProfileSignout signout_source_metric) override;
   void OnSignedOut() override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
   network::mojom::CookieManager* GetCookieManager() override;
@@ -74,9 +77,6 @@
   void PostSignedIn(const std::string& account_id,
                     const std::string& username,
                     const std::string& password) override;
-  void PreSignOut(
-      const base::Callback<void()>& sign_out,
-      signin_metrics::ProfileSignout signout_source_metric) override;
 
   // SigninErrorController::Observer implementation.
   void OnErrorChanged() override;
@@ -116,7 +116,6 @@
   void MaybeFetchSigninTokenHandle();
   void VerifySyncToken();
   void OnCloseBrowsersSuccess(
-      const base::Callback<void()>& sign_out,
       const signin_metrics::ProfileSignout signout_source_metric,
       const base::FilePath& profile_path);
   void OnCloseBrowsersAborted(const base::FilePath& profile_path);
@@ -124,6 +123,10 @@
   Profile* profile_;
 
   SigninErrorController* signin_error_controller_;
+
+  // Stored callback from PreSignOut();
+  base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached_;
+
 #if !defined(OS_CHROMEOS)
   std::list<base::Closure> delayed_callbacks_;
 #endif
diff --git a/chrome/browser/signin/chrome_signin_client_unittest.cc b/chrome/browser/signin/chrome_signin_client_unittest.cc
index 6dfd70a5..f386f0f 100644
--- a/chrome/browser/signin/chrome_signin_client_unittest.cc
+++ b/chrome/browser/signin/chrome_signin_client_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/chrome_signin_client_factory.h"
@@ -150,10 +151,11 @@
     DCHECK(signin_error_controller);
   }
 
-  MOCK_METHOD3(DoSignOut,
+  MOCK_METHOD4(OnSignoutDecisionReached,
                void(signin_metrics::ProfileSignout,
                     signin_metrics::SignoutDelete,
-                    RemoveAccountsOption remove_option));
+                    RemoveAccountsOption remove_option,
+                    SigninClient::SignoutDecision signout_decision));
 
   AccountTrackerService fake_service_;
 };
@@ -196,10 +198,11 @@
       .Times(1);
   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
       .Times(1);
-  EXPECT_CALL(
-      *manager_,
-      DoSignOut(source_metric, delete_metric,
-                SigninManager::RemoveAccountsOption::kRemoveAllAccounts))
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  source_metric, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
       .Times(1);
 
   manager_->SignOut(source_metric, delete_metric);
@@ -218,10 +221,11 @@
       .Times(0);
   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
       .Times(1);
-  EXPECT_CALL(
-      *manager_,
-      DoSignOut(source_metric, delete_metric,
-                SigninManager::RemoveAccountsOption::kRemoveAllAccounts))
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  source_metric, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
       .Times(1);
   manager_->SignOut(source_metric, delete_metric);
 
@@ -231,10 +235,11 @@
       .Times(1);
   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
       .Times(1);
-  EXPECT_CALL(
-      *manager_,
-      DoSignOut(source_metric, delete_metric,
-                SigninManager::RemoveAccountsOption::kRemoveAllAccounts))
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  source_metric, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
       .Times(1);
   manager_->SignOut(source_metric, delete_metric);
 }
@@ -254,10 +259,11 @@
       .Times(0);
   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
       .Times(0);
-  EXPECT_CALL(
-      *manager_,
-      DoSignOut(source_metric, delete_metric,
-                SigninManager::RemoveAccountsOption::kRemoveAllAccounts))
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  source_metric, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
       .Times(1);
   manager_->SignOut(source_metric, delete_metric);
 }
@@ -280,13 +286,126 @@
       .Times(0);
   EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
       .Times(0);
-  EXPECT_CALL(
-      *manager_,
-      DoSignOut(source_metric, delete_metric,
-                SigninManager::RemoveAccountsOption::kRemoveAllAccounts))
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  source_metric, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
       .Times(1);
   manager_->SignOut(source_metric, delete_metric);
 }
 
+class ChromeSigninClientSignoutSourceTest
+    : public ::testing::WithParamInterface<signin_metrics::ProfileSignout>,
+      public ChromeSigninClientSignoutTest {};
+
+bool IsUserDrivenSignout(signin_metrics::ProfileSignout signout_source) {
+  switch (signout_source) {
+    // NOTE: SIGNOUT_TEST == SIGNOUT_PREF_CHANGED.
+    case signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED:
+    case signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED:
+    case signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN:
+    case signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS:
+    case signin_metrics::ProfileSignout::ABORT_SIGNIN:
+    case signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE:
+    case signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS:
+    case signin_metrics::ProfileSignout::
+        AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN:
+    case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI:
+      return true;
+    case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE:
+      // TODO(chcunningham): Add more of the above cases to this "false" branch.
+      // For now only ACCOUNT_REMOVED_FROM_DEVICE is here to preserve the status
+      // quo. Additional internal sources of sign-out will be moved here in a
+      // follow up CL.
+      return false;
+    case signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS:
+      NOTREACHED();
+      return false;
+  }
+}
+
+TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutAllowed) {
+  signin_metrics::ProfileSignout signout_source = GetParam();
+
+  TestingProfile::Builder builder;
+  builder.SetGuestSession();
+  std::unique_ptr<TestingProfile> profile = builder.Build();
+
+  CreateClient(profile.get());
+  manager_ = std::make_unique<MockSigninManager>(client_.get(),
+                                                 fake_controller_.get());
+
+  // User sign-out is allowed for this test.
+  ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
+
+  // Verify SigninManager gets callback indicating sign-out is always allowed.
+  signin_metrics::SignoutDelete delete_metric =
+      signin_metrics::SignoutDelete::IGNORE_METRIC;
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  signout_source, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  SigninClient::SignoutDecision::ALLOW_SIGNOUT))
+      .Times(1);
+
+  manager_->SignOut(signout_source, delete_metric);
+}
+
+TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) {
+  signin_metrics::ProfileSignout signout_source = GetParam();
+
+  TestingProfile::Builder builder;
+  builder.SetGuestSession();
+  std::unique_ptr<TestingProfile> profile = builder.Build();
+
+  CreateClient(profile.get());
+  manager_ = std::make_unique<MockSigninManager>(client_.get(),
+                                                 fake_controller_.get());
+
+  // Disallow user sign-out.
+  ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
+  signin_util::SetUserSignoutAllowedForProfile(profile.get(), false);
+  ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
+
+  // Verify SigninManager gets callback indicating sign-out is disallowed iff
+  // the source of the sign-out is a user-action.
+  SigninClient::SignoutDecision signout_decision =
+      IsUserDrivenSignout(signout_source)
+          ? SigninClient::SignoutDecision::DISALLOW_SIGNOUT
+          : SigninClient::SignoutDecision::ALLOW_SIGNOUT;
+  signin_metrics::SignoutDelete delete_metric =
+      signin_metrics::SignoutDelete::IGNORE_METRIC;
+  EXPECT_CALL(*manager_,
+              OnSignoutDecisionReached(
+                  signout_source, delete_metric,
+                  SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
+                  signout_decision))
+      .Times(1);
+
+  manager_->SignOut(signout_source, delete_metric);
+}
+
+const signin_metrics::ProfileSignout kSignoutSources[] = {
+    // NOTE: SIGNOUT_TEST == SIGNOUT_PREF_CHANGED.
+    signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED,
+    signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED,
+    signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN,
+    signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS,
+    signin_metrics::ProfileSignout::ABORT_SIGNIN,
+    signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE,
+    signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS,
+    signin_metrics::ProfileSignout::AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN,
+    signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI,
+    signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE,
+};
+static_assert(base::size(kSignoutSources) ==
+                  signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS,
+              "kSignoutSources should enumerate all ProfileSignout values");
+
+INSTANTIATE_TEST_CASE_P(AllSignoutSources,
+                        ChromeSigninClientSignoutSourceTest,
+                        testing::ValuesIn(kSignoutSources));
+
 #endif  // !defined(OS_ANDROID)
 #endif  // !defined(OS_CHROMEOS)
diff --git a/chrome/browser/signin/signin_util.cc b/chrome/browser/signin/signin_util.cc
index 5c6103b..90096d8 100644
--- a/chrome/browser/signin/signin_util.cc
+++ b/chrome/browser/signin/signin_util.cc
@@ -4,13 +4,46 @@
 
 #include "chrome/browser/signin/signin_util.h"
 
+#include <memory>
+
+#include "base/supports_user_data.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 
 namespace signin_util {
 namespace {
 
+constexpr char kSignoutSettingKey[] = "signout_setting";
+
+class UserSignoutSetting : public base::SupportsUserData::Data {
+ public:
+  // Fetch from Profile. Make and store if not already present.
+  static UserSignoutSetting* GetForProfile(Profile* profile) {
+    UserSignoutSetting* signout_setting = static_cast<UserSignoutSetting*>(
+        profile->GetUserData(kSignoutSettingKey));
+
+    if (!signout_setting) {
+      profile->SetUserData(kSignoutSettingKey,
+                           std::make_unique<UserSignoutSetting>());
+      signout_setting = static_cast<UserSignoutSetting*>(
+          profile->GetUserData(kSignoutSettingKey));
+    }
+
+    return signout_setting;
+  }
+
+  bool is_user_signout_allowed() const { return is_user_signout_allowed_; }
+  void set_is_user_signout_allowed(bool is_allowed) {
+    is_user_signout_allowed_ = is_allowed;
+  }
+
+ private:
+  // User sign-out allowed by default.
+  bool is_user_signout_allowed_ = true;
+};
+
 enum ForceSigninPolicyCache {
   NOT_CACHED = 0,
   ENABLE,
@@ -42,4 +75,13 @@
   g_is_force_signin_enabled_cache = NOT_CACHED;
 }
 
+void SetUserSignoutAllowedForProfile(Profile* profile, bool is_allowed) {
+  UserSignoutSetting::GetForProfile(profile)->set_is_user_signout_allowed(
+      is_allowed);
+}
+
+bool IsUserSignoutAllowedForProfile(Profile* profile) {
+  return UserSignoutSetting::GetForProfile(profile)->is_user_signout_allowed();
+}
+
 }  // namespace signin_util
diff --git a/chrome/browser/signin/signin_util.h b/chrome/browser/signin/signin_util.h
index be53b9d8..b24763ae 100644
--- a/chrome/browser/signin/signin_util.h
+++ b/chrome/browser/signin/signin_util.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_SIGNIN_SIGNIN_UTIL_H_
 #define CHROME_BROWSER_SIGNIN_SIGNIN_UTIL_H_
 
+class Profile;
+
 namespace signin_util {
 
 // Return whether the force sign in policy is enabled or not.
@@ -17,6 +19,15 @@
 // Reset force sign in to uninitialized state for testing.
 void ResetForceSigninForTesting();
 
+// Sign-out is allowed by default, but some Chrome profiles (e.g. for cloud-
+// managed enterprise accounts) may wish to disallow user-initiated sign-out.
+// Note that this exempts sign-outs that are not user-initiated (e.g. sign-out
+// triggered when cloud policy no longer allows current email pattern). See
+// ChromeSigninClient::PreSignOut().
+void SetUserSignoutAllowedForProfile(Profile* profile, bool is_allowed);
+
+bool IsUserSignoutAllowedForProfile(Profile* profile);
+
 }  // namespace signin_util
 
 #endif  // CHROME_BROWSER_SIGNIN_SIGNIN_UTIL_H_
diff --git a/chrome/browser/supervised_user/child_accounts/child_account_service.cc b/chrome/browser/supervised_user/child_accounts/child_account_service.cc
index ca6ac82..77bdf64 100644
--- a/chrome/browser/supervised_user/child_accounts/child_account_service.cc
+++ b/chrome/browser/supervised_user/child_accounts/child_account_service.cc
@@ -39,6 +39,8 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#else
+#include "chrome/browser/signin/signin_util.h"
 #endif
 
 const char kGaiaCookieManagerSource[] = "child_account_service";
@@ -195,7 +197,7 @@
     // This is also used by user policies (UserPolicySigninService), but since
     // child accounts can not also be Dasher accounts, there shouldn't be any
     // problems.
-    SigninManagerFactory::GetForProfile(profile_)->ProhibitSignout(true);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, false);
 #endif
 
     // TODO(treib): Maybe store the last update time in a pref, so we don't
@@ -227,7 +229,7 @@
 #endif
 
 #if !defined(OS_CHROMEOS)
-    SigninManagerFactory::GetForProfile(profile_)->ProhibitSignout(false);
+    signin_util::SetUserSignoutAllowedForProfile(profile_, true);
 #endif
 
     CancelFetchingFamilyInfo();
diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc
index 735a580..2b28b944 100644
--- a/chrome/browser/sync/sync_ui_util.cc
+++ b/chrome/browser/sync/sync_ui_util.cc
@@ -22,8 +22,7 @@
 #include "ui/base/l10n/l10n_util.h"
 
 #if !defined(OS_CHROMEOS)
-#include "chrome/browser/signin/signin_manager_factory.h"
-#include "components/signin/core/browser/signin_manager.h"
+#include "chrome/browser/signin/signin_util.h"
 #endif  // defined(OS_CHROMEOS)
 
 using browser_sync::ProfileSyncService;
@@ -114,7 +113,7 @@
     status_label->assign(l10n_util::GetStringUTF16(
         IDS_SYNC_STATUS_UNRECOVERABLE_ERROR));
     // The message for managed accounts is the same as that of the cros.
-    if (SigninManagerFactory::GetForProfile(profile)->IsSignoutProhibited()) {
+    if (!signin_util::IsUserSignoutAllowedForProfile(profile)) {
       status_label->assign(l10n_util::GetStringUTF16(
           IDS_SYNC_STATUS_UNRECOVERABLE_ERROR_NEEDS_SIGNOUT));
     }
@@ -352,7 +351,7 @@
     service->QueryDetailedSyncStatus(&status);
     if (status.sync_protocol_error.action != syncer::UPGRADE_CLIENT) {
       // Display different messages and buttons for managed accounts.
-      if (SigninManagerFactory::GetForProfile(profile)->IsSignoutProhibited()) {
+      if (!signin_util::IsUserSignoutAllowedForProfile(profile)) {
         // For a managed user, the user is directed to the signout
         // confirmation dialogue in the settings page.
         *content_string_id = IDS_SYNC_ERROR_USER_MENU_SIGNOUT_MESSAGE;
diff --git a/chrome/browser/sync_file_system/local/canned_syncable_file_system.cc b/chrome/browser/sync_file_system/local/canned_syncable_file_system.cc
index 312b76a0..74d9880 100644
--- a/chrome/browser/sync_file_system/local/canned_syncable_file_system.cc
+++ b/chrome/browser/sync_file_system/local/canned_syncable_file_system.cc
@@ -36,6 +36,7 @@
 #include "storage/browser/test/mock_special_storage_policy.h"
 #include "storage/browser/test/test_file_system_options.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
 
 using base::File;
 using storage::FileSystemContext;
@@ -672,7 +673,7 @@
   EXPECT_TRUE(is_filesystem_opened_);
   DCHECK(quota_manager_.get());
   quota_manager_->GetUsageAndQuota(
-      origin_, storage_type(),
+      url::Origin::Create(origin_), storage_type(),
       base::BindOnce(&DidGetUsageAndQuota, std::move(callback), usage, quota));
 }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index d0db65d24..5566278 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1559,6 +1559,8 @@
       "views/location_bar/intent_picker_view.h",
       "views/platform_keys_certificate_selector_chromeos.cc",
       "views/platform_keys_certificate_selector_chromeos.h",
+      "views/profiles/profile_indicator_icon.cc",
+      "views/profiles/profile_indicator_icon.h",
 
       # On chromeos, file manager extension handles the file open/save dialog.
       "views/select_file_dialog_extension.cc",
@@ -1896,8 +1898,6 @@
       "views/close_bubble_on_tab_activation_helper.h",
       "views/external_protocol_dialog.cc",
       "views/external_protocol_dialog.h",
-      "views/profiles/avatar_button.cc",
-      "views/profiles/avatar_button.h",
       "views/profiles/badged_profile_photo.cc",
       "views/profiles/badged_profile_photo.h",
       "views/profiles/dice_accounts_menu.cc",
@@ -2549,8 +2549,6 @@
       "views/folder_upload_confirmation_view.h",
       "views/frame/app_menu_button.cc",
       "views/frame/app_menu_button.h",
-      "views/frame/avatar_button_manager.cc",
-      "views/frame/avatar_button_manager.h",
       "views/frame/browser_frame.cc",
       "views/frame/browser_frame.h",
       "views/frame/browser_non_client_frame_view.cc",
@@ -2784,11 +2782,8 @@
       "views/permission_bubble/permission_prompt_impl.cc",
       "views/permission_bubble/permission_prompt_impl.h",
       "views/permission_bubble/permission_prompt_impl_views.cc",
-      "views/profiles/avatar_button_style.h",
       "views/profiles/avatar_toolbar_button.cc",
       "views/profiles/avatar_toolbar_button.h",
-      "views/profiles/profile_indicator_icon.cc",
-      "views/profiles/profile_indicator_icon.h",
       "views/profiles/signin_view_controller_delegate_views.cc",
       "views/profiles/signin_view_controller_delegate_views.h",
       "views/proximity_auth/proximity_auth_error_bubble_view.cc",
@@ -2829,8 +2824,8 @@
       "views/tab_icon_view.h",
       "views/tab_modal_confirm_dialog_views.cc",
       "views/tab_modal_confirm_dialog_views.h",
-      "views/tabs/alert_indicator_button.cc",
-      "views/tabs/alert_indicator_button.h",
+      "views/tabs/alert_indicator.cc",
+      "views/tabs/alert_indicator.h",
       "views/tabs/browser_tab_strip_controller.cc",
       "views/tabs/browser_tab_strip_controller.h",
       "views/tabs/glow_hover_controller.cc",
diff --git a/chrome/browser/ui/ash/chrome_accessibility_delegate.cc b/chrome/browser/ui/ash/chrome_accessibility_delegate.cc
index 10072534..8001a75 100644
--- a/chrome/browser/ui/ash/chrome_accessibility_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_accessibility_delegate.cc
@@ -8,9 +8,6 @@
 
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
-#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
-#include "chrome/common/extensions/chrome_extension_messages.h"
-#include "ui/aura/env.h"
 
 using chromeos::AccessibilityManager;
 using chromeos::MagnificationManager;
@@ -45,25 +42,3 @@
 
   return std::numeric_limits<double>::min();
 }
-
-void ChromeAccessibilityDelegate::DispatchAccessibilityEvent(
-    const ui::AXTreeID& tree_id,
-    const std::vector<ui::AXTreeUpdate>& updates,
-    const ui::AXEvent& event) {
-  ExtensionMsg_AccessibilityEventBundleParams event_bundle;
-  event_bundle.tree_id = tree_id;
-  for (const ui::AXTreeUpdate& update : updates)
-    event_bundle.updates.push_back(update);
-  event_bundle.events.push_back(event);
-  event_bundle.mouse_location = aura::Env::GetInstance()->last_mouse_location();
-
-  // Forward the tree updates and the event to the accessibility extension.
-  extensions::AutomationEventRouter::GetInstance()->DispatchAccessibilityEvents(
-      event_bundle);
-}
-
-void ChromeAccessibilityDelegate::DispatchTreeDestroyedEvent(
-    const ui::AXTreeID& tree_id) {
-  extensions::AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
-      tree_id, nullptr /* browser_context */);
-}
diff --git a/chrome/browser/ui/ash/chrome_accessibility_delegate.h b/chrome/browser/ui/ash/chrome_accessibility_delegate.h
index 773acc7..015bd0f 100644
--- a/chrome/browser/ui/ash/chrome_accessibility_delegate.h
+++ b/chrome/browser/ui/ash/chrome_accessibility_delegate.h
@@ -20,10 +20,6 @@
   bool ShouldShowAccessibilityMenu() const override;
   void SaveScreenMagnifierScale(double scale) override;
   double GetSavedScreenMagnifierScale() override;
-  void DispatchAccessibilityEvent(const ui::AXTreeID& tree_id,
-                                  const std::vector<ui::AXTreeUpdate>& updates,
-                                  const ui::AXEvent& event) override;
-  void DispatchTreeDestroyedEvent(const ui::AXTreeID& tree_id) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ChromeAccessibilityDelegate);
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.cc b/chrome/browser/ui/ash/chrome_new_window_client.cc
index 5e52647..d4e1a718 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client.cc
@@ -37,6 +37,7 @@
 #include "components/url_formatter/url_fixer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/service_manager_connection.h"
+#include "content/public/common/was_activated_option.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/constants.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -151,8 +152,9 @@
   browser->SetFocusToLocationBar(false);
 }
 
-void ChromeNewWindowClient::NewTabWithUrl(const GURL& url) {
-  OpenUrlImpl(url);
+void ChromeNewWindowClient::NewTabWithUrl(const GURL& url,
+                                          bool from_user_interaction) {
+  OpenUrlImpl(url, from_user_interaction);
 }
 
 void ChromeNewWindowClient::NewWindow(bool is_incognito) {
@@ -269,7 +271,8 @@
     url_to_open = arc::ArcUrlToExternalFileUrl(url_to_open);
   }
 
-  content::WebContents* tab = OpenUrlImpl(url_to_open);
+  content::WebContents* tab =
+      OpenUrlImpl(url_to_open, false /* from_user_interaction */);
   if (!tab)
     return;
 
@@ -278,7 +281,9 @@
                    std::make_unique<arc::ArcWebContentsData>());
 }
 
-content::WebContents* ChromeNewWindowClient::OpenUrlImpl(const GURL& url) {
+content::WebContents* ChromeNewWindowClient::OpenUrlImpl(
+    const GURL& url,
+    bool from_user_interaction) {
   // If the url is for system settings, show the settings in a window instead of
   // a browser tab.
   if (url.GetContent() == "settings" &&
@@ -293,6 +298,10 @@
       ProfileManager::GetActiveUserProfile(), url,
       ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                 ui::PAGE_TRANSITION_FROM_API));
+
+  if (from_user_interaction)
+    navigate_params.was_activated = content::WasActivatedOption::kYes;
+
   Navigate(&navigate_params);
 
   if (navigate_params.browser) {
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.h b/chrome/browser/ui/ash/chrome_new_window_client.h
index b739697..8ada39d 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client.h
+++ b/chrome/browser/ui/ash/chrome_new_window_client.h
@@ -29,7 +29,7 @@
 
   // Overridden from ash::mojom::NewWindowClient:
   void NewTab() override;
-  void NewTabWithUrl(const GURL& url) override;
+  void NewTabWithUrl(const GURL& url, bool from_user_interaction) override;
   void NewWindow(bool incognito) override;
   void OpenFileManager() override;
   void OpenCrosh() override;
@@ -48,8 +48,11 @@
 
   // Opens a URL in a new tab. Returns the WebContents for the tab that
   // opened the URL. If the URL is for a chrome://settings page, opens settings
-  // in a new window and returns null.
-  content::WebContents* OpenUrlImpl(const GURL& url);
+  // in a new window and returns null. If the |from_user_interaction| is true
+  // then the page will load with a user activation. This means it will be able
+  // to autoplay media without restriction.
+  content::WebContents* OpenUrlImpl(const GURL& url,
+                                    bool from_user_interaction);
 
   std::unique_ptr<TabRestoreHelper> tab_restore_helper_;
 
diff --git a/chrome/browser/ui/aura/accessibility/DEPS b/chrome/browser/ui/aura/accessibility/DEPS
index fbced7a8..b2306413 100644
--- a/chrome/browser/ui/aura/accessibility/DEPS
+++ b/chrome/browser/ui/aura/accessibility/DEPS
@@ -1,7 +1,6 @@
 specific_include_rules = {
   "automation_manager_aura\.cc": [
     # TODO(mash): Fix. https://crbug.com/756054
-    "+ash/accessibility/ax_host_service.h",
     "+ash/shell.h",
     "+ash/wm/window_util.h",
   ],
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
index abda461..cc6ceae 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -25,9 +25,9 @@
 #include "ui/views/widget/widget.h"
 
 #if defined(OS_CHROMEOS)
-#include "ash/accessibility/ax_host_service.h"
 #include "ash/shell.h"
 #include "ash/wm/window_util.h"
+#include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/views/widget/widget_delegate.h"
 #endif
@@ -58,8 +58,7 @@
     }
   }
   // Gain access to out-of-process native windows.
-  // TODO(mash): Split AXHostService into chrome and ash parts.
-  ash::AXHostService::SetAutomationEnabled(true);
+  AXHostService::SetAutomationEnabled(true);
 #endif
 }
 
@@ -68,7 +67,7 @@
   Reset(true);
 
 #if defined(OS_CHROMEOS)
-  ash::AXHostService::SetAutomationEnabled(false);
+  AXHostService::SetAutomationEnabled(false);
 #endif
 }
 
@@ -250,13 +249,14 @@
     ui::AXNodeData node_data;
     widget->widget_delegate()->GetContentsView()->GetAccessibleNodeData(
         &node_data);
-    child_ax_tree_id =
-        node_data.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
+    child_ax_tree_id = ui::AXTreeID::FromString(
+        node_data.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
     DCHECK_NE(child_ax_tree_id, ui::AXTreeIDUnknown());
     DCHECK_NE(child_ax_tree_id, ui::DesktopAXTreeID());
   } else {
     // For normal windows the (optional) child tree is an aura window property.
-    std::string* child_ax_tree_id_ptr = window->GetProperty(ui::kChildAXTreeID);
+    ui::AXTreeID* child_ax_tree_id_ptr =
+        window->GetProperty(ui::kChildAXTreeID);
     if (child_ax_tree_id_ptr)
       child_ax_tree_id = *child_ax_tree_id_ptr;
   }
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
index b15a37a..cda6bd1 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
@@ -31,8 +31,8 @@
     std::vector<views::AXAuraObjWrapper*>* web_hosts) {
   ui::AXNodeData node_data;
   tree->SerializeNode(node, &node_data);
-  if (node_data.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId) ==
-      target_ax_tree_id) {
+  if (ui::AXTreeID::FromString(node_data.GetStringAttribute(
+          ax::mojom::StringAttribute::kChildTreeId)) == target_ax_tree_id) {
     web_hosts->push_back(node);
   }
 
diff --git a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
index f22ec7a..f6b86e91 100644
--- a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
@@ -164,7 +164,7 @@
   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
   local_card_migration_bubble_ =
       browser->window()->ShowLocalCardMigrationBubble(web_contents(), this,
-                                                      true);
+                                                      is_reshow_);
   DCHECK(local_card_migration_bubble_);
   UpdateIcon();
   timer_.reset(new base::ElapsedTimer());
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 0605c54..15a9342 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -305,6 +305,18 @@
   return nullptr;
 }
 
+void UnmuteIfMutedByExtension(content::WebContents* contents,
+                              const std::string& extension_id) {
+  LastMuteMetadata::CreateForWebContents(contents);  // Ensures metadata exists.
+  LastMuteMetadata* const metadata =
+      LastMuteMetadata::FromWebContents(contents);
+  if (metadata->reason == TabMutedReason::EXTENSION &&
+      metadata->extension_id == extension_id) {
+    chrome::SetTabAudioMuted(contents, false, TabMutedReason::EXTENSION,
+                             extension_id);
+  }
+}
+
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2219,7 +2231,7 @@
            extension->id())) {
         tab_strip_model_->CloseWebContentsAt(i, TabStripModel::CLOSE_NONE);
       } else {
-        chrome::UnmuteIfMutedByExtension(web_contents, extension->id());
+        UnmuteIfMutedByExtension(web_contents, extension->id());
       }
     }
   }
diff --git a/chrome/browser/ui/libgtkui/nav_button_provider_gtk.cc b/chrome/browser/ui/libgtkui/nav_button_provider_gtk.cc
index 042b2d9..9652018a 100644
--- a/chrome/browser/ui/libgtkui/nav_button_provider_gtk.cc
+++ b/chrome/browser/ui/libgtkui/nav_button_provider_gtk.cc
@@ -6,7 +6,6 @@
 
 #include <gtk/gtk.h>
 
-#include "chrome/browser/ui/libgtkui/gtk_background_painter.h"
 #include "chrome/browser/ui/libgtkui/gtk_util.h"
 #include "ui/base/glib/scoped_gobject.h"
 #include "ui/gfx/image/image_skia.h"
@@ -183,13 +182,6 @@
       MarginFromStyleContext(button_context, GTK_STATE_FLAG_NORMAL);
 }
 
-ScopedStyleContext CreateAvatarButtonContext(GtkStyleContext* header_context) {
-  return AppendCssNodeToStyleContext(
-      header_context, GtkVersionCheck(3, 20)
-                          ? "GtkButton#button.image-button.toggle"
-                          : "GtkToggleButton#button.image-button");
-}
-
 class NavButtonImageSource : public gfx::ImageSkiaSource {
  public:
   NavButtonImageSource(chrome::FrameButtonDisplayType type,
@@ -417,84 +409,4 @@
   return inter_button_spacing_;
 }
 
-std::unique_ptr<views::Background>
-NavButtonProviderGtk::CreateAvatarButtonBackground(
-    const views::Button* avatar_button) const {
-  auto header_context = CreateHeaderContext(false);
-  auto button_context = CreateAvatarButtonContext(header_context);
-  return std::make_unique<GtkBackgroundPainter>(avatar_button,
-                                                std::move(button_context));
-}
-
-void NavButtonProviderGtk::CalculateCaptionButtonLayout(
-    const gfx::Size& content_size,
-    int top_area_height,
-    gfx::Size* caption_button_size,
-    gfx::Insets* caption_button_spacing) const {
-  auto header_context = CreateHeaderContext(false);
-  gfx::InsetsF header_padding =
-      PaddingFromStyleContext(header_context, GTK_STATE_FLAG_NORMAL);
-
-  auto button_context = CreateAvatarButtonContext(header_context);
-  gfx::InsetsF button_padding =
-      PaddingFromStyleContext(button_context, GTK_STATE_FLAG_NORMAL);
-  gfx::InsetsF button_border =
-      BorderFromStyleContext(button_context, GTK_STATE_FLAG_NORMAL);
-  gfx::InsetsF button_margin =
-      MarginFromStyleContext(button_context, GTK_STATE_FLAG_NORMAL);
-
-  float content_width = content_size.width();
-  float content_height = content_size.height();
-  if (GtkVersionCheck(3, 20)) {
-    int min_width, min_height;
-#if GTK_CHECK_VERSION(3, 90, 0)
-    gtk_style_context_get(button_context, "min-width", &min_width, "min-height",
-                          &min_height, nullptr);
-#else
-    gtk_style_context_get(button_context, GTK_STATE_FLAG_NORMAL, "min-width",
-                          &min_width, "min-height", &min_height, nullptr);
-#endif
-    content_width = std::max(content_width, static_cast<float>(min_width));
-    content_height = std::max(content_height, static_cast<float>(min_height));
-  }
-
-  gfx::InsetsF scalable_insets =
-      header_padding + button_padding + button_border + button_margin;
-  float scalable_height =
-      scalable_insets.top() + scalable_insets.bottom() + content_height;
-
-  float scale = scalable_height > top_area_height && scalable_height != 0
-                    ? top_area_height / scalable_height
-                    : 1.0f;
-  header_padding = header_padding.Scale(scale);
-  button_padding = button_padding.Scale(scale);
-  button_border = button_border.Scale(scale);
-  button_margin = button_margin.Scale(scale);
-  // Don't scale |content_width| down if the button is wide.
-  if (content_width <= content_height)
-    content_width *= scale;
-  content_height *= scale;
-
-  float button_height = content_height + button_border.top() +
-                        button_border.bottom() + button_padding.top() +
-                        button_padding.bottom();
-  float button_height_with_margin =
-      button_height + button_margin.top() + button_margin.bottom();
-  float shiftable_region_start = header_padding.top();
-  float shiftable_region_end = top_area_height - header_padding.bottom();
-  float button_offset_in_shiftable_region =
-      (shiftable_region_end - shiftable_region_start -
-       button_height_with_margin) /
-      2;
-
-  *caption_button_size = gfx::Size(
-      std::round(content_width + button_border.left() + button_border.right() +
-                 button_padding.left() + button_padding.right()),
-      std::round(button_height));
-  *caption_button_spacing = gfx::Insets(
-      std::round(shiftable_region_start + button_margin.top() +
-                 button_offset_in_shiftable_region),
-      std::round(button_margin.left()), 0, std::round(button_margin.right()));
-}
-
 }  // namespace libgtkui
diff --git a/chrome/browser/ui/libgtkui/nav_button_provider_gtk.h b/chrome/browser/ui/libgtkui/nav_button_provider_gtk.h
index e2994cd..175dc194 100644
--- a/chrome/browser/ui/libgtkui/nav_button_provider_gtk.h
+++ b/chrome/browser/ui/libgtkui/nav_button_provider_gtk.h
@@ -28,13 +28,6 @@
       chrome::FrameButtonDisplayType type) const override;
   gfx::Insets GetTopAreaSpacing() const override;
   int GetInterNavButtonSpacing() const override;
-  std::unique_ptr<views::Background> CreateAvatarButtonBackground(
-      const views::Button* avatar_button) const override;
-  void CalculateCaptionButtonLayout(
-      const gfx::Size& content_size,
-      int top_area_height,
-      gfx::Size* caption_button_size,
-      gfx::Insets* caption_button_spacing) const override;
 
  private:
   std::map<chrome::FrameButtonDisplayType,
diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
index 9561463..7242589 100644
--- a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
@@ -535,9 +535,8 @@
           : (pending_password_.federation_origin.opaque()
                  ? PasswordTitleType::SAVE_PASSWORD
                  : PasswordTitleType::SAVE_ACCOUNT);
-  GetSavePasswordDialogTitleTextAndLinkRange(
-      GetWebContents()->GetVisibleURL(), origin_, IsSyncUser(GetProfile()),
-      type, &title_, &title_brand_link_range_);
+  GetSavePasswordDialogTitleTextAndLinkRange(GetWebContents()->GetVisibleURL(),
+                                             origin_, type, &title_);
 }
 
 void ManagePasswordsBubbleModel::UpdateManageStateTitle() {
diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model.h b/chrome/browser/ui/passwords/manage_passwords_bubble_model.h
index bd03214..6141d53 100644
--- a/chrome/browser/ui/passwords/manage_passwords_bubble_model.h
+++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model.h
@@ -173,6 +173,7 @@
   base::string16 title_;
   // Range of characters in the title that contains the Smart Lock Brand and
   // should point to an article. For the default title the range is empty.
+  // TODO(crbug/890336): remove.
   gfx::Range title_brand_link_range_;
   autofill::PasswordForm pending_password_;
   std::vector<autofill::PasswordForm> local_credentials_;
diff --git a/chrome/browser/ui/passwords/manage_passwords_view_utils.cc b/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
index 338d09e7..3dd2618 100644
--- a/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_view_utils.cc
@@ -24,7 +24,6 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.h"
-#include "ui/gfx/range/range.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -74,13 +73,10 @@
       form.username_value + base::ASCIIToUTF16("\n") + federation);
 }
 
-void GetSavePasswordDialogTitleTextAndLinkRange(
-    const GURL& user_visible_url,
-    const GURL& form_origin_url,
-    bool is_smartlock_branding_enabled,
-    PasswordTitleType dialog_type,
-    base::string16* title,
-    gfx::Range* title_link_range) {
+void GetSavePasswordDialogTitleTextAndLinkRange(const GURL& user_visible_url,
+                                                const GURL& form_origin_url,
+                                                PasswordTitleType dialog_type,
+                                                base::string16* title) {
   DCHECK(!password_manager::IsValidAndroidFacetURI(form_origin_url.spec()));
   std::vector<size_t> offsets;
   std::vector<base::string16> replacements;
@@ -109,24 +105,8 @@
     replacements.push_back(url_formatter::FormatUrlForSecurityDisplay(
         form_origin_url, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS));
   }
-  base::string16 title_link;
-  if (title_id == IDS_SAVE_ACCOUNT) {
-    title_link =
-        is_smartlock_branding_enabled
-            ? l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK)
-            : l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_TITLE_BRAND);
-    replacements.insert(replacements.begin(), title_link);
-  }
 
   *title = l10n_util::GetStringFUTF16(title_id, replacements, &offsets);
-  if (title_id == IDS_SAVE_ACCOUNT && is_smartlock_branding_enabled &&
-      !offsets.empty()) {
-    // |offsets| can be empty when the localised string associated with
-    // |title_id| could not be found. While this situation is an error, it
-    // needs to be handled gracefully, see http://crbug.com/658902#c18.
-    *title_link_range =
-        gfx::Range(offsets[0], offsets[0] + title_link.length());
-  }
 }
 
 void GetManagePasswordsDialogTitleText(const GURL& user_visible_url,
@@ -152,39 +132,6 @@
   }
 }
 
-void GetAccountChooserDialogTitleTextAndLinkRange(
-    bool is_smartlock_branding_enabled,
-    bool many_accounts,
-    base::string16* title,
-    gfx::Range* title_link_range) {
-  int string_id = many_accounts
-      ? IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE_MANY_ACCOUNTS
-      : IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE_ONE_ACCOUNT;
-  GetBrandedTextAndLinkRange(is_smartlock_branding_enabled,
-                             string_id, string_id,
-                             title, title_link_range);
-}
-
-void GetBrandedTextAndLinkRange(bool is_smartlock_branding_enabled,
-                                int smartlock_string_id,
-                                int default_string_id,
-                                base::string16* out_string,
-                                gfx::Range* link_range) {
-  if (is_smartlock_branding_enabled) {
-    size_t offset;
-    base::string16 brand_name =
-        l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK);
-    *out_string = l10n_util::GetStringFUTF16(smartlock_string_id, brand_name,
-                                             &offset);
-    *link_range = gfx::Range(offset, offset + brand_name.length());
-  } else {
-    *out_string = l10n_util::GetStringFUTF16(
-        default_string_id,
-        l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_TITLE_BRAND));
-    *link_range = gfx::Range();
-  }
-}
-
 base::string16 GetDisplayUsername(const autofill::PasswordForm& form) {
   return form.username_value.empty()
              ? l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN)
diff --git a/chrome/browser/ui/passwords/manage_passwords_view_utils.h b/chrome/browser/ui/passwords/manage_passwords_view_utils.h
index 6330d067..3af5e27 100644
--- a/chrome/browser/ui/passwords/manage_passwords_view_utils.h
+++ b/chrome/browser/ui/passwords/manage_passwords_view_utils.h
@@ -13,7 +13,6 @@
 
 namespace gfx {
 class ImageSkia;
-class Range;
 }  // namespace gfx
 
 class GURL;
@@ -45,13 +44,10 @@
 // bubble (depending on |dialog_type|). If the registry controlled domain of
 // |user_visible_url| (i.e. the one seen in the omnibox) differs from the
 // registry controlled domain of |form_origin_url|, it adds the site name.
-void GetSavePasswordDialogTitleTextAndLinkRange(
-    const GURL& user_visible_url,
-    const GURL& form_origin_url,
-    bool is_smartlock_branding_enabled,
-    PasswordTitleType dialog_type,
-    base::string16* title,
-    gfx::Range* title_link_range);
+void GetSavePasswordDialogTitleTextAndLinkRange(const GURL& user_visible_url,
+                                                const GURL& form_origin_url,
+                                                PasswordTitleType dialog_type,
+                                                base::string16* title);
 
 // Sets the formatted |title| in the Manage Passwords bubble. If the registry
 // controlled domain of |user_visible_url| (i.e. the one seen in the omnibox)
@@ -69,29 +65,6 @@
                                        bool has_credentials,
                                        base::string16* title);
 
-// Sets the formatted |title| in the Account Chooser UI.
-// If |is_smartlock_branding_enabled| is true, sets the |title_link_range| for
-// the "Google Smart Lock" text range to be set visibly as a hyperlink in the
-// dialog bubble otherwise chooses the title which doesn't contain Smart Lock
-// branding.
-void GetAccountChooserDialogTitleTextAndLinkRange(
-    bool is_smartlock_branding_enabled,
-    bool many_accounts,
-    base::string16* title,
-    gfx::Range* title_link_range);
-
-// Loads |smartlock_string_id| or |default_string_id| string from the resources
-// and substitutes the placeholder with the correct password manager branding
-// (Google Smart Lock, Google Chrome or Chromium) according to
-// |is_smartlock_branding_enabled|. If |is_smartlock_branding_enabled| is true
-// then |link_range| contains the link range for the brand name.
-void GetBrandedTextAndLinkRange(
-    bool is_smartlock_branding_enabled,
-    int smartlock_string_id,
-    int default_string_id,
-    base::string16* out_string,
-    gfx::Range* link_range);
-
 // Returns an username in the form that should be shown in the bubble.
 base::string16 GetDisplayUsername(const autofill::PasswordForm& form);
 
diff --git a/chrome/browser/ui/passwords/manage_passwords_view_utils_unittest.cc b/chrome/browser/ui/passwords/manage_passwords_view_utils_unittest.cc
index ba4e9f6..77286ad 100644
--- a/chrome/browser/ui/passwords/manage_passwords_view_utils_unittest.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_view_utils_unittest.cc
@@ -65,71 +65,68 @@
 const struct {
   const char* const user_visible_url;
   const char* const form_origin_url;
-  bool is_smartlock_branding_enabled;
   PasswordTitleType bubble_type;
   const char* const expected_domain_placeholder;  // domain name
-  size_t expected_link_range_start;
-  size_t expected_link_range_end;
 } kDomainsTestCases[] = {
     // Same domains.
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     false, PasswordTitleType::SAVE_PASSWORD, "", 0, 0},
+     PasswordTitleType::SAVE_PASSWORD, ""},
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     true, PasswordTitleType::SAVE_PASSWORD, "", 0, 0},
+     PasswordTitleType::SAVE_PASSWORD, ""},
 
     // Different subdomains.
     {"https://a.example.com/landing",
-     "https://b.example.com/login#form?value=3", false,
-     PasswordTitleType::SAVE_PASSWORD, "", 0, 0},
+     "https://b.example.com/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, ""},
     {"https://a.example.com/landing",
-     "https://b.example.com/login#form?value=3", true,
-     PasswordTitleType::SAVE_PASSWORD, "", 0, 0},
+     "https://b.example.com/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, ""},
 
     // Different domains.
-    {"https://another.org", "https://example.com:/login#form?value=3", false,
-     PasswordTitleType::SAVE_PASSWORD, "example.com", 0, 0},
-    {"https://another.org", "https://example.com/login#form?value=3", true,
-     PasswordTitleType::SAVE_PASSWORD, "example.com", 0, 0},
+    {"https://another.org", "https://example.com:/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, "example.com"},
+    {"https://another.org", "https://example.com/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, "example.com"},
 
     // Different domains and password form origin url with
     // default port for the scheme.
-    {"https://another.org", "https://example.com:443/login#form?value=3", false,
-     PasswordTitleType::SAVE_PASSWORD, "example.com", 0, 0},
-    {"https://another.org", "http://example.com:80/login#form?value=3", true,
-     PasswordTitleType::SAVE_PASSWORD, "example.com", 0, 0},
+    {"https://another.org", "https://example.com:443/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, "example.com"},
+    {"https://another.org", "http://example.com:80/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, "example.com"},
 
     // Different domains and password form origin url with
     // non-default port for the scheme.
     {"https://another.org", "https://example.com:8001/login#form?value=3",
-     false, PasswordTitleType::SAVE_PASSWORD, "example.com:8001", 0, 0},
-    {"https://another.org", "https://example.com:8001/login#form?value=3", true,
-     PasswordTitleType::SAVE_PASSWORD, "example.com:8001", 0, 0},
+     PasswordTitleType::SAVE_PASSWORD, "example.com:8001"},
+    {"https://another.org", "https://example.com:8001/login#form?value=3",
+     PasswordTitleType::SAVE_PASSWORD, "example.com:8001"},
 
     // Update bubble, same domains.
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     false, PasswordTitleType::UPDATE_PASSWORD, "", 0, 0},
+     PasswordTitleType::UPDATE_PASSWORD, ""},
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     true, PasswordTitleType::UPDATE_PASSWORD, "", 0, 0},
+     PasswordTitleType::UPDATE_PASSWORD, ""},
 
     // Update bubble, different domains.
-    {"https://another.org", "http://example.com/login#form?value=3", false,
-     PasswordTitleType::UPDATE_PASSWORD, "example.com", 0, 0},
-    {"https://another.org", "http://example.com/login#form?value=3", true,
-     PasswordTitleType::UPDATE_PASSWORD, "example.com", 0, 0},
+    {"https://another.org", "http://example.com/login#form?value=3",
+     PasswordTitleType::UPDATE_PASSWORD, "example.com"},
+    {"https://another.org", "http://example.com/login#form?value=3",
+     PasswordTitleType::UPDATE_PASSWORD, "example.com"},
 
     // Same domains, federated credential.
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     false, PasswordTitleType::SAVE_ACCOUNT, "", 0, 0},
+     PasswordTitleType::SAVE_ACCOUNT, ""},
     {"http://example.com/landing", "http://example.com/login#form?value=3",
-     true, PasswordTitleType::SAVE_ACCOUNT, "", 12, 29},
+     PasswordTitleType::SAVE_ACCOUNT, ""},
 
     // Different subdomains, federated credential.
     {"https://a.example.com/landing",
-     "https://b.example.com/login#form?value=3", false,
-     PasswordTitleType::SAVE_ACCOUNT, "", 0, 0},
+     "https://b.example.com/login#form?value=3",
+     PasswordTitleType::SAVE_ACCOUNT, ""},
     {"https://a.example.com/landing",
-     "https://b.example.com/login#form?value=3", true,
-     PasswordTitleType::SAVE_ACCOUNT, "", 12, 29}};
+     "https://b.example.com/login#form?value=3",
+     PasswordTitleType::SAVE_ACCOUNT, ""}};
 
 }  // namespace
 
@@ -142,31 +139,21 @@
                                     << kDomainsTestCases[i].form_origin_url);
 
     base::string16 title;
-    gfx::Range title_link_range;
     GetSavePasswordDialogTitleTextAndLinkRange(
         GURL(kDomainsTestCases[i].user_visible_url),
         GURL(kDomainsTestCases[i].form_origin_url),
-        kDomainsTestCases[i].is_smartlock_branding_enabled,
-        kDomainsTestCases[i].bubble_type, &title, &title_link_range);
+        kDomainsTestCases[i].bubble_type, &title);
 
     // Verify against expectations.
     base::string16 domain =
         base::ASCIIToUTF16(kDomainsTestCases[i].expected_domain_placeholder);
     EXPECT_TRUE(title.find(domain) != base::string16::npos);
-    EXPECT_EQ(kDomainsTestCases[i].expected_link_range_start,
-              title_link_range.start());
-    EXPECT_EQ(kDomainsTestCases[i].expected_link_range_end,
-              title_link_range.end());
     if (kDomainsTestCases[i].bubble_type ==
         PasswordTitleType::UPDATE_PASSWORD) {
       EXPECT_TRUE(title.find(base::ASCIIToUTF16("Update")) !=
                   base::string16::npos);
-    } else if (kDomainsTestCases[i].bubble_type ==
-               PasswordTitleType::SAVE_PASSWORD) {
-      EXPECT_TRUE(title.find(base::ASCIIToUTF16("Save")) !=
-                  base::string16::npos);
     } else {
-      EXPECT_TRUE(title.find(base::ASCIIToUTF16("save")) !=
+      EXPECT_TRUE(title.find(base::ASCIIToUTF16("Save")) !=
                   base::string16::npos);
     }
   }
@@ -182,9 +169,7 @@
                                                  base::string16());
 
   base::string16 title;
-  gfx::Range title_link_range;
   const GURL kExample("http://example.org");
-  const bool kBrandingEnabled = true;
   // The arguments passed below have this importance for the codepath:
   // * The first two URLs need to be the same, otherwise
   //   IDS_SAVE_PASSWORD_DIFFERENT_DOMAINS_TITLE will be used instead of
@@ -195,13 +180,11 @@
   // * SAVE_PASSWORD dialog type needs to be passed to match the
   //   IDS_SAVE_PASSWORD overridden above.
   GetSavePasswordDialogTitleTextAndLinkRange(
-      kExample, kExample, kBrandingEnabled, PasswordTitleType::SAVE_PASSWORD,
-      &title, &title_link_range);
+      kExample, kExample, PasswordTitleType::SAVE_PASSWORD, &title);
   // Verify that the test did not pass just because
   // GetSavePasswordDialogTitleTextAndLinkRange changed the resource IDs it uses
   // (and hence did not get the overridden empty string). If the empty localised
   // string was used, the title and the range will be empty as well.
-  EXPECT_TRUE(title_link_range.is_empty());
   EXPECT_THAT(title, testing::IsEmpty());
 }
 
@@ -223,45 +206,3 @@
     EXPECT_TRUE(title.find(domain) != base::string16::npos);
   }
 }
-
-// The parameter is |many_accounts| passed to
-// GetAccountChooserDialogTitleTextAndLinkRange
-class AccountChooserDialogTitleTest : public ::testing::TestWithParam<bool> {
-};
-
-TEST_P(AccountChooserDialogTitleTest,
-       GetAccountChooserDialogTitleTextAndLinkRangeSmartLockUsers) {
-  base::string16 branding =
-      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK);
-  base::string16 title;
-  gfx::Range title_link_range;
-  GetAccountChooserDialogTitleTextAndLinkRange(
-      true /* is_smartlock_branding_enabled */,
-      GetParam(),
-      &title, &title_link_range);
-
-  // Check that branding string is a part of a title.
-  EXPECT_LT(title.find(branding, 0), title.size());
-  EXPECT_GT(title.find(branding, 0), 0U);
-  // Check that link range is not empty.
-  EXPECT_NE(0U, title_link_range.start());
-  EXPECT_NE(0U, title_link_range.end());
-}
-
-TEST_P(AccountChooserDialogTitleTest,
-       GetAccountChooserDialogTitleTextAndLinkRangeNonSmartLockUsers) {
-  base::string16 branding =
-      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK);
-  base::string16 title;
-  gfx::Range title_link_range;
-  GetAccountChooserDialogTitleTextAndLinkRange(
-      false /* is_smartlock_branding_enabled */,
-      GetParam(),
-      &title, &title_link_range);
-  EXPECT_GE(title.find(branding, 0), title.size());
-  EXPECT_EQ(0U, title_link_range.start());
-  EXPECT_EQ(0U, title_link_range.end());
-}
-
-INSTANTIATE_TEST_CASE_P(, AccountChooserDialogTitleTest,
-                        ::testing::Bool());
diff --git a/chrome/browser/ui/passwords/password_dialog_controller_impl.cc b/chrome/browser/ui/passwords/password_dialog_controller_impl.cc
index 55e9d60..791b516 100644
--- a/chrome/browser/ui/passwords/password_dialog_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_dialog_controller_impl.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
 #include "chrome/browser/ui/passwords/password_dialog_prompts.h"
 #include "chrome/browser/ui/passwords/passwords_model_delegate.h"
+#include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/browser_sync/profile_sync_service.h"
@@ -18,16 +19,6 @@
 #include "components/prefs/pref_service.h"
 #include "ui/base/l10n/l10n_util.h"
 
-namespace {
-
-bool IsSmartLockBrandingEnabled(Profile* profile) {
-  const browser_sync::ProfileSyncService* sync_service =
-      ProfileSyncServiceFactory::GetForProfile(profile);
-  return password_bubble_experiment::IsSmartLockUser(sync_service);
-}
-
-}  // namespace
-
 PasswordDialogControllerImpl::PasswordDialogControllerImpl(
     Profile* profle,
     PasswordsModelDelegate* delegate)
@@ -69,11 +60,8 @@
 std::pair<base::string16, gfx::Range>
 PasswordDialogControllerImpl::GetAccoutChooserTitle() const {
   std::pair<base::string16, gfx::Range> result;
-  GetAccountChooserDialogTitleTextAndLinkRange(
-      IsSmartLockBrandingEnabled(profile_),
-      local_credentials_.size() > 1,
-      &result.first,
-      &result.second);
+  result.first =
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_ACCOUNT_CHOOSER_TITLE);
   return result;
 }
 
@@ -91,10 +79,9 @@
 std::pair<base::string16, gfx::Range>
 PasswordDialogControllerImpl::GetAutoSigninText() const {
   std::pair<base::string16, gfx::Range> result;
-  GetBrandedTextAndLinkRange(IsSmartLockBrandingEnabled(profile_),
-                             IDS_AUTO_SIGNIN_FIRST_RUN_SMART_LOCK_TEXT,
-                             IDS_AUTO_SIGNIN_FIRST_RUN_TEXT, &result.first,
-                             &result.second);
+  result.first = l10n_util::GetStringFUTF16(
+      IDS_AUTO_SIGNIN_FIRST_RUN_TEXT,
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_TITLE_BRAND));
   return result;
 }
 
diff --git a/chrome/browser/ui/passwords/password_dialog_controller_impl.h b/chrome/browser/ui/passwords/password_dialog_controller_impl.h
index 6a2d1b6..d9520544 100644
--- a/chrome/browser/ui/passwords/password_dialog_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_dialog_controller_impl.h
@@ -34,9 +34,11 @@
 
   // PasswordDialogController:
   const FormsVector& GetLocalForms() const override;
+  // TODO(890336): get rid of the range.
   std::pair<base::string16, gfx::Range> GetAccoutChooserTitle() const override;
   bool ShouldShowSignInButton() const override;
   base::string16 GetAutoSigninPromoTitle() const override;
+  // TODO(890336): get rid of the range.
   std::pair<base::string16, gfx::Range> GetAutoSigninText() const override;
   void OnSmartLockLinkClicked() override;
   void OnChooseCredentials(
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index b5fe118..15bda0bf 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -16,9 +16,11 @@
 #include "base/metrics/user_metrics.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/extensions/tab_helper.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
@@ -26,6 +28,7 @@
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/web_contents_sizer.h"
 #include "chrome/common/url_constants.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/feature_engagement/buildflags.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/render_process_host.h"
@@ -1121,7 +1124,7 @@
         base::RecordAction(
             UserMetricsAction("SoundContentSetting.UnmuteBy.TabStrip"));
       }
-      chrome::SetSitesMuted(*this, indices, mute);
+      SetSitesMuted(indices, mute);
       break;
     }
 
@@ -1606,6 +1609,35 @@
   }
 }
 
+// Sets the sound content setting for each site at the |indices|.
+void TabStripModel::SetSitesMuted(const std::vector<int>& indices,
+                                  bool mute) const {
+  for (int tab_index : indices) {
+    content::WebContents* web_contents = GetWebContentsAt(tab_index);
+    GURL url = web_contents->GetLastCommittedURL();
+    if (url.SchemeIs(content::kChromeUIScheme)) {
+      // chrome:// URLs don't have content settings but can be muted, so just
+      // mute the WebContents.
+      chrome::SetTabAudioMuted(web_contents, mute,
+                               TabMutedReason::CONTENT_SETTING_CHROME,
+                               std::string());
+    } else {
+      Profile* profile =
+          Profile::FromBrowserContext(web_contents->GetBrowserContext());
+      HostContentSettingsMap* settings =
+          HostContentSettingsMapFactory::GetForProfile(profile);
+      ContentSetting setting =
+          mute ? CONTENT_SETTING_BLOCK : CONTENT_SETTING_ALLOW;
+      if (setting == settings->GetDefaultContentSetting(
+                         CONTENT_SETTINGS_TYPE_SOUND, nullptr)) {
+        setting = CONTENT_SETTING_DEFAULT;
+      }
+      settings->SetContentSettingDefaultScope(
+          url, url, CONTENT_SETTINGS_TYPE_SOUND, std::string(), setting);
+    }
+  }
+}
+
 // static
 bool TabStripModel::OpenerMatches(const std::unique_ptr<WebContentsData>& data,
                                   const WebContents* opener,
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index 3fe845c..496bc3a 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -544,6 +544,9 @@
   // starting at |start| to |index|. See MoveSelectedTabsTo for more details.
   void MoveSelectedTabsToImpl(int index, size_t start, size_t length);
 
+  // Sets the sound content setting for each site at the |indices|.
+  void SetSitesMuted(const std::vector<int>& indices, bool mute) const;
+
   // Returns true if the tab represented by the specified data has an opener
   // that matches the specified one. If |use_group| is true, then this will
   // fall back to check the group relationship as well.
diff --git a/chrome/browser/ui/tabs/tab_utils.cc b/chrome/browser/ui/tabs/tab_utils.cc
index 5d476e6..1111dc6 100644
--- a/chrome/browser/ui/tabs/tab_utils.cc
+++ b/chrome/browser/ui/tabs/tab_utils.cc
@@ -6,152 +6,20 @@
 
 #include <utility>
 
-#include "base/command_line.h"
 #include "base/feature_list.h"
-#include "base/strings/string16.h"
-#include "build/build_config.h"
-#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/recently_audible_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/usb/usb_tab_helper.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/grit/generated_resources.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "content/public/browser/picture_in_picture_window_controller.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/url_constants.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/material_design/material_design_controller.h"
-#include "ui/base/theme_provider.h"
-#include "ui/gfx/animation/multi_animation.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/native_theme/common_theme.h"
-#include "ui/native_theme/native_theme.h"
-
-#if defined(OS_MACOSX)
-#include "ui/base/resource/resource_bundle.h"
-#endif
-
-struct LastMuteMetadata
-    : public content::WebContentsUserData<LastMuteMetadata> {
-  TabMutedReason reason = TabMutedReason::NONE;
-  std::string extension_id;
-
- private:
-  explicit LastMuteMetadata(content::WebContents* contents) {}
-  friend class content::WebContentsUserData<LastMuteMetadata>;
-};
 
 namespace chrome {
 
-namespace {
-
-// Interval between frame updates of the tab indicator animations.  This is not
-// the usual 60 FPS because a trade-off must be made between tab UI animation
-// smoothness and media recording/playback performance on low-end hardware.
-const int kIndicatorFrameIntervalMs = 50;  // 20 FPS
-
-// Fade-in/out duration for the tab indicator animations.  Fade-in is quick to
-// immediately notify the user.  Fade-out is more gradual, so that the user has
-// a chance of finding a tab that has quickly "blipped" on and off.
-const int kIndicatorFadeInDurationMs = 200;
-const int kIndicatorFadeOutDurationMs = 1000;
-
-// Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
-// "in" state.
-class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
- public:
-  ~TabRecordingIndicatorAnimation() override {}
-
-  // Overridden to provide alternating "towards in" and "towards out" behavior.
-  double GetCurrentValue() const override;
-
-  static std::unique_ptr<TabRecordingIndicatorAnimation> Create();
-
- private:
-  TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
-                                 const base::TimeDelta interval)
-      : MultiAnimation(parts, interval) {}
-
-  // Number of times to "toggle throb" the recording and tab capture indicators
-  // when they first appear.
-  static const int kCaptureIndicatorThrobCycles = 5;
-};
-
-double TabRecordingIndicatorAnimation::GetCurrentValue() const {
-  return current_part_index() % 2 ?
-      1.0 - MultiAnimation::GetCurrentValue() :
-      MultiAnimation::GetCurrentValue();
-}
-
-std::unique_ptr<TabRecordingIndicatorAnimation>
-TabRecordingIndicatorAnimation::Create() {
-  MultiAnimation::Parts parts;
-  static_assert(kCaptureIndicatorThrobCycles % 2 != 0,
-        "odd number of cycles required so animation finishes in showing state");
-  for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) {
-    parts.push_back(MultiAnimation::Part(
-        i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs,
-        gfx::Tween::EASE_IN));
-  }
-  const base::TimeDelta interval =
-      base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
-  std::unique_ptr<TabRecordingIndicatorAnimation> animation(
-      new TabRecordingIndicatorAnimation(parts, interval));
-  animation->set_continuous(false);
-  return animation;
-}
-
-}  // namespace
-
-bool ShouldTabShowFavicon(int capacity,
-                          bool is_pinned_tab,
-                          bool is_active_tab,
-                          bool has_favicon,
-                          TabAlertState alert_state) {
-  if (!has_favicon)
-    return false;
-  int required_capacity = 1;
-  if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
-    ++required_capacity;
-  if (ShouldTabShowAlertIndicator(capacity, is_pinned_tab, is_active_tab,
-                                  has_favicon, alert_state)) {
-    ++required_capacity;
-  }
-  return capacity >= required_capacity;
-}
-
-bool ShouldTabShowAlertIndicator(int capacity,
-                                 bool is_pinned_tab,
-                                 bool is_active_tab,
-                                 bool has_favicon,
-                                 TabAlertState alert_state) {
-  if (alert_state == TabAlertState::NONE)
-    return false;
-  if (ShouldTabShowCloseButton(capacity, is_pinned_tab, is_active_tab))
-    return capacity >= 2;
-  return capacity >= 1;
-}
-
-bool ShouldTabShowCloseButton(int capacity,
-                              bool is_pinned_tab,
-                              bool is_active_tab) {
-  if (is_pinned_tab)
-    return false;
-  else if (is_active_tab)
-    return true;
-  else
-    return capacity >= 3;
-}
-
 TabAlertState GetTabAlertStateForContents(content::WebContents* contents) {
   if (!contents)
     return TabAlertState::NONE;
@@ -198,196 +66,8 @@
   return TabAlertState::NONE;
 }
 
-gfx::Image GetTabAlertIndicatorImage(TabAlertState alert_state,
-                                     SkColor button_color) {
-  const gfx::VectorIcon* icon = nullptr;
-  int image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_ICON_WIDTH);
-  const bool is_touch_optimized_ui =
-      ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
-  switch (alert_state) {
-    case TabAlertState::AUDIO_PLAYING:
-      icon = is_touch_optimized_ui ? &kTabAudioRoundedIcon : &kTabAudioIcon;
-      break;
-    case TabAlertState::AUDIO_MUTING:
-      icon = is_touch_optimized_ui ? &kTabAudioMutingRoundedIcon
-                                   : &kTabAudioMutingIcon;
-      break;
-    case TabAlertState::MEDIA_RECORDING:
-    case TabAlertState::DESKTOP_CAPTURING:
-      icon = &kTabMediaRecordingIcon;
-      break;
-    case TabAlertState::TAB_CAPTURING:
-      icon = is_touch_optimized_ui ? &kTabMediaCapturingWithArrowIcon
-                                   : &kTabMediaCapturingIcon;
-      // Tab capturing and presenting icon uses a different width compared to
-      // the other tab alert indicator icons.
-      image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_CAPTURE_ICON_WIDTH);
-      break;
-    case TabAlertState::BLUETOOTH_CONNECTED:
-      icon = &kTabBluetoothConnectedIcon;
-      break;
-    case TabAlertState::USB_CONNECTED:
-      icon = &kTabUsbConnectedIcon;
-      break;
-    case TabAlertState::PIP_PLAYING:
-      icon = &kPictureInPictureAltIcon;
-      break;
-    case TabAlertState::NONE:
-      return gfx::Image();
-  }
-  DCHECK(icon);
-  return gfx::Image(gfx::CreateVectorIcon(*icon, image_width, button_color));
-}
-
-gfx::Image GetTabAlertIndicatorAffordanceImage(TabAlertState alert_state,
-                                               SkColor button_color) {
-  switch (alert_state) {
-    case TabAlertState::AUDIO_PLAYING:
-      return GetTabAlertIndicatorImage(TabAlertState::AUDIO_MUTING,
-                                       button_color);
-    case TabAlertState::AUDIO_MUTING:
-      return GetTabAlertIndicatorImage(TabAlertState::AUDIO_PLAYING,
-                                       button_color);
-    case TabAlertState::NONE:
-    case TabAlertState::MEDIA_RECORDING:
-    case TabAlertState::TAB_CAPTURING:
-    case TabAlertState::BLUETOOTH_CONNECTED:
-    case TabAlertState::USB_CONNECTED:
-    case TabAlertState::PIP_PLAYING:
-    case TabAlertState::DESKTOP_CAPTURING:
-      return GetTabAlertIndicatorImage(alert_state, button_color);
-  }
-  NOTREACHED();
-  return GetTabAlertIndicatorImage(alert_state, button_color);
-}
-
-std::unique_ptr<gfx::Animation> CreateTabAlertIndicatorFadeAnimation(
-    TabAlertState alert_state) {
-  if (alert_state == TabAlertState::MEDIA_RECORDING ||
-      alert_state == TabAlertState::TAB_CAPTURING ||
-      alert_state == TabAlertState::DESKTOP_CAPTURING) {
-    return TabRecordingIndicatorAnimation::Create();
-  }
-
-  // Note: While it seems silly to use a one-part MultiAnimation, it's the only
-  // gfx::Animation implementation that lets us control the frame interval.
-  gfx::MultiAnimation::Parts parts;
-  const bool is_for_fade_in = (alert_state != TabAlertState::NONE);
-  parts.push_back(gfx::MultiAnimation::Part(
-      is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs,
-      gfx::Tween::EASE_IN));
-  const base::TimeDelta interval =
-      base::TimeDelta::FromMilliseconds(kIndicatorFrameIntervalMs);
-  std::unique_ptr<gfx::MultiAnimation> animation(
-      new gfx::MultiAnimation(parts, interval));
-  animation->set_continuous(false);
-  return std::move(animation);
-}
-
-base::string16 AssembleTabTooltipText(const base::string16& title,
-                                      TabAlertState alert_state) {
-  if (alert_state == TabAlertState::NONE)
-    return title;
-
-  base::string16 result = title;
-  if (!result.empty())
-    result.append(1, '\n');
-  switch (alert_state) {
-    case TabAlertState::AUDIO_PLAYING:
-      result.append(
-          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_PLAYING));
-      break;
-    case TabAlertState::AUDIO_MUTING:
-      result.append(
-          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_MUTING));
-      break;
-    case TabAlertState::MEDIA_RECORDING:
-      result.append(l10n_util::GetStringUTF16(
-          IDS_TOOLTIP_TAB_ALERT_STATE_MEDIA_RECORDING));
-      break;
-    case TabAlertState::TAB_CAPTURING:
-      result.append(
-          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_TAB_CAPTURING));
-      break;
-    case TabAlertState::BLUETOOTH_CONNECTED:
-      result.append(l10n_util::GetStringUTF16(
-          IDS_TOOLTIP_TAB_ALERT_STATE_BLUETOOTH_CONNECTED));
-      break;
-    case TabAlertState::USB_CONNECTED:
-      result.append(
-          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_USB_CONNECTED));
-      break;
-    case TabAlertState::PIP_PLAYING:
-      result.append(
-          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_PIP_PLAYING));
-      break;
-    case TabAlertState::DESKTOP_CAPTURING:
-      result.append(l10n_util::GetStringUTF16(
-          IDS_TOOLTIP_TAB_ALERT_STATE_DESKTOP_CAPTURING));
-      break;
-    case TabAlertState::NONE:
-      NOTREACHED();
-      break;
-  }
-  return result;
-}
-
-base::string16 AssembleTabAccessibilityLabel(const base::string16& title,
-                                             bool is_crashed,
-                                             bool is_network_error,
-                                             TabAlertState alert_state) {
-  // Tab has crashed.
-  if (is_crashed)
-    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title);
-
-  // Network error interstitial.
-  if (is_network_error) {
-    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_NETWORK_ERROR_FORMAT,
-                                      title);
-  }
-
-  // Alert tab states.
-  switch (alert_state) {
-    case TabAlertState::AUDIO_PLAYING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT,
-                                        title);
-    case TabAlertState::USB_CONNECTED:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT,
-                                        title);
-    case TabAlertState::BLUETOOTH_CONNECTED:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT, title);
-    case TabAlertState::MEDIA_RECORDING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT,
-                                        title);
-    case TabAlertState::AUDIO_MUTING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT,
-                                        title);
-    case TabAlertState::TAB_CAPTURING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT,
-                                        title);
-    case TabAlertState::PIP_PLAYING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_PIP_PLAYING_FORMAT,
-                                        title);
-
-    case TabAlertState::DESKTOP_CAPTURING:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_DESKTOP_CAPTURING_FORMAT, title);
-    case TabAlertState::NONE:
-      return title;
-  }
-
-  NOTREACHED();
-  return base::string16();
-}
-
-bool AreExperimentalMuteControlsEnabled() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableTabAudioMuting);
-}
-
 bool CanToggleAudioMute(content::WebContents* contents) {
-  switch (GetTabAlertStateForContents(contents)) {
+  switch (chrome::GetTabAlertStateForContents(contents)) {
     case TabAlertState::NONE:
     case TabAlertState::AUDIO_PLAYING:
     case TabAlertState::AUDIO_MUTING:
@@ -427,26 +107,15 @@
   return metadata->reason;
 }
 
-const std::string& GetExtensionIdForMutedTab(content::WebContents* contents) {
-  DCHECK_EQ(GetTabAudioMutedReason(contents) != TabMutedReason::EXTENSION,
-            LastMuteMetadata::FromWebContents(contents)->extension_id.empty());
-  return LastMuteMetadata::FromWebContents(contents)->extension_id;
-}
-
-TabMutedResult SetTabAudioMuted(content::WebContents* contents,
-                                bool mute,
-                                TabMutedReason reason,
-                                const std::string& extension_id) {
+bool SetTabAudioMuted(content::WebContents* contents,
+                      bool mute,
+                      TabMutedReason reason,
+                      const std::string& extension_id) {
   DCHECK(contents);
   DCHECK(TabMutedReason::NONE != reason);
 
-  if (reason == TabMutedReason::AUDIO_INDICATOR &&
-      !AreExperimentalMuteControlsEnabled()) {
-    return TabMutedResult::FAIL_NOT_ENABLED;
-  }
-
   if (!chrome::CanToggleAudioMute(contents))
-    return TabMutedResult::FAIL_TABCAPTURE;
+    return false;
 
   contents->SetAudioMuted(mute);
 
@@ -461,18 +130,7 @@
     metadata->extension_id.clear();
   }
 
-  return TabMutedResult::SUCCESS;
-}
-
-void UnmuteIfMutedByExtension(content::WebContents* contents,
-                              const std::string& extension_id) {
-  LastMuteMetadata::CreateForWebContents(contents);  // Ensures metadata exists.
-  LastMuteMetadata* const metadata =
-      LastMuteMetadata::FromWebContents(contents);
-  if (metadata->reason == TabMutedReason::EXTENSION &&
-      metadata->extension_id == extension_id) {
-    SetTabAudioMuted(contents, false, TabMutedReason::EXTENSION, extension_id);
-  }
+  return true;
 }
 
 bool AreAllTabsMuted(const TabStripModel& tab_strip,
@@ -485,39 +143,9 @@
   return true;
 }
 
-void SetSitesMuted(const TabStripModel& tab_strip,
-                   const std::vector<int>& indices,
-                   const bool mute) {
-  for (int tab_index : indices) {
-    content::WebContents* web_contents = tab_strip.GetWebContentsAt(tab_index);
-    GURL url = web_contents->GetLastCommittedURL();
-    if (url.SchemeIs(content::kChromeUIScheme)) {
-      // chrome:// URLs don't have content settings but can be muted, so just
-      // mute the WebContents.
-      SetTabAudioMuted(web_contents, mute,
-                       TabMutedReason::CONTENT_SETTING_CHROME, std::string());
-    } else {
-      Profile* profile =
-          Profile::FromBrowserContext(web_contents->GetBrowserContext());
-      HostContentSettingsMap* settings =
-          HostContentSettingsMapFactory::GetForProfile(profile);
-      ContentSetting setting =
-          mute ? CONTENT_SETTING_BLOCK : CONTENT_SETTING_ALLOW;
-      if (setting == settings->GetDefaultContentSetting(
-                         CONTENT_SETTINGS_TYPE_SOUND, nullptr)) {
-        setting = CONTENT_SETTING_DEFAULT;
-      }
-      settings->SetContentSettingDefaultScope(
-          url, url, CONTENT_SETTINGS_TYPE_SOUND, std::string(), setting);
-    }
-  }
-}
-
 bool IsSiteMuted(const TabStripModel& tab_strip, const int index) {
   content::WebContents* web_contents = tab_strip.GetWebContentsAt(index);
 
-  // TODO(steimel): Why was this not a problem for AreAllTabsMuted? Is this
-  // going to be a problem for SetSitesMuted?
   // Prevent crashes with null WebContents (https://crbug.com/797647).
   if (!web_contents)
     return false;
diff --git a/chrome/browser/ui/tabs/tab_utils.h b/chrome/browser/ui/tabs/tab_utils.h
index e69e8b0d..d9b7301 100644
--- a/chrome/browser/ui/tabs/tab_utils.h
+++ b/chrome/browser/ui/tabs/tab_utils.h
@@ -9,7 +9,6 @@
 #include <string>
 #include <vector>
 
-#include "base/strings/string16.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "third_party/skia/include/core/SkColor.h"
 
@@ -19,11 +18,6 @@
 class WebContents;
 }  // namespace content
 
-namespace gfx {
-class Animation;
-class Image;
-}  // namespace gfx
-
 // Alert state for a tab.  In reality, more than one of these may apply.  See
 // comments for GetTabAlertStateForContents() below.
 enum class TabAlertState {
@@ -41,127 +35,49 @@
 enum class TabMutedReason {
   NONE,                    // The tab has never been muted or unmuted.
   CONTEXT_MENU,            // Mute/Unmute chosen from tab context menu.
-  AUDIO_INDICATOR,         // Mute toggled via tab-strip audio icon.
   MEDIA_CAPTURE,           // Media recording/capture was started.
   EXTENSION,               // Mute state changed via extension API.
   CONTENT_SETTING,         // The sound content setting was set to BLOCK.
   CONTENT_SETTING_CHROME,  // Mute toggled on chrome:// URL.
 };
 
-enum class TabMutedResult {
-  SUCCESS,
-  FAIL_NOT_ENABLED,
-  FAIL_TABCAPTURE,
+struct LastMuteMetadata
+    : public content::WebContentsUserData<LastMuteMetadata> {
+  TabMutedReason reason = TabMutedReason::NONE;
+  std::string extension_id;  // Only valid when |reason| is EXTENSION.
+
+ private:
+  explicit LastMuteMetadata(content::WebContents* contents) {}
+  friend class content::WebContentsUserData<LastMuteMetadata>;
 };
 
 namespace chrome {
 
-// Logic to determine which components (i.e., close button, favicon, and alert
-// indicator) of a tab should be shown, given current state.  |capacity|
-// specifies how many components can be shown, given available tab width.
-//
-// Precedence rules for deciding what to show when capacity is insufficient to
-// show everything:
-//
-//   Active tab: Always show the close button, then the alert indicator, then
-//               the favicon.
-//   Inactive tab: Alert indicator, then the favicon, then the close button.
-//   Pinned tab: Show only the alert indicator, or only the favicon
-//               (TabAlertState::NONE).  Never show the close button.
-bool ShouldTabShowFavicon(int capacity,
-                          bool is_pinned_tab,
-                          bool is_active_tab,
-                          bool has_favicon,
-                          TabAlertState alert_state);
-bool ShouldTabShowAlertIndicator(int capacity,
-                                 bool is_pinned_tab,
-                                 bool is_active_tab,
-                                 bool has_favicon,
-                                 TabAlertState alert_state);
-bool ShouldTabShowCloseButton(int capacity,
-                              bool is_pinned_tab,
-                              bool is_active_tab);
-
 // Returns the alert state to be shown by the tab's alert indicator.  When
 // multiple states apply (e.g., tab capture with audio playback), the one most
 // relevant to user privacy concerns is selected.
 TabAlertState GetTabAlertStateForContents(content::WebContents* contents);
 
-// Returns a cached image, to be shown by the alert indicator for the given
-// |alert_state|.  Uses the global ui::ResourceBundle shared instance.
-gfx::Image GetTabAlertIndicatorImage(TabAlertState alert_state,
-                                     SkColor button_color);
-
-// Returns the cached image, to be shown by the alert indicator button for mouse
-// hover/pressed, when the indicator is in the given |alert_state|.  Uses the
-// global ui::ResourceBundle shared instance.
-gfx::Image GetTabAlertIndicatorAffordanceImage(TabAlertState alert_state,
-                                               SkColor button_color);
-
-// Returns a non-continuous Animation that performs a fade-in or fade-out
-// appropriate for the given |next_alert_state|.  This is used by the tab alert
-// indicator to alert the user that recording, tab capture, or audio playback
-// has started/stopped.
-std::unique_ptr<gfx::Animation> CreateTabAlertIndicatorFadeAnimation(
-    TabAlertState next_alert_state);
-
-// Returns the text to show in a tab's tooltip: The contents |title|, followed
-// by a break, followed by a localized string describing the |alert_state|.
-base::string16 AssembleTabTooltipText(const base::string16& title,
-                                      TabAlertState alert_state);
-
-// Returns the text to use for a tab's accessibility label: the |title|,
-// followed by text describing |is_crashed|, |is_network_error|, and
-// |alert_state|.
-base::string16 AssembleTabAccessibilityLabel(const base::string16& title,
-                                             bool is_crashed,
-                                             bool is_network_error,
-                                             TabAlertState alert_state);
-
-// Returns true if experimental audio mute controls (UI or extension API) are
-// enabled.  Currently, toggling mute from a tab's context menu is the only
-// non-experimental control method.
-bool AreExperimentalMuteControlsEnabled();
-
 // Returns true if audio mute can be activated/deactivated for the given
 // |contents|.
 bool CanToggleAudioMute(content::WebContents* contents);
 
-// Unmute a tab if it is currently muted at the request of the extension having
-// the given |extension_id|.
-void UnmuteIfMutedByExtension(content::WebContents* contents,
-                              const std::string& extension_id);
-
 // Sets whether all audio output from |contents| is muted, along with the
 // |reason| it is to be muted/unmuted (via UI or extension API).  When |reason|
 // is TAB_MUTED_REASON_EXTENSION, |extension_id| must be provided; otherwise, it
-// is ignored.
-//
-// If the |reason| is an experimental feature and the experiment is not enabled,
-// this will have no effect and TAB_MUTED_RESULT_FAIL_NOT_ENABLED will be
-// returned.
-TabMutedResult SetTabAudioMuted(content::WebContents* contents,
-                                bool mute,
-                                TabMutedReason reason,
-                                const std::string& extension_id);
+// is ignored.  Returns whether the tab was actually muted.
+bool SetTabAudioMuted(content::WebContents* contents,
+                      bool mute,
+                      TabMutedReason reason,
+                      const std::string& extension_id);
 
 // Returns the last reason a tab's mute state was changed.
 TabMutedReason GetTabAudioMutedReason(content::WebContents* contents);
 
-// If the last reason a tab's mute state was changed was due to use of the
-// extension API, this returns the extension's ID string.  Otherwise, the empty
-// string is returned.
-const std::string& GetExtensionIdForMutedTab(content::WebContents* contents);
-
 // Returns true if the tabs at the |indices| in |tab_strip| are all muted.
 bool AreAllTabsMuted(const TabStripModel& tab_strip,
                      const std::vector<int>& indices);
 
-// Sets the sound content setting for each site at the |indices| in |tab_strip|.
-void SetSitesMuted(const TabStripModel& tab_strip,
-                   const std::vector<int>& indices,
-                   const bool mute);
-
 // Returns true if the site at |index| in |tab_strip| is muted.
 bool IsSiteMuted(const TabStripModel& tab_strip, const int index);
 
diff --git a/chrome/browser/ui/toolbar/toolbar_model_unittest.cc b/chrome/browser/ui/toolbar/toolbar_model_unittest.cc
index 318ede5e..67b1086 100644
--- a/chrome/browser/ui/toolbar/toolbar_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/toolbar_model_unittest.cc
@@ -10,10 +10,12 @@
 #include "base/macros.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
+#include "components/toolbar/toolbar_field_trial.h"
 #include "components/toolbar/toolbar_model.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/ssl_status.h"
@@ -189,6 +191,12 @@
 
 // Test URL display.
 TEST_F(ToolbarModelTest, ShouldDisplayURL) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {toolbar::features::kHideSteadyStateUrlScheme,
+       toolbar::features::kHideSteadyStateUrlTrivialSubdomains},
+      {});
+
   AddTab(browser(), GURL(url::kAboutBlankURL));
 
   for (const TestItem& test_item : test_items) {
@@ -199,6 +207,56 @@
   }
 }
 
+// Tests every combination of Steady State Elision flags.
+TEST_F(ToolbarModelTest, SteadyStateElisionsFlags) {
+  AddTab(browser(), GURL(url::kAboutBlankURL));
+
+  // Hide Scheme and Hide Trivial Subdomains both Disabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        {}, {toolbar::features::kHideSteadyStateUrlScheme,
+             toolbar::features::kHideSteadyStateUrlTrivialSubdomains});
+    NavigateAndCheckText(GURL("https://www.google.com/"),
+                         base::ASCIIToUTF16("https://www.google.com"),
+                         base::ASCIIToUTF16("https://www.google.com"));
+  }
+
+  // Only Hide Scheme Enabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        {toolbar::features::kHideSteadyStateUrlScheme},
+        {toolbar::features::kHideSteadyStateUrlTrivialSubdomains});
+    NavigateAndCheckText(GURL("https://www.google.com/"),
+                         base::ASCIIToUTF16("https://www.google.com"),
+                         base::ASCIIToUTF16("www.google.com"));
+  }
+
+  // Only Hide Trivial Subdomains Enabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        {toolbar::features::kHideSteadyStateUrlTrivialSubdomains},
+        {toolbar::features::kHideSteadyStateUrlScheme});
+    NavigateAndCheckText(GURL("https://www.google.com/"),
+                         base::ASCIIToUTF16("https://www.google.com"),
+                         base::ASCIIToUTF16("https://google.com"));
+  }
+
+  // Hide Scheme and Hide Trivial Subdomains both Enabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        {toolbar::features::kHideSteadyStateUrlScheme,
+         toolbar::features::kHideSteadyStateUrlTrivialSubdomains},
+        {});
+    NavigateAndCheckText(GURL("https://www.google.com/"),
+                         base::ASCIIToUTF16("https://www.google.com"),
+                         base::ASCIIToUTF16("google.com"));
+  }
+}
+
 TEST_F(ToolbarModelTest, ShouldElideLongURLs) {
   AddTab(browser(), GURL(url::kAboutBlankURL));
   const std::string long_text(content::kMaxURLDisplayChars + 1024, '0');
diff --git a/chrome/browser/ui/view_ids.h b/chrome/browser/ui/view_ids.h
index 823099e..234cb55 100644
--- a/chrome/browser/ui/view_ids.h
+++ b/chrome/browser/ui/view_ids.h
@@ -22,8 +22,6 @@
   VIEW_ID_CLOSE_BUTTON,
   VIEW_ID_WINDOW_ICON,
   VIEW_ID_WINDOW_TITLE,
-  VIEW_ID_PROFILE_INDICATOR_ICON,
-  VIEW_ID_AVATAR_BUTTON,
   VIEW_ID_HOSTED_APP_BUTTON_CONTAINER,
 
   // Tabs within a window/tab strip, counting from the left.
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
index da87d21..8c146411 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
@@ -108,6 +108,8 @@
       kMigrationBubbleGooglePayLogoHeight);
   views::ImageView* icon_view = new views::ImageView();
   icon_view->SetImage(&image);
+  icon_view->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_AUTOFILL_GOOGLE_PAY_LOGO_ACCESSIBLE_NAME));
   title_container->AddChildView(icon_view);
   GetBubbleFrameView()->SetTitleView(std::move(title_container));
 }
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
index 2cd1517..94a4758 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
@@ -221,6 +221,8 @@
   std::unique_ptr<views::ImageView> image =
       std::make_unique<views::ImageView>();
   image->SetImage(rb.GetImageSkiaNamed(GetHeaderImageId()));
+  image->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_AUTOFILL_GOOGLE_PAY_LOGO_ACCESSIBLE_NAME));
   image_container->AddChildView(image.release());
   main_container->AddChildView(image_container.release());
 
diff --git a/chrome/browser/ui/views/autofill/migratable_card_view.cc b/chrome/browser/ui/views/autofill/migratable_card_view.cc
index c8acad4d..338af4ea 100644
--- a/chrome/browser/ui/views/autofill/migratable_card_view.cc
+++ b/chrome/browser/ui/views/autofill/migratable_card_view.cc
@@ -84,10 +84,14 @@
   checkbox_ = new views::Checkbox(base::string16(), listener);
   checkbox_->SetChecked(true);
   checkbox_->set_tag(card_index);
-  checkbox_->SetVisible(true);
   // TODO(crbug/867194): Currently the ink drop animation circle is cut by the
   // border of scroll bar view. Find a way to adjust the format.
   checkbox_->SetInkDropMode(views::InkDropHostView::InkDropMode::OFF);
+  std::unique_ptr<views::Label> card_description =
+      std::make_unique<views::Label>(
+          migratable_credit_card.credit_card().NetworkAndLastFourDigits(),
+          views::style::CONTEXT_LABEL);
+  checkbox_->SetAssociatedLabel(card_description.get());
   AddChildView(checkbox_);
 
   constexpr int kMigrationResultImageSize = 16;
@@ -116,12 +120,10 @@
       rb.GetImageNamed(CreditCard::IconResourceId(
                            migratable_credit_card.credit_card().network()))
           .AsImageSkia());
+  card_image->SetAccessibleName(
+      migratable_credit_card.credit_card().NetworkForDisplay());
   card_network_and_last_four_digits->AddChildView(card_image.release());
 
-  std::unique_ptr<views::Label> card_description =
-      std::make_unique<views::Label>(
-          migratable_credit_card.credit_card().NetworkAndLastFourDigits(),
-          views::style::CONTEXT_LABEL);
   card_network_and_last_four_digits->AddChildView(card_description.release());
 
   std::unique_ptr<views::Label> card_expiration =
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
index 2a8e0c1..791259e3 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/browser/bookmark_node.h"
+#include "ui/views/controls/button/label_button.h"
 
 // BookmarkBarViewObserverImpl is used to observe when the bookmark bar has
 // finished animating, then show the bookmark bubble. It's important to wait
diff --git a/chrome/browser/ui/views/frame/avatar_button_manager.cc b/chrome/browser/ui/views/frame/avatar_button_manager.cc
deleted file mode 100644
index 93dab04..0000000
--- a/chrome/browser/ui/views/frame/avatar_button_manager.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
-
-#if !defined(OS_CHROMEOS)
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/frame/browser_frame.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "ui/base/material_design/material_design_controller.h"
-#endif  // !defined(OS_CHROMEOS)
-
-AvatarButtonManager::AvatarButtonManager(BrowserNonClientFrameView* frame_view)
-#if !defined(OS_CHROMEOS)
-    : frame_view_(frame_view)
-#endif  // defined(OS_CHROMEOS)
-{
-}
-
-void AvatarButtonManager::Update(AvatarButtonStyle style) {
-#if defined(OS_CHROMEOS)
-  DCHECK_EQ(style, AvatarButtonStyle::NONE);
-#else
-  // Note: This code is being replaced by a toolbar button, see ToolbarView.
-  if (ui::MaterialDesignController::IsNewerMaterialUi())
-    return;
-  BrowserView* browser_view = frame_view_->browser_view();
-  BrowserFrame* frame = frame_view_->frame();
-  Profile* profile = browser_view->browser()->profile();
-
-  // This should never be called in incognito mode.
-  DCHECK(browser_view->IsRegularOrGuestSession());
-  ProfileAttributesEntry* unused;
-  if (style != AvatarButtonStyle::NONE &&
-      ((browser_view->IsBrowserTypeNormal() &&
-        // Tests may not have a profile manager.
-        g_browser_process->profile_manager() &&
-        g_browser_process->profile_manager()
-            ->GetProfileAttributesStorage()
-            .GetProfileAttributesWithPath(profile->GetPath(), &unused)) ||
-       // Desktop guest shows the avatar button.
-       browser_view->IsIncognito())) {
-    if (!avatar_button_) {
-      avatar_button_ = new AvatarButton(this, style, profile, this);
-      avatar_button_->set_id(VIEW_ID_AVATAR_BUTTON);
-      frame_view_->AddChildView(avatar_button_);
-      frame->GetRootView()->Layout();
-    }
-  } else if (avatar_button_) {
-    delete avatar_button_;
-    avatar_button_ = nullptr;
-    frame->GetRootView()->Layout();
-  }
-#endif  // defined(OS_CHROMEOS)
-}
-
-void AvatarButtonManager::OnMenuButtonClicked(views::MenuButton* sender,
-                                              const gfx::Point& point,
-                                              const ui::Event* event) {
-#if defined(OS_CHROMEOS)
-  NOTREACHED();
-#else
-  DCHECK(!ui::MaterialDesignController::IsNewerMaterialUi());
-  DCHECK_EQ(avatar_button_, sender);
-  BrowserWindow::AvatarBubbleMode mode =
-      BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT;
-  if ((event->IsMouseEvent() && event->AsMouseEvent()->IsRightMouseButton()) ||
-      (event->type() == ui::ET_GESTURE_LONG_PRESS)) {
-    return;
-  }
-  frame_view_->browser_view()->ShowAvatarBubbleFromAvatarButton(
-      mode, signin::ManageAccountsParams(),
-      signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN, false);
-  avatar_button_->OnAvatarButtonPressed(event);
-#endif  // defined(OS_CHROMEOS)
-}
diff --git a/chrome/browser/ui/views/frame/avatar_button_manager.h b/chrome/browser/ui/views/frame/avatar_button_manager.h
deleted file mode 100644
index bc7c336d..0000000
--- a/chrome/browser/ui/views/frame/avatar_button_manager.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_AVATAR_BUTTON_MANAGER_H_
-#define CHROME_BROWSER_UI_VIEWS_FRAME_AVATAR_BUTTON_MANAGER_H_
-
-#include "chrome/browser/ui/views/profiles/avatar_button.h"
-#include "chrome/browser/ui/views/profiles/avatar_button_style.h"
-#include "ui/views/controls/button/menu_button_listener.h"
-#include "ui/views/features.h"
-
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-namespace views {
-class NavButtonProvider;
-}  // namespace views
-#endif
-
-class BrowserNonClientFrameView;
-
-// Manages an avatar button displayed in a browser frame. The button displays
-// the name of the active or guest profile, and may be null.
-class AvatarButtonManager : public views::MenuButtonListener {
- public:
-  explicit AvatarButtonManager(BrowserNonClientFrameView* frame_view);
-
-  // Adds or removes the avatar button from the frame, based on the BrowserView
-  // properties.
-  void Update(AvatarButtonStyle style);
-
-  AvatarButton* avatar_button() const {
-#if defined(OS_CHROMEOS)
-    return nullptr;
-#else
-    return avatar_button_;
-#endif  // defined(OS_CHROMEOS)
-  }
-
-  // views::MenuButtonListener:
-  void OnMenuButtonClicked(views::MenuButton* source,
-                           const gfx::Point& point,
-                           const ui::Event* event) override;
-
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-  views::NavButtonProvider* get_nav_button_provider() {
-    return nav_button_provider_;
-  }
-  void set_nav_button_provider(views::NavButtonProvider* nav_button_provider) {
-    nav_button_provider_ = nav_button_provider;
-  }
-#endif
-
- private:
-#if !defined(OS_CHROMEOS)
-  BrowserNonClientFrameView* frame_view_;  // Weak. Owns |this|.
-
-  // Menu button that displays the name of the active or guest profile.
-  // May be null and will not be displayed for off the record profiles.
-  AvatarButton* avatar_button_ = nullptr;  // Owned by views hierarchy.
-#endif                                     // !defined(OS_CHROMEOS)
-
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-  views::NavButtonProvider* nav_button_provider_ = nullptr;
-#endif
-
-  DISALLOW_COPY_AND_ASSIGN(AvatarButtonManager);
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_AVATAR_BUTTON_MANAGER_H_
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index d06e2cbb..ab7ba4e5 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/views/frame/top_container_view.h"
 #include "chrome/common/chrome_switches.h"
 #include "ui/base/hit_test.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/events/event_handler.h"
 #include "ui/gfx/font_list.h"
 #include "ui/native_theme/native_theme_dark_aura.h"
@@ -56,10 +57,10 @@
   set_is_secondary_widget(false);
   // Don't focus anything on creation, selecting a tab will set the focus.
   set_focus_on_creation(false);
+  md_observer_.Add(ui::MaterialDesignController::GetInstance());
 }
 
-BrowserFrame::~BrowserFrame() {
-}
+BrowserFrame::~BrowserFrame() {}
 
 void BrowserFrame::InitBrowserFrame() {
   native_browser_frame_ =
@@ -241,12 +242,12 @@
   return menu_model_builder_->menu_model();
 }
 
-views::Button* BrowserFrame::GetNewAvatarMenuButton() {
-  // Note: This profile switcher is being replaced with a toolbar menu button.
-  // See ToolbarView.
-  return browser_frame_view_->GetProfileSwitcherButton();
-}
-
 void BrowserFrame::OnMenuClosed() {
   menu_runner_.reset();
 }
+
+void BrowserFrame::OnMdModeChanged() {
+  client_view()->InvalidateLayout();
+  non_client_view()->InvalidateLayout();
+  GetRootView()->Layout();
+}
diff --git a/chrome/browser/ui/views/frame/browser_frame.h b/chrome/browser/ui/views/frame/browser_frame.h
index 26c6a6cf..ec2b989 100644
--- a/chrome/browser/ui/views/frame/browser_frame.h
+++ b/chrome/browser/ui/views/frame/browser_frame.h
@@ -8,9 +8,11 @@
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/scoped_observer.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
+#include "ui/base/material_design/material_design_controller_observer.h"
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/widget/widget.h"
 
@@ -34,15 +36,14 @@
 }
 
 namespace views {
-class Button;
 class MenuRunner;
 class View;
 }
 
 // This is a virtual interface that allows system specific browser frames.
-class BrowserFrame
-    : public views::Widget,
-      public views::ContextMenuController {
+class BrowserFrame : public views::Widget,
+                     public views::ContextMenuController,
+                     public ui::MaterialDesignControllerObserver {
  public:
   explicit BrowserFrame(BrowserView* browser_view);
   ~BrowserFrame() override;
@@ -98,7 +99,7 @@
   // Called when BrowserView creates all it's child views.
   void OnBrowserViewInitViewsComplete();
 
-  // Overridden from views::Widget:
+  // views::Widget:
   views::internal::RootView* CreateRootView() override;
   views::NonClientFrameView* CreateNonClientFrameView() override;
   bool GetAccelerator(int command_id,
@@ -108,13 +109,11 @@
   void OnNativeWidgetWorkspaceChanged() override;
   void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
 
-  // Overridden from views::ContextMenuController:
+  // views::ContextMenuController:
   void ShowContextMenuForView(views::View* source,
                               const gfx::Point& p,
                               ui::MenuSourceType source_type) override;
 
-  views::Button* GetNewAvatarMenuButton();
-
   // Returns the menu model. BrowserFrame owns the returned model.
   // Note that in multi user mode this will upon each call create a new model.
   ui::MenuModel* GetSystemMenuModel();
@@ -123,6 +122,10 @@
     return native_browser_frame_;
   }
 
+ protected:
+  // ui::MaterialDesignControllerObserver:
+  void OnMdModeChanged() override;
+
  private:
   // Callback for MenuRunner.
   void OnMenuClosed();
@@ -148,6 +151,10 @@
 
   std::unique_ptr<ui::EventHandler> browser_command_handler_;
 
+  ScopedObserver<ui::MaterialDesignController,
+                 ui::MaterialDesignControllerObserver>
+      md_observer_{this};
+
   DISALLOW_COPY_AND_ASSIGN(BrowserFrame);
 };
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
index d2834b5c7..a3648bb 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
@@ -5,12 +5,10 @@
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 
 #include "base/metrics/histogram_macros.h"
-#include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/avatar_menu.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
@@ -20,7 +18,6 @@
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/theme_resources.h"
-#include "components/signin/core/browser/profile_management_switches.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/theme_provider.h"
@@ -31,11 +28,6 @@
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/views/background.h"
 
-#if defined(OS_CHROMEOS)
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
-#include "chrome/browser/ui/ash/session_util.h"
-#endif  // defined(OS_CHROMEOS)
-
 #if defined(OS_WIN)
 #include "chrome/browser/ui/views/frame/taskbar_decorator_win.h"
 #endif
@@ -49,8 +41,6 @@
                                                      BrowserView* browser_view)
     : frame_(frame),
       browser_view_(browser_view),
-      profile_switcher_(this),
-      profile_indicator_icon_(nullptr),
       tab_strip_observer_(this) {
   // The profile manager may by null in tests.
   if (g_browser_process->profile_manager()) {
@@ -69,11 +59,6 @@
 }
 
 // static
-int BrowserNonClientFrameView::GetAvatarIconPadding() {
-  return MD::IsNewerMaterialUi() ? 8 : 4;
-}
-
-// static
 int BrowserNonClientFrameView::GetTabstripPadding() {
   // In Refresh, the apparent padding around the tabstrip is contained within
   // the tabs and/or new tab button.
@@ -158,12 +143,6 @@
          HasVisibleBackgroundTabShapes(kInactive);
 }
 
-gfx::ImageSkia BrowserNonClientFrameView::GetIncognitoAvatarIcon() const {
-  const SkColor icon_color = color_utils::PickContrastingColor(
-      SK_ColorWHITE, gfx::kChromeIconGrey, GetFrameColor());
-  return gfx::CreateVectorIcon(kIncognitoIcon, icon_color);
-}
-
 SkColor BrowserNonClientFrameView::GetFrameColor(
     ActiveState active_state) const {
   ThemeProperties::OverwritableByUserThemeProperty color_id;
@@ -285,27 +264,12 @@
   return id;
 }
 
-views::Button* BrowserNonClientFrameView::GetProfileSwitcherButton() const {
-  return profile_switcher_.avatar_button();
-}
-
 void BrowserNonClientFrameView::UpdateClientArea() {}
 
 void BrowserNonClientFrameView::UpdateMinimumSize() {}
 
 int BrowserNonClientFrameView::GetTabStripLeftInset() const {
-  int left_inset = GetTabstripPadding();
-  if (profile_indicator_icon())
-    left_inset += GetAvatarIconPadding() + GetIncognitoAvatarIcon().width();
-  return left_inset;
-}
-
-void BrowserNonClientFrameView::ChildPreferredSizeChanged(views::View* child) {
-  if (child == GetProfileSwitcherButton()) {
-    // Perform a re-layout if the avatar button has changed, since that can
-    // affect the size of the tabs.
-    frame_->GetRootView()->Layout();
-  }
+  return GetTabstripPadding();
 }
 
 void BrowserNonClientFrameView::VisibilityChanged(views::View* starting_from,
@@ -391,84 +355,12 @@
              : gfx::ImageSkia();
 }
 
-void BrowserNonClientFrameView::UpdateProfileIcons() {
-  const AvatarButtonStyle avatar_button_style = GetAvatarButtonStyle();
-  if (avatar_button_style != AvatarButtonStyle::NONE &&
-      browser_view_->IsRegularOrGuestSession()) {
-    // Platform supports a profile switcher that will be shown. Skip the rest.
-    profile_switcher_.Update(avatar_button_style);
-    return;
-  }
-
-  if (!ShouldShowProfileIndicatorIcon()) {
-    if (profile_indicator_icon_) {
-      delete profile_indicator_icon_;
-      profile_indicator_icon_ = nullptr;
-      frame_->GetRootView()->Layout();
-    }
-    return;
-  }
-
-  if (!profile_indicator_icon_) {
-    profile_indicator_icon_ = new ProfileIndicatorIcon();
-    profile_indicator_icon_->set_id(VIEW_ID_PROFILE_INDICATOR_ICON);
-    AddChildView(profile_indicator_icon_);
-    // Invalidate here because adding a child does not invalidate the layout.
-    InvalidateLayout();
-    frame_->GetRootView()->Layout();
-  }
-
-  gfx::Image icon;
-  Profile* profile = browser_view_->browser()->profile();
-  const bool is_incognito =
-      profile->GetProfileType() == Profile::INCOGNITO_PROFILE;
-  if (is_incognito) {
-    icon = gfx::Image(GetIncognitoAvatarIcon());
-    profile_indicator_icon_->set_stroke_color(SK_ColorTRANSPARENT);
-  } else {
-#if defined(OS_CHROMEOS)
-    icon = gfx::Image(GetAvatarImageForContext(profile));
-    // Draw a stroke around the profile icon only for the avatar.
-    profile_indicator_icon_->set_stroke_color(GetToolbarTopSeparatorColor());
-#else
-    NOTREACHED();
-#endif
-  }
-
-  profile_indicator_icon_->SetIcon(icon);
-}
-
-void BrowserNonClientFrameView::LayoutIncognitoButton() {
-  DCHECK(profile_indicator_icon());
-#if !defined(OS_CHROMEOS)
-  // ChromeOS shows avatar on V1 app.
-  DCHECK(browser_view_->IsTabStripVisible());
-#endif
-  gfx::ImageSkia incognito_icon = GetIncognitoAvatarIcon();
-  int avatar_bottom = GetTopInset(false) + browser_view_->GetTabStripHeight() -
-                      GetAvatarIconPadding();
-  int avatar_y = avatar_bottom - incognito_icon.height();
-  int avatar_height = incognito_icon.height();
-  gfx::Rect avatar_bounds(GetAvatarIconPadding(), avatar_y,
-                          incognito_icon.width(), avatar_height);
-
-  profile_indicator_icon()->SetBoundsRect(avatar_bounds);
-  profile_indicator_icon()->SetVisible(true);
-}
-
-void BrowserNonClientFrameView::ViewHierarchyChanged(
-    const ViewHierarchyChangedDetails& details) {
-  if (details.is_add && details.child == this)
-    UpdateProfileIcons();
-}
-
 void BrowserNonClientFrameView::ActivationChanged(bool active) {
   // On Windows, while deactivating the widget, this is called before the
-  // active HWND has actually been changed.  Since we want the avatar state to
-  // reflect that the window is inactive, we force NonClientFrameView to see the
+  // active HWND has actually been changed.  Since we want the state to reflect
+  // that the window is inactive, we force NonClientFrameView to see the
   // "correct" state as an override.
   set_active_state_override(&active);
-  UpdateProfileIcons();
 
   if (MD::IsRefreshUi()) {
     // Single-tab mode's availability depends on activation, but even if it's
@@ -541,8 +433,7 @@
   }
 
   // We claim |rect| because it is above the bottom of the tabstrip, but
-  // not in the tabstrip itself. In particular, the avatar label/button is left
-  // of the tabstrip and the window controls are right of the tabstrip.
+  // not in the tabstrip itself.
   return !should_leave_to_top_container;
 }
 
@@ -560,7 +451,6 @@
 void BrowserNonClientFrameView::OnProfileAvatarChanged(
     const base::FilePath& profile_path) {
   UpdateTaskbarDecoration();
-  UpdateProfileIcons();
 }
 
 void BrowserNonClientFrameView::OnProfileHighResAvatarLoaded(
@@ -594,8 +484,7 @@
     return;
   }
 
-  // For popups and panels which don't have the avatar button, we still
-  // need to draw the taskbar decoration. Even though we have an icon on the
+  // We need to draw the taskbar decoration. Even though we have an icon on the
   // window's relaunch details, we draw over it because the user may have
   // pinned the badge-less Chrome shortcut which will cause Windows to ignore
   // the relaunch details.
@@ -622,38 +511,6 @@
 #endif
 }
 
-bool BrowserNonClientFrameView::ShouldShowProfileIndicatorIcon() const {
-#if !defined(OS_CHROMEOS)
-  // Outside ChromeOS, in Material Refresh, we use a toolbar button for all
-  // profile/incognito-related purposes. ChromeOS uses it for teleportation (see
-  // below).
-  if (MD::IsRefreshUi())
-    return false;
-#endif  // !defined(OS_CHROMEOS)
-
-  Browser* browser = browser_view_->browser();
-  Profile* profile = browser->profile();
-  const bool is_incognito =
-      profile->GetProfileType() == Profile::INCOGNITO_PROFILE;
-
-  // In newer material UIs we only show the avatar icon for the teleported
-  // browser windows between multi-user sessions (Chrome OS only). Note that you
-  // can't teleport an incognito window.
-  if (is_incognito && MD::IsNewerMaterialUi())
-    return false;
-
-#if defined(OS_CHROMEOS)
-  if (!browser->is_type_tabbed() && !browser->is_app())
-    return false;
-
-  if (!is_incognito && !MultiUserWindowManager::ShouldShowAvatar(
-                           browser_view_->GetNativeWindow())) {
-    return false;
-  }
-#endif  // defined(OS_CHROMEOS)
-  return true;
-}
-
 SkColor BrowserNonClientFrameView::GetThemeOrDefaultColor(int color_id) const {
   // During shutdown, there may no longer be a widget, and thus no theme
   // provider.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
index 91bb9dd3..598a2dff 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
@@ -6,9 +6,8 @@
 #define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_H_
 
 #include "base/scoped_observer.h"
+#include "build/build_config.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_types.h"
 #include "ui/views/window/non_client_view.h"
@@ -37,20 +36,12 @@
   BrowserNonClientFrameView(BrowserFrame* frame, BrowserView* browser_view);
   ~BrowserNonClientFrameView() override;
 
-  // Returns the padding on the left, right, and bottom of the avatar icon.
-  static int GetAvatarIconPadding();
-
   // Returns the padding on the sides of the tabstrip.
   static int GetTabstripPadding();
 
   BrowserView* browser_view() const { return browser_view_; }
   BrowserFrame* frame() const { return frame_; }
 
-  const views::View* profile_indicator_icon() const {
-    return profile_indicator_icon_;
-  }
-  views::View* profile_indicator_icon() { return profile_indicator_icon_; }
-
   // Called when BrowserView creates all it's child views.
   virtual void OnBrowserViewInitViewsComplete();
 
@@ -104,9 +95,6 @@
   // for either active or inactive windows.
   bool EverHasVisibleBackgroundTabShapes() const;
 
-  // Retrieves the icon to use in the frame to indicate an incognito window.
-  gfx::ImageSkia GetIncognitoAvatarIcon() const;
-
   // Returns the color of the browser frame, which is also the color of the
   // tabstrip background.
   SkColor GetFrameColor(ActiveState active_state = kUseCurrent) const;
@@ -136,10 +124,6 @@
   // Updates the throbber.
   virtual void UpdateThrobber(bool running) = 0;
 
-  // Returns the profile switcher button, if this frame has any, nullptr if it
-  // doesn't.
-  views::Button* GetProfileSwitcherButton() const;
-
   // Provided for mus. Updates the client-area of the WindowTreeHostMus.
   virtual void UpdateClientArea();
 
@@ -161,7 +145,6 @@
 
   // views::NonClientFrameView:
   using views::NonClientFrameView::ShouldPaintAsActive;
-  void ChildPreferredSizeChanged(views::View* child) override;
   void VisibilityChanged(views::View* starting_from, bool is_visible) override;
 
   // TabStripObserver:
@@ -185,27 +168,11 @@
   gfx::ImageSkia GetFrameOverlayImage(
       ActiveState active_state = kUseCurrent) const;
 
-  // Returns the style of the profile switcher avatar button.
-  virtual AvatarButtonStyle GetAvatarButtonStyle() const = 0;
-
-  // Updates all the profile icons as necessary (profile switcher button, or the
-  // icon that indicates incognito (or a teleported window in ChromeOS)).
-  void UpdateProfileIcons();
-
-  void LayoutIncognitoButton();
-
   // views::NonClientFrameView:
   void ActivationChanged(bool active) override;
   bool DoesIntersectRect(const views::View* target,
                          const gfx::Rect& rect) const override;
 
-  AvatarButtonManager* profile_switcher() { return &profile_switcher_; }
-
- private:
-  // views::NonClientFrameView:
-  void ViewHierarchyChanged(
-      const ViewHierarchyChangedDetails& details) override;
-
   // ProfileAttributesStorage::Observer:
   void OnProfileAdded(const base::FilePath& profile_path) override;
   void OnProfileWasRemoved(const base::FilePath& profile_path,
@@ -214,18 +181,16 @@
   void OnProfileHighResAvatarLoaded(
       const base::FilePath& profile_path) override;
 
+ private:
   void MaybeObserveTabstrip();
 
   // Gets a theme provider that should be non-null even before we're added to a
   // view hierarchy.
   const ui::ThemeProvider* GetThemeProviderForProfile() const;
 
-  // Draws a taskbar icon if avatars are enabled, erases it otherwise.
+  // Draws a taskbar icon for non-guest sessions, erases it otherwise.
   void UpdateTaskbarDecoration();
 
-  // Returns true if |profile_indicator_icon_| should be shown.
-  bool ShouldShowProfileIndicatorIcon() const;
-
   // Returns the color of the given |color_id| from the theme provider or the
   // default theme properties.
   SkColor GetThemeOrDefaultColor(int color_id) const;
@@ -236,14 +201,6 @@
   // The BrowserView hosted within this View.
   BrowserView* browser_view_;
 
-  // Wrapper around the in-frame profile switcher. Might not be used on all
-  // platforms.
-  AvatarButtonManager profile_switcher_;
-
-  // On desktop, this is used to show an incognito icon. On CrOS, it's also used
-  // for teleported windows (in multi-profile mode).
-  ProfileIndicatorIcon* profile_indicator_icon_;
-
   ScopedObserver<TabStrip, BrowserNonClientFrameView> tab_strip_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(BrowserNonClientFrameView);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index ae43dac..a9bf0ff 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
+#include "chrome/browser/ui/ash/session_util.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_command_controller.h"
@@ -76,6 +77,9 @@
 constexpr SkColor kNormalWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
 constexpr SkColor kIncognitoWindowTitleTextColor = SK_ColorWHITE;
 
+// The indicator for teleported windows is 24 DIP on a side.
+constexpr int kProfileIndicatorSize = 24;
+
 bool IsV1AppBackButtonEnabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       ash::switches::kAshEnableV1AppBackButton);
@@ -106,6 +110,12 @@
   return views::WindowManagerFrameValues::instance();
 }
 
+// Returns the padding on the left, right, and bottom of the profile
+// indicator.
+int GetProfileIndicatorPadding() {
+  return ui::MaterialDesignController::IsNewerMaterialUi() ? 8 : 4;
+}
+
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -171,8 +181,7 @@
     window_icon_->Update();
   }
 
-  if (browser->is_app() && IsV1AppBackButtonEnabled())
-    browser->command_controller()->AddCommandObserver(IDC_BACK, this);
+  UpdateProfileIcons();
 
   aura::Window* window = frame()->GetNativeWindow();
   window->SetProperty(
@@ -203,6 +212,7 @@
     TabletModeClient::Get()->AddObserver(this);
 
   if (browser->is_app() && IsV1AppBackButtonEnabled()) {
+    browser->command_controller()->AddCommandObserver(IDC_BACK, this);
     back_button_ = new ash::FrameBackButton();
     AddChildView(back_button_);
     // TODO(oshima): Add Tooltip, accessibility name.
@@ -294,8 +304,11 @@
 }
 
 int BrowserNonClientFrameViewAsh::GetTabStripLeftInset() const {
-  return BrowserNonClientFrameView::GetTabStripLeftInset() +
-         frame_values().normal_insets.left();
+  int left_inset = BrowserNonClientFrameView::GetTabStripLeftInset() +
+                   frame_values().normal_insets.left();
+  if (profile_indicator_icon_)
+    left_inset += GetProfileIndicatorPadding() + kProfileIndicatorSize;
+  return left_inset;
 }
 
 void BrowserNonClientFrameViewAsh::OnTabsMaxXChanged() {
@@ -368,6 +381,8 @@
 void BrowserNonClientFrameViewAsh::ActivationChanged(bool active) {
   BrowserNonClientFrameView::ActivationChanged(active);
 
+  UpdateProfileIcons();
+
   const bool should_paint_as_active = ShouldPaintAsActive();
   frame_header_->SetPaintAsActive(should_paint_as_active);
 
@@ -400,8 +415,8 @@
 
   frame_header_->SetHeaderHeightForPainting(painted_height);
 
-  if (profile_indicator_icon())
-    LayoutIncognitoButton();
+  if (profile_indicator_icon_)
+    LayoutProfileIndicator();
   if (hosted_app_button_container_) {
     hosted_app_button_container_->LayoutInContainer(
         0, caption_button_container_->x(), 0, painted_height);
@@ -656,10 +671,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserNonClientFrameViewAsh, protected:
 
-// BrowserNonClientFrameView:
-AvatarButtonStyle BrowserNonClientFrameViewAsh::GetAvatarButtonStyle() const {
-  // Ash doesn't support a profile switcher button.
-  return AvatarButtonStyle::NONE;
+void BrowserNonClientFrameViewAsh::OnProfileAvatarChanged(
+    const base::FilePath& profile_path) {
+  BrowserNonClientFrameView::OnProfileAvatarChanged(profile_path);
+  UpdateProfileIcons();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -804,6 +819,55 @@
   frame()->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, inset);
 }
 
+bool BrowserNonClientFrameViewAsh::ShouldShowProfileIndicatorIcon() const {
+  // We only show the profile indicator for the teleported browser windows
+  // between multi-user sessions. Note that you can't teleport an incognito
+  // window.
+  Browser* browser = browser_view()->browser();
+  if (browser->profile()->GetProfileType() == Profile::INCOGNITO_PROFILE)
+    return false;
+
+  if (!browser->is_type_tabbed() && !browser->is_app())
+    return false;
+
+  return MultiUserWindowManager::ShouldShowAvatar(
+      browser_view()->GetNativeWindow());
+}
+
+void BrowserNonClientFrameViewAsh::UpdateProfileIcons() {
+  View* root_view = frame()->GetRootView();
+  if (ShouldShowProfileIndicatorIcon()) {
+    if (!profile_indicator_icon_) {
+      profile_indicator_icon_ = new ProfileIndicatorIcon();
+      AddChildView(profile_indicator_icon_);
+      if (root_view) {
+        // Adding a child does not invalidate the layout.
+        InvalidateLayout();
+        root_view->Layout();
+      }
+    }
+
+    profile_indicator_icon_->SetIcon(gfx::Image(
+        GetAvatarImageForContext(browser_view()->browser()->profile())));
+    profile_indicator_icon_->set_stroke_color(GetToolbarTopSeparatorColor());
+  } else if (profile_indicator_icon_) {
+    delete profile_indicator_icon_;
+    profile_indicator_icon_ = nullptr;
+    if (root_view)
+      root_view->Layout();
+  }
+}
+
+void BrowserNonClientFrameViewAsh::LayoutProfileIndicator() {
+  DCHECK(profile_indicator_icon_);
+  const int bottom = GetTopInset(false) + browser_view()->GetTabStripHeight() -
+                     GetProfileIndicatorPadding();
+  profile_indicator_icon_->SetBounds(
+      GetProfileIndicatorPadding(), bottom - kProfileIndicatorSize,
+      kProfileIndicatorSize, kProfileIndicatorSize);
+  profile_indicator_icon_->SetVisible(true);
+}
+
 ws::Id BrowserNonClientFrameViewAsh::GetServerWindowId() const {
   DCHECK(features::IsUsingWindowService());
   return aura::WindowMus::Get(GetFrameWindow())->server_id();
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
index 306bcbe..a238d122 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -29,6 +29,7 @@
 }
 
 class HostedAppButtonContainer;
+class ProfileIndicatorIcon;
 class TabIconView;
 
 namespace ash {
@@ -134,7 +135,7 @@
 
  protected:
   // BrowserNonClientFrameView:
-  AvatarButtonStyle GetAvatarButtonStyle() const override;
+  void OnProfileAvatarChanged(const base::FilePath& profile_path) override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
@@ -202,6 +203,14 @@
   // Updates the kTopViewInset window property after a layout.
   void UpdateTopViewInset();
 
+  // Returns true if |profile_indicator_icon_| should be shown.
+  bool ShouldShowProfileIndicatorIcon() const;
+
+  // Updates the icon that indicates a teleported window.
+  void UpdateProfileIcons();
+
+  void LayoutProfileIndicator();
+
   ws::Id GetServerWindowId() const;
 
   // Returns whether this window is currently in the overview list.
@@ -219,6 +228,9 @@
   // For popups, the window icon.
   TabIconView* window_icon_ = nullptr;
 
+  // This is used for teleported windows (in multi-profile mode).
+  ProfileIndicatorIcon* profile_indicator_icon_ = nullptr;
+
   // Helper class for painting the header.
   std::unique_ptr<ash::FrameHeader> frame_header_;
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index 4163dda2..74b1b2c 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -58,7 +58,6 @@
 #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_container_view.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/toolbar/app_menu.h"
@@ -310,7 +309,7 @@
   aura::Window* window = browser()->window()->GetNativeWindow();
 
   EXPECT_FALSE(MultiUserWindowManager::ShouldShowAvatar(window));
-  EXPECT_FALSE(frame_view->profile_indicator_icon());
+  EXPECT_FALSE(frame_view->profile_indicator_icon_);
 
   const AccountId account_id1 =
       multi_user_util::GetAccountIdFromProfile(browser()->profile());
@@ -325,7 +324,7 @@
   // Teleport the window back to owner desktop.
   manager->ShowWindowForUser(window, account_id1);
   EXPECT_FALSE(MultiUserWindowManager::ShouldShowAvatar(window));
-  EXPECT_FALSE(frame_view->profile_indicator_icon());
+  EXPECT_FALSE(frame_view->profile_indicator_icon_);
 }
 
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest_win.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest_win.cc
deleted file mode 100644
index 224fbfd..0000000
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest_win.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
-
-#include <windows.h>
-
-#include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "ui/base/material_design/material_design_controller.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/win/hwnd_util.h"
-
-using BrowserNonClientFrameViewTestWin = InProcessBrowserTest;
-
-namespace {
-
-int NonClientHitTest(HWND hwnd, int x, int y) {
-  return ::SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(x, y));
-}
-
-void HitTestPerimeter(views::View* view) {
-  ASSERT_TRUE(view);
-  ASSERT_TRUE(view->visible());
-
-  HWND hwnd = views::HWNDForView(view);
-  gfx::Rect rect = view->GetBoundsInScreen();
-
-  // Coordinates within the bounds: left/middle/right and top/middle/bottom.
-  const int xs[] = { rect.x(), rect.x() + rect.width() / 2, rect.right() - 1 };
-  const int ys[] = { rect.y(), rect.y() + rect.height() / 2, rect.bottom() - 1};
-
-  for (int y : ys) {
-    EXPECT_NE(HTCLIENT, NonClientHitTest(hwnd, xs[0] - 1, y));
-    EXPECT_EQ(HTCLIENT, NonClientHitTest(hwnd, xs[0], y));
-    EXPECT_EQ(HTCLIENT, NonClientHitTest(hwnd, xs[2], y));
-    EXPECT_NE(HTCLIENT, NonClientHitTest(hwnd, xs[2] + 1, y));
-  }
-  for (int x : xs) {
-    EXPECT_NE(HTCLIENT, NonClientHitTest(hwnd, x, ys[0] - 1));
-    EXPECT_EQ(HTCLIENT, NonClientHitTest(hwnd, x, ys[0]));
-    EXPECT_EQ(HTCLIENT, NonClientHitTest(hwnd, x, ys[2]));
-    EXPECT_NE(HTCLIENT, NonClientHitTest(hwnd, x, ys[2] + 1));
-  }
-}
-
-}  // namespace
-
-IN_PROC_BROWSER_TEST_F(BrowserNonClientFrameViewTestWin, HitTestFrameItems) {
-  if (ui::MaterialDesignController::IsRefreshUi()) {
-    // In Refresh the avatar button in the frame area is replaced by a toolbar
-    // button. Since there is nothing else in the frame area, skip this test.
-    return;
-  }
-
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  views::Widget* widget = browser_view->GetWidget();
-
-  BrowserNonClientFrameView* frame_view =
-      static_cast<BrowserNonClientFrameView*>(
-          widget->non_client_view()->frame_view());
-
-  EXPECT_NO_FATAL_FAILURE(
-      HitTestPerimeter(frame_view->GetViewByID(VIEW_ID_AVATAR_BUTTON)));
-}
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
index 7ffde12..c6703c0 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
@@ -5,11 +5,11 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_MAC_H_
 #define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_MAC_H_
 
+#import <CoreGraphics/CGBase.h>
+
 #include "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "components/prefs/pref_change_registrar.h"
 
 @class FullscreenToolbarControllerViews;
@@ -43,22 +43,15 @@
   void SizeConstraintsChanged() override;
 
   // views::View:
-  void Layout() override;
   gfx::Size GetMinimumSize() const override;
 
  protected:
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
 
-  // BrowserNonClientFrameView:
-  AvatarButtonStyle GetAvatarButtonStyle() const override;
-
  private:
   void PaintThemedFrame(gfx::Canvas* canvas);
 
-  // Returns the width taken by any items after the tabstrip, to the edge of the
-  // window.  Does not include any padding between the tabstrip and these items.
-  int GetAfterTabstripItemWidth() const;
   CGFloat FullscreenBackingBarHeight() const;
 
   // Calculate the y offset the top UI needs to shift down due to showing the
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
index 5cda6be8..bab415a 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
@@ -14,7 +14,6 @@
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/browser_view_layout.h"
-#include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/hit_test.h"
@@ -83,12 +82,10 @@
   // calling through private APIs.
   DCHECK(tabstrip);
 
+  const int x = GetTabStripLeftInset();
   const bool restored = !frame()->IsMaximized() && !frame()->IsFullscreen();
-  gfx::Rect bounds = gfx::Rect(0, GetTopInset(restored), width(),
-                               tabstrip->GetPreferredSize().height());
-  bounds.Inset(GetTabStripLeftInset(), 0,
-               GetAfterTabstripItemWidth() + GetTabstripPadding(), 0);
-  return bounds;
+  return gfx::Rect(x, GetTopInset(restored), width() - x - GetTabstripPadding(),
+                   tabstrip->GetPreferredSize().height());
 }
 
 int BrowserNonClientFrameViewMac::GetTopInset(bool restored) const {
@@ -124,18 +121,6 @@
   return y_offset + top_inset;
 }
 
-int BrowserNonClientFrameViewMac::GetAfterTabstripItemWidth() const {
-  int item_width;
-  views::View* profile_switcher_button = GetProfileSwitcherButton();
-  if (profile_indicator_icon() && browser_view()->IsTabStripVisible())
-    item_width = profile_indicator_icon()->width();
-  else if (profile_switcher_button)
-    item_width = profile_switcher_button->GetPreferredSize().width();
-  else
-    return 0;
-  return item_width + GetAvatarIconPadding();
-}
-
 int BrowserNonClientFrameViewMac::GetThemeBackgroundXInset() const {
   return 0;
 }
@@ -187,11 +172,8 @@
 
 int BrowserNonClientFrameViewMac::GetTabStripLeftInset() const {
   constexpr int kTabstripLeftInset = 70;  // Make room for caption buttons.
-
-  if (frame()->IsFullscreen())
-    return 0;  // Do not draw caption buttons on fullscreen.
-  else
-    return kTabstripLeftInset;
+  // Do not draw caption buttons on fullscreen.
+  return frame()->IsFullscreen() ? 0 : kTabstripLeftInset;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -207,24 +189,12 @@
 }
 
 int BrowserNonClientFrameViewMac::NonClientHitTest(const gfx::Point& point) {
-  views::View* profile_switcher_view = GetProfileSwitcherButton();
-  if (profile_switcher_view) {
-    gfx::Point point_in_switcher(point);
-    views::View::ConvertPointToTarget(this, profile_switcher_view,
-                                      &point_in_switcher);
-    if (profile_switcher_view->HitTestPoint(point_in_switcher)) {
-      return HTCLIENT;
-    }
-  }
-  int component = frame()->client_view()->NonClientHitTest(point);
-
   // BrowserView::NonClientHitTest will return HTNOWHERE for points that hit
   // the native title bar. On Mac, we need to explicitly return HTCAPTION for
   // those points.
-  if (component == HTNOWHERE && bounds().Contains(point))
-    return HTCAPTION;
-
-  return component;
+  const int component = frame()->client_view()->NonClientHitTest(point);
+  return (component == HTNOWHERE && bounds().Contains(point)) ? HTCAPTION
+                                                              : component;
 }
 
 void BrowserNonClientFrameViewMac::GetWindowMask(const gfx::Size& size,
@@ -261,31 +231,6 @@
 
 // views::View:
 
-void BrowserNonClientFrameViewMac::Layout() {
-  DCHECK(browser_view());
-  views::View* profile_switcher_button = GetProfileSwitcherButton();
-  if (profile_indicator_icon() && browser_view()->IsTabStripVisible()) {
-    LayoutIncognitoButton();
-    // Mac lays out the incognito icon on the right, as the stoplight
-    // buttons live in its Windows/Linux location.
-    profile_indicator_icon()->SetX(width() - GetAfterTabstripItemWidth());
-  } else if (profile_switcher_button) {
-    gfx::Size button_size = profile_switcher_button->GetPreferredSize();
-    int button_x = width() - GetAfterTabstripItemWidth();
-    int button_y = 0;
-    TabStrip* tabstrip = browser_view()->tabstrip();
-    if (tabstrip && browser_view()->IsTabStripVisible()) {
-      int new_tab_button_bottom =
-          tabstrip->bounds().y() + tabstrip->new_tab_button_bounds().height();
-      // Align the switcher's bottom to bottom of the new tab button;
-      button_y = new_tab_button_bottom - button_size.height();
-    }
-    profile_switcher_button->SetBounds(button_x, button_y, button_size.width(),
-                                       button_size.height());
-  }
-  BrowserNonClientFrameView::Layout();
-}
-
 void BrowserNonClientFrameViewMac::OnPaint(gfx::Canvas* canvas) {
   if (!browser_view()->IsBrowserTypeNormal())
     return;
@@ -296,11 +241,6 @@
     PaintThemedFrame(canvas);
 }
 
-// BrowserNonClientFrameView:
-AvatarButtonStyle BrowserNonClientFrameViewMac::GetAvatarButtonStyle() const {
-  return AvatarButtonStyle::NATIVE;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserNonClientFrameViewMac, private:
 
diff --git a/chrome/browser/ui/views/frame/browser_root_view.h b/chrome/browser/ui/views/frame/browser_root_view.h
index 63d2133..8b5da77 100644
--- a/chrome/browser/ui/views/frame/browser_root_view.h
+++ b/chrome/browser/ui/views/frame/browser_root_view.h
@@ -57,7 +57,7 @@
   BrowserRootView(BrowserView* browser_view, views::Widget* widget);
   ~BrowserRootView() override;
 
-  // Overridden from views::View:
+  // views::View:
   bool GetDropFormats(
       int* formats,
       std::set<ui::Clipboard::FormatType>* format_types) override;
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 65881ea..fa5eb90 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1812,11 +1812,52 @@
   if (index == -1)
     return base::string16();
 
-  base::string16 window_title =
+  base::string16 title =
       browser_->GetWindowTitleForTab(include_app_name, index);
-  return chrome::AssembleTabAccessibilityLabel(
-      window_title, tabstrip_->IsTabCrashed(index),
-      tabstrip_->TabHasNetworkError(index), tabstrip_->GetTabAlertState(index));
+
+  // Tab has crashed.
+  if (tabstrip_->IsTabCrashed(index))
+    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT, title);
+
+  // Network error interstitial.
+  if (tabstrip_->TabHasNetworkError(index)) {
+    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_NETWORK_ERROR_FORMAT,
+                                      title);
+  }
+
+  // Alert tab states.
+  switch (tabstrip_->GetTabAlertState(index)) {
+    case TabAlertState::AUDIO_PLAYING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT,
+                                        title);
+    case TabAlertState::USB_CONNECTED:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT,
+                                        title);
+    case TabAlertState::BLUETOOTH_CONNECTED:
+      return l10n_util::GetStringFUTF16(
+          IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT, title);
+    case TabAlertState::MEDIA_RECORDING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT,
+                                        title);
+    case TabAlertState::AUDIO_MUTING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT,
+                                        title);
+    case TabAlertState::TAB_CAPTURING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT,
+                                        title);
+    case TabAlertState::PIP_PLAYING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_PIP_PLAYING_FORMAT,
+                                        title);
+
+    case TabAlertState::DESKTOP_CAPTURING:
+      return l10n_util::GetStringFUTF16(
+          IDS_TAB_AX_LABEL_DESKTOP_CAPTURING_FORMAT, title);
+    case TabAlertState::NONE:
+      return title;
+  }
+
+  NOTREACHED();
+  return base::string16();
 }
 
 void BrowserView::NativeThemeUpdated(const ui::NativeTheme* theme) {
@@ -2851,10 +2892,9 @@
   // Never show any avatar bubble in Incognito.
   if (!IsRegularOrGuestSession())
     return;
-  views::Button* avatar_button = toolbar_->avatar_button();
-  if (!avatar_button)
-    avatar_button = frame_->GetNewAvatarMenuButton();
+
   // Do not show avatar bubble if there is no avatar menu button.
+  views::Button* avatar_button = toolbar_->avatar_button();
   if (!avatar_button)
     return;
 
diff --git a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view.cc b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view.cc
index 0fb105cd..42788cc 100644
--- a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view.cc
@@ -15,7 +15,6 @@
     std::unique_ptr<views::NavButtonProvider> nav_button_provider)
     : OpaqueBrowserFrameView(frame, browser_view, layout),
       nav_button_provider_(std::move(nav_button_provider)) {
-  profile_switcher()->set_nav_button_provider(nav_button_provider_.get());
 }
 
 DesktopLinuxBrowserFrameView::~DesktopLinuxBrowserFrameView() {}
diff --git a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.cc b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.cc
index b2d75966..55b4ed6 100644
--- a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.cc
+++ b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.cc
@@ -36,34 +36,6 @@
   return spacing;
 }
 
-void DesktopLinuxBrowserFrameViewLayout::LayoutNewStyleAvatar(
-    views::View* host) {
-  if (!new_avatar_button_)
-    return;
-
-  gfx::Size button_size;
-  gfx::Insets button_spacing;
-  nav_button_provider_->CalculateCaptionButtonLayout(
-      new_avatar_button_->GetPreferredSize(),
-      delegate_->GetTopAreaHeight() - TitlebarTopThickness(), &button_size,
-      &button_spacing);
-  const int extra_offset =
-      has_trailing_buttons() ? nav_button_provider_->GetInterNavButtonSpacing()
-                             : 0;
-
-  const int total_width =
-      button_size.width() + button_spacing.right() + extra_offset;
-
-  const int button_x = available_space_trailing_x_ - total_width;
-  const int button_y = button_spacing.top() + TitlebarTopThickness();
-
-  minimum_size_for_buttons_ += total_width;
-  available_space_trailing_x_ -= total_width;
-
-  new_avatar_button_->SetBounds(button_x, button_y, button_size.width(),
-                                button_size.height());
-}
-
 bool DesktopLinuxBrowserFrameViewLayout::ShouldDrawImageMirrored(
     views::ImageButton* button,
     ButtonAlignment alignment) const {
diff --git a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.h b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.h
index e276dde..0cf3de1 100644
--- a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.h
+++ b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout.h
@@ -21,7 +21,6 @@
   int GetWindowCaptionSpacing(views::FrameButton button_id,
                               bool leading_spacing,
                               bool is_leading_button) const override;
-  void LayoutNewStyleAvatar(views::View* host) override;
   bool ShouldDrawImageMirrored(views::ImageButton* button,
                                ButtonAlignment alignment) const override;
 
diff --git a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout_unittest.cc b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout_unittest.cc
index 0c5a6d83d..37320917 100644
--- a/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/desktop_linux_browser_frame_view_layout_unittest.cc
@@ -50,9 +50,6 @@
   }
   bool ShouldShowCaptionButtons() const override { return true; }
   bool IsRegularOrGuestSession() const override { return true; }
-  gfx::ImageSkia GetIncognitoAvatarIcon() const override {
-    return gfx::ImageSkia();
-  }
   bool IsMaximized() const override { return false; }
   bool IsMinimized() const override { return false; }
   bool IsTabStripVisible() const override { return true; }
@@ -63,9 +60,6 @@
   gfx::Size GetTabstripPreferredSize() const override {
     return gfx::Size(78, 29);
   }
-  gfx::Size GetNewTabButtonPreferredSize() const override {
-    return gfx::Size(28, 28);
-  }
   int GetTopAreaHeight() const override { return 0; }
   bool UseCustomFrame() const override { return true; }
   bool IsFrameCondensed() const override { return false; }
@@ -120,17 +114,6 @@
   int GetInterNavButtonSpacing() const override {
     return kInterNavButtonSpacing;
   }
-
-  std::unique_ptr<views::Background> CreateAvatarButtonBackground(
-      const views::Button* avatar_button) const override {
-    return nullptr;
-  }
-
-  void CalculateCaptionButtonLayout(
-      const gfx::Size& content_size,
-      int top_area_height,
-      gfx::Size* caption_button_size,
-      gfx::Insets* caption_button_spacing) const override {}
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
index 90877f1..dd1db05 100644
--- a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
@@ -11,13 +11,11 @@
 #include "base/win/windows_version.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/chrome_dll_resource.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tabs/new_tab_button.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -48,13 +46,8 @@
 HICON GlassBrowserFrameView::throbber_icons_[
     GlassBrowserFrameView::kThrobberIconCount];
 
-using MD = ui::MaterialDesignController;
-
 namespace {
 
-// How far the profile switcher button is from the left of the minimize button.
-constexpr int kProfileSwitcherButtonOffset = 1;
-
 // Converts the |image| to a Windows icon and returns the corresponding HICON
 // handle. |image| is resized to desired |width| and |height| if needed.
 base::win::ScopedHICON CreateHICONFromSkBitmapSizedTo(
@@ -245,7 +238,9 @@
 }
 
 int GlassBrowserFrameView::GetTabStripLeftInset() const {
-  return incognito_bounds_.right() + GetTabstripPadding();
+  if (CaptionButtonsOnLeadingEdge())
+    return width() - frame()->GetMinimizeButtonOffset() + GetTabstripPadding();
+  return BrowserNonClientFrameView::GetTabStripLeftInset();
 }
 
 bool GlassBrowserFrameView::IsSingleTabModeAvailable() const {
@@ -314,15 +309,6 @@
   if (!bounds().Contains(point))
     return HTNOWHERE;
 
-  // See if the point is within the incognito icon or the profile switcher menu.
-  views::View* profile_switcher_view = GetProfileSwitcherButton();
-  if ((profile_indicator_icon() &&
-       profile_indicator_icon()->GetMirroredBounds().Contains(point)) ||
-      (profile_switcher_view &&
-       profile_switcher_view->GetMirroredBounds().Contains(point))) {
-    return HTCLIENT;
-  }
-
   if (hosted_app_button_container_) {
     // TODO(alancutter): Assign hit test components to all children and refactor
     // this entire function call to just be GetHitTestComponent(this, point).
@@ -454,21 +440,6 @@
   return frame()->widget_delegate()->GetWindowIcon();
 }
 
-void GlassBrowserFrameView::OnTabRemoved(int index) {
-  BrowserNonClientFrameView::OnTabRemoved(index);
-  // The profile switcher button may need to change height here, too.
-  // TabStripMaxXChanged is not enough when a tab other than the last tab is
-  // closed.
-  LayoutProfileSwitcher();
-}
-
-void GlassBrowserFrameView::OnTabsMaxXChanged() {
-  BrowserNonClientFrameView::OnTabsMaxXChanged();
-  // The profile switcher button's height depends on the position of the new
-  // tab button, which may have changed if the tabs max X changed.
-  LayoutProfileSwitcher();
-}
-
 bool GlassBrowserFrameView::IsMaximized() const {
   return frame()->IsMaximized();
 }
@@ -489,26 +460,15 @@
   TRACE_EVENT0("views.frame", "GlassBrowserFrameView::OnPaint");
   if (ShouldCustomDrawSystemTitlebar())
     PaintTitlebar(canvas);
-  if (!browser_view()->IsTabStripVisible())
-    return;
-  if (ClientBorderThickness(false) > 0)
+  if (browser_view()->IsTabStripVisible() && (ClientBorderThickness(false) > 0))
     PaintClientEdge(canvas);
 }
 
 void GlassBrowserFrameView::Layout() {
   TRACE_EVENT0("views.frame", "GlassBrowserFrameView::Layout");
-  // The profile switcher and incognito icon depends on the caption button
-  // layout, so always call it first.
   if (ShouldCustomDrawSystemTitlebar())
     LayoutCaptionButtons();
 
-  LayoutProfileSwitcher();
-
-  // The incognito area must be laid out even if we're not in incognito as
-  // tab-strip insets depend on it. When not in incognito the bounds will be
-  // zero-width but positioned correctly for the titlebar to start after it.
-  LayoutIncognitoIcon();
-
   if (ShouldCustomDrawSystemTitlebar())
     LayoutTitleBar();
 
@@ -516,14 +476,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// GlassBrowserFrameView, protected:
-
-// BrowserNonClientFrameView:
-AvatarButtonStyle GlassBrowserFrameView::GetAvatarButtonStyle() const {
-  return AvatarButtonStyle::NATIVE;
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // GlassBrowserFrameView, private:
 
 void GlassBrowserFrameView::ActivationChanged(bool active) {
@@ -682,29 +634,8 @@
   // similar vertical coordinates, we need to reserve a larger, 16 px gap to
   // avoid looking too cluttered.
   constexpr int kNewTabCaptionMaximizedSpacing = 16;
-  const int caption_spacing = IsMaximized() ? kNewTabCaptionMaximizedSpacing
-                                            : kNewTabCaptionRestoredSpacing;
-
-  // The profile switcher button is optionally displayed to the left of the
-  // minimize button.
-  views::View* profile_switcher = GetProfileSwitcherButton();
-  if (!profile_switcher)
-    return caption_spacing;
-
-  int profile_spacing =
-      profile_switcher->width() + kProfileSwitcherButtonOffset;
-
-  // In maximized mode, simply treat the profile switcher button as another
-  // caption button.
-  if (IsMaximized())
-    return caption_spacing + profile_spacing;
-
-  // When not maximized, allow the new tab button to slide completely under the
-  // the profile switcher button.
-  const auto* new_tab_button = browser_view()->tabstrip()->new_tab_button();
-  profile_spacing -= new_tab_button->GetPreferredSize().width();
-
-  return std::max(caption_spacing, profile_spacing);
+  return IsMaximized() ? kNewTabCaptionMaximizedSpacing
+                       : kNewTabCaptionRestoredSpacing;
 }
 
 bool GlassBrowserFrameView::IsToolbarVisible() const {
@@ -713,11 +644,8 @@
 }
 
 bool GlassBrowserFrameView::ShowCustomIcon() const {
-  // Don't show the window icon when the incognito badge is visible, since
-  // they're competing for the same space.
   // Hosted app windows don't include the window icon as per UI mocks.
-  return !profile_indicator_icon() && !hosted_app_button_container_ &&
-         ShouldCustomDrawSystemTitlebar() &&
+  return !hosted_app_button_container_ && ShouldCustomDrawSystemTitlebar() &&
          browser_view()->ShouldShowWindowIcon();
 }
 
@@ -868,66 +796,6 @@
   canvas->FillRect(side, color);
 }
 
-void GlassBrowserFrameView::LayoutProfileSwitcher() {
-  if (!browser_view()->IsRegularOrGuestSession())
-    return;
-
-  View* profile_switcher = GetProfileSwitcherButton();
-  if (!profile_switcher)
-    return;
-
-  gfx::Size button_size = profile_switcher->GetPreferredSize();
-  int button_width = button_size.width();
-  int button_height = button_size.height();
-
-  int button_x;
-  if (CaptionButtonsOnLeadingEdge()) {
-    button_x = width() - frame()->GetMinimizeButtonOffset() +
-               kProfileSwitcherButtonOffset;
-  } else {
-    button_x = MinimizeButtonX() - kProfileSwitcherButtonOffset - button_width;
-  }
-
-  int button_y = WindowTopY();
-  if (IsMaximized()) {
-    // In maximized mode the caption buttons appear only 19 pixels high, but
-    // their contents are aligned as if they were 20 pixels high and extended
-    // 1 pixel off the top of the screen. We position the profile switcher
-    // button the same way to match.
-    button_y -= 1;
-  }
-
-  // Shrink the button height when it's atop part of the tabstrip. In RTL the
-  // new tab button is on the left, so it can never slide under the avatar
-  // button, which is still on the right [http://crbug.com/560619].
-  TabStrip* tabstrip = browser_view()->tabstrip();
-  if (tabstrip && !CaptionButtonsOnLeadingEdge() &&
-      (tabstrip->new_tab_button_bounds().right() > button_x))
-    button_height = profile_switcher->GetMinimumSize().height();
-
-  profile_switcher->SetBounds(button_x, button_y, button_width, button_height);
-}
-
-void GlassBrowserFrameView::LayoutIncognitoIcon() {
-  const gfx::Size size(GetIncognitoAvatarIcon().size());
-  int x = ClientBorderThickness(false);
-  // In RTL, the icon needs to start after the caption buttons.
-  if (CaptionButtonsOnLeadingEdge()) {
-    x = width() - frame()->GetMinimizeButtonOffset() +
-        (GetProfileSwitcherButton() ? (GetProfileSwitcherButton()->width() +
-                                       kProfileSwitcherButtonOffset)
-                                    : 0);
-  }
-  const int bottom = GetTopInset(false) + browser_view()->GetTabStripHeight() -
-                     GetAvatarIconPadding();
-  incognito_bounds_.SetRect(
-      x + (profile_indicator_icon() ? GetAvatarIconPadding() : 0),
-      bottom - size.height(), profile_indicator_icon() ? size.width() : 0,
-      size.height());
-  if (profile_indicator_icon())
-    profile_indicator_icon()->SetBoundsRect(incognito_bounds_);
-}
-
 void GlassBrowserFrameView::LayoutTitleBar() {
   TRACE_EVENT0("views.frame", "GlassBrowserFrameView::LayoutTitleBar");
   if (!ShowCustomIcon() && !ShowCustomTitle())
@@ -955,13 +823,9 @@
   if (ShowCustomIcon()) {
     window_icon_->SetBoundsRect(window_icon_bounds);
     next_leading_x = window_icon_bounds.right() + kIconTitleSpacing;
-  } else if (profile_indicator_icon()) {
-    next_leading_x =
-        profile_indicator_icon()->bounds().right() + kIconTitleSpacing;
   }
 
   if (hosted_app_button_container_) {
-    DCHECK(!GetProfileSwitcherButton());
     next_trailing_x = hosted_app_button_container_->LayoutInContainer(
         next_leading_x, next_trailing_x, window_top, titlebar_visual_height);
   }
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.h b/chrome/browser/ui/views/frame/glass_browser_frame_view.h
index db422273..3a3a2cc2 100644
--- a/chrome/browser/ui/views/frame/glass_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.h
@@ -8,7 +8,6 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/win/scoped_gdi_object.h"
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
 #include "chrome/browser/ui/views/tab_icon_view.h"
@@ -47,8 +46,6 @@
   int GetTabStripLeftInset() const override;
   bool IsSingleTabModeAvailable() const override;
   bool ShouldDrawStrokes() const override;
-  void OnTabRemoved(int index) override;
-  void OnTabsMaxXChanged() override;
 
   // views::NonClientFrameView:
   gfx::Rect GetBoundsForClientView() const override;
@@ -89,9 +86,6 @@
   void OnPaint(gfx::Canvas* canvas) override;
   void Layout() override;
 
-  // BrowserNonClientFrameView:
-  AvatarButtonStyle GetAvatarButtonStyle() const override;
-
  private:
   // views::NonClientFrameView:
   void ActivationChanged(bool active) override;
@@ -161,9 +155,7 @@
                            gfx::Canvas* canvas) const;
 
   // Layout various sub-components of this view.
-  void LayoutIncognitoIcon();
   void LayoutTitleBar();
-  void LayoutProfileSwitcher();
   void LayoutCaptionButtons();
   void LayoutCaptionButton(Windows10CaptionButton* button,
                            int previous_button_x);
@@ -183,9 +175,6 @@
   // Displays the next throbber frame.
   void DisplayNextThrobberFrame();
 
-  // The layout rect of the incognito icon, if visible.
-  gfx::Rect incognito_bounds_;
-
   // The bounds of the ClientView.
   gfx::Rect client_view_bounds_;
 
diff --git a/chrome/browser/ui/views/frame/minimize_button_metrics_win.cc b/chrome/browser/ui/views/frame/minimize_button_metrics_win.cc
index 55e0086..ad8eb45 100644
--- a/chrome/browser/ui/views/frame/minimize_button_metrics_win.cc
+++ b/chrome/browser/ui/views/frame/minimize_button_metrics_win.cc
@@ -9,8 +9,6 @@
 #include "base/win/windows_version.h"
 #include "dwmapi.h"
 #include "ui/base/win/shell.h"
-#include "ui/display/display.h"
-#include "ui/display/win/dpi.h"
 #include "ui/display/win/screen_win.h"
 #include "ui/gfx/geometry/point.h"
 
@@ -53,29 +51,6 @@
 MinimizeButtonMetrics::~MinimizeButtonMetrics() {
 }
 
-// static
-int MinimizeButtonMetrics::GetCaptionButtonHeightInDIPs() {
-  // At DPI scaling settings other than 100% the result won't be exactly right.
-  // TODO: return a more accurate approximation [http://crbug.com/716365]
-
-  // SM_CYSIZE returns the caption button height, but to get the full height
-  // from the top of the window we add SM_CYSIZEFRAME.
-  const int caption_height = GetSystemMetrics(SM_CYSIZE);
-  const int frame_thickness = GetSystemMetrics(SM_CYSIZEFRAME);
-
-  // The result of GetSystemMetrics depends on the scale factor of the primary
-  // display. Divide the sum by that to convert to DIPs. (Converting SM_CYSIZE
-  // and SM_CYSIZEFRAME to DIPs individually adds a bigger rounding error.)
-  float primary_device_scale_factor =
-      display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
-  float height_dips =
-      (caption_height + frame_thickness) / primary_device_scale_factor;
-
-  // Testing shows that floor() gives a more accurate approximation than
-  // round() here.
-  return std::floor(height_dips);
-}
-
 void MinimizeButtonMetrics::Init(HWND hwnd) {
   DCHECK(!hwnd_);
   hwnd_ = hwnd;
diff --git a/chrome/browser/ui/views/frame/minimize_button_metrics_win.h b/chrome/browser/ui/views/frame/minimize_button_metrics_win.h
index 305784f..cfc7823 100644
--- a/chrome/browser/ui/views/frame/minimize_button_metrics_win.h
+++ b/chrome/browser/ui/views/frame/minimize_button_metrics_win.h
@@ -18,9 +18,6 @@
   MinimizeButtonMetrics();
   ~MinimizeButtonMetrics();
 
-  // Returns the height of the native caption buttons in DIPs.
-  static int GetCaptionButtonHeightInDIPs();
-
   void Init(HWND hwnd);
 
   // Obtain the X offset of the native minimize button. Since Windows can lie
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 3c9ee52e..1edd5cd 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tab_icon_view.h"
 #include "chrome/browser/ui/views/tabs/new_tab_button.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -257,29 +256,10 @@
   return layout_->GetWindowBoundsForClientBounds(client_bounds);
 }
 
-bool OpaqueBrowserFrameView::IsWithinAvatarMenuButtons(
-    const gfx::Point& point) const {
-  if (profile_indicator_icon() &&
-      profile_indicator_icon()->GetMirroredBounds().Contains(point)) {
-    return true;
-  }
-  views::View* profile_switcher_view = GetProfileSwitcherButton();
-  if (profile_switcher_view &&
-      profile_switcher_view->GetMirroredBounds().Contains(point)) {
-    return true;
-  }
-
-  return false;
-}
-
 int OpaqueBrowserFrameView::NonClientHitTest(const gfx::Point& point) {
   if (!bounds().Contains(point))
     return HTNOWHERE;
 
-  // See if the point is within the avatar menu button.
-  if (IsWithinAvatarMenuButtons(point))
-    return HTCLIENT;
-
   int frame_component = frame()->client_view()->NonClientHitTest(point);
 
   // See if we're in the sysmenu region.  We still have to check the tabstrip
@@ -496,10 +476,6 @@
   return browser_view()->IsRegularOrGuestSession();
 }
 
-gfx::ImageSkia OpaqueBrowserFrameView::GetIncognitoAvatarIcon() const {
-  return BrowserNonClientFrameView::GetIncognitoAvatarIcon();
-}
-
 bool OpaqueBrowserFrameView::IsMaximized() const {
   return frame()->IsMaximized();
 }
@@ -529,10 +505,6 @@
   return browser_view()->tabstrip()->GetPreferredSize();
 }
 
-gfx::Size OpaqueBrowserFrameView::GetNewTabButtonPreferredSize() const {
-  return browser_view()->tabstrip()->new_tab_button()->GetPreferredSize();
-}
-
 int OpaqueBrowserFrameView::GetTopAreaHeight() const {
   const int non_client_top_height = layout_->NonClientTopHeight(false);
   if (!browser_view()->IsTabStripVisible())
@@ -602,10 +574,6 @@
          platform_observer_->IsUsingSystemTheme();
 }
 
-AvatarButtonStyle OpaqueBrowserFrameView::GetAvatarButtonStyle() const {
-  return AvatarButtonStyle::THEMED;
-}
-
 void OpaqueBrowserFrameView::MaybeRedrawFrameButtons() {}
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
index 5176ba3d..17adead2 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
@@ -9,7 +9,6 @@
 
 #include "base/macros.h"
 #include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h"
@@ -99,14 +98,12 @@
   gfx::Size GetBrowserViewMinimumSize() const override;
   bool ShouldShowCaptionButtons() const override;
   bool IsRegularOrGuestSession() const override;
-  gfx::ImageSkia GetIncognitoAvatarIcon() const override;
   bool IsMaximized() const override;
   bool IsMinimized() const override;
   bool IsTabStripVisible() const override;
   int GetTabStripHeight() const override;
   bool IsToolbarVisible() const override;
   gfx::Size GetTabstripPreferredSize() const override;
-  gfx::Size GetNewTabButtonPreferredSize() const override;
   int GetTopAreaHeight() const override;
   bool UseCustomFrame() const override;
   bool IsFrameCondensed() const override;
@@ -127,7 +124,6 @@
 
   // BrowserNonClientFrameView:
   bool ShouldPaintAsThemed() const override;
-  AvatarButtonStyle GetAvatarButtonStyle() const override;
 
   OpaqueBrowserFrameViewLayout* layout() { return layout_; }
 
@@ -176,9 +172,6 @@
   // window is restored regardless of the actual mode.
   int FrameTopBorderThickness(bool restored) const;
 
-  // Returns true if the specified point is within the avatar menu buttons.
-  bool IsWithinAvatarMenuButtons(const gfx::Point& point) const;
-
   // Returns the thickness of the entire nonclient left, right, and bottom
   // borders, including both the window frame and any client edge.
   int NonClientBorderThickness() const;
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
index 551d79f..0021cb17 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
@@ -8,11 +8,8 @@
 #include "base/containers/adapters.h"
 #include "base/stl_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/common/chrome_switches.h"
-#include "components/signin/core/browser/profile_management_switches.h"
 #include "ui/base/material_design/material_design_controller.h"
 #include "ui/gfx/font.h"
 #include "ui/views/controls/button/image_button.h"
@@ -68,8 +65,7 @@
 const int OpaqueBrowserFrameViewLayout::kNewTabCaptionCondensedSpacing = 16;
 
 OpaqueBrowserFrameViewLayout::OpaqueBrowserFrameViewLayout()
-    : new_avatar_button_(nullptr),
-      available_space_leading_x_(0),
+    : available_space_leading_x_(0),
       available_space_trailing_x_(0),
       minimum_size_for_buttons_(0),
       has_leading_buttons_(false),
@@ -82,7 +78,6 @@
       close_button_(nullptr),
       window_icon_(nullptr),
       window_title_(nullptr),
-      incognito_icon_(nullptr),
       trailing_buttons_{views::FRAME_BUTTON_MINIMIZE,
                         views::FRAME_BUTTON_MAXIMIZE,
                         views::FRAME_BUTTON_CLOSE} {}
@@ -113,7 +108,7 @@
   min_size.Enlarge(2 * border_thickness,
                    NonClientTopHeight(false) + border_thickness);
 
-  // Ensure that we can, at minimum, hold our window controls and avatar icon.
+  // Ensure that we can, at minimum, hold our window controls.
   min_size.set_width(std::max(min_size.width(), minimum_size_for_buttons_));
 
   // Ensure that the minimum width is enough to hold a minimum width tab strip
@@ -296,32 +291,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameViewLayout, protected:
 
-void OpaqueBrowserFrameViewLayout::LayoutNewStyleAvatar(views::View* host) {
-  if (!new_avatar_button_)
-    return;
-
-  const int button_width = new_avatar_button_->GetPreferredSize().width();
-  int button_width_with_offset = button_width;
-  if (!trailing_buttons_.empty())
-    button_width_with_offset += kCaptionSpacing;
-
-  const int button_x = available_space_trailing_x_ - button_width_with_offset;
-  const int button_y = DefaultCaptionButtonY(!delegate_->IsFrameCondensed());
-
-  minimum_size_for_buttons_ += button_width_with_offset;
-  available_space_trailing_x_ -= button_width_with_offset;
-
-  // In non-maximized mode, allow the new tab button to completely slide under
-  // the avatar button.
-  if (!delegate_->IsFrameCondensed()) {
-    available_space_trailing_x_ +=
-        delegate_->GetNewTabButtonPreferredSize().width() + kCaptionSpacing;
-  }
-
-  new_avatar_button_->SetBounds(button_x, button_y, button_width,
-                                kCaptionButtonHeight);
-}
-
 bool OpaqueBrowserFrameViewLayout::ShouldDrawImageMirrored(
     views::ImageButton* button,
     ButtonAlignment alignment) const {
@@ -332,12 +301,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameViewLayout, private:
 
-bool OpaqueBrowserFrameViewLayout::ShouldIncognitoIconBeOnRight() const {
-  // The incognito should be shown either on the end of the left or the
-  // beginning of the right, depending on which side has fewer buttons.
-  return trailing_buttons_.size() < leading_buttons_.size();
-}
-
 int OpaqueBrowserFrameViewLayout::TabStripCaptionSpacing() const {
   // In Refresh, any necessary padding after the tabstrip is contained within
   // the tabs and/or new tab button.
@@ -446,42 +409,6 @@
   }
 }
 
-void OpaqueBrowserFrameViewLayout::LayoutIncognitoIcon(views::View* host) {
-  const int old_button_size =
-      available_space_leading_x_ + host->width() - available_space_trailing_x_;
-
-  // Any buttons/icon/title were laid out based on the frame border thickness,
-  // but the tabstrip bounds need to be based on the non-client border thickness
-  // on any side where there aren't other buttons forcing a larger inset.
-  int min_button_width = NonClientBorderThickness();
-  available_space_leading_x_ =
-      std::max(available_space_leading_x_, min_button_width);
-  // The trailing corner is a mirror of the leading one.
-  available_space_trailing_x_ =
-      std::min(available_space_trailing_x_, host->width() - min_button_width);
-
-  if (incognito_icon_) {
-    const int pad = OpaqueBrowserFrameView::GetAvatarIconPadding();
-    const gfx::Size size(delegate_->GetIncognitoAvatarIcon().size());
-    const int incognito_width = pad + size.width();
-    int x;
-    if (ShouldIncognitoIconBeOnRight()) {
-      available_space_trailing_x_ -= incognito_width;
-      x = available_space_trailing_x_;
-    } else {
-      x = available_space_leading_x_ + pad;
-      available_space_leading_x_ += incognito_width;
-    }
-    const int bottom =
-        GetTabStripInsetsTop(false) + delegate_->GetTabStripHeight() - pad;
-    incognito_icon_->SetBounds(x, bottom - size.height(), size.width(),
-                               size.height());
-  }
-
-  minimum_size_for_buttons_ += (available_space_leading_x_ + host->width() -
-                                available_space_trailing_x_ - old_button_size);
-}
-
 void OpaqueBrowserFrameViewLayout::ConfigureButton(views::View* host,
                                                    views::FrameButton button_id,
                                                    ButtonAlignment alignment) {
@@ -614,8 +541,7 @@
 void OpaqueBrowserFrameViewLayout::SetView(int id, views::View* view) {
   // Why do things this way instead of having an Init() method, where we're
   // passed the views we'll handle? Because OpaqueBrowserFrameView doesn't own
-  // all the views which are part of it. The avatar stuff, for example, will be
-  // added and removed by the base class of OpaqueBrowserFrameView.
+  // all the views which are part of it.
   switch (id) {
     case VIEW_ID_MINIMIZE_BUTTON:
       if (view) {
@@ -655,12 +581,6 @@
       }
       window_title_ = static_cast<views::Label*>(view);
       break;
-    case VIEW_ID_PROFILE_INDICATOR_ICON:
-      incognito_icon_ = view;
-      break;
-    case VIEW_ID_AVATAR_BUTTON:
-      new_avatar_button_ = view;
-      break;
     case VIEW_ID_HOSTED_APP_BUTTON_CONTAINER:
       DCHECK_EQ(view->GetClassName(), HostedAppButtonContainer::kViewClassName);
       hosted_app_button_container_ =
@@ -689,9 +609,19 @@
   LayoutWindowControls(host);
   LayoutTitleBar(host);
 
-  if (delegate_->IsRegularOrGuestSession())
-    LayoutNewStyleAvatar(host);
-  LayoutIncognitoIcon(host);
+  // Any buttons/icon/title were laid out based on the frame border thickness,
+  // but the tabstrip bounds need to be based on the non-client border thickness
+  // on any side where there aren't other buttons forcing a larger inset.
+  const int old_button_size =
+      available_space_leading_x_ + host->width() - available_space_trailing_x_;
+  const int min_button_width = NonClientBorderThickness();
+  available_space_leading_x_ =
+      std::max(available_space_leading_x_, min_button_width);
+  // The trailing corner is a mirror of the leading one.
+  available_space_trailing_x_ =
+      std::min(available_space_trailing_x_, host->width() - min_button_width);
+  minimum_size_for_buttons_ += (available_space_leading_x_ + host->width() -
+                                available_space_trailing_x_ - old_button_size);
 
   client_view_bounds_ = CalculateClientAreaBounds(
       host->width(), host->height());
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
index c831e59..255f82d 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
@@ -85,9 +85,8 @@
   int GetTabStripInsetsTop(bool restored) const;
 
   // Returns the y-coordinate of the caption button when native frame buttons
-  // are disabled.  Also used to position the profile chooser button.  If
-  // |restored| is true, acts as if the window is restored regardless of the
-  // real mode.
+  // are disabled.  If |restored| is true, acts as if the window is restored
+  // regardless of the real mode.
   int DefaultCaptionButtonY(bool restored) const;
 
   // Returns the y-coordinate of button |button_id|.  If |restored| is true,
@@ -150,30 +149,21 @@
 
   bool has_trailing_buttons() const { return has_trailing_buttons_; }
 
-  virtual void LayoutNewStyleAvatar(views::View* host);
-
   virtual bool ShouldDrawImageMirrored(views::ImageButton* button,
                                        ButtonAlignment alignment) const;
 
   OpaqueBrowserFrameViewLayoutDelegate* delegate_;
 
-  views::View* new_avatar_button_;
-
   // The leading and trailing x positions of the empty space available for
   // laying out titlebar elements.
   int available_space_leading_x_;
   int available_space_trailing_x_;
 
-  // The size of the window buttons, and the avatar menu item (if any). This
-  // does not count labels or other elements that should be counted in a
-  // minimal frame.
+  // The size of the window buttons. This does not count labels or other
+  // elements that should be counted in a minimal frame.
   int minimum_size_for_buttons_;
 
  private:
-  // Determines whether the incognito icon should be shown on the right side of
-  // the tab strip (instead of the usual left).
-  bool ShouldIncognitoIconBeOnRight() const;
-
   // Determines the amount of spacing between the tabstrip and the caption
   // buttons.
   int TabStripCaptionSpacing() const;
@@ -181,7 +171,6 @@
   // Layout various sub-components of this view.
   void LayoutWindowControls(views::View* host);
   void LayoutTitleBar(views::View* host);
-  void LayoutIncognitoIcon(views::View* host);
 
   void ConfigureButton(views::View* host,
                        views::FrameButton button_id,
@@ -235,8 +224,6 @@
 
   HostedAppButtonContainer* hosted_app_button_container_ = nullptr;
 
-  views::View* incognito_icon_;
-
   std::vector<views::FrameButton> leading_buttons_;
   std::vector<views::FrameButton> trailing_buttons_;
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
index 1f8bb467..178bb9e 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
@@ -6,7 +6,6 @@
 #define CHROME_BROWSER_UI_VIEWS_FRAME_OPAQUE_BROWSER_FRAME_VIEW_LAYOUT_DELEGATE_H_
 
 namespace gfx {
-class ImageSkia;
 class Size;
 }
 
@@ -35,10 +34,6 @@
   // Returns true if in guest mode or a non off the record session.
   virtual bool IsRegularOrGuestSession() const = 0;
 
-  // We don't have a ThemeProvider in the layout manager, so plumb in the icon
-  // source here.
-  virtual gfx::ImageSkia GetIncognitoAvatarIcon() const = 0;
-
   // Controls window state.
   virtual bool IsMaximized() const = 0;
   virtual bool IsMinimized() const = 0;
@@ -51,9 +46,6 @@
   // it.
   virtual gfx::Size GetTabstripPreferredSize() const = 0;
 
-  // Returns the New Tab Button's preferred size.
-  virtual gfx::Size GetNewTabButtonPreferredSize() const = 0;
-
   // Computes the height of the top area of the frame.
   virtual int GetTopAreaHeight() const = 0;
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
index ae3a293..423367e 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
@@ -8,11 +8,9 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/layout_constants.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tab_icon_view.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/views/chrome_views_test_base.h"
-#include "components/signin/core/browser/profile_management_switches.h"
 #include "ui/base/material_design/material_design_controller.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_rep.h"
@@ -65,9 +63,6 @@
     return show_caption_buttons_;
   }
   bool IsRegularOrGuestSession() const override { return true; }
-  gfx::ImageSkia GetIncognitoAvatarIcon() const override {
-    return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(40, 29), 1.0f));
-  }
   bool IsMaximized() const override { return maximized_; }
   bool IsMinimized() const override { return false; }
   bool IsTabStripVisible() const override { return window_title_.empty(); }
@@ -78,9 +73,6 @@
   gfx::Size GetTabstripPreferredSize() const override {
     return IsTabStripVisible() ? gfx::Size(78, 29) : gfx::Size();
   }
-  gfx::Size GetNewTabButtonPreferredSize() const override {
-    return gfx::Size(28, 28);
-  }
   int GetTopAreaHeight() const override { return 0; }
   bool UseCustomFrame() const override { return true; }
   bool IsFrameCondensed() const override {
@@ -171,12 +163,6 @@
     root_view_->AddChildView(window_title_);
   }
 
-  void AddNewAvatarButton() {
-    avatar_button_ = new views::MenuButton(base::string16(), nullptr, false);
-    avatar_button_->set_id(VIEW_ID_AVATAR_BUTTON);
-    root_view_->AddChildView(avatar_button_);
-  }
-
   int CaptionY() const {
     return delegate_->IsMaximized() ?
         0 : views::NonClientFrameView::kFrameShadowThickness;
@@ -247,21 +233,14 @@
   }
 
   void ExpectTabStripAndMinimumSize(bool caption_buttons_on_left) {
-    int caption_buttons_width = kCaptionButtonsWidth;
     bool show_caption_buttons = delegate_->ShouldShowCaptionButtons();
     bool maximized = delegate_->IsMaximized() || !show_caption_buttons;
-    if (avatar_button_) {
-      caption_buttons_width +=
-          avatar_button_->GetPreferredSize().width() +
-          (maximized ? OpaqueBrowserFrameViewLayout::kCaptionSpacing
-                     : -delegate_->GetNewTabButtonPreferredSize().width());
-    }
     int tabstrip_x = OpaqueBrowserFrameView::GetTabstripPadding();
     if (show_caption_buttons && caption_buttons_on_left) {
       int right_of_close =
           maximized ? kMaximizedExtraCloseWidth
                     : OpaqueBrowserFrameViewLayout::kFrameBorderThickness;
-      tabstrip_x += caption_buttons_width + right_of_close;
+      tabstrip_x += kCaptionButtonsWidth + right_of_close;
     } else if (!maximized) {
       tabstrip_x += NonClientBorderThickness();
     }
@@ -284,7 +263,7 @@
     const bool showing_caption_buttons_on_right =
         show_caption_buttons && !caption_buttons_on_left;
     const int caption_width =
-        showing_caption_buttons_on_right ? caption_buttons_width : 0;
+        showing_caption_buttons_on_right ? kCaptionButtonsWidth : 0;
     int maximized_spacing =
         showing_caption_buttons_on_right ? kMaximizedExtraCloseWidth : 0;
     int restored_spacing =
@@ -365,14 +344,6 @@
     EXPECT_EQ(icon_size, title_bounds.height());
   }
 
-  void ExpectAvatar() {
-    int avatar_width = avatar_button_->GetPreferredSize().width();
-    gfx::Rect avatar_bounds(avatar_button_->bounds());
-    EXPECT_EQ(CaptionLeft() - avatar_width, avatar_bounds.x());
-    EXPECT_EQ(CaptionY(), avatar_bounds.y());
-    EXPECT_EQ(avatar_width, avatar_bounds.width());
-    EXPECT_EQ(kCaptionButtonHeight, avatar_bounds.height());
-  }
 
   views::Widget* widget_ = nullptr;
   views::View* root_view_ = nullptr;
@@ -388,14 +359,12 @@
   TabIconView* tab_icon_view_ = nullptr;
   views::Label* window_title_ = nullptr;
 
-  views::MenuButton* avatar_button_ = nullptr;
-
   DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayoutTest);
 };
 
 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindow) {
-  // Tests the layout of a default chrome window with no avatars, no window
-  // titles, and a tabstrip.
+  // Tests the layout of a default chrome window with a tabstrip and no window
+  // title.
 
   for (int i = 0; i < 2; ++i) {
     root_view_->Layout();
@@ -467,17 +436,3 @@
     delegate_->set_maximized(true);
   }
 }
-
-TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithNewAvatar) {
-  // Tests a normal tabstrip window with the new style avatar icon.
-  AddNewAvatarButton();
-
-  for (int i = 0; i < 2; ++i) {
-    root_view_->Layout();
-    SCOPED_TRACE(i == 0 ? "Window is restored" : "Window is maximized");
-    ExpectCaptionButtons(false, 0);
-    ExpectTabStripAndMinimumSize(false);
-    ExpectAvatar();
-    delegate_->set_maximized(true);
-  }
-}
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
index 9792013..1857798 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
@@ -22,7 +22,13 @@
       text_input_flags_(text_input_flags),
       caret_bounds_(caret_bounds) {}
 
-RemoteTextInputClient::~RemoteTextInputClient() {}
+RemoteTextInputClient::~RemoteTextInputClient() {
+  while (!pending_callbacks_.empty()) {
+    auto callback = std::move(pending_callbacks_.front());
+    pending_callbacks_.pop();
+    std::move(callback).Run(false);
+  }
+}
 
 void RemoteTextInputClient::SetTextInputType(
     ui::TextInputType text_input_type) {
@@ -33,6 +39,15 @@
   caret_bounds_ = caret_bounds;
 }
 
+void RemoteTextInputClient::OnDispatchKeyEventPostIMECompleted(bool completed) {
+  DCHECK(!pending_callbacks_.empty());
+  base::OnceCallback<void(bool)> callback =
+      std::move(pending_callbacks_.front());
+  pending_callbacks_.pop();
+  if (callback)
+    std::move(callback).Run(completed);
+}
+
 void RemoteTextInputClient::SetCompositionText(
     const ui::CompositionText& composition) {
   remote_client_->SetCompositionText(composition);
@@ -189,8 +204,10 @@
 ui::EventDispatchDetails RemoteTextInputClient::DispatchKeyEventPostIME(
     ui::KeyEvent* event,
     base::OnceCallback<void(bool)> ack_callback) {
+  pending_callbacks_.push(std::move(ack_callback));
   remote_client_->DispatchKeyEventPostIME(
       ui::Event::Clone(*event),
-      ack_callback ? std::move(ack_callback) : base::DoNothing());
+      base::BindOnce(&RemoteTextInputClient::OnDispatchKeyEventPostIMECompleted,
+                     weak_ptr_factory_.GetWeakPtr()));
   return ui::EventDispatchDetails();
 }
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
index 837d683..579ade1 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_IME_DRIVER_REMOTE_TEXT_INPUT_CLIENT_H_
 #define CHROME_BROWSER_UI_VIEWS_IME_DRIVER_REMOTE_TEXT_INPUT_CLIENT_H_
 
+#include "base/containers/queue.h"
+#include "base/memory/weak_ptr.h"
 #include "services/ws/public/mojom/ime/ime.mojom.h"
 #include "ui/base/ime/input_method_delegate.h"
 #include "ui/base/ime/text_input_client.h"
@@ -27,6 +29,9 @@
   void SetCaretBounds(const gfx::Rect& caret_bounds);
 
  private:
+  // See |pending_callbacks_| for details.
+  void OnDispatchKeyEventPostIMECompleted(bool completed);
+
   // ui::TextInputClient:
   void SetCompositionText(const ui::CompositionText& composition) override;
   void ConfirmCompositionText() override;
@@ -71,8 +76,16 @@
   base::i18n::TextDirection text_direction_;
   int text_input_flags_;
   gfx::Rect caret_bounds_;
-  std::deque<std::unique_ptr<base::OnceCallback<void(bool)>>>
-      pending_callbacks_;
+
+  // Callbacks supplied to DispatchKeyEventPostIME() are added here. When the
+  // response from the remote side is received
+  // (OnDispatchKeyEventPostIMECompleted()), the callback is removed and run.
+  // This is done to ensure if we are destroyed all the callbacks are run.
+  // This is necessary as the callbacks may have originated from a remote
+  // client.
+  base::queue<base::OnceCallback<void(bool)>> pending_callbacks_;
+
+  base::WeakPtrFactory<RemoteTextInputClient> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(RemoteTextInputClient);
 };
diff --git a/chrome/browser/ui/views/nav_button_provider.h b/chrome/browser/ui/views/nav_button_provider.h
index 6cbe9324..50f3f2b 100644
--- a/chrome/browser/ui/views/nav_button_provider.h
+++ b/chrome/browser/ui/views/nav_button_provider.h
@@ -53,17 +53,6 @@
 
   // Gets the spacing to be used to separate buttons.
   virtual int GetInterNavButtonSpacing() const = 0;
-
-  // Creates a background for the profile chooser button.
-  virtual std::unique_ptr<Background> CreateAvatarButtonBackground(
-      const views::Button* button) const = 0;
-
-  // Calculates the profile chooser button's size and spacing.
-  virtual void CalculateCaptionButtonLayout(
-      const gfx::Size& content_size,
-      int top_area_height,
-      gfx::Size* caption_button_size,
-      gfx::Insets* caption_button_spacing) const = 0;
 };
 
 }  // namespace views
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 630c326..8e1f29e 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -34,6 +34,7 @@
 #include "components/omnibox/browser/omnibox_popup_model.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/toolbar/toolbar_field_trial.h"
 #include "components/toolbar/toolbar_model.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/escape.h"
@@ -631,8 +632,11 @@
 }
 
 bool OmniboxViewViews::UnapplySteadyStateElisions(UnelisionGesture gesture) {
-  if (!OmniboxFieldTrial::IsHideSteadyStateUrlSchemeAndSubdomainsEnabled())
+  // Early exit if no steady state elision features are enabled.
+  if (!toolbar::features::IsHideSteadyStateUrlSchemeEnabled() &&
+      !toolbar::features::IsHideSteadyStateUrlTrivialSubdomainsEnabled()) {
     return false;
+  }
 
   // No need to update the text if the user is already inputting text.
   if (model()->user_input_in_progress())
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
index c6d8c32a..edbf2f0c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
@@ -25,6 +25,7 @@
 #include "components/omnibox/browser/omnibox_edit_model.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/toolbar/test_toolbar_model.h"
+#include "components/toolbar/toolbar_field_trial.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ime/input_method.h"
@@ -496,8 +497,10 @@
 class OmniboxViewViewsSteadyStateElisionsTest : public OmniboxViewViewsTest {
  public:
   OmniboxViewViewsSteadyStateElisionsTest()
-      : OmniboxViewViewsTest(
-            {omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains}) {}
+      : OmniboxViewViewsTest({
+            toolbar::features::kHideSteadyStateUrlScheme,
+            toolbar::features::kHideSteadyStateUrlTrivialSubdomains,
+        }) {}
 
  protected:
   explicit OmniboxViewViewsSteadyStateElisionsTest(
@@ -897,7 +900,8 @@
  public:
   OmniboxViewViewsSteadyStateElisionsAndQueryInOmniboxTest()
       : OmniboxViewViewsSteadyStateElisionsTest({
-            omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains,
+            toolbar::features::kHideSteadyStateUrlScheme,
+            toolbar::features::kHideSteadyStateUrlTrivialSubdomains,
             omnibox::kQueryInOmnibox,
         }) {}
 };
diff --git a/chrome/browser/ui/views/profiles/avatar_button.cc b/chrome/browser/ui/views/profiles/avatar_button.cc
deleted file mode 100644
index e1a0431..0000000
--- a/chrome/browser/ui/views/profiles/avatar_button.cc
+++ /dev/null
@@ -1,557 +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 "chrome/browser/ui/views/profiles/avatar_button.h"
-
-#include <memory>
-#include <utility>
-
-#include "build/build_config.h"
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/signin/account_consistency_mode_manager.h"
-#include "chrome/browser/signin/signin_manager_factory.h"
-#include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/themes/theme_service.h"
-#include "chrome/browser/themes/theme_service_factory.h"
-#include "chrome/browser/ui/views/frame/avatar_button_manager.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/profiles/profile_chooser_view.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/grit/theme_resources.h"
-#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
-#include "components/signin/core/browser/signin_manager.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "ui/base/theme_provider.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/color_utils.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/animation/ink_drop_mask.h"
-#include "ui/views/controls/button/label_button_border.h"
-
-#if defined(OS_WIN)
-#include "base/win/windows_version.h"
-#include "chrome/browser/ui/views/frame/minimize_button_metrics_win.h"
-#endif
-
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-#include "chrome/browser/ui/views/nav_button_provider.h"
-#endif
-
-namespace {
-
-constexpr int kGenericAvatarIconSize = 16;
-
-// TODO(emx): Calculate width based on caption button [http://crbug.com/716365]
-constexpr int kCondensibleButtonMinWidth = 46;
-// TODO(emx): Should this be calculated based on average character width?
-constexpr int kCondensibleButtonMaxWidth = 98;
-
-#if defined(OS_WIN)
-constexpr gfx::Insets kBorderInsets(2, 8, 4, 8);
-
-std::unique_ptr<views::Border> CreateThemedBorder(
-    const int normal_image_set[],
-    const int hot_image_set[],
-    const int pushed_image_set[]) {
-  std::unique_ptr<views::LabelButtonAssetBorder> border(
-      new views::LabelButtonAssetBorder(views::Button::STYLE_TEXTBUTTON));
-
-  border->SetPainter(false, views::Button::STATE_NORMAL,
-                     views::Painter::CreateImageGridPainter(normal_image_set));
-  border->SetPainter(false, views::Button::STATE_HOVERED,
-                     views::Painter::CreateImageGridPainter(hot_image_set));
-  border->SetPainter(false, views::Button::STATE_PRESSED,
-                     views::Painter::CreateImageGridPainter(pushed_image_set));
-
-  border->set_insets(kBorderInsets);
-
-  return std::move(border);
-}
-#endif
-
-#if defined(OS_MACOSX)
-constexpr int kMacButtonHeight = 24;
-#endif
-
-// This class draws the border (and background) of the avatar button for
-// "themed" browser windows, i.e. OpaqueBrowserFrameView. Currently it's only
-// used on Linux as the shape specifically matches the Linux caption buttons.
-// TODO(estade): make this look nice on Windows and use it there as well.
-class AvatarButtonThemedBorder : public views::Border {
- public:
-  AvatarButtonThemedBorder() {}
-  ~AvatarButtonThemedBorder() override {}
-
-  void Paint(const views::View& view, gfx::Canvas* canvas) override {
-    // Fill the color/background image from the theme.
-    cc::PaintFlags fill_flags;
-    fill_flags.setAntiAlias(true);
-    const ui::ThemeProvider* theme = view.GetThemeProvider();
-    fill_flags.setColor(
-        theme->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND));
-    SkPath fill_path;
-    gfx::Rect fill_bounds = view.GetLocalBounds();
-    // The fill should overlap the inner stroke but not the outer stroke. But we
-    // don't inset the top because as it stands, the asset-based window controls
-    // fill one pixel higher due to how the background masking works out. Not
-    // matching that is very noticeable. TODO(estade): when the window
-    // controls use this same code, inset all sides equally.
-    fill_bounds.Inset(gfx::Insets(0, kStrokeWidth, kStrokeWidth, kStrokeWidth));
-    fill_path.addRoundRect(gfx::RectToSkRect(fill_bounds), kCornerRadius,
-                           kCornerRadius);
-    canvas->DrawPath(fill_path, fill_flags);
-    fill_flags.setColor(SK_ColorBLACK);
-    canvas->DrawImageInPath(
-        *theme->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND), 0, 0,
-        fill_path, fill_flags);
-
-    // Paint an outer dark stroke.
-    cc::PaintFlags stroke_flags;
-    stroke_flags.setStyle(cc::PaintFlags::kStroke_Style);
-    // The colors are chosen to match the assets we use for Linux.
-    stroke_flags.setColor(SkColorSetA(SK_ColorBLACK, 0x2B));
-    stroke_flags.setStrokeWidth(kStrokeWidth);
-    stroke_flags.setAntiAlias(true);
-    gfx::RectF stroke_bounds(view.GetLocalBounds());
-    stroke_bounds.Inset(gfx::InsetsF(0.5f));
-    canvas->DrawRoundRect(stroke_bounds, kCornerRadius, stroke_flags);
-
-    // There's a second, light stroke that matches the fill bounds.
-    stroke_bounds.Inset(gfx::InsetsF(kStrokeWidth));
-    stroke_flags.setColor(SkColorSetA(SK_ColorWHITE, 0x3F));
-    canvas->DrawRoundRect(stroke_bounds, kCornerRadius, stroke_flags);
-  }
-
-  gfx::Insets GetInsets() const override {
-    auto insets = views::LabelButtonAssetBorder::GetDefaultInsetsForStyle(
-        views::Button::STYLE_TEXTBUTTON);
-    return kBorderStrokeInsets +
-           gfx::Insets(0, insets.left(), 0, insets.right());
-  }
-
-  gfx::Size GetMinimumSize() const override {
-    return gfx::Size(GetInsets().width(), GetInsets().height());
-  }
-
-  static std::unique_ptr<views::InkDropMask> CreateInkDropMask(
-      const gfx::Size& size) {
-    return std::make_unique<views::RoundRectInkDropMask>(
-        size, kBorderStrokeInsets, kCornerRadius);
-  }
-
- private:
-  static constexpr int kStrokeWidth = 1;
-
-  // Insets between view bounds and the interior of the strokes.
-  static constexpr gfx::Insets kBorderStrokeInsets{kStrokeWidth * 2};
-
-  // Corner radius of the roundrect.
-  static constexpr float kCornerRadius = 1;
-
-  DISALLOW_COPY_AND_ASSIGN(AvatarButtonThemedBorder);
-};
-
-constexpr int AvatarButtonThemedBorder::kStrokeWidth;
-constexpr gfx::Insets AvatarButtonThemedBorder::kBorderStrokeInsets;
-constexpr float AvatarButtonThemedBorder::kCornerRadius;
-
-class AvatarButtonShutdownNotifierFactory
-    : public BrowserContextKeyedServiceShutdownNotifierFactory {
- public:
-  static AvatarButtonShutdownNotifierFactory* GetInstance() {
-    return base::Singleton<AvatarButtonShutdownNotifierFactory>::get();
-  }
-
- private:
-  friend struct base::DefaultSingletonTraits<
-      AvatarButtonShutdownNotifierFactory>;
-
-  AvatarButtonShutdownNotifierFactory()
-      : BrowserContextKeyedServiceShutdownNotifierFactory(
-            "AvatarButtonShutdownNotifierFactory") {
-    DependsOn(SigninManagerFactory::GetInstance());
-  }
-  ~AvatarButtonShutdownNotifierFactory() override {}
-
-  DISALLOW_COPY_AND_ASSIGN(AvatarButtonShutdownNotifierFactory);
-};
-
-#if defined(OS_WIN) || defined(OS_MACOSX)
-SkColor BaseColorForButton(const ui::ThemeProvider* theme_provider) {
-  return color_utils::IsDark(
-             theme_provider->GetColor(ThemeProperties::COLOR_FRAME))
-             ? SK_ColorWHITE
-             : SK_ColorBLACK;
-}
-
-gfx::ImageSkia AvatarIconWithBaseColor(const SkColor base_color) {
-  const SkColor icon_color =
-      SkColorSetA(base_color, static_cast<SkAlpha>(0.54 * 0xFF));
-  return gfx::CreateVectorIcon(kAccountCircleIcon, kGenericAvatarIconSize,
-                               icon_color);
-}
-#endif
-
-}  // namespace
-
-AvatarButton::AvatarButton(views::MenuButtonListener* listener,
-                           AvatarButtonStyle button_style,
-                           Profile* profile,
-                           AvatarButtonManager* manager)
-    : MenuButton(base::string16(), listener, false),
-      error_controller_(this, profile),
-      profile_(profile),
-      profile_observer_(this),
-      button_style_(button_style),
-      widget_observer_(this) {
-  DCHECK_NE(button_style, AvatarButtonStyle::NONE);
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-  views::NavButtonProvider* nav_button_provider =
-      manager->get_nav_button_provider();
-  render_native_nav_buttons_ = nav_button_provider != nullptr;
-#endif
-  set_notify_action(Button::NOTIFY_ON_PRESS);
-  set_triggerable_event_flags(ui::EF_LEFT_MOUSE_BUTTON |
-                              ui::EF_RIGHT_MOUSE_BUTTON);
-  set_animate_on_state_change(false);
-#if !defined(OS_MACOSX)
-  SetEnabledTextColors(SK_ColorWHITE);
-  SetTextSubpixelRenderingEnabled(false);
-#endif
-  SetHorizontalAlignment(gfx::ALIGN_CENTER);
-
-  profile_observer_.Add(
-      &g_browser_process->profile_manager()->GetProfileAttributesStorage());
-
-  // The largest text height that fits in the button. If the font list height
-  // is larger than this, it will be shrunk to match it.
-  // TODO(noms): Calculate this constant algorithmically from the button's size.
-  const int kDisplayFontHeight = 16;
-  label()->SetFontList(
-      label()->font_list().DeriveWithHeightUpperBound(kDisplayFontHeight));
-
-  bool apply_ink_drop = ShouldApplyInkDrop();
-  if (render_native_nav_buttons_) {
-#if BUILDFLAG(ENABLE_NATIVE_WINDOW_NAV_BUTTONS)
-    SetBackground(nav_button_provider->CreateAvatarButtonBackground(this));
-    SetBorder(nullptr);
-    generic_avatar_ =
-        gfx::CreateVectorIcon(kProfileSwitcherOutlineIcon,
-                              kGenericAvatarIconSize, gfx::kChromeIconGrey);
-#endif
-  } else if (apply_ink_drop) {
-    SetInkDropMode(InkDropMode::ON);
-    SetFocusPainter(nullptr);
-#if defined(OS_LINUX)
-    set_ink_drop_base_color(SK_ColorWHITE);
-    SetBorder(std::make_unique<AvatarButtonThemedBorder>());
-    generic_avatar_ =
-        gfx::CreateVectorIcon(kProfileSwitcherOutlineIcon,
-                              kGenericAvatarIconSize, gfx::kChromeIconGrey);
-#elif defined(OS_WIN)
-    DCHECK_EQ(AvatarButtonStyle::NATIVE, button_style);
-    SetBorder(views::CreateEmptyBorder(kBorderInsets));
-  } else if (button_style == AvatarButtonStyle::THEMED) {
-    const int kNormalImageSet[] = IMAGE_GRID(IDR_AVATAR_THEMED_BUTTON_NORMAL);
-    const int kHoverImageSet[] = IMAGE_GRID(IDR_AVATAR_THEMED_BUTTON_HOVER);
-    const int kPressedImageSet[] = IMAGE_GRID(IDR_AVATAR_THEMED_BUTTON_PRESSED);
-    SetButtonAvatar(IDR_AVATAR_THEMED_BUTTON_AVATAR);
-    SetBorder(
-        CreateThemedBorder(kNormalImageSet, kHoverImageSet, kPressedImageSet));
-  } else if (base::win::GetVersion() < base::win::VERSION_WIN8) {
-    const int kNormalImageSet[] = IMAGE_GRID(IDR_AVATAR_GLASS_BUTTON_NORMAL);
-    const int kHoverImageSet[] = IMAGE_GRID(IDR_AVATAR_GLASS_BUTTON_HOVER);
-    const int kPressedImageSet[] = IMAGE_GRID(IDR_AVATAR_GLASS_BUTTON_PRESSED);
-    SetButtonAvatar(IDR_AVATAR_GLASS_BUTTON_AVATAR);
-    SetBorder(
-        CreateThemedBorder(kNormalImageSet, kHoverImageSet, kPressedImageSet));
-  } else {
-    const int kNormalImageSet[] = IMAGE_GRID(IDR_AVATAR_NATIVE_BUTTON_NORMAL);
-    const int kHoverImageSet[] = IMAGE_GRID(IDR_AVATAR_NATIVE_BUTTON_HOVER);
-    const int kPressedImageSet[] = IMAGE_GRID(IDR_AVATAR_NATIVE_BUTTON_PRESSED);
-    SetButtonAvatar(IDR_AVATAR_NATIVE_BUTTON_AVATAR);
-    SetBorder(
-        CreateThemedBorder(kNormalImageSet, kHoverImageSet, kPressedImageSet));
-#endif
-  }
-
-  profile_shutdown_notifier_ =
-      AvatarButtonShutdownNotifierFactory::GetInstance()
-          ->Get(profile_)
-          ->Subscribe(base::Bind(&AvatarButton::OnProfileShutdown,
-                                 base::Unretained(this)));
-}
-
-AvatarButton::~AvatarButton() {}
-
-void AvatarButton::SetupThemeColorButton() {
-#if defined(OS_WIN)
-  if (IsCondensible()) {
-    // TODO(bsep): This needs to also be called when the Windows accent color
-    // updates, but there is currently no signal for that.
-    const SkColor base_color = BaseColorForButton(GetThemeProvider());
-    set_ink_drop_base_color(base_color);
-    generic_avatar_ = AvatarIconWithBaseColor(base_color);
-  }
-#elif defined(OS_MACOSX)
-  const SkColor base_color = BaseColorForButton(GetThemeProvider());
-  SetEnabledTextColors(base_color);
-  generic_avatar_ = AvatarIconWithBaseColor(base_color);
-#endif
-}
-
-void AvatarButton::OnAvatarButtonPressed(const ui::Event* event) {
-  views::Widget* bubble_widget = ProfileChooserView::GetCurrentBubbleWidget();
-  if (bubble_widget && !widget_observer_.IsObserving(bubble_widget)) {
-    widget_observer_.Add(bubble_widget);
-    pressed_lock_ = std::make_unique<PressedLock>(
-        this, false, ui::LocatedEvent::FromIfValid(event));
-  }
-}
-
-void AvatarButton::AddedToWidget() {
-  SetupThemeColorButton();
-  Update();
-}
-
-void AvatarButton::OnGestureEvent(ui::GestureEvent* event) {
-  // TODO(wjmaclean): The check for ET_GESTURE_LONG_PRESS is done here since
-  // no other UI button based on Button appears to handle mouse
-  // right-click. If other cases are identified, it may make sense to move this
-  // check to Button.
-  if (event->type() == ui::ET_GESTURE_LONG_PRESS)
-    NotifyClick(*event);
-  else
-    MenuButton::OnGestureEvent(event);
-}
-
-gfx::Size AvatarButton::GetMinimumSize() const {
-  if (IsCondensible()) {
-    // Returns the size of the button when it is atop the tabstrip. Called by
-    // GlassBrowserFrameView::LayoutProfileSwitcher().
-    // TODO(emx): Calculate the height based on the top of the new tab button.
-    return gfx::Size(kCondensibleButtonMinWidth, 20);
-  }
-
-  return MenuButton::GetMinimumSize();
-}
-
-gfx::Size AvatarButton::CalculatePreferredSize() const {
-  if (render_native_nav_buttons_)
-    return MenuButton::CalculatePreferredSize();
-
-  // TODO(estade): Calculate the height instead of hardcoding to 20 for the
-  // not-condensible case.
-  gfx::Size size(MenuButton::CalculatePreferredSize().width(), 20);
-
-  if (IsCondensible()) {
-    // Returns the normal size of the button (when it does not overlap the
-    // tabstrip).
-    size.set_width(std::min(std::max(size.width(), kCondensibleButtonMinWidth),
-                            kCondensibleButtonMaxWidth));
-#if defined(OS_WIN)
-    size.set_height(MinimizeButtonMetrics::GetCaptionButtonHeightInDIPs());
-#endif
-  }
-#if defined(OS_MACOSX)
-  size.set_height(kMacButtonHeight);
-#endif
-  return size;
-}
-
-std::unique_ptr<views::InkDropMask> AvatarButton::CreateInkDropMask() const {
-#if defined(OS_MACOSX)
-  // On Mac, this looks and behaves like a regular MD button, so we need a hover
-  // background.
-  // TODO (lgrey): Determine and set the correct insets.
-  constexpr int kHoverCornerRadius = 2;
-  return std::make_unique<views::RoundRectInkDropMask>(size(), gfx::Insets(),
-                                                       kHoverCornerRadius);
-#else
-  if (button_style_ == AvatarButtonStyle::THEMED)
-    return AvatarButtonThemedBorder::CreateInkDropMask(size());
-  return MenuButton::CreateInkDropMask();
-#endif
-}
-
-std::unique_ptr<views::InkDropHighlight> AvatarButton::CreateInkDropHighlight()
-    const {
-  if (button_style_ == AvatarButtonStyle::THEMED)
-    return MenuButton::CreateInkDropHighlight();
-
-  auto ink_drop_highlight = std::make_unique<views::InkDropHighlight>(
-      size(), 0, gfx::RectF(GetLocalBounds()).CenterPoint(),
-      GetInkDropBaseColor());
-  constexpr float kInkDropHighlightOpacity = 0.08f;
-  ink_drop_highlight->set_visible_opacity(kInkDropHighlightOpacity);
-  return ink_drop_highlight;
-}
-
-SkColor AvatarButton::GetInkDropBaseColor() const {
-#if defined(OS_MACOSX)
-  return GetThemeProvider()->GetColor(
-      ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
-#else
-  return MenuButton::GetInkDropBaseColor();
-#endif
-}
-
-bool AvatarButton::ShouldEnterPushedState(const ui::Event& event) {
-  if (ProfileChooserView::IsShowing())
-    return false;
-
-  return MenuButton::ShouldEnterPushedState(event);
-}
-
-bool AvatarButton::ShouldUseFloodFillInkDrop() const {
-  return true;
-}
-
-void AvatarButton::OnAvatarErrorChanged() {
-  Update();
-}
-
-void AvatarButton::OnProfileAdded(const base::FilePath& profile_path) {
-  Update();
-}
-
-void AvatarButton::OnProfileWasRemoved(const base::FilePath& profile_path,
-                                       const base::string16& profile_name) {
-  // If deleting the active profile, don't bother updating the avatar
-  // button, as the browser window is being closed anyway.
-  if (profile_->GetPath() != profile_path)
-    Update();
-}
-
-void AvatarButton::OnProfileNameChanged(
-    const base::FilePath& profile_path,
-    const base::string16& old_profile_name) {
-  if (profile_->GetPath() == profile_path)
-    Update();
-}
-
-void AvatarButton::OnProfileSupervisedUserIdChanged(
-    const base::FilePath& profile_path) {
-  if (profile_->GetPath() == profile_path)
-    Update();
-}
-
-void AvatarButton::OnWidgetDestroying(views::Widget* widget) {
-  pressed_lock_.reset();
-  if (render_native_nav_buttons_)
-    SchedulePaint();
-  widget_observer_.Remove(widget);
-}
-
-void AvatarButton::OnProfileShutdown() {
-  // It looks like in some mysterious cases, the AvatarButton outlives the
-  // profile (see http://crbug.com/id=579690). The avatar button is owned by
-  // the browser frame (which is owned by the BrowserWindow), and there is an
-  // expectation for the UI to be destroyed before the profile is destroyed.
-  CHECK(false) << "Avatar button must not outlive the profile.";
-}
-
-void AvatarButton::Update() {
-  // It looks like in some mysterious cases, the AvatarButton outlives the
-  // profile manager (see http://crbug.com/id=579690). The avatar button is
-  // owned by the browser frame (which is owned by the BrowserWindow), and
-  // there is an expectation for the UI to be destroyed before the profile
-  // manager is destroyed.
-  CHECK(g_browser_process->profile_manager())
-      << "Avatar button must not outlive the profile manager";
-
-  ProfileAttributesStorage& storage =
-      g_browser_process->profile_manager()->GetProfileAttributesStorage();
-
-  // If we have a single local profile, then use the generic avatar
-  // button instead of the profile name. Never use the generic button if
-  // the active profile is Guest.
-  const bool use_generic_button =
-      !profile_->IsGuestSession() && storage.GetNumberOfProfiles() == 1 &&
-      !SigninManagerFactory::GetForProfile(profile_)->IsAuthenticated();
-
-  // Always set the accessible name as accessible text, but don't display it if
-  // is just a generic button.
-  base::string16 name =
-      use_generic_button
-          ? l10n_util::GetStringUTF16(IDS_GENERIC_USER_AVATAR_LABEL)
-          : profiles::GetAvatarButtonTextForProfile(profile_);
-  if (use_generic_button) {
-    SetText(base::string16());
-    SetAccessibleName(name);  // Must be set after setting text to override it.
-  } else {
-    SetText(name);
-  }
-
-#if !defined(OS_MACOSX)
-  // If the button has no text, clear the text shadows to make sure the
-  // image is centered correctly. macOS doesn't use a shadow.
-  SetTextShadows(
-      use_generic_button
-          ? gfx::ShadowValues()
-          : gfx::ShadowValues(
-                10, gfx::ShadowValue(gfx::Vector2d(), 2.0f, SK_ColorDKGRAY)));
-#endif
-
-  if (use_generic_button) {
-    SetImage(views::Button::STATE_NORMAL, generic_avatar_);
-  } else if (profile_->IsSyncAllowed() && error_controller_.HasAvatarError()) {
-    // When DICE is enabled and the error is an auth error, the sync-paused icon
-    // is shown.
-    int dummy;
-    const bool should_show_sync_paused_ui =
-        AccountConsistencyModeManager::IsDiceEnabledForProfile(profile_) &&
-        sync_ui_util::GetMessagesForAvatarSyncError(
-            profile_, *SigninManagerFactory::GetForProfile(profile_), &dummy,
-            &dummy) == sync_ui_util::AUTH_ERROR;
-    SetImage(
-        views::Button::STATE_NORMAL,
-        should_show_sync_paused_ui
-            ? gfx::CreateVectorIcon(kSyncPausedIcon, 16, gfx::kGoogleBlue500)
-            : gfx::CreateVectorIcon(kSyncProblemIcon, 16, gfx::kGoogleRed700));
-  } else {
-    SetImage(views::Button::STATE_NORMAL, gfx::ImageSkia());
-  }
-
-  // If we are not using the generic button, then reset the spacing between
-  // the text and the possible authentication error icon.
-  const int kDefaultImageTextSpacing = 5;
-  SetImageLabelSpacing(use_generic_button ? 0 : kDefaultImageTextSpacing);
-
-  PreferredSizeChanged();
-}
-
-void AvatarButton::SetButtonAvatar(int avatar_idr) {
-  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
-  generic_avatar_ = *rb->GetImageNamed(avatar_idr).ToImageSkia();
-}
-
-// TODO(estade): all versions of this button should condense.
-bool AvatarButton::IsCondensible() const {
-#if defined(OS_WIN)
-  return (base::win::GetVersion() >= base::win::VERSION_WIN10) &&
-         button_style_ == AvatarButtonStyle::NATIVE;
-#else
-  return false;
-#endif
-}
-bool AvatarButton::ShouldApplyInkDrop() const {
-#if defined(OS_LINUX)
-  DCHECK_EQ(AvatarButtonStyle::THEMED, button_style_);
-  return true;
-#elif defined(OS_MACOSX)
-  return true;
-#else
-  if (render_native_nav_buttons_)
-    return false;
-  return IsCondensible();
-#endif
-}
diff --git a/chrome/browser/ui/views/profiles/avatar_button.h b/chrome/browser/ui/views/profiles/avatar_button.h
deleted file mode 100644
index e1b127198..0000000
--- a/chrome/browser/ui/views/profiles/avatar_button.h
+++ /dev/null
@@ -1,119 +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 CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_H_
-#define CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_H_
-
-#include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "build/build_config.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
-#include "chrome/browser/ui/avatar_button_error_controller.h"
-#include "chrome/browser/ui/avatar_button_error_controller_delegate.h"
-#include "chrome/browser/ui/views/profiles/avatar_button_style.h"
-#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
-#include "ui/views/controls/button/menu_button.h"
-#include "ui/views/widget/widget_observer.h"
-
-class AvatarButtonManager;
-class Profile;
-
-// Base class for avatar buttons that display the active profile's name in the
-// caption area.
-class AvatarButton : public views::MenuButton,
-                     public AvatarButtonErrorControllerDelegate,
-                     public ProfileAttributesStorage::Observer,
-                     public views::WidgetObserver {
- public:
-  AvatarButton(views::MenuButtonListener* listener,
-               AvatarButtonStyle button_style,
-               Profile* profile,
-               AvatarButtonManager* manager);
-  ~AvatarButton() override;
-
-  void SetupThemeColorButton();
-
-  // Called by AvatarButtonManager when the profile chooser menu is
-  // shown or hidden.
-  void OnAvatarButtonPressed(const ui::Event* event);
-
-  // views::LabelButton:
-  void AddedToWidget() override;
-  void OnGestureEvent(ui::GestureEvent* event) override;
-  gfx::Size GetMinimumSize() const override;
-  gfx::Size CalculatePreferredSize() const override;
-  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
-      const override;
-  SkColor GetInkDropBaseColor() const override;
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
-
- protected:
-  // views::LabelButton:
-  bool ShouldEnterPushedState(const ui::Event& event) override;
-  bool ShouldUseFloodFillInkDrop() const override;
-
- private:
-  friend class ProfileChooserViewExtensionsTest;
-
-  // AvatarButtonErrorControllerDelegate:
-  void OnAvatarErrorChanged() override;
-
-  // ProfileAttributesStorage::Observer:
-  void OnProfileAdded(const base::FilePath& profile_path) override;
-  void OnProfileWasRemoved(const base::FilePath& profile_path,
-                           const base::string16& profile_name) override;
-  void OnProfileNameChanged(const base::FilePath& profile_path,
-                            const base::string16& old_profile_name) override;
-  void OnProfileSupervisedUserIdChanged(
-      const base::FilePath& profile_path) override;
-
-  // views::WidgetObserver
-  void OnWidgetDestroying(views::Widget* widget) override;
-
-  // Called when |profile_| is shutting down.
-  void OnProfileShutdown();
-
-  // Called when the profile info cache or signin/sync error has changed, which
-  // means we might have to update the icon/text of the button.
-  void Update();
-
-  // Sets generic_avatar_ to the image with the specified IDR.
-  void SetButtonAvatar(int avatar_idr);
-
-  // Returns true when the button can get smaller to accomodate a more crowded
-  // browser frame.
-  bool IsCondensible() const;
-
-  // Returns true if this button should show an ink drop on hover.
-  bool ShouldApplyInkDrop() const;
-
-  AvatarButtonErrorController error_controller_;
-  Profile* profile_;
-
-  // TODO(msarda): Remove |profile_shutdown_notifier_| when
-  // http://crbug.com/579690 is fixed (it was added to track down the crash in
-  // that bug).
-  std::unique_ptr<KeyedServiceShutdownNotifier::Subscription>
-      profile_shutdown_notifier_;
-  ScopedObserver<ProfileAttributesStorage, AvatarButton> profile_observer_;
-
-  // The icon displayed instead of the profile name in the local profile case.
-  // Different assets are used depending on the OS version.
-  gfx::ImageSkia generic_avatar_;
-
-  AvatarButtonStyle button_style_;
-
-  // Set on desktop Linux to indicate if the avatar button should be
-  // drawn using the system theme.
-  bool render_native_nav_buttons_ = false;
-
-  // Shows the button in a pressed state while the bubble is open.
-  std::unique_ptr<PressedLock> pressed_lock_;
-
-  ScopedObserver<views::Widget, views::WidgetObserver> widget_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(AvatarButton);
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_H_
diff --git a/chrome/browser/ui/views/profiles/avatar_button_style.h b/chrome/browser/ui/views/profiles/avatar_button_style.h
deleted file mode 100644
index b8a54e8..0000000
--- a/chrome/browser/ui/views/profiles/avatar_button_style.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_STYLE_H_
-#define CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_STYLE_H_
-
-// Different Avatar button styles that can be applied.
-enum class AvatarButtonStyle {
-  NONE,    // No avatar button should be used.
-  THEMED,  // Used in a themed browser window.
-  NATIVE,  // Used in a native aero or metro window.
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_AVATAR_BUTTON_STYLE_H_
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
index af44241..125c152 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -80,7 +80,6 @@
   set_triggerable_event_flags(ui::EF_LEFT_MOUSE_BUTTON);
 
   set_tag(IDC_SHOW_AVATAR_MENU);
-  set_id(VIEW_ID_AVATAR_BUTTON);
 
   // The avatar should not flip with RTL UI. This does not affect text rendering
   // and LabelButton image/label placement is still flipped like usual.
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index c96cdef..c97290a 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -355,11 +355,6 @@
 }
 
 // static
-views::Widget* ProfileChooserView::GetCurrentBubbleWidget() {
-  return profile_bubble_ ? profile_bubble_->GetWidget() : nullptr;
-}
-
-// static
 void ProfileChooserView::Hide() {
   if (IsShowing())
     profile_bubble_->GetWidget()->Close();
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.h b/chrome/browser/ui/views/profiles/profile_chooser_view.h
index 15aec59..83709a3 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.h
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.h
@@ -66,7 +66,6 @@
       Browser* browser,
       bool is_source_keyboard);
   static bool IsShowing();
-  static views::Widget* GetCurrentBubbleWidget();
   static void Hide();
 
   const Browser* browser() const { return browser_; }
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_chooser_view_browsertest.cc
index 794b87ee..baff434 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view_browsertest.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/user_manager.h"
-#include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/profiles/user_manager_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/chrome_paths.h"
@@ -181,13 +180,8 @@
 
   void OpenProfileChooserViews(Browser* browser) {
     BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-    views::View* button;
-    if (ui::MaterialDesignController::IsRefreshUi())
-      button = browser_view->toolbar()->avatar_button();
-    else
-      button = browser_view->frame()->GetNewAvatarMenuButton();
-    if (!button)
-      NOTREACHED() << "Avatar button not found.";
+    views::View* button = browser_view->toolbar()->avatar_button();
+    DCHECK(button);
 
     ui::MouseEvent e(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                      ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0);
diff --git a/chrome/browser/ui/views/profiles/profile_indicator_icon.h b/chrome/browser/ui/views/profiles/profile_indicator_icon.h
index dbdbe75..4f1ee76 100644
--- a/chrome/browser/ui/views/profiles/profile_indicator_icon.h
+++ b/chrome/browser/ui/views/profiles/profile_indicator_icon.h
@@ -18,10 +18,9 @@
 
 // ProfileIndicatorIcon
 //
-// A view used to show either the incognito avatar, or in the case of CrOS multi
-// profile mode with teleported windows, a profile avatar. The icon set via
-// SetIcon() will be resized and drawn inside a circle if it's too big to fit in
-// the frame.
+// A view used to show a profile avatar for teleported windows in CrOS. The icon
+// set via SetIcon() will be resized and drawn inside a circle if it's too big
+// to fit in the frame.
 class ProfileIndicatorIcon : public views::View {
  public:
   ProfileIndicatorIcon();
diff --git a/chrome/browser/ui/views/tabs/alert_indicator.cc b/chrome/browser/ui/views/tabs/alert_indicator.cc
new file mode 100644
index 0000000..5681f7b
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/alert_indicator.cc
@@ -0,0 +1,245 @@
+// 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 "chrome/browser/ui/views/tabs/alert_indicator.h"
+
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/views/tabs/tab.h"
+#include "ui/base/material_design/material_design_controller.h"
+#include "ui/gfx/animation/animation_delegate.h"
+#include "ui/gfx/animation/multi_animation.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/paint_vector_icon.h"
+
+namespace {
+
+// Fade-in/out duration for the tab indicator animations.  Fade-in is quick to
+// immediately notify the user.  Fade-out is more gradual, so that the user has
+// a chance of finding a tab that has quickly "blipped" on and off.
+constexpr int kIndicatorFadeInDurationMs = 200;
+constexpr int kIndicatorFadeOutDurationMs = 1000;
+
+// Interval between frame updates of the tab indicator animations.  This is not
+// the usual 60 FPS because a trade-off must be made between tab UI animation
+// smoothness and media recording/playback performance on low-end hardware.
+constexpr base::TimeDelta kIndicatorFrameInterval =
+    base::TimeDelta::FromMilliseconds(50);  // 20 FPS
+
+// Animation that throbs in (towards 1.0) and out (towards 0.0), and ends in the
+// "in" state.
+class TabRecordingIndicatorAnimation : public gfx::MultiAnimation {
+ public:
+  ~TabRecordingIndicatorAnimation() override {}
+
+  // Overridden to provide alternating "towards in" and "towards out" behavior.
+  double GetCurrentValue() const override;
+
+  static std::unique_ptr<TabRecordingIndicatorAnimation> Create();
+
+ private:
+  TabRecordingIndicatorAnimation(const gfx::MultiAnimation::Parts& parts,
+                                 const base::TimeDelta interval)
+      : MultiAnimation(parts, interval) {}
+
+  // Number of times to "toggle throb" the recording and tab capture indicators
+  // when they first appear.
+  static const int kCaptureIndicatorThrobCycles = 5;
+};
+
+double TabRecordingIndicatorAnimation::GetCurrentValue() const {
+  return current_part_index() % 2 ? 1.0 - MultiAnimation::GetCurrentValue()
+                                  : MultiAnimation::GetCurrentValue();
+}
+
+std::unique_ptr<TabRecordingIndicatorAnimation>
+TabRecordingIndicatorAnimation::Create() {
+  MultiAnimation::Parts parts;
+  static_assert(
+      kCaptureIndicatorThrobCycles % 2 != 0,
+      "odd number of cycles required so animation finishes in showing state");
+  for (int i = 0; i < kCaptureIndicatorThrobCycles; ++i) {
+    parts.push_back(MultiAnimation::Part(
+        i % 2 ? kIndicatorFadeOutDurationMs : kIndicatorFadeInDurationMs,
+        gfx::Tween::EASE_IN));
+  }
+
+  std::unique_ptr<TabRecordingIndicatorAnimation> animation(
+      new TabRecordingIndicatorAnimation(parts, kIndicatorFrameInterval));
+  animation->set_continuous(false);
+  return animation;
+}
+
+// Returns a cached image, to be shown by the alert indicator for the given
+// |alert_state|.  Uses the global ui::ResourceBundle shared instance.
+gfx::Image GetTabAlertIndicatorImage(TabAlertState alert_state,
+                                     SkColor button_color) {
+  const gfx::VectorIcon* icon = nullptr;
+  int image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_ICON_WIDTH);
+  const bool is_touch_optimized_ui =
+      ui::MaterialDesignController::IsTouchOptimizedUiEnabled();
+  switch (alert_state) {
+    case TabAlertState::AUDIO_PLAYING:
+      icon = is_touch_optimized_ui ? &kTabAudioRoundedIcon : &kTabAudioIcon;
+      break;
+    case TabAlertState::AUDIO_MUTING:
+      icon = is_touch_optimized_ui ? &kTabAudioMutingRoundedIcon
+                                   : &kTabAudioMutingIcon;
+      break;
+    case TabAlertState::MEDIA_RECORDING:
+    case TabAlertState::DESKTOP_CAPTURING:
+      icon = &kTabMediaRecordingIcon;
+      break;
+    case TabAlertState::TAB_CAPTURING:
+      icon = is_touch_optimized_ui ? &kTabMediaCapturingWithArrowIcon
+                                   : &kTabMediaCapturingIcon;
+      // Tab capturing and presenting icon uses a different width compared to
+      // the other tab alert indicator icons.
+      image_width = GetLayoutConstant(TAB_ALERT_INDICATOR_CAPTURE_ICON_WIDTH);
+      break;
+    case TabAlertState::BLUETOOTH_CONNECTED:
+      icon = &kTabBluetoothConnectedIcon;
+      break;
+    case TabAlertState::USB_CONNECTED:
+      icon = &kTabUsbConnectedIcon;
+      break;
+    case TabAlertState::PIP_PLAYING:
+      icon = &kPictureInPictureAltIcon;
+      break;
+    case TabAlertState::NONE:
+      return gfx::Image();
+  }
+  DCHECK(icon);
+  return gfx::Image(gfx::CreateVectorIcon(*icon, image_width, button_color));
+}
+
+// Returns a non-continuous Animation that performs a fade-in or fade-out
+// appropriate for the given |next_alert_state|.  This is used by the tab alert
+// indicator to alert the user that recording, tab capture, or audio playback
+// has started/stopped.
+std::unique_ptr<gfx::Animation> CreateTabAlertIndicatorFadeAnimation(
+    TabAlertState alert_state) {
+  if (alert_state == TabAlertState::MEDIA_RECORDING ||
+      alert_state == TabAlertState::TAB_CAPTURING ||
+      alert_state == TabAlertState::DESKTOP_CAPTURING) {
+    return TabRecordingIndicatorAnimation::Create();
+  }
+
+  // Note: While it seems silly to use a one-part MultiAnimation, it's the only
+  // gfx::Animation implementation that lets us control the frame interval.
+  gfx::MultiAnimation::Parts parts;
+  const bool is_for_fade_in = (alert_state != TabAlertState::NONE);
+  parts.push_back(gfx::MultiAnimation::Part(
+      is_for_fade_in ? kIndicatorFadeInDurationMs : kIndicatorFadeOutDurationMs,
+      gfx::Tween::EASE_IN));
+  std::unique_ptr<gfx::MultiAnimation> animation(
+      new gfx::MultiAnimation(parts, kIndicatorFrameInterval));
+  animation->set_continuous(false);
+  return std::move(animation);
+}
+
+}  // namespace
+
+class AlertIndicator::FadeAnimationDelegate : public gfx::AnimationDelegate {
+ public:
+  explicit FadeAnimationDelegate(AlertIndicator* indicator)
+      : indicator_(indicator) {}
+  ~FadeAnimationDelegate() override {}
+
+ private:
+  // gfx::AnimationDelegate
+  void AnimationProgressed(const gfx::Animation* animation) override {
+    indicator_->SchedulePaint();
+  }
+
+  void AnimationCanceled(const gfx::Animation* animation) override {
+    AnimationEnded(animation);
+  }
+
+  void AnimationEnded(const gfx::Animation* animation) override {
+    indicator_->showing_alert_state_ = indicator_->alert_state_;
+    indicator_->SchedulePaint();
+    indicator_->parent_tab_->AlertStateChanged();
+  }
+
+  AlertIndicator* const indicator_;
+
+  DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate);
+};
+
+AlertIndicator::AlertIndicator(Tab* parent_tab)
+    : views::ImageView(),
+      parent_tab_(parent_tab),
+      alert_state_(TabAlertState::NONE),
+      showing_alert_state_(TabAlertState::NONE) {
+  DCHECK(parent_tab_);
+}
+
+AlertIndicator::~AlertIndicator() {}
+
+void AlertIndicator::OnPaint(gfx::Canvas* canvas) {
+  double opaqueness = 1.0;
+  if (fade_animation_) {
+    opaqueness = fade_animation_->GetCurrentValue();
+    if (alert_state_ == TabAlertState::NONE)
+      opaqueness = 1.0 - opaqueness;  // Fading out, not in.
+  }
+  if (opaqueness < 1.0)
+    canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
+  ImageView::OnPaint(canvas);
+  if (opaqueness < 1.0)
+    canvas->Restore();
+}
+
+void AlertIndicator::TransitionToAlertState(TabAlertState next_state) {
+  if (next_state == alert_state_)
+    return;
+
+  TabAlertState previous_alert_showing_state = showing_alert_state_;
+
+  if (next_state != TabAlertState::NONE)
+    ResetImage(next_state);
+
+  if ((alert_state_ == TabAlertState::AUDIO_PLAYING &&
+       next_state == TabAlertState::AUDIO_MUTING) ||
+      (alert_state_ == TabAlertState::AUDIO_MUTING &&
+       next_state == TabAlertState::AUDIO_PLAYING)) {
+    // Instant user feedback: No fade animation.
+    showing_alert_state_ = next_state;
+    fade_animation_.reset();
+  } else {
+    if (next_state == TabAlertState::NONE)
+      showing_alert_state_ = alert_state_;  // Fading-out indicator.
+    else
+      showing_alert_state_ = next_state;  // Fading-in to next indicator.
+    fade_animation_ = CreateTabAlertIndicatorFadeAnimation(next_state);
+    if (!fade_animation_delegate_)
+      fade_animation_delegate_.reset(new FadeAnimationDelegate(this));
+    fade_animation_->set_delegate(fade_animation_delegate_.get());
+    fade_animation_->Start();
+  }
+
+  alert_state_ = next_state;
+
+  if (previous_alert_showing_state != showing_alert_state_)
+    parent_tab_->AlertStateChanged();
+}
+
+void AlertIndicator::OnParentTabButtonColorChanged() {
+  if (alert_state_ == TabAlertState::AUDIO_PLAYING ||
+      alert_state_ == TabAlertState::AUDIO_MUTING)
+    ResetImage(alert_state_);
+}
+
+views::View* AlertIndicator::GetTooltipHandlerForPoint(
+    const gfx::Point& point) {
+  return nullptr;  // Tab (the parent View) provides the tooltip.
+}
+
+void AlertIndicator::ResetImage(TabAlertState state) {
+  SkColor color = parent_tab_->GetAlertIndicatorColor(state);
+  gfx::ImageSkia image = GetTabAlertIndicatorImage(state, color).AsImageSkia();
+  SetImage(&image);
+}
diff --git a/chrome/browser/ui/views/tabs/alert_indicator.h b/chrome/browser/ui/views/tabs/alert_indicator.h
new file mode 100644
index 0000000..6327526f
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/alert_indicator.h
@@ -0,0 +1,64 @@
+// 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 CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_H_
+#define CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/tabs/tab_utils.h"
+#include "ui/views/controls/image_view.h"
+
+class Tab;
+
+namespace gfx {
+class Animation;
+class AnimationDelegate;
+}  // namespace gfx
+
+// This is an ImageView subclass that serves as an indicator of various states,
+// primarily media playing/recording, but also device connectivity.  It is meant
+// to only be used as a child view of Tab.
+class AlertIndicator : public views::ImageView {
+ public:
+  explicit AlertIndicator(Tab* parent_tab);
+  ~AlertIndicator() override;
+
+  // views::ImageView:
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  // Returns the current TabAlertState except, while the indicator image is
+  // fading out, returns the prior TabAlertState.
+  TabAlertState showing_alert_state() const { return showing_alert_state_; }
+
+  // Calls ResetImages() and starts fade animations as appropriate.
+  void TransitionToAlertState(TabAlertState next_state);
+
+  // Called when the parent tab's button color changes.  Determines whether
+  // ResetImages() needs to be called.
+  void OnParentTabButtonColorChanged();
+
+ protected:
+  View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
+
+ private:
+  friend class AlertIndicatorTest;
+  friend class TabTest;
+  class FadeAnimationDelegate;
+
+  // Resets the images to display on the button to reflect |state| and the
+  // parent tab's button color.  Should be called when either of these changes.
+  void ResetImage(TabAlertState state);
+
+  Tab* const parent_tab_;
+  TabAlertState alert_state_;
+  std::unique_ptr<gfx::AnimationDelegate> fade_animation_delegate_;
+  std::unique_ptr<gfx::Animation> fade_animation_;
+  TabAlertState showing_alert_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(AlertIndicator);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_H_
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button.cc b/chrome/browser/ui/views/tabs/alert_indicator_button.cc
deleted file mode 100644
index d74b43dd..0000000
--- a/chrome/browser/ui/views/tabs/alert_indicator_button.cc
+++ /dev/null
@@ -1,319 +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 "chrome/browser/ui/views/tabs/alert_indicator_button.h"
-
-#include "base/macros.h"
-#include "base/metrics/user_metrics.h"
-#include "base/timer/timer.h"
-#include "chrome/browser/ui/views/tabs/tab.h"
-#include "chrome/browser/ui/views/tabs/tab_controller.h"
-#include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
-#include "ui/gfx/animation/animation_delegate.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/image/image.h"
-#include "ui/views/metrics.h"
-
-using base::UserMetricsAction;
-
-namespace {
-
-// The minimum required click-to-select area of an inactive Tab before allowing
-// the click-to-mute functionality to be enabled.  These values are in terms of
-// some percentage of the AlertIndicatorButton's width.  See comments in
-// UpdateEnabledForMuteToggle().
-const int kMinMouseSelectableAreaPercent = 250;
-const int kMinGestureSelectableAreaPercent = 400;
-
-// Returns true if either Shift or Control are being held down.  In this case,
-// mouse events are delegated to the Tab, to perform tab selection in the tab
-// strip instead.
-bool IsShiftOrControlDown(const ui::Event& event) {
-  return (event.flags() & (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)) != 0;
-}
-
-}  // namespace
-
-const char AlertIndicatorButton::kViewClassName[] = "AlertIndicatorButton";
-
-class AlertIndicatorButton::FadeAnimationDelegate
-    : public gfx::AnimationDelegate {
- public:
-  explicit FadeAnimationDelegate(AlertIndicatorButton* button)
-      : button_(button) {}
-  ~FadeAnimationDelegate() override {}
-
- private:
-  // gfx::AnimationDelegate
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    button_->SchedulePaint();
-  }
-
-  void AnimationCanceled(const gfx::Animation* animation) override {
-    AnimationEnded(animation);
-  }
-
-  void AnimationEnded(const gfx::Animation* animation) override {
-    button_->showing_alert_state_ = button_->alert_state_;
-    button_->SchedulePaint();
-    button_->parent_tab_->AlertStateChanged();
-  }
-
-  AlertIndicatorButton* const button_;
-
-  DISALLOW_COPY_AND_ASSIGN(FadeAnimationDelegate);
-};
-
-AlertIndicatorButton::AlertIndicatorButton(Tab* parent_tab)
-    : views::ImageButton(nullptr),
-      parent_tab_(parent_tab),
-      alert_state_(TabAlertState::NONE),
-      showing_alert_state_(TabAlertState::NONE) {
-  DCHECK(parent_tab_);
-  SetEventTargeter(
-      std::unique_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
-
-  // Disable animations of hover state change, to be consistent with the
-  // behavior of the tab close button.
-  set_animate_on_state_change(false);
-}
-
-AlertIndicatorButton::~AlertIndicatorButton() {}
-
-void AlertIndicatorButton::TransitionToAlertState(TabAlertState next_state) {
-  if (next_state == alert_state_)
-    return;
-
-  TabAlertState previous_alert_showing_state = showing_alert_state_;
-
-  if (next_state != TabAlertState::NONE)
-    ResetImages(next_state);
-
-  if ((alert_state_ == TabAlertState::AUDIO_PLAYING &&
-       next_state == TabAlertState::AUDIO_MUTING) ||
-      (alert_state_ == TabAlertState::AUDIO_MUTING &&
-       next_state == TabAlertState::AUDIO_PLAYING)) {
-    // Instant user feedback: No fade animation.
-    showing_alert_state_ = next_state;
-    fade_animation_.reset();
-  } else {
-    if (next_state == TabAlertState::NONE)
-      showing_alert_state_ = alert_state_;  // Fading-out indicator.
-    else
-      showing_alert_state_ = next_state;  // Fading-in to next indicator.
-    fade_animation_ = chrome::CreateTabAlertIndicatorFadeAnimation(next_state);
-    if (!fade_animation_delegate_)
-      fade_animation_delegate_.reset(new FadeAnimationDelegate(this));
-    fade_animation_->set_delegate(fade_animation_delegate_.get());
-    fade_animation_->Start();
-  }
-
-  alert_state_ = next_state;
-
-  if (previous_alert_showing_state != showing_alert_state_)
-    parent_tab_->AlertStateChanged();
-
-  UpdateEnabledForMuteToggle();
-}
-
-void AlertIndicatorButton::UpdateEnabledForMuteToggle() {
-  const bool was_enabled = enabled();
-
-  bool enable = chrome::AreExperimentalMuteControlsEnabled() &&
-                (alert_state_ == TabAlertState::AUDIO_PLAYING ||
-                 alert_state_ == TabAlertState::AUDIO_MUTING);
-
-  // If the tab is not the currently-active tab, make sure it is wide enough
-  // before enabling click-to-mute.  This ensures that there is enough click
-  // area for the user to activate a tab rather than unintentionally muting it.
-  // Note that IsTriggerableEvent() is also overridden to provide an even wider
-  // requirement for tap gestures.
-  if (enable && !GetTab()->IsActive()) {
-    const int required_width = width() * kMinMouseSelectableAreaPercent / 100;
-    enable = (GetTab()->GetWidthOfLargestSelectableRegion() >= required_width);
-  }
-
-  if (enable == was_enabled)
-    return;
-
-  SetEnabled(enable);
-
-  // If the button has become enabled, check whether the mouse is currently
-  // hovering.  If it is, enter a dormant period where extra user clicks are
-  // prevented from having an effect (i.e., before the user has realized the
-  // button has become enabled underneath their cursor).
-  if (!was_enabled && state() == views::Button::STATE_HOVERED)
-    EnterDormantPeriod();
-  else if (!enabled())
-    ExitDormantPeriod();
-}
-
-void AlertIndicatorButton::OnParentTabButtonColorChanged() {
-  if (alert_state_ == TabAlertState::AUDIO_PLAYING ||
-      alert_state_ == TabAlertState::AUDIO_MUTING)
-    ResetImages(alert_state_);
-}
-
-const char* AlertIndicatorButton::GetClassName() const {
-  return kViewClassName;
-}
-
-views::View* AlertIndicatorButton::GetTooltipHandlerForPoint(
-    const gfx::Point& point) {
-  return nullptr;  // Tab (the parent View) provides the tooltip.
-}
-
-bool AlertIndicatorButton::OnMousePressed(const ui::MouseEvent& event) {
-  // Do not handle this mouse event when anything but the left mouse button is
-  // pressed or when any modifier keys are being held down.  Instead, the Tab
-  // should react (e.g., middle-click for close, right-click for context menu).
-  if (!event.IsOnlyLeftMouseButton() || IsShiftOrControlDown(event)) {
-    if (state() != views::Button::STATE_DISABLED)
-      SetState(views::Button::STATE_NORMAL);  // Turn off hover.
-    return false;  // Event to be handled by Tab.
-  }
-  return ImageButton::OnMousePressed(event);
-}
-
-bool AlertIndicatorButton::OnMouseDragged(const ui::MouseEvent& event) {
-  const ButtonState previous_state = state();
-  const bool ret = ImageButton::OnMouseDragged(event);
-  if (previous_state != views::Button::STATE_NORMAL &&
-      state() == views::Button::STATE_NORMAL)
-    base::RecordAction(UserMetricsAction("AlertIndicatorButton_Dragged"));
-  return ret;
-}
-
-void AlertIndicatorButton::OnMouseEntered(const ui::MouseEvent& event) {
-  // If any modifier keys are being held down, do not turn on hover.
-  if (state() != views::Button::STATE_DISABLED && IsShiftOrControlDown(event)) {
-    SetState(views::Button::STATE_NORMAL);
-    return;
-  }
-  ImageButton::OnMouseEntered(event);
-}
-
-void AlertIndicatorButton::OnMouseExited(const ui::MouseEvent& event) {
-  ExitDormantPeriod();
-  ImageButton::OnMouseExited(event);
-}
-
-void AlertIndicatorButton::OnMouseMoved(const ui::MouseEvent& event) {
-  // If any modifier keys are being held down, turn off hover.
-  if (state() != views::Button::STATE_DISABLED && IsShiftOrControlDown(event)) {
-    SetState(views::Button::STATE_NORMAL);
-    return;
-  }
-  ImageButton::OnMouseMoved(event);
-}
-
-void AlertIndicatorButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
-  UpdateEnabledForMuteToggle();
-}
-
-bool AlertIndicatorButton::DoesIntersectRect(const views::View* target,
-                                             const gfx::Rect& rect) const {
-  // If this button is not enabled, Tab (the parent View) handles all mouse
-  // events.
-  return enabled() &&
-         views::ViewTargeterDelegate::DoesIntersectRect(target, rect);
-}
-
-void AlertIndicatorButton::NotifyClick(const ui::Event& event) {
-  EnterDormantPeriod();
-
-  // Call TransitionToAlertState() to change the image, providing the user with
-  // instant feedback.  In the very unlikely event that the mute toggle fails,
-  // TransitionToAlertState() will be called again, via another code path, to
-  // set the image to be consistent with the final outcome.
-  if (alert_state_ == TabAlertState::AUDIO_PLAYING) {
-    base::RecordAction(UserMetricsAction("AlertIndicatorButton_Mute"));
-    TransitionToAlertState(TabAlertState::AUDIO_MUTING);
-  } else {
-    DCHECK(alert_state_ == TabAlertState::AUDIO_MUTING);
-    base::RecordAction(UserMetricsAction("AlertIndicatorButton_Unmute"));
-    TransitionToAlertState(TabAlertState::AUDIO_PLAYING);
-  }
-
-  GetTab()->controller()->ToggleTabAudioMute(GetTab());
-}
-
-bool AlertIndicatorButton::IsTriggerableEvent(const ui::Event& event) {
-  if (is_dormant())
-    return false;
-
-  // For mouse events, only trigger on the left mouse button and when no
-  // modifier keys are being held down.
-  if (event.IsMouseEvent() &&
-      (!static_cast<const ui::MouseEvent*>(&event)->IsOnlyLeftMouseButton() ||
-       IsShiftOrControlDown(event)))
-    return false;
-
-  // For gesture events on an inactive tab, require an even wider tab before
-  // click-to-mute can be triggered.  See comments in
-  // UpdateEnabledForMuteToggle().
-  if (event.IsGestureEvent() && !GetTab()->IsActive()) {
-    const int required_width = width() * kMinGestureSelectableAreaPercent / 100;
-    if (GetTab()->GetWidthOfLargestSelectableRegion() < required_width)
-      return false;
-  }
-
-  return views::ImageButton::IsTriggerableEvent(event);
-}
-
-void AlertIndicatorButton::PaintButtonContents(gfx::Canvas* canvas) {
-  double opaqueness = 1.0;
-  if (fade_animation_) {
-    opaqueness = fade_animation_->GetCurrentValue();
-    if (alert_state_ == TabAlertState::NONE)
-      opaqueness = 1.0 - opaqueness;  // Fading out, not in.
-  } else if (is_dormant()) {
-    opaqueness = 0.5;
-  }
-  if (opaqueness < 1.0)
-    canvas->SaveLayerAlpha(opaqueness * SK_AlphaOPAQUE);
-  ImageButton::PaintButtonContents(canvas);
-  if (opaqueness < 1.0)
-    canvas->Restore();
-}
-
-gfx::ImageSkia AlertIndicatorButton::GetImageToPaint() {
-  if (is_dormant())
-    return views::ImageButton::images_[views::Button::STATE_NORMAL];
-  return views::ImageButton::GetImageToPaint();
-}
-
-Tab* AlertIndicatorButton::GetTab() const {
-  DCHECK_EQ(static_cast<views::View*>(parent_tab_), parent());
-  return parent_tab_;
-}
-
-void AlertIndicatorButton::ResetImages(TabAlertState state) {
-  SkColor color = parent_tab_->GetAlertIndicatorColor(state);
-  gfx::ImageSkia indicator_image =
-      chrome::GetTabAlertIndicatorImage(state, color).AsImageSkia();
-  SetImage(views::Button::STATE_NORMAL, &indicator_image);
-  SetImage(views::Button::STATE_DISABLED, &indicator_image);
-  gfx::ImageSkia affordance_image =
-      chrome::GetTabAlertIndicatorAffordanceImage(state, color).AsImageSkia();
-  SetImage(views::Button::STATE_HOVERED, &affordance_image);
-  SetImage(views::Button::STATE_PRESSED, &affordance_image);
-}
-
-void AlertIndicatorButton::EnterDormantPeriod() {
-  wake_up_timer_.reset(new base::OneShotTimer());
-  wake_up_timer_->Start(
-      FROM_HERE,
-      base::TimeDelta::FromMilliseconds(views::GetDoubleClickInterval()),
-      this,
-      &AlertIndicatorButton::ExitDormantPeriod);
-  SchedulePaint();
-}
-
-void AlertIndicatorButton::ExitDormantPeriod() {
-  const bool needs_repaint = is_dormant();
-  wake_up_timer_.reset();
-  if (needs_repaint)
-    SchedulePaint();
-}
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button.h b/chrome/browser/ui/views/tabs/alert_indicator_button.h
deleted file mode 100644
index 7e8e258..0000000
--- a/chrome/browser/ui/views/tabs/alert_indicator_button.h
+++ /dev/null
@@ -1,125 +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 CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_BUTTON_H_
-#define CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_BUTTON_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "chrome/browser/ui/tabs/tab_utils.h"
-#include "ui/views/controls/button/image_button.h"
-#include "ui/views/view_targeter_delegate.h"
-
-class Tab;
-
-namespace base {
-class OneShotTimer;
-}
-
-namespace gfx {
-class Animation;
-class AnimationDelegate;
-}
-
-// This is an ImageButton subclass that serves as both the alert indicator icon
-// (audio, tab capture, etc.), and as a mute button.  It is meant to only be
-// used as a child view of Tab.
-//
-// When the indicator is transitioned to the audio playing or muting state, the
-// button functionality is enabled and begins handling mouse events.  Otherwise,
-// this view behaves like an image and all mouse events will be handled by the
-// Tab (its parent View).
-class AlertIndicatorButton : public views::ImageButton,
-                             public views::ViewTargeterDelegate {
- public:
-  // The AlertIndicatorButton's class name.
-  static const char kViewClassName[];
-
-  explicit AlertIndicatorButton(Tab* parent_tab);
-  ~AlertIndicatorButton() override;
-
-  // Returns the current TabAlertState except, while the indicator image is
-  // fading out, returns the prior TabAlertState.
-  TabAlertState showing_alert_state() const { return showing_alert_state_; }
-
-  // Calls ResetImages(), starts fade animations, and activates/deactivates
-  // button functionality as appropriate.
-  void TransitionToAlertState(TabAlertState next_state);
-
-  // Determines whether the AlertIndicatorButton will be clickable for toggling
-  // muting.  This should be called whenever the active/inactive state of a tab
-  // has changed.  Internally, TransitionToAlertState() and OnBoundsChanged()
-  // calls this when the TabAlertState or the bounds have changed.
-  void UpdateEnabledForMuteToggle();
-
-  // Called when the parent tab's button color changes.  Determines whether
-  // ResetImages() needs to be called.
-  void OnParentTabButtonColorChanged();
-
- protected:
-  // views::View:
-  const char* GetClassName() const override;
-  View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
-  bool OnMousePressed(const ui::MouseEvent& event) override;
-  bool OnMouseDragged(const ui::MouseEvent& event) override;
-  void OnMouseEntered(const ui::MouseEvent& event) override;
-  void OnMouseExited(const ui::MouseEvent& event) override;
-  void OnMouseMoved(const ui::MouseEvent& event) override;
-  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-
-  // views::ViewTargeterDelegate
-  bool DoesIntersectRect(const View* target,
-                         const gfx::Rect& rect) const override;
-
-  // views::Button:
-  void NotifyClick(const ui::Event& event) override;
-
-  // views::Button:
-  bool IsTriggerableEvent(const ui::Event& event) override;
-  void PaintButtonContents(gfx::Canvas* canvas) override;
-
-  // views::ImageButton:
-  gfx::ImageSkia GetImageToPaint() override;
-
- private:
-  friend class AlertIndicatorButtonTest;
-  friend class TabTest;
-  class FadeAnimationDelegate;
-
-  // Returns the tab (parent view) of this AlertIndicatorButton.
-  Tab* GetTab() const;
-
-  // Resets the images to display on the button to reflect |state| and the
-  // parent tab's button color.  Should be called when either of these changes.
-  void ResetImages(TabAlertState state);
-
-  // Enters a temporary "dormant period" where this AlertIndicatorButton will
-  // not trigger on clicks.  The user is provided a visual affordance during
-  // this period.  Sets a timer to call ExitDormantPeriod().
-  void EnterDormantPeriod();
-
-  // Leaves the "dormant period," allowing clicks to once again trigger an
-  // enabled AlertIndicatorButton.
-  void ExitDormantPeriod();
-
-  bool is_dormant() const { return !!wake_up_timer_; }
-
-  Tab* const parent_tab_;
-
-  TabAlertState alert_state_;
-
-  // Alert indicator fade-in/out animation (i.e., only on show/hide, not a
-  // continuous animation).
-  std::unique_ptr<gfx::AnimationDelegate> fade_animation_delegate_;
-  std::unique_ptr<gfx::Animation> fade_animation_;
-  TabAlertState showing_alert_state_;
-
-  // Created on-demand, this fires to exit the "dormant period."
-  std::unique_ptr<base::OneShotTimer> wake_up_timer_;
-
-  DISALLOW_COPY_AND_ASSIGN(AlertIndicatorButton);
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_TABS_ALERT_INDICATOR_BUTTON_H_
diff --git a/chrome/browser/ui/views/tabs/alert_indicator_button_unittest.cc b/chrome/browser/ui/views/tabs/alert_indicator_button_unittest.cc
deleted file mode 100644
index e7bc548..0000000
--- a/chrome/browser/ui/views/tabs/alert_indicator_button_unittest.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/tabs/alert_indicator_button.h"
-
-#include <utility>
-
-#include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
-#include "chrome/browser/ui/views/tabs/tab.h"
-#include "chrome/browser/ui/views/tabs/tab_strip.h"
-#include "chrome/test/views/chrome_views_test_base.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class AlertIndicatorButtonTest : public ChromeViewsTestBase {
- public:
-  AlertIndicatorButtonTest() {}
-
-  ~AlertIndicatorButtonTest() override {}
-
-  void SetUp() override {
-    ChromeViewsTestBase::SetUp();
-
-    controller_ = new FakeBaseTabStripController;
-    tab_strip_ = new TabStrip(std::unique_ptr<TabStripController>(controller_));
-    controller_->set_tab_strip(tab_strip_);
-    // The tab strip must be added to the view hierarchy for it to create the
-    // buttons.
-    parent_.AddChildView(tab_strip_);
-    parent_.set_owned_by_client();
-
-    widget_.reset(new views::Widget);
-    views::Widget::InitParams init_params =
-        CreateParams(views::Widget::InitParams::TYPE_POPUP);
-    init_params.ownership =
-        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-    init_params.bounds = gfx::Rect(0, 0, 400, 400);
-    widget_->Init(init_params);
-    widget_->SetContentsView(&parent_);
-  }
-
-  void TearDown() override {
-    // All windows need to be closed before tear down.
-    widget_.reset();
-
-    ChromeViewsTestBase::TearDown();
-  }
-
- protected:
-  bool showing_close_button(Tab* tab) const {
-    return tab->showing_close_button_;
-  }
-  bool showing_icon(Tab* tab) const { return tab->showing_icon_; }
-  bool showing_alert_indicator(Tab* tab) const {
-    return tab->showing_alert_indicator_;
-  }
-
-  void StopAnimation(Tab* tab) {
-    ASSERT_TRUE(tab->alert_indicator_button_->fade_animation_);
-    tab->alert_indicator_button_->fade_animation_->Stop();
-  }
-
-  // Owned by TabStrip.
-  FakeBaseTabStripController* controller_ = nullptr;
-  // Owns |tab_strip_|.
-  views::View parent_;
-  TabStrip* tab_strip_ = nullptr;
-  std::unique_ptr<views::Widget> widget_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(AlertIndicatorButtonTest);
-};
-
-// This test verifies that the tab has its icon state updated when the alert
-// animation fade-out finishes.
-TEST_F(AlertIndicatorButtonTest, ButtonUpdateOnAudioStateAnimation) {
-  controller_->AddPinnedTab(0, false);
-  controller_->AddTab(1, true);
-  Tab* media_tab = tab_strip_->tab_at(0);
-
-  // Pinned inactive tab only has an icon.
-  EXPECT_TRUE(showing_icon(media_tab));
-  EXPECT_FALSE(showing_alert_indicator(media_tab));
-  EXPECT_FALSE(showing_close_button(media_tab));
-
-  TabRendererData start_media;
-  start_media.alert_state = TabAlertState::AUDIO_PLAYING;
-  start_media.pinned = media_tab->data().pinned;
-  media_tab->SetData(std::move(start_media));
-
-  // When audio starts, pinned inactive tab shows indicator.
-  EXPECT_FALSE(showing_icon(media_tab));
-  EXPECT_TRUE(showing_alert_indicator(media_tab));
-  EXPECT_FALSE(showing_close_button(media_tab));
-
-  TabRendererData stop_media;
-  stop_media.alert_state = TabAlertState::NONE;
-  stop_media.pinned = media_tab->data().pinned;
-  media_tab->SetData(std::move(stop_media));
-
-  // When audio ends, pinned inactive tab fades out indicator.
-  EXPECT_FALSE(showing_icon(media_tab));
-  EXPECT_TRUE(showing_alert_indicator(media_tab));
-  EXPECT_FALSE(showing_close_button(media_tab));
-
-  // Rather than flakily waiting some unknown number of seconds for the fade
-  // out animation to stop, reach out and stop the fade animation directly,
-  // to make sure that it updates the tab appropriately when it's done.
-  StopAnimation(media_tab);
-
-  EXPECT_TRUE(showing_icon(media_tab));
-  EXPECT_FALSE(showing_alert_indicator(media_tab));
-  EXPECT_FALSE(showing_close_button(media_tab));
-}
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index f0ede22..2861ac9a 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -271,12 +271,6 @@
                              TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
 }
 
-void BrowserTabStripController::ToggleTabAudioMute(int model_index) {
-  content::WebContents* const contents = model_->GetWebContentsAt(model_index);
-  chrome::SetTabAudioMuted(contents, !contents->IsAudioMuted(),
-                           TabMutedReason::AUDIO_INDICATOR, std::string());
-}
-
 void BrowserTabStripController::ShowContextMenuForTab(
     Tab* tab,
     const gfx::Point& p,
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index 6d79dc4..0093f8b 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -60,7 +60,6 @@
   void ToggleSelected(int model_index) override;
   void AddSelectionFromAnchorTo(int model_index) override;
   void CloseTab(int model_index, CloseTabSource source) override;
-  void ToggleTabAudioMute(int model_index) override;
   void ShowContextMenuForTab(Tab* tab,
                              const gfx::Point& p,
                              ui::MenuSourceType source_type) override;
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
index 178eaeb..d8249d6 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
@@ -95,9 +95,6 @@
   RemoveTab(index);
 }
 
-void FakeBaseTabStripController::ToggleTabAudioMute(int index) {
-}
-
 void FakeBaseTabStripController::ShowContextMenuForTab(
     Tab* tab,
     const gfx::Point& p,
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
index df1cd01..171c85d 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
@@ -36,7 +36,6 @@
   void ToggleSelected(int index) override;
   void AddSelectionFromAnchorTo(int index) override;
   void CloseTab(int index, CloseTabSource source) override;
-  void ToggleTabAudioMute(int index) override;
   void ShowContextMenuForTab(Tab* tab,
                              const gfx::Point& p,
                              ui::MenuSourceType source_type) override;
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 9fb2bad..5d06416 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -28,7 +28,7 @@
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/tabs/alert_indicator_button.h"
+#include "chrome/browser/ui/views/tabs/alert_indicator.h"
 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_close_button.h"
 #include "chrome/browser/ui/views/tabs/tab_controller.h"
@@ -401,8 +401,8 @@
   icon_ = new TabIcon;
   AddChildView(icon_);
 
-  alert_indicator_button_ = new AlertIndicatorButton(this);
-  AddChildView(alert_indicator_button_);
+  alert_indicator_ = new AlertIndicator(this);
+  AddChildView(alert_indicator_);
 
   // Unretained is safe here because this class outlives its close button, and
   // the controller outlives this Tab.
@@ -455,10 +455,8 @@
 }
 
 void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) {
-  if (!alert_indicator_button_ || !alert_indicator_button_->visible())
+  if (!alert_indicator_ || !alert_indicator_->visible())
     base::RecordAction(UserMetricsAction("CloseTab_NoAlertIndicator"));
-  else if (alert_indicator_button_->enabled())
-    base::RecordAction(UserMetricsAction("CloseTab_MuteToggleAvailable"));
   else if (data_.alert_state == TabAlertState::AUDIO_PLAYING)
     base::RecordAction(UserMetricsAction("CloseTab_AudioIndicator"));
   else
@@ -579,15 +577,15 @@
                      : kAlertIndicatorCloseButtonPadding;
       }
     }
-    const gfx::Size image_size = alert_indicator_button_->GetPreferredSize();
+    const gfx::Size image_size = alert_indicator_->GetPreferredSize();
     gfx::Rect bounds(
         std::max(contents_rect.x(), right - image_size.width()),
         contents_rect.y() + Center(contents_rect.height(), image_size.height()),
         image_size.width(), image_size.height());
     MaybeAdjustLeftForPinnedTab(&bounds, bounds.width());
-    alert_indicator_button_->SetBoundsRect(bounds);
+    alert_indicator_->SetBoundsRect(bounds);
   }
-  alert_indicator_button_->SetVisible(showing_alert_indicator_);
+  alert_indicator_->SetVisible(showing_alert_indicator_);
 
   // Size the title to fill the remaining width and use all available height.
   bool show_title = ShouldRenderAsNormalTab();
@@ -604,7 +602,7 @@
     }
     int title_right = contents_rect.right();
     if (showing_alert_indicator_) {
-      title_right = alert_indicator_button_->x() - after_title_padding;
+      title_right = alert_indicator_->x() - after_title_padding;
     } else if (showing_close_button_) {
       // Allow the title to overlay the close button's empty border padding.
       title_right = close_x - after_title_padding;
@@ -723,11 +721,6 @@
     // selection. Reset it now to handle the case where multiple tabs were
     // selected.
     controller_->SelectTab(this);
-
-    if (alert_indicator_button_ && alert_indicator_button_->visible() &&
-        alert_indicator_button_->bounds().Contains(event.location())) {
-      base::RecordAction(UserMetricsAction("TabAlertIndicator_Clicked"));
-    }
   }
 }
 
@@ -795,7 +788,7 @@
 bool Tab::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
   // Note: Anything that affects the tooltip text should be accounted for when
   // calling TooltipTextChanged() from Tab::SetData().
-  *tooltip = chrome::AssembleTabTooltipText(data_.title, data_.alert_state);
+  *tooltip = GetTooltipText(data_.title, data_.alert_state);
   return !tooltip->empty();
 }
 
@@ -906,7 +899,6 @@
 void Tab::ActiveStateChanged() {
   UpdateTabIconNeedsAttentionBlocked();
   UpdateForegroundColors();
-  alert_indicator_button_->UpdateEnabledForMuteToggle();
   Layout();
 }
 
@@ -955,7 +947,7 @@
   title_->SetText(title);
 
   if (data_.alert_state != old.alert_state)
-    alert_indicator_button_->TransitionToAlertState(data_.alert_state);
+    alert_indicator_->TransitionToAlertState(data_.alert_state);
   if (old.pinned != data_.pinned)
     showing_alert_indicator_ = false;
 
@@ -1006,18 +998,6 @@
              : controller_->GetStrokeThickness();
 }
 
-int Tab::GetWidthOfLargestSelectableRegion() const {
-  // Assume the entire region to the left of the alert indicator and/or close
-  // buttons is available for click-to-select.  If neither are visible, the
-  // entire tab region is available.
-  const int indicator_left = alert_indicator_button_->visible()
-                                 ? alert_indicator_button_->x()
-                                 : width();
-  const int close_button_left =
-      close_button_->visible() ? close_button_->x() : width();
-  return std::min(indicator_left, close_button_left);
-}
-
 gfx::Insets Tab::GetContentsInsets() const {
   const float stroke_thickness = GetStrokeThickness();
   return GetContentsHorizontalInsets() +
@@ -1061,6 +1041,14 @@
 }
 
 // static
+int Tab::GetTabSeparatorHeight() {
+  constexpr int kTabSeparatorHeight = 20;
+  constexpr int kTabSeparatorTouchHeight = 24;
+  return MD::IsTouchOptimizedUiEnabled() ? kTabSeparatorTouchHeight
+                                         : kTabSeparatorHeight;
+}
+
+// static
 int Tab::GetCornerRadius() {
   return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
       views::EMPHASIS_HIGH);
@@ -1078,11 +1066,52 @@
 }
 
 // static
-int Tab::GetTabSeparatorHeight() {
-  constexpr int kTabSeparatorHeight = 20;
-  constexpr int kTabSeparatorTouchHeight = 24;
-  return MD::IsTouchOptimizedUiEnabled() ? kTabSeparatorTouchHeight
-                                         : kTabSeparatorHeight;
+base::string16 Tab::GetTooltipText(const base::string16& title,
+                                   TabAlertState alert_state) {
+  if (alert_state == TabAlertState::NONE)
+    return title;
+
+  base::string16 result = title;
+  if (!result.empty())
+    result.append(1, '\n');
+  switch (alert_state) {
+    case TabAlertState::AUDIO_PLAYING:
+      result.append(
+          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_PLAYING));
+      break;
+    case TabAlertState::AUDIO_MUTING:
+      result.append(
+          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_MUTING));
+      break;
+    case TabAlertState::MEDIA_RECORDING:
+      result.append(l10n_util::GetStringUTF16(
+          IDS_TOOLTIP_TAB_ALERT_STATE_MEDIA_RECORDING));
+      break;
+    case TabAlertState::TAB_CAPTURING:
+      result.append(
+          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_TAB_CAPTURING));
+      break;
+    case TabAlertState::BLUETOOTH_CONNECTED:
+      result.append(l10n_util::GetStringUTF16(
+          IDS_TOOLTIP_TAB_ALERT_STATE_BLUETOOTH_CONNECTED));
+      break;
+    case TabAlertState::USB_CONNECTED:
+      result.append(
+          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_USB_CONNECTED));
+      break;
+    case TabAlertState::PIP_PLAYING:
+      result.append(
+          l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_PIP_PLAYING));
+      break;
+    case TabAlertState::DESKTOP_CAPTURING:
+      result.append(l10n_util::GetStringUTF16(
+          IDS_TOOLTIP_TAB_ALERT_STATE_DESKTOP_CAPTURING));
+      break;
+    case TabAlertState::NONE:
+      NOTREACHED();
+      break;
+  }
+  return result;
 }
 
 void Tab::MaybeAdjustLeftForPinnedTab(gfx::Rect* bounds,
@@ -1356,8 +1385,8 @@
 
   const bool has_favicon = data().show_icon;
   const bool has_alert_icon =
-      (alert_indicator_button_ ? alert_indicator_button_->showing_alert_state()
-                               : data().alert_state) != TabAlertState::NONE;
+      (alert_indicator_ ? alert_indicator_->showing_alert_state()
+                        : data().alert_state) != TabAlertState::NONE;
 
   if (data().pinned) {
     // When the tab is pinned, we can show one of the two icons; the alert icon
@@ -1372,8 +1401,7 @@
 
   const bool is_touch_optimized = MD::IsTouchOptimizedUiEnabled();
   const int favicon_width = gfx::kFaviconSize;
-  const int alert_icon_width =
-      alert_indicator_button_->GetPreferredSize().width();
+  const int alert_icon_width = alert_indicator_->GetPreferredSize().width();
   // In case of touch optimized UI, the close button has an extra padding on the
   // left that needs to be considered.
   const int close_button_width =
@@ -1647,7 +1675,7 @@
 
   if (button_color_ != generated_icon_color) {
     button_color_ = generated_icon_color;
-    alert_indicator_button_->OnParentTabButtonColorChanged();
+    alert_indicator_->OnParentTabButtonColorChanged();
   }
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index c124d80..45db570 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -25,7 +25,7 @@
 #include "ui/views/masked_targeter_delegate.h"
 #include "ui/views/view.h"
 
-class AlertIndicatorButton;
+class AlertIndicator;
 class TabCloseButton;
 class TabController;
 class TabIcon;
@@ -183,10 +183,6 @@
   // Returns the thickness of the stroke drawn below the tab.
   float GetBottomStrokeThickness(bool should_paint_as_active = false) const;
 
-  // Returns the width of the largest part of the tab that is available for the
-  // user to click to select/activate the tab.
-  int GetWidthOfLargestSelectableRegion() const;
-
   // Returns the insets to use for laying out tab contents.
   gfx::Insets GetContentsInsets() const;
 
@@ -222,8 +218,14 @@
   // Returns the overlap between adjacent tabs.
   static int GetOverlap();
 
+  // Returns the text to show in a tab's tooltip: The contents |title|, followed
+  // by a break, followed by a localized string describing the |alert_state|.
+  // Exposed publicly for tests.
+  static base::string16 GetTooltipText(const base::string16& title,
+                                       TabAlertState alert_state);
+
  private:
-  friend class AlertIndicatorButtonTest;
+  friend class AlertIndicatorTest;
   friend class TabTest;
   friend class TabStripTest;
   FRIEND_TEST_ALL_PREFIXES(TabStripTest, TabCloseButtonVisibilityWhenStacked);
@@ -327,7 +329,7 @@
   scoped_refptr<gfx::AnimationContainer> animation_container_;
 
   TabIcon* icon_ = nullptr;
-  AlertIndicatorButton* alert_indicator_button_ = nullptr;
+  AlertIndicator* alert_indicator_ = nullptr;
   TabCloseButton* close_button_ = nullptr;
 
   views::Label* title_;
diff --git a/chrome/browser/ui/views/tabs/tab_controller.h b/chrome/browser/ui/views/tabs/tab_controller.h
index 617588c..593834995 100644
--- a/chrome/browser/ui/views/tabs/tab_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_controller.h
@@ -67,9 +67,6 @@
   // Closes the tab.
   virtual void CloseTab(Tab* tab, CloseTabSource source) = 0;
 
-  // Toggles whether tab-wide audio muting is active.
-  virtual void ToggleTabAudioMute(Tab* tab) = 0;
-
   // Shows a context menu for the tab at the specified point in screen coords.
   virtual void ShowContextMenuForTab(Tab* tab,
                                      const gfx::Point& p,
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 3234b745..f815caf 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -14,9 +14,11 @@
 
 #include "base/compiler_specific.h"
 #include "base/containers/adapters.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
+#include "base/no_destructor.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -91,8 +93,6 @@
 int g_drop_indicator_width = 0;
 int g_drop_indicator_height = 0;
 
-TabSizeInfo* g_tab_size_info = nullptr;
-
 // Animation delegate used for any automatic tab movement.  Hides the tab if it
 // is not fully visible within the tabstrip area, to prevent overflow clipping.
 class TabAnimationDelegate : public gfx::AnimationDelegate {
@@ -154,6 +154,14 @@
   AnimationEnded(animation);
 }
 
+base::flat_map<ui::MaterialDesignController::Mode, TabSizeInfo>*
+GetTabSizeInfoMap() {
+  static base::NoDestructor<
+      base::flat_map<ui::MaterialDesignController::Mode, TabSizeInfo>>
+      tab_size_info_map;
+  return tab_size_info_map.get();
+}
+
 // If |dest| contains the point |point_in_source| the event handler from |dest|
 // is returned. Otherwise returns null.
 views::View* ConvertPointToViewAndGetEventHandler(
@@ -185,17 +193,19 @@
 }
 
 const TabSizeInfo& GetTabSizeInfo() {
-  if (g_tab_size_info)
-    return *g_tab_size_info;
+  TabSizeInfo& tab_size_info =
+      (*GetTabSizeInfoMap())[ui::MaterialDesignController::GetMode()];
 
-  g_tab_size_info = new TabSizeInfo;
-  g_tab_size_info->pinned_tab_width = Tab::GetPinnedWidth();
-  g_tab_size_info->min_active_width = Tab::GetMinimumActiveWidth();
-  g_tab_size_info->min_inactive_width = Tab::GetMinimumInactiveWidth();
-  g_tab_size_info->standard_size =
+  if (!tab_size_info.standard_size.IsEmpty())
+    return tab_size_info;
+
+  tab_size_info.pinned_tab_width = Tab::GetPinnedWidth();
+  tab_size_info.min_active_width = Tab::GetMinimumActiveWidth();
+  tab_size_info.min_inactive_width = Tab::GetMinimumInactiveWidth();
+  tab_size_info.standard_size =
       gfx::Size(Tab::GetStandardWidth(), GetLayoutConstant(TAB_HEIGHT));
-  g_tab_size_info->tab_overlap = Tab::GetOverlap();
-  return *g_tab_size_info;
+  tab_size_info.tab_overlap = Tab::GetOverlap();
+  return tab_size_info;
 }
 
 int GetStackableTabWidth() {
@@ -885,12 +895,6 @@
   controller_->CloseTab(model_index, source);
 }
 
-void TabStrip::ToggleTabAudioMute(Tab* tab) {
-  int model_index = GetModelIndexOfTab(tab);
-  if (IsValidModelIndex(model_index))
-    controller_->ToggleTabAudioMute(model_index);
-}
-
 void TabStrip::ShowContextMenuForTab(Tab* tab,
                                      const gfx::Point& p,
                                      ui::MenuSourceType source_type) {
@@ -2304,10 +2308,7 @@
 
 // static
 void TabStrip::ResetTabSizeInfoForTesting() {
-  if (g_tab_size_info) {
-    delete g_tab_size_info;
-    g_tab_size_info = nullptr;
-  }
+  *GetTabSizeInfoMap() = {};
 }
 
 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index eec04e4..5bdf393 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -221,7 +221,6 @@
   void ToggleSelected(Tab* tab) override;
   void AddSelectionFromAnchorTo(Tab* tab) override;
   void CloseTab(Tab* tab, CloseTabSource source) override;
-  void ToggleTabAudioMute(Tab* tab) override;
   void ShowContextMenuForTab(Tab* tab,
                              const gfx::Point& p,
                              ui::MenuSourceType source_type) override;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_controller.h b/chrome/browser/ui/views/tabs/tab_strip_controller.h
index f8ac993d..16407c8 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_controller.h
@@ -66,9 +66,6 @@
   // Closes the tab at the specified index in the model.
   virtual void CloseTab(int index, CloseTabSource source) = 0;
 
-  // Toggles audio muting for the tab at the specified index in the model.
-  virtual void ToggleTabAudioMute(int index) = 0;
-
   // Shows a context menu for the tab at the specified point in screen coords.
   virtual void ShowContextMenuForTab(Tab* tab,
                                      const gfx::Point& p,
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index 63a5865e..093d24a 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -25,6 +25,7 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/path.h"
 #include "ui/gfx/skia_util.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/view.h"
 #include "ui/views/view_targeter.h"
 #include "ui/views/widget/widget.h"
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index 444e3f9..0e6048b 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -10,10 +10,12 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
-#include "chrome/browser/ui/views/tabs/alert_indicator_button.h"
+#include "chrome/browser/ui/views/tabs/alert_indicator.h"
+#include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_close_button.h"
 #include "chrome/browser/ui/views/tabs/tab_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_icon.h"
+#include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/grit/theme_resources.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -52,7 +54,6 @@
   void ToggleSelected(Tab* tab) override {}
   void AddSelectionFromAnchorTo(Tab* tab) override {}
   void CloseTab(Tab* tab, CloseTabSource source) override {}
-  void ToggleTabAudioMute(Tab* tab) override {}
   void ShowContextMenuForTab(Tab* tab,
                              const gfx::Point& p,
                              ui::MenuSourceType source_type) override {}
@@ -144,8 +145,8 @@
 
   static views::Label* GetTabTitle(const Tab& tab) { return tab.title_; }
 
-  static views::ImageButton* GetAlertIndicator(const Tab& tab) {
-    return tab.alert_indicator_button_;
+  static views::ImageView* GetAlertIndicator(const Tab& tab) {
+    return tab.alert_indicator_;
   }
 
   static views::ImageButton* GetCloseButton(const Tab& tab) {
@@ -286,10 +287,10 @@
   static void StopFadeAnimationIfNecessary(const Tab& tab) {
     // Stop the fade animation directly instead of waiting an unknown number of
     // seconds.
-    if (gfx::Animation* fade_animation =
-            tab.alert_indicator_button_->fade_animation_.get()) {
+    gfx::Animation* fade_animation =
+        tab.alert_indicator_->fade_animation_.get();
+    if (fade_animation)
       fade_animation->Stop();
-    }
   }
 
  protected:
@@ -302,16 +303,74 @@
 
  private:
   static gfx::Rect GetAlertIndicatorBounds(const Tab& tab) {
-    if (!tab.alert_indicator_button_) {
+    if (!tab.alert_indicator_) {
       ADD_FAILURE();
       return gfx::Rect();
     }
-    return tab.alert_indicator_button_->bounds();
+    return tab.alert_indicator_->bounds();
   }
 
   std::string original_locale_;
 };
 
+class AlertIndicatorTest : public ChromeViewsTestBase {
+ public:
+  AlertIndicatorTest() {}
+  ~AlertIndicatorTest() override {}
+
+  void SetUp() override {
+    ChromeViewsTestBase::SetUp();
+
+    controller_ = new FakeBaseTabStripController;
+    tab_strip_ = new TabStrip(std::unique_ptr<TabStripController>(controller_));
+    controller_->set_tab_strip(tab_strip_);
+    // The tab strip must be added to the view hierarchy for it to create the
+    // buttons.
+    parent_.AddChildView(tab_strip_);
+    parent_.set_owned_by_client();
+
+    widget_.reset(new views::Widget);
+    views::Widget::InitParams init_params =
+        CreateParams(views::Widget::InitParams::TYPE_POPUP);
+    init_params.ownership =
+        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    init_params.bounds = gfx::Rect(0, 0, 400, 400);
+    widget_->Init(init_params);
+    widget_->SetContentsView(&parent_);
+  }
+
+  void TearDown() override {
+    // All windows need to be closed before tear down.
+    widget_.reset();
+
+    ChromeViewsTestBase::TearDown();
+  }
+
+ protected:
+  bool showing_close_button(Tab* tab) const {
+    return tab->showing_close_button_;
+  }
+  bool showing_icon(Tab* tab) const { return tab->showing_icon_; }
+  bool showing_alert_indicator(Tab* tab) const {
+    return tab->showing_alert_indicator_;
+  }
+
+  void StopAnimation(Tab* tab) {
+    ASSERT_TRUE(tab->alert_indicator_->fade_animation_);
+    tab->alert_indicator_->fade_animation_->Stop();
+  }
+
+  // Owned by TabStrip.
+  FakeBaseTabStripController* controller_ = nullptr;
+  // Owns |tab_strip_|.
+  views::View parent_;
+  TabStrip* tab_strip_ = nullptr;
+  std::unique_ptr<views::Widget> widget_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AlertIndicatorTest);
+};
+
 TEST_F(TabTest, HitTestTopPixel) {
   Widget widget;
   InitWidget(&widget);
@@ -397,8 +456,7 @@
 }
 
 // Regression test for http://crbug.com/420313: Confirms that any child Views of
-// Tab do not attempt to provide their own tooltip behavior/text. It also tests
-// that Tab provides the expected tooltip text (according to tab_utils).
+// Tab do not attempt to provide their own tooltip behavior/text.
 TEST_F(TabTest, TooltipProvidedByTab) {
   Widget widget;
   InitWidget(&widget);
@@ -424,10 +482,11 @@
   for (int i = 0; i < 2; ++i) {
     data.alert_state =
         (i == 0 ? TabAlertState::NONE : TabAlertState::AUDIO_PLAYING);
-    SCOPED_TRACE(::testing::Message()
-                 << "Tab with alert indicator state "
-                 << static_cast<uint8_t>(data.alert_state));
+    SCOPED_TRACE(::testing::Message() << "Tab with alert indicator state "
+                                      << static_cast<int>(data.alert_state));
     tab.SetData(data);
+    const base::string16 expected_tooltip =
+        Tab::GetTooltipText(data.title, data.alert_state);
 
     for (int j = 0; j < tab.child_count(); ++j) {
       views::View& child = *tab.child_at(j);
@@ -443,10 +502,8 @@
       const gfx::Point mouse_hover_point =
           midpoint + child.GetMirroredPosition().OffsetFromOrigin();
       base::string16 tooltip;
-      EXPECT_TRUE(static_cast<views::View&>(tab).GetTooltipText(
-          mouse_hover_point, &tooltip));
-      EXPECT_EQ(chrome::AssembleTabTooltipText(data.title, data.alert_state),
-                tooltip);
+      EXPECT_TRUE(tab.GetTooltipText(mouse_hover_point, &tooltip));
+      EXPECT_EQ(expected_tooltip, tooltip);
     }
   }
 }
@@ -752,3 +809,45 @@
     }
   }
 }
+
+// This test verifies that the tab has its icon state updated when the alert
+// animation fade-out finishes.
+TEST_F(AlertIndicatorTest, ShowsAndHidesAlertIndicator) {
+  controller_->AddPinnedTab(0, false);
+  controller_->AddTab(1, true);
+  Tab* media_tab = tab_strip_->tab_at(0);
+
+  // Pinned inactive tab only has an icon.
+  EXPECT_TRUE(showing_icon(media_tab));
+  EXPECT_FALSE(showing_alert_indicator(media_tab));
+  EXPECT_FALSE(showing_close_button(media_tab));
+
+  TabRendererData start_media;
+  start_media.alert_state = TabAlertState::AUDIO_PLAYING;
+  start_media.pinned = media_tab->data().pinned;
+  media_tab->SetData(std::move(start_media));
+
+  // When audio starts, pinned inactive tab shows indicator.
+  EXPECT_FALSE(showing_icon(media_tab));
+  EXPECT_TRUE(showing_alert_indicator(media_tab));
+  EXPECT_FALSE(showing_close_button(media_tab));
+
+  TabRendererData stop_media;
+  stop_media.alert_state = TabAlertState::NONE;
+  stop_media.pinned = media_tab->data().pinned;
+  media_tab->SetData(std::move(stop_media));
+
+  // When audio ends, pinned inactive tab fades out indicator.
+  EXPECT_FALSE(showing_icon(media_tab));
+  EXPECT_TRUE(showing_alert_indicator(media_tab));
+  EXPECT_FALSE(showing_close_button(media_tab));
+
+  // Rather than flakily waiting some unknown number of seconds for the fade
+  // out animation to stop, reach out and stop the fade animation directly,
+  // to make sure that it updates the tab appropriately when it's done.
+  StopAnimation(media_tab);
+
+  EXPECT_TRUE(showing_icon(media_tab));
+  EXPECT_FALSE(showing_alert_indicator(media_tab));
+  EXPECT_FALSE(showing_close_button(media_tab));
+}
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index b303bb6..91e0c60 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -54,8 +54,7 @@
 bool BrowserAppMenuButton::g_open_app_immediately_for_testing = false;
 
 BrowserAppMenuButton::BrowserAppMenuButton(ToolbarView* toolbar_view)
-    : AppMenuButton(toolbar_view),
-      toolbar_view_(toolbar_view) {
+    : AppMenuButton(toolbar_view), toolbar_view_(toolbar_view) {
   SetInkDropMode(InkDropMode::ON);
   SetFocusPainter(nullptr);
   SetHorizontalAlignment(gfx::ALIGN_CENTER);
@@ -73,6 +72,8 @@
   const int radii = ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
       views::EMPHASIS_MAXIMUM, gfx::Size(size, size));
   set_ink_drop_corner_radii(radii, radii);
+
+  md_observer_.Add(ui::MaterialDesignController::GetInstance());
 }
 
 BrowserAppMenuButton::~BrowserAppMenuButton() {}
@@ -241,6 +242,11 @@
   InvalidateLayout();
 }
 
+void BrowserAppMenuButton::OnMdModeChanged() {
+  UpdateIcon(false);
+  PreferredSizeChanged();
+}
+
 void BrowserAppMenuButton::AnimateIconIfPossible(bool with_delay) {
   if (!new_icon_ || !should_use_new_icon_ ||
       severity_ == AppMenuIconController::Severity::NONE) {
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
index 052fe35..7370ba0 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/toolbar/app_menu_icon_controller.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
+#include "ui/base/material_design/material_design_controller_observer.h"
 #include "ui/views/controls/animated_icon_view.h"
 #include "ui/views/view.h"
 
@@ -26,7 +27,8 @@
 // The app menu button in the main browser window (as opposed to hosted app
 // windows, which is implemented in HostedAppMenuButton).
 class BrowserAppMenuButton : public AppMenuButton,
-                             public TabStripModelObserver {
+                             public TabStripModelObserver,
+                             public ui::MaterialDesignControllerObserver {
  public:
   explicit BrowserAppMenuButton(ToolbarView* toolbar_view);
   ~BrowserAppMenuButton() override;
@@ -70,6 +72,10 @@
   // Used only in testing.
   static bool g_open_app_immediately_for_testing;
 
+ protected:
+  // ui::MaterialDesignControllerObserver:
+  void OnMdModeChanged() override;
+
  private:
   // Animates the icon if possible. The icon will not animate if the severity
   // level is none, |animation_| is nullptr or |should_use_new_icon_| is false.
@@ -123,6 +129,10 @@
   // a maximized state to extend to the full window width.
   int margin_trailing_ = 0;
 
+  ScopedObserver<ui::MaterialDesignController,
+                 ui::MaterialDesignControllerObserver>
+      md_observer_{this};
+
   // Used to spawn weak pointers for delayed tasks to open the overflow menu.
   base::WeakPtrFactory<BrowserAppMenuButton> weak_factory_{this};
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index 398bbc0..91b751444 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -38,7 +38,6 @@
       model_(std::move(model)),
       tab_strip_model_(tab_strip_model),
       trigger_menu_on_long_press_(trigger_menu_on_long_press),
-      layout_insets_(GetLayoutInsets(TOOLBAR_BUTTON)),
       show_menu_factory_(this) {
   set_has_ink_drop_action_on_click(true);
   set_context_menu_controller(this);
@@ -92,7 +91,9 @@
     SetEnabledTextColors(*highlight_color_);
   }
 
-  gfx::Insets insets = layout_insets_ + gfx::Insets(0, leading_margin_, 0, 0);
+  gfx::Insets insets = (layout_insets_ ? layout_insets_.value()
+                                       : GetLayoutInsets(TOOLBAR_BUTTON)) +
+                       gfx::Insets(0, leading_margin_, 0, 0);
   if (highlight_color_)
     insets += gfx::Insets(0, ink_drop_large_corner_radius() / 2, 0, 0);
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index d02bb15e..1075824 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/controls/button/button.h"
@@ -136,11 +137,11 @@
   // to extend to the full window width.
   int leading_margin_ = 0;
 
-  // Base layout insets (normally GetLayoutInsets(TOOLBAR_BUTTON)) that are used
-  // for the button. This is overridable as AvatarToolbarButton uses smaller
-  // insets to accomodate for a larger avatar avatar icon. |leading_margin_| and
-  // |ink_drop_large_corner_radius()| are also used to calculate final insets.
-  gfx::Insets layout_insets_;
+  // Base layout insets that are used for the button. This is overridable as
+  // AvatarToolbarButton uses smaller insets to accommodate for a larger avatar
+  // avatar icon. |leading_margin_| and |ink_drop_large_corner_radius()| are
+  // also used to calculate final insets.
+  base::Optional<gfx::Insets> layout_insets_;
 
   // A highlight color is used to signal error states. When set this color is
   // used as a base for background, text and ink drops. When not set, uses the
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 2e209be..149ff54 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -127,6 +127,7 @@
   chrome::AddCommandObserver(browser_, IDC_LOAD_NEW_TAB_PAGE, this);
 
   UpgradeDetector::GetInstance()->AddObserver(this);
+  md_observer_.Add(ui::MaterialDesignController::GetInstance());
 }
 
 ToolbarView::~ToolbarView() {
@@ -680,6 +681,12 @@
   location_bar_->SetFullKeyboardAcessibilityMode(false);
 }
 
+// ui::MaterialDesignControllerObserver:
+void ToolbarView::OnMdModeChanged() {
+  LoadImages();
+  PreferredSizeChanged();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // ToolbarView, private:
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index ae7beb3..a5d60a07 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -10,6 +10,7 @@
 
 #include "base/macros.h"
 #include "base/observer_list.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/command_observer.h"
 #include "chrome/browser/ui/toolbar/app_menu_icon_controller.h"
 #include "chrome/browser/ui/toolbar/back_forward_menu_model.h"
@@ -23,6 +24,7 @@
 #include "components/translate/core/browser/translate_step.h"
 #include "components/translate/core/common/translate_errors.h"
 #include "ui/base/accelerators/accelerator.h"
+#include "ui/base/material_design/material_design_controller_observer.h"
 #include "ui/views/accessible_pane_view.h"
 #include "ui/views/controls/button/menu_button.h"
 #include "ui/views/controls/button/menu_button_listener.h"
@@ -62,7 +64,8 @@
                     public AppMenuIconController::Delegate,
                     public UpgradeObserver,
                     public ToolbarButtonProvider,
-                    public BrowserRootView::DropTarget {
+                    public BrowserRootView::DropTarget,
+                    public ui::MaterialDesignControllerObserver {
  public:
   // The view class name.
   static const char kViewClassName[];
@@ -175,6 +178,9 @@
   bool SetPaneFocusAndFocusDefault() override;
   void RemovePaneFocus() override;
 
+  // ui::MaterialDesignControllerObserver:
+  void OnMdModeChanged() override;
+
   bool is_display_mode_normal() const {
     return display_mode_ == DISPLAYMODE_NORMAL;
   }
@@ -250,6 +256,10 @@
   // The display mode used when laying out the toolbar.
   const DisplayMode display_mode_;
 
+  ScopedObserver<ui::MaterialDesignController,
+                 ui::MaterialDesignControllerObserver>
+      md_observer_{this};
+
   // Whether this toolbar has been initialized.
   bool initialized_ = false;
 
diff --git a/chrome/browser/ui/views_mode_controller.cc b/chrome/browser/ui/views_mode_controller.cc
index 9c641bd9..ffe958a 100644
--- a/chrome/browser/ui/views_mode_controller.cc
+++ b/chrome/browser/ui/views_mode_controller.cc
@@ -12,7 +12,9 @@
 namespace views_mode_controller {
 
 bool IsViewsBrowserCocoa() {
-  return features::IsViewsBrowserCocoa();
+  // TODO(https://crbug.com/832676): Delete all code guarded on this function
+  // returning true and then remove this function.
+  return false;
 }
 
 }  // namespace views_mode_controller
diff --git a/chrome/browser/ui/views_mode_controller.h b/chrome/browser/ui/views_mode_controller.h
index 9c3fadd..f9833125 100644
--- a/chrome/browser/ui/views_mode_controller.h
+++ b/chrome/browser/ui/views_mode_controller.h
@@ -14,7 +14,9 @@
 namespace views_mode_controller {
 
 // Returns whether a Views-capable browser build should use the Cocoa browser
-// UI.
+// UI. Always returns false.
+// TODO(https://crbug.com/832676): Delete all code guarded on this function
+// returning true and then remove this function.
 bool IsViewsBrowserCocoa();
 
 }  // namespace views_mode_controller
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_localized_strings_provider.cc
index edd2778..e80a1cf 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_localized_strings_provider.cc
@@ -37,6 +37,7 @@
   int id;
 } const kLocalizedStringsWithoutPlaceholders[] = {
     {"accept", IDS_MULTIDEVICE_SETUP_ACCEPT_LABEL},
+    {"back", IDS_MULTIDEVICE_SETUP_BACK_LABEL},
     {"cancel", IDS_CANCEL},
     {"done", IDS_DONE},
     {"passwordPageHeader", IDS_MULTIDEVICE_SETUP_PASSWORD_PAGE_HEADER},
diff --git a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc b/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc
index e600e7e7..90123fb 100644
--- a/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc
+++ b/chrome/browser/ui/webui/quota_internals/quota_internals_proxy.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/webui/quota_internals/quota_internals_types.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "net/base/url_util.h"
+#include "url/origin.h"
 
 using blink::mojom::StorageType;
 using content::BrowserThread;
@@ -138,12 +139,11 @@
   std::vector<PerOriginStorageInfo> origin_info;
   origin_info.reserve(entries.size());
 
-  typedef OriginInfoTableEntries::const_iterator iterator;
-  for (iterator itr(entries.begin()); itr != entries.end(); ++itr) {
-    PerOriginStorageInfo info(itr->origin, itr->type);
-    info.set_used_count(itr->used_count);
-    info.set_last_access_time(itr->last_access_time);
-    info.set_last_modified_time(itr->last_modified_time);
+  for (const auto& entry : entries) {
+    PerOriginStorageInfo info(entry.origin.GetURL(), entry.type);
+    info.set_used_count(entry.used_count);
+    info.set_last_access_time(entry.last_access_time);
+    info.set_last_modified_time(entry.last_modified_time);
 
     origin_info.push_back(info);
   }
@@ -175,7 +175,7 @@
 void QuotaInternalsProxy::RequestPerOriginInfo(StorageType type) {
   DCHECK(quota_manager_.get());
 
-  std::set<GURL> origins;
+  std::set<url::Origin> origins;
   quota_manager_->GetCachedOrigins(type, &origins);
 
   std::vector<PerOriginStorageInfo> origin_info;
@@ -184,13 +184,12 @@
   std::set<std::string> hosts;
   std::vector<PerHostStorageInfo> host_info;
 
-  for (std::set<GURL>::iterator itr(origins.begin());
-       itr != origins.end(); ++itr) {
-    PerOriginStorageInfo info(*itr, type);
-    info.set_in_use(quota_manager_->IsOriginInUse(*itr));
+  for (const url::Origin& origin : origins) {
+    PerOriginStorageInfo info(origin.GetURL(), type);
+    info.set_in_use(quota_manager_->IsOriginInUse(origin));
     origin_info.push_back(info);
 
-    std::string host(net::GetHostOrSpecFromURL(*itr));
+    std::string host(net::GetHostOrSpecFromURL(origin.GetURL()));
     if (hosts.insert(host).second) {
       PerHostStorageInfo info(host, type);
       host_info.push_back(info);
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index bc10d2f..1dcaf52 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -2648,8 +2648,6 @@
       {"multideviceForgetDevice", IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE},
       {"multideviceForgetDeviceSummary",
        IDS_SETTINGS_MULTIDEVICE_FORGET_THIS_DEVICE_EXPLANATION},
-      {"multideviceForgetDeviceDialogHeading",
-       IDS_SETTINGS_MULTIDEVICE_FORGET_DEVICE_DIALOG_HEADING},
       {"multideviceForgetDeviceDialogMessage",
        IDS_SETTINGS_MULTIDEVICE_FORGET_DEVICE_DIALOG_MESSAGE},
   };
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index 4559bd51..c0a3873a 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -65,6 +65,7 @@
 #include "chrome/browser/chromeos/login/quick_unlock/pin_backend.h"
 #include "components/signin/core/browser/signin_manager_base.h"
 #else
+#include "chrome/browser/signin/signin_util.h"
 #include "chrome/browser/ui/user_manager.h"
 #include "chrome/browser/ui/webui/profile_helper.h"
 #include "components/signin/core/browser/signin_manager.h"
@@ -775,11 +776,12 @@
   bool delete_profile = false;
   args->GetBoolean(0, &delete_profile);
 
-  SigninManager* signin_manager = SigninManagerFactory::GetForProfile(profile_);
-  if (signin_manager->IsSignoutProhibited()) {
+  if (!signin_util::IsUserSignoutAllowedForProfile(profile_)) {
     // If the user cannot signout, the profile must be destroyed.
     DCHECK(delete_profile);
   } else {
+    SigninManager* signin_manager =
+        SigninManagerFactory::GetForProfile(profile_);
     if (signin_manager->IsAuthenticated()) {
       if (GetSyncService())
         ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
@@ -945,7 +947,7 @@
   DCHECK(signin);
 #if !defined(OS_CHROMEOS)
   // Signout is not allowed if the user has policy (crbug.com/172204).
-  if (SigninManagerFactory::GetForProfile(profile_)->IsSignoutProhibited()) {
+  if (!signin_util::IsUserSignoutAllowedForProfile(profile_)) {
     std::string username = signin->GetAuthenticatedAccountInfo().email;
 
     // If there is no one logged in or if the profile name is empty then the
diff --git a/chrome/browser/ui/webui/welcome/nux/BUILD.gn b/chrome/browser/ui/webui/welcome/nux/BUILD.gn
index 02ddf2c..5aae5db 100644
--- a/chrome/browser/ui/webui/welcome/nux/BUILD.gn
+++ b/chrome/browser/ui/webui/welcome/nux/BUILD.gn
@@ -19,6 +19,7 @@
 
     deps = [
       "//chrome/app:generated_resources",
+      "//chrome/browser:resources",
       "//components/resources",
       "//components/strings",
       "//components/variations",
@@ -52,6 +53,7 @@
 
     deps = [
       "//chrome/app:generated_resources",
+      "//chrome/browser:resources",
       "//components/bookmarks/browser",
       "//components/bookmarks/common",
       "//components/favicon/core",
@@ -79,6 +81,7 @@
 
     deps = [
       "//chrome/app:generated_resources",
+      "//chrome/browser:resources",
       "//components/bookmarks/browser",
       "//components/bookmarks/common",
       "//components/favicon/core",
diff --git a/chrome/browser/ui/webui/welcome/nux/email_handler.cc b/chrome/browser/ui/webui/welcome/nux/email_handler.cc
index e8aa7fa1..5e45b1e1 100644
--- a/chrome/browser/ui/webui/welcome/nux/email_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/email_handler.cc
@@ -10,6 +10,7 @@
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
+#include "chrome/grit/browser_resources.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/favicon/core/favicon_service.h"
diff --git a/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
index cbb480f..4fa31afc 100644
--- a/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
@@ -10,6 +10,7 @@
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
+#include "chrome/grit/browser_resources.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
diff --git a/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
index 2683067..d153e238 100644
--- a/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
@@ -9,6 +9,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/grit/browser_resources.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/grit/components_resources.h"
 #include "components/grit/components_scaled_resources.h"
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 7f8517fa1..da12d89 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -30,6 +30,7 @@
 constexpr base::TimeDelta XrBrowserTestBase::kPollCheckIntervalShort;
 constexpr base::TimeDelta XrBrowserTestBase::kPollCheckIntervalLong;
 constexpr base::TimeDelta XrBrowserTestBase::kPollTimeoutShort;
+constexpr base::TimeDelta XrBrowserTestBase::kPollTimeoutMedium;
 constexpr base::TimeDelta XrBrowserTestBase::kPollTimeoutLong;
 constexpr char XrBrowserTestBase::kVrOverrideEnvVar[];
 constexpr char XrBrowserTestBase::kVrOverrideVal[];
@@ -91,7 +92,7 @@
 void XrBrowserTestBase::LoadUrlAndAwaitInitialization(const GURL& url) {
   ui_test_utils::NavigateToURL(browser(), url);
   EXPECT_TRUE(PollJavaScriptBoolean(
-      "isInitializationComplete()", kPollTimeoutShort,
+      "isInitializationComplete()", kPollTimeoutMedium,
       browser()->tab_strip_model()->GetActiveWebContents()))
       << "Timed out waiting for JavaScript test initialization.";
 }
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index 213ab3839..5efd9e4d 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -34,6 +34,8 @@
       base::TimeDelta::FromMilliseconds(100);
   static constexpr base::TimeDelta kPollTimeoutShort =
       base::TimeDelta::FromMilliseconds(1000);
+  static constexpr base::TimeDelta kPollTimeoutMedium =
+      base::TimeDelta::FromMilliseconds(5000);
   static constexpr base::TimeDelta kPollTimeoutLong =
       base::TimeDelta::FromMilliseconds(10000);
   // Still considered XR-wide instead of VR-specific since OpenVR can be used
diff --git a/chrome/browser/vr/webxr_vr_transition_browser_test.cc b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
index 106544ba..5b4f7cb2 100644
--- a/chrome/browser/vr/webxr_vr_transition_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
@@ -80,14 +80,8 @@
 
 // Tests that WebVR does not return any devices if OpenVR support is disabled.
 // Since WebVR isn't actually used, we can remove the GPU requirement.
-#if defined(OS_WIN)
-#define MAYBE_TestWebVrNoDevicesWithoutOpenVr \
-  DISABLED_TestWebVrNoDevicesWithoutOpenVr
-#else
-#define MAYBE_TestWebVrNoDevicesWithoutOpenVr TestWebVrNoDevicesWithoutOpenVr
-#endif
 IN_PROC_BROWSER_TEST_F(WebVrBrowserTestOpenVrDisabled,
-                       MAYBE_TestWebVrNoDevicesWithoutOpenVr) {
+                       TestWebVrNoDevicesWithoutOpenVr) {
   LoadUrlAndAwaitInitialization(GetHtmlTestFile("generic_webvr_page"));
   EXPECT_FALSE(XrDeviceFound())
       << "Found a VRDisplay even with OpenVR disabled";
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 81d7e2c..7c22b38 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -329,9 +329,6 @@
 const char kEnablePrintPreviewRegisterPromos[] =
     "enable-print-preview-register-promos";
 
-// Enables user control over muting tab audio from the tab strip.
-const char kEnableTabAudioMuting[]  = "enable-tab-audio-muting";
-
 // Enables DevTools server for UI (mus, ash, etc). Value should be the port the
 // server is started on. Default port is 9223.
 const char kEnableUiDevTools[] = "enable-ui-devtools";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index fe990df2..77929830 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -105,7 +105,6 @@
 extern const char kEnablePotentiallyAnnoyingSecurityFeatures[];
 extern const char kEnablePowerOverlay[];
 extern const char kEnablePrintPreviewRegisterPromos[];
-extern const char kEnableTabAudioMuting[];
 extern const char kEnableUiDevTools[];
 extern const char kExtensionContentVerification[];
 extern const char kExtensionContentVerificationBootstrap[];
diff --git a/chrome/common/extensions/api/cryptotoken_private.idl b/chrome/common/extensions/api/cryptotoken_private.idl
index 0e4b70d..99c2e3f6 100644
--- a/chrome/common/extensions/api/cryptotoken_private.idl
+++ b/chrome/common/extensions/api/cryptotoken_private.idl
@@ -17,6 +17,8 @@
     // The AppId (see definition, above) that was used in the registration
     // request and which has been authenticated by |canOriginAssertAppId|.
     DOMString appId;
+    // The origin of the caller.
+    DOMString origin;
     // Identifies the tab in which the registration is occuring so that any
     // permissions prompt is correctly located.
     long tabId;
diff --git a/chrome/common/extensions/chrome_extension_messages.h b/chrome/common/extensions/chrome_extension_messages.h
index 2d2ab50..32de1f09 100644
--- a/chrome/common/extensions/chrome_extension_messages.h
+++ b/chrome/common/extensions/chrome_extension_messages.h
@@ -79,7 +79,7 @@
 
 IPC_STRUCT_BEGIN(ExtensionMsg_AccessibilityEventBundleParams)
   // ID of the accessibility tree that this event applies to.
-  IPC_STRUCT_MEMBER(std::string, tree_id)
+  IPC_STRUCT_MEMBER(ui::AXTreeID, tree_id)
 
   // Zero or more updates to the accessibility tree to apply first.
   IPC_STRUCT_MEMBER(std::vector<ui::AXTreeUpdate>, updates)
@@ -93,7 +93,7 @@
 
 IPC_STRUCT_BEGIN(ExtensionMsg_AccessibilityLocationChangeParams)
   // ID of the accessibility tree that this event applies to.
-  IPC_STRUCT_MEMBER(std::string, tree_id)
+  IPC_STRUCT_MEMBER(ui::AXTreeID, tree_id)
 
   // ID of the object whose location is changing.
   IPC_STRUCT_MEMBER(int, id)
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
index 2a02173..f932966e 100644
--- a/chrome/common/extensions/docs/server2/app.yaml
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -1,5 +1,5 @@
 application: chrome-apps-doc
-version: 3-61-0
+version: 3-62-0
 runtime: python27
 api_version: 1
 threadsafe: false
diff --git a/chrome/common/extensions/docs/static/images/runtime_host_permissions_badging.png b/chrome/common/extensions/docs/static/images/runtime_host_permissions_badging.png
new file mode 100644
index 0000000..c62c81d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/runtime_host_permissions_badging.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/runtime_host_permissions_controls.png b/chrome/common/extensions/docs/static/images/runtime_host_permissions_controls.png
new file mode 100644
index 0000000..b93694bd
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/runtime_host_permissions_controls.png
Binary files differ
diff --git a/chrome/common/extensions/docs/templates/articles/runtime_host_permissions.html b/chrome/common/extensions/docs/templates/articles/runtime_host_permissions.html
new file mode 100644
index 0000000..017bdb54
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/runtime_host_permissions.html
@@ -0,0 +1,191 @@
+<h1>User Controls For Host Permissions: Transition Guide</h1>
+
+<h2 id="summary">Summary</h2>
+
+<h3 id="changes">What's changing?</h3>
+<p>
+  Beginning in Chrome 70, users have the ability to restrict extension host
+  access to a custom list of sites, or to configure extensions to require a
+  click to gain access to the current page.
+</p>
+
+<h3 id="affected-apis">Which APIs are affected?</h3>
+<p>
+  This change affects any APIs that are affected by the host permissions
+  specified in your extension's manifest, as well as content scripts.  APIs
+  that require host permissions include
+  <a href="/webRequest">webRequest</a>,
+  <a href="/cookies">cookies</a>,
+  <a href="/tabs#method-executeScript">tabs.executeScript()</a> and
+  <a href="/tabs#method-insertCSS">tabs.insertCSS()</a>, and performing
+  cross-origin requests, such as through an <code>XMLHTTPRequest</code> or the
+  <code>fetch()</code> API.
+</p>
+
+<h2 id="restricting-access">Restricting Access</h2>
+
+<h3 id="user-restricting-access">How can the user restrict access?</h3>
+<p>
+  Users can choose to allow your extension to run on click, on a specific set of
+  sites, or on all requested sites.  These options are presented to users on the
+  <code>chrome://extensions</code> page as well as in the extension context
+  menu.
+</p>
+
+<img src="{{static}}/images/runtime_host_permissions_controls.png"
+    height="250"
+    alt="A screenshot of the context menu controls for runtime host permissions, including options to run the extension on click, on a specific site, or on all sites.">
+
+<h3 id="access-on-click">
+  What happens if a user chooses to run my extension "on click"?
+</h3>
+<p>
+  The extension essentially behaves as though it used the
+  <a href="/activeTab">activeTab</a> permission. The extension is granted
+  temporary access to any host the user clicks the extension on, if that host
+  was requested by the extension (and isn't a restricted site, like
+  chrome://settings).  When set to run on click, Chrome badges your extension
+  with a circle and drop shadow (see below) to indicate that is requesting
+  access on a particular site.
+</p>
+
+<img src="{{static}}/images/runtime_host_permissions_badging.png"
+    height="150"
+    alt="A screenshot of the badging Chrome adds to the extension icon in the toolbar">
+
+<h3 id="access-on-specific-sites">
+  What happens if a user chooses to run my extension on specific sites?
+</h3>
+<p>
+  Your extension is allowed to run automatically on any sites the user has
+  chosen, and can access the site without further user action.  On other sites
+  that your extension requested, but the user did not grant permission to, the
+  behavior is the same as if the user had set the extension to run on click.
+</p>
+
+<h3 id="access-on-all-sites">
+  What happens if a user chooses to run my extension on all sites?
+</h3>
+<p>
+  The extension can automatically access any sites requested in the manifest.
+</p>
+
+<h2 id="api-behaviors">API Behaviors</h2>
+
+<h3 id="web-request-behavior">Web Request API</h3>
+<p>
+  The extension can still intercept, modify, and block any requests from sites
+  it has access to.  For sites the extension does not have access to, Chrome
+  badges the extension to indicate that the extension requests access to the
+  page.  The user can then grant access to the extension; Chrome then prompts
+  the user to refresh the page to allow your extension to intercept the network
+  requests.
+</p>
+
+<h3 id="scripts-behavior">
+  Content Scripts, tabs.executeScript(), tabs.insertCSS()
+</h3>
+<p>
+  The extension can still inject scripts and style sheets automatically for any
+  sites it has access to. For sites the extension does not have access to,
+  Chrome badges the extension to indicate that the extension requests access to
+  the page.  The user can then grant access to the extension.  If the content
+  script was set to inject at document_idle, the script will inject immediately.
+  Otherwise, Chrome prompts the user to refresh the page to allow your extension
+  to inject scripts earlier in page load (at document_start or document_end).
+  The callbacks for the
+  <a href="/tabs#method-executeScript">tabs.executeScript()</a> and
+  <a href="/tabs#method-insertCSS">tabs.insertCSS()</a> methods are only invoked
+  if the user grants access to the site.
+</p>
+
+<h3 id="background-behavior">Cookies and Background Page XHR</h3>
+<p>
+  The extension can still read and modify any cookies from and perform a
+  cross-origin XHR to sites it has access to.  Because there is no tab
+  associated with an extension page accessing another origin's cookies or
+  XHRing to another host, Chrome does not badge the extension to indicate to the
+  user that the extension is requesting to access a site.  Trying to access a
+  cookie for another site or make a cross-origin XHR will fail with an error as
+  if the extension's manifest did not include the host permission.  For these
+  cases, we encourage you to use optional permissions in order to allow the user
+  to grant runtime access to different sites.
+</p>
+
+<p>The example below illustrates how this may work for the cookies API.</p>
+
+<p>Before:</p>
+<pre data-filename="manifest.json">
+  {
+    ...
+    "permissions": ["cookies", "https://example.com"]
+  }
+</pre>
+
+<pre data-filename="background.js">
+  chrome.cookies.get({url: 'https://example.com', name: 'mycookie'},
+                     function(cookie) {
+                       // Use the cookie.
+                     });
+</pre>
+
+<p>After:</p>
+<pre data-filename="manifest.json">
+  {
+    ...
+    "permissions": ["cookies"],
+    "optional_permissions": ["https://example.com"]
+  }
+</pre>
+
+<pre data-filename="background.js">
+  // Note: permissions.request() requires a user gesture, so this
+  // may only be done in response to a user action.
+  chrome.permissions.request(
+      {origins: ['https://example.com']},
+      function(granted) {
+        if (granted) {
+          chrome.cookies.get({url: 'https://example.com', name: 'mycookie'},
+                             function(cookie) {
+                               // Use the cookie.
+                             });
+        } else {
+          // Handle grant failure
+        }
+      });
+</pre>
+
+<h2 id="migration">Migration</h2>
+
+<h3 id="best-practices">
+  What are best practices to avoid being negatively impacted?
+</h3>
+<p>
+  Extensions can use the <a href="/permissions">optional permissions</a>,
+  <a href="/activeTab">activeTab</a>, and
+  <a href="/declarativeContent">declarativeContent</a> APIs to follow best
+  practices.  Optional permissions are granted at runtime, and allow the
+  extension to request specific access to a site.  The
+  <a href="/activeTab">activeTab</a> permission is not affected, and extensions
+  using it continue to work normally. The
+  <a href="/declarativeContent">declarativeContent</a> API is a substitute for
+  many needs to inject scripts into every page.
+</p>
+
+<h3 id="current-settings">What happens to my current users' settings?</h3>
+<p>
+  This change will not immediately affect any current permissions granted to
+  your extension.  That is, it will continue to operate as before unless the
+  user takes action to restrict the sites it is allowed to access.  In future
+  releases, Chrome will provide more controls to users to adjust settings.
+</p>
+
+<h3 id="checking-access">
+  How can I check if my extension has permission to run on a site?
+</h3>
+<p>
+  You can use the
+  <a href="/permissions/#method-contains">permissions.contains()</a> API in
+  order to check whether your extension has been granted access to a given
+  origin.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/declarativeNetRequest.html b/chrome/common/extensions/docs/templates/intros/declarativeNetRequest.html
new file mode 100644
index 0000000..586d4a9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/declarativeNetRequest.html
@@ -0,0 +1,287 @@
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must declare the <code>"declarativeNetRequest"</code> permission in the
+extension <a href="manifest">manifest</a> to use this API, along with the
+necessary <a href="declare_permissions">host permissions</a>. You must also
+declare the <code>"declarative_net_request"</code> manifest key, which should be
+a dictionary with a single key called <code>"rule_resources"</code>. It should
+be a list containing a single element, the path to a JSON file containing the
+declarative rules to use, as shown below.
+</p>
+
+<pre data-filename="manifest.json">
+{
+  "name": "My extension",
+  ...
+<b>
+  "declarative_net_request" : {
+    "rule_resources" : ["rules.json"]
+  },
+  "permissions": [
+    "declarativeNetRequest",
+    "*://example.com/*"
+  ],</b>
+  ...
+}
+</pre>
+
+<h2 id="rule-resources">Rule Resources</h2>
+
+<p>
+The JSON file specified as part of <code>"rule_resources"</code> in the manifest
+contains a list of rules. The number of rules that an extension can specify is
+bound by the <a href="#property-MAX_NUMBER_OF_RULES">
+chrome.declarativeNetRequest.MAX_NUMBER_OF_RULES</a> constant.
+</p>
+
+<h2 id="rules">Rules</h2>
+
+<p>
+A single declarative rule consists of four fields: <code>id</code>,
+<code>priority</code>, <code>condition</code> and <code>action</code>. See the
+<a href="#type-Rule">Rule</a> section below for more information on the format.
+There are three kinds of rules:
+</p>
+
+<ul>
+  <li>
+    Rules that block a network request.
+  </li>
+  <li>
+    Rules that prevent a request from getting blocked by negating any matching
+    blocked rules.
+  </li>
+  <li>
+    Rules that redirect a network request.
+  </li>
+</ul>
+
+<p>An example rule:</p>
+<pre>
+{
+  "id" : 1,
+  "action" : { "type" : "block" },
+  "condition" : {
+    "urlFilter" : "abc",
+    "domains" : ["foo.com"],
+    "resource_types" : ["script"]
+  }
+}
+</pre>
+
+<p>
+The above rule will block all script requests originating from
+<code>"foo.com"</code> to any URL with <code>"abc"</code> as a substring.
+</p>
+
+<p>
+The <code>urlFilter</code> field of a rule condition is used to specify the
+pattern which is matched against the request URL. Some examples of URL filters:
+</p>
+
+<table>
+  <tr>
+    <th><code><b>urlFilter</b></code></th>
+    <th>Matches</th>
+    <th>Does not match</th>
+  </tr>
+
+  <tr>
+    <td><code>"abc"</code></td>
+    <td>
+      https://abcd.com<br/>
+      https://example.com/abcd
+    </td>
+    <td>http://ab.com</td>
+  </tr>
+
+  <tr>
+    <td><code>"abc*d"</code></td>
+    <td>
+      https://abcd.com<br/>
+      https://example.com/abcxyzd
+    </td>
+    <td>http://abc.com</td>
+  </tr>
+
+  <tr>
+    <td><code>"||a.example.com"</code></td>
+    <td>
+      https://a.example.com/<br/>
+      https://b.a.example.com/xyz
+    </td>
+    <td>http://example.com/</td>
+  </tr>
+
+  <tr>
+    <td><code>"|https*"</code></td>
+    <td>https://example.com</td>
+    <td>
+      http://example.com/<br/>
+      http://https.com
+    </td>
+  </tr>
+
+  <tr>
+    <td><code>"example*^123|"</code></td>
+    <td>
+      https://example.com/123<br/>
+      http://abc.com/example?123
+    </td>
+    <td>
+      https://example.com/1234<br/>
+      https://abc.com/example0123
+    </td>
+  </tr>
+</table>
+
+<h2 id="page-allowlisting">Page allowlisting</h2>
+
+<p>
+To disable the extension ruleset on a particular page, extensions can use the
+<a href="#method-addAllowedPages">
+chrome.declarativeNetRequest.addAllowedPages</a> API. This allows
+extensions to specify match patterns that are compared against the tab URL. Any
+requests originating from these pages are not evaluated against the extension
+ruleset. The number of such patterns an extension can add is bound by the
+<a href="#property-MAX_NUMBER_OF_ALLOWED_PAGES">chrome.declarativeNetRequest.MAX_NUMBER_OF_ALLOWED_PAGES</a> constant.
+</p>
+
+<h2 id="implementation-details">Implementation details</h2>
+
+<h3 id="matching-algorithm">Matching algorithm</h3>
+
+<ul>
+  <li>
+    If any extension ruleset wants to block a request<sup>[1]</sup>, the network
+    request is blocked.
+  </li>
+  <li>
+    If the request is not blocked, the request is redirected if any extension
+    ruleset wants to redirect the request<sup>[2]</sup>. The most recently
+    installed extension gets preference here. If multiple redirect rules from an
+    extension’s ruleset match, the rule with the greatest priority determines
+    the redirect URL.
+  </li>
+</ul>
+
+[1] An extension wants to block a request, if all the following are true:
+<ul>
+  <li>The extension has a matching <code>block</code> rule.</li>
+  <li>The extension has no matching <code>allow</code> rule.</li>
+  <li>
+    The page from which the request originated is not allow-listed by the
+    extension using <a href="#method-addAllowedPages">
+    chrome.declarativeNetRequest.addAllowedPages</a>.
+  </li>
+</ul>
+
+[2] An extension wants to redirect a request, if all the following are true:
+<ul>
+  <li>The extension has a matching <code>redirect</code> rule.</li>
+  <li>
+    The page from which the request originated is not allow-listed by the
+    extension using <a href="#method-addAllowedPages">
+    chrome.declarativeNetRequest.addAllowedPages</a>.
+  </li>
+</ul>
+
+<h3 id="web-request-comparison">
+  Comparison with the <a href="webRequest">webRequest</a> API
+</h3>
+
+<ul>
+  <li>
+    The declarativeNetRequest API allows for evaluating network requests in the
+    browser itself. This makes it more performant than the webRequest API, where
+    each network request is evaluated in JavaScript in the extension process.
+  </li>
+  <li>
+    Because the requests are not intercepted by the extension process,
+    declarativeNetRequest removes the need for extensions to have a background
+    page; resulting in less memory consumption.
+  </li>
+  <li>
+    Unlike the webRequest API, blocking and redirecting requests using the
+    declarativeNetRequest API requires host permissions to both the request URL
+    and the request initiator.
+  </li>
+  <li>
+    The declarativeNetRequest API provides better privacy to users because
+    extensions can't actually read the network requests made on the user's
+    behalf.
+  </li>
+  <li>
+    Unlike the webRequest API, any images or iframes blocked using the
+    declarativeNetRequest API are automatically collapsed in the DOM.
+  </li>
+  <li>
+    While deciding whether a request is to be blocked or redirected, the
+    declarativeNetRequest API is given priority over the webRequest API because
+    it allows for synchronous interception.
+  </li>
+  <li>
+    Evaluation of declarativeNetRequest rulesets is performed during the
+    <code>onBeforeRequest</code> stage of a network request. Extensions using
+    the webRequest API can intercept requests at different stages of the
+    request.
+  </li>
+  <li>
+    The declarativeNetRequest API only allows extensions to block or redirect
+    requests. The webRequest api is more flexible as compared to the
+    declarativeNetRequest API because it allows extensions to evaluate a request
+    programmatically.
+  </li>
+</ul>
+
+<h2 id="examples">Example</h2>
+
+<p><b>manifest.json</b></p>
+<pre data-filename="manifest.json">
+{
+  "name" : "declarativeNetRequest extension",
+  "version" : "1",
+  "declarative_net_request" : {
+    "rule_resources" : ["rules.json"]
+  },
+  "permissions" : ["*://google.com/*", "declarativeNetRequest"],
+  "manifest_version" : 2
+}
+</pre>
+
+<p><b>rules.json</b></p>
+<pre data-filename="rules.json">
+[
+  {
+    "id" : 1,
+    "action": { "type" : "block" },
+    "condition" : {"urlFilter" : "google.com", "resourceTypes" : ["main_frame"] }
+  },
+  {
+    "id" : 2,
+    "action" : { "type" : "allow" },
+    "condition" : { "urlFilter" : "google.com/123", "resourceTypes" : ["main_frame"] }
+  },
+  {
+    "id" : 3,
+    "priority" : 1,
+    "action" : { "type" : "redirect", "redirectUrl" : "https://example.com" },
+    "condition" : { "urlFilter" : "google.com", "resourceTypes" : ["main_frame"] }
+  }
+]
+</pre>
+
+<ul>
+  <li>
+    Consider a navigation to <code>"http://google.com"</code>. Rules with id (1)
+    and (3) match. The request will be blocked because blocking rules have
+    higher priority than redirect rules.
+  </li>
+  <li>
+    Consider a navigation to <code>"http://google.com/1234"</code>. Rules with
+    id (1), (2), and (3) match. Because the request has a matching
+    <code>allow</code> rule, the request is not blocked and instead redirected
+    to <code>"https://example.com"</code>.
+  </li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/json/intro_tables.json b/chrome/common/extensions/docs/templates/json/intro_tables.json
index 11e63c7..aae113b 100644
--- a/chrome/common/extensions/docs/templates/json/intro_tables.json
+++ b/chrome/common/extensions/docs/templates/json/intro_tables.json
@@ -83,6 +83,18 @@
       }
     ]
   },
+  "declarativeNetRequest": {
+    "Permissions": [
+      {
+        "class": "code",
+        "text": "\"declarativeNetRequest\""
+      },
+      {
+        "link": "declare_permissions#host-permissions",
+        "text": "host permissions"
+      }
+    ]
+  },
   "declarativeWebRequest": {
     "Permissions": [
       {
diff --git a/chrome/common/extensions/docs/templates/public/extensions/declarativeNetRequest.html b/chrome/common/extensions/docs/templates/public/extensions/declarativeNetRequest.html
index 4942395..82cd2f9 100644
--- a/chrome/common/extensions/docs/templates/public/extensions/declarativeNetRequest.html
+++ b/chrome/common/extensions/docs/templates/public/extensions/declarativeNetRequest.html
@@ -1 +1 @@
-{{+partials.standard_extensions_api api:apis.extensions.declarative_net_request/}}
+{{+partials.standard_extensions_api api:apis.extensions.declarative_net_request intro:intros.declarativeNetRequest/}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/runtime_host_permissions.html b/chrome/common/extensions/docs/templates/public/extensions/runtime_host_permissions.html
new file mode 100644
index 0000000..e54df3e5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/runtime_host_permissions.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:articles.runtime_host_permissions/}}
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index c145cee5..eb518ec0 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -140,7 +140,8 @@
     if (args.Length() != 1 || !args[0]->IsString())
       ThrowInvalidArgumentsException(automation_bindings_);
 
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
     if (!tree_wrapper)
@@ -187,7 +188,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
 
     AutomationAXTreeWrapper* tree_wrapper =
@@ -241,7 +243,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
     std::string attribute = *v8::String::Utf8Value(isolate, args[2]);
 
@@ -297,7 +300,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
     int start = args[2]->Int32Value(context).FromMaybe(0);
     int end = args[3]->Int32Value(context).FromMaybe(0);
@@ -348,7 +352,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
     std::string str_val = *v8::String::Utf8Value(isolate, args[2]);
     bool bool_val = args[3].As<v8::Boolean>()->Value();
@@ -565,7 +570,7 @@
         ui::AXNode* parent = GetParent(node, &tree_wrapper);
         if (parent) {
           gin::DataObjectBuilder response(isolate);
-          response.Set("treeId", tree_wrapper->tree_id());
+          response.Set("treeId", tree_wrapper->tree_id().ToString());
           response.Set("nodeId", parent->id());
           result.Set(response.Build());
         }
@@ -591,28 +596,7 @@
   RouteNodeIDFunction(
       "GetRole", [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
                     AutomationAXTreeWrapper* tree_wrapper, ui::AXNode* node) {
-        auto role = node->data().role;
-        auto mapped_role = role;
-
-        // These roles are remapped for simplicity in handling them in AT.
-        switch (role) {
-          case ax::mojom::Role::kLayoutTable:
-            mapped_role = ax::mojom::Role::kTable;
-            break;
-          case ax::mojom::Role::kLayoutTableCell:
-            mapped_role = ax::mojom::Role::kCell;
-            break;
-          case ax::mojom::Role::kLayoutTableColumn:
-            mapped_role = ax::mojom::Role::kColumn;
-            break;
-          case ax::mojom::Role::kLayoutTableRow:
-            mapped_role = ax::mojom::Role::kRow;
-            break;
-          default:
-            break;
-        }
-
-        std::string role_name = ui::ToString(mapped_role);
+        std::string role_name = ui::ToString(node->data().role);
         result.Set(v8::String::NewFromUtf8(isolate, role_name.c_str(),
                                            v8::NewStringType::kNormal)
                        .ToLocalChecked());
@@ -663,7 +647,7 @@
         }
 
         gin::DataObjectBuilder response(isolate);
-        response.Set("treeId", tree_wrapper->tree_id());
+        response.Set("treeId", tree_wrapper->tree_id().ToString());
         response.Set("nodeIds", child_ids);
         result.Set(response.Build());
       });
@@ -1065,7 +1049,8 @@
           if (base::i18n::StringSearchIgnoringCaseAndAccents(
                   search_str_16, name, nullptr, nullptr)) {
             gin::DataObjectBuilder response(isolate);
-            response.Set("treeId", (*target_tree_wrapper)->tree_id());
+            response.Set("treeId",
+                         (*target_tree_wrapper)->tree_id().ToString());
             response.Set("nodeId", node->id());
             result.Set(response.Build());
             return;
@@ -1219,7 +1204,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
   if (iter == tree_id_to_tree_wrapper_map_.end())
     return;
@@ -1283,8 +1269,9 @@
     // Try to keep following focus recursively, by letting |tree_id| be the
     // new subtree to search in, while keeping |focus_tree_id| set to the tree
     // where we know we found a focused node.
-    ui::AXTreeID child_tree_id = focus->data().GetStringAttribute(
-        ax::mojom::StringAttribute::kChildTreeId);
+    ui::AXTreeID child_tree_id =
+        ui::AXTreeID::FromString(focus->data().GetStringAttribute(
+            ax::mojom::StringAttribute::kChildTreeId));
 
     AutomationAXTreeWrapper* child_tree_wrapper =
         GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
@@ -1326,7 +1313,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   AutomationAXTreeWrapper* tree_wrapper =
       GetAutomationAXTreeWrapperFromTreeID(tree_id);
   if (!tree_wrapper)
@@ -1337,10 +1325,11 @@
   if (!GetFocusInternal(tree_wrapper, &focused_tree_wrapper, &focused_node))
     return;
 
-  args.GetReturnValue().Set(gin::DataObjectBuilder(GetIsolate())
-                                .Set("treeId", focused_tree_wrapper->tree_id())
-                                .Set("nodeId", focused_node->id())
-                                .Build());
+  args.GetReturnValue().Set(
+      gin::DataObjectBuilder(GetIsolate())
+          .Set("treeId", focused_tree_wrapper->tree_id().ToString())
+          .Set("nodeId", focused_node->id())
+          .Build());
 }
 
 void AutomationInternalCustomBindings::GetHtmlAttributes(
@@ -1349,7 +1338,8 @@
   if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+  ui::AXTreeID tree_id =
+      ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
@@ -1373,7 +1363,8 @@
   if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+  ui::AXTreeID tree_id =
+      ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
@@ -1458,8 +1449,8 @@
         parent_tree_wrapper->tree()->GetFromId(host_node_id);
     if (host_node) {
       DCHECK_EQ((*in_out_tree_wrapper)->tree_id(),
-                host_node->GetStringAttribute(
-                    ax::mojom::StringAttribute::kChildTreeId));
+                ui::AXTreeID::FromString(host_node->GetStringAttribute(
+                    ax::mojom::StringAttribute::kChildTreeId)));
       *in_out_tree_wrapper = parent_tree_wrapper;
       return host_node;
     }
@@ -1471,14 +1462,15 @@
 bool AutomationInternalCustomBindings::GetRootOfChildTree(
     ui::AXNode** in_out_node,
     AutomationAXTreeWrapper** in_out_tree_wrapper) const {
-  ui::AXTreeID child_tree_id;
+  std::string child_tree_id_str;
   if (!(*in_out_node)
            ->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId,
-                                &child_tree_id))
+                                &child_tree_id_str))
     return false;
 
   AutomationAXTreeWrapper* child_tree_wrapper =
-      GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
+      GetAutomationAXTreeWrapperFromTreeID(
+          ui::AXTreeID::FromString(child_tree_id_str));
   if (!child_tree_wrapper)
     return false;
 
@@ -1589,7 +1581,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   const auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
@@ -1613,7 +1606,7 @@
     child_id = node->children()[index]->id();
 
   gin::DataObjectBuilder response(GetIsolate());
-  response.Set("treeId", tree_wrapper->tree_id());
+  response.Set("treeId", tree_wrapper->tree_id().ToString());
   response.Set("nodeId", child_id);
   args.GetReturnValue().Set(response.Build());
 }
@@ -1642,7 +1635,7 @@
   if (!tree_wrapper->OnAccessibilityEvents(event_bundle, is_active_profile)) {
     LOG(ERROR) << tree_wrapper->tree()->error();
     base::ListValue args;
-    args.AppendString(tree_id);
+    args.AppendString(tree_id.ToString());
     bindings_system_->DispatchEventInContext(
         "automationInternal.onAccessibilityTreeSerializationError", &args,
         nullptr, context());
@@ -1675,9 +1668,10 @@
 
   // Notify custom bindings when there's an unloaded tree; js will enable the
   // renderer and wait for it to load.
-  ui::AXTreeID child_tree_id;
+  std::string child_tree_id_str;
   if (node->data().GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId,
-                                      &child_tree_id)) {
+                                      &child_tree_id_str)) {
+    ui::AXTreeID child_tree_id = ui::AXTreeID::FromString(child_tree_id_str);
     auto* tree_wrapper = GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
     if (!tree_wrapper || !tree_wrapper->tree()->data().loaded)
       SendChildTreeIDEvent(child_tree_id);
@@ -1740,7 +1734,7 @@
     did_send_event = true;
     base::ListValue args;
     args.AppendInteger(observer.id);
-    args.AppendString(tree_id);
+    args.AppendString(tree_id.ToString());
     args.AppendInteger(node->id());
     args.AppendString(ToString(change_type));
     bindings_system_->DispatchEventInContext("automationInternal.onTreeChange",
@@ -1756,7 +1750,7 @@
     const ui::AXEvent& event,
     api::automation::EventType event_type) {
   auto event_params = std::make_unique<base::DictionaryValue>();
-  event_params->SetString("treeID", tree_id);
+  event_params->SetString("treeID", tree_id.ToString());
   event_params->SetInteger("targetID", event.id);
   event_params->SetString("eventType", api::automation::ToString(event_type));
   event_params->SetString("eventFrom", ui::ToString(event.event_from));
@@ -1772,7 +1766,7 @@
 void AutomationInternalCustomBindings::SendChildTreeIDEvent(
     ui::AXTreeID child_tree_id) {
   base::ListValue args;
-  args.AppendString(child_tree_id);
+  args.AppendString(child_tree_id.ToString());
   bindings_system_->DispatchEventInContext("automationInternal.onChildTreeID",
                                            &args, nullptr, context());
 }
@@ -1787,7 +1781,7 @@
   ui::AXTreeID tree_id = iter->second->tree_id();
 
   base::ListValue args;
-  args.AppendString(tree_id);
+  args.AppendString(tree_id.ToString());
   {
     auto nodes = std::make_unique<base::ListValue>();
     for (auto id : ids)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6e7bb4c..b192d5e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1485,7 +1485,6 @@
         "../browser/ui/views/extensions/pwa_confirmation_view_browsertest.cc",
         "../browser/ui/views/external_protocol_dialog_browsertest.cc",
         "../browser/ui/views/folder_upload_confirmation_view_browsertest.cc",
-        "../browser/ui/views/frame/browser_non_client_frame_view_browsertest_win.cc",
         "../browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc",
         "../browser/ui/views/frame/glass_browser_frame_view_browsertest_win.cc",
         "../browser/ui/views/importer/import_lock_dialog_view_browsertest.cc",
@@ -3400,6 +3399,7 @@
       "../browser/chromeos/policy/policy_cert_verifier_unittest.cc",
       "../browser/component_updater/cros_component_installer_chromeos_unittest.cc",
       "../browser/component_updater/metadata_table_chromeos_unittest.cc",
+      "../browser/google/google_brand_code_map_chromeos_unittest.cc",
       "../browser/media/webrtc/desktop_media_list_ash_unittest.cc",
       "../browser/notifications/chrome_ash_message_center_client_unittest.cc",
       "../browser/renderer_context_menu/mock_render_view_context_menu.cc",
@@ -3631,6 +3631,7 @@
       "../browser/extensions/forced_extensions/installation_tracker_unittest.cc",
       "../browser/extensions/install_tracker_unittest.cc",
       "../browser/extensions/install_verifier_unittest.cc",
+      "../browser/extensions/installed_loader_unittest.cc",
       "../browser/extensions/menu_manager_unittest.cc",
       "../browser/extensions/ntp_overridden_bubble_delegate_unittest.cc",
       "../browser/extensions/pack_extension_unittest.cc",
@@ -4339,7 +4340,6 @@
         "../browser/ui/views/status_icons/status_tray_win_unittest.cc",
         "../browser/ui/views/sync/bubble_sync_promo_view_unittest.cc",
         "../browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_unittest.cc",
-        "../browser/ui/views/tabs/alert_indicator_button_unittest.cc",
         "../browser/ui/views/tabs/fake_base_tab_strip_controller.cc",
         "../browser/ui/views/tabs/fake_base_tab_strip_controller.h",
         "../browser/ui/views/tabs/stacked_tab_strip_layout_unittest.cc",
diff --git a/chrome/test/data/webui/cr_elements/cr_slider_test.js b/chrome/test/data/webui/cr_elements/cr_slider_test.js
index 5cf29f2..2978110 100644
--- a/chrome/test/data/webui/cr_elements/cr_slider_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_slider_test.js
@@ -3,23 +3,194 @@
 // found in the LICENSE file.
 
 suite('cr-slider', function() {
-  let element;
+  let crSlider;
 
   setup(function() {
     PolymerTest.clearBody();
-    document.body.innerHTML = `
-      <cr-slider min="0" max="100"></cr-slider>
-    `;
+    document.body.innerHTML = '<cr-slider min="0" max="100"></cr-slider>';
 
-    element = document.body.querySelector('cr-slider');
+    crSlider = document.body.querySelector('cr-slider');
   });
 
-  // Ensure that immediate-value-changed event bubbles, since users of
-  // cr-slider rely on such event.
-  test('immediate-value-changed bubbles', function() {
-    const whenFired =
-        test_util.eventToPromise('immediate-value-changed', element);
-    element.value = 50;
+  function pressArrowRight() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 39, [], 'ArrowRight');
+  }
+
+  function pressArrowLeft() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 37, [], 'ArrowLeft');
+  }
+
+  function pressPageUp() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 33, [], 'PageUp');
+  }
+
+  function pressPageDown() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 34, [], 'PageDown');
+  }
+
+  function pressArrowUp() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 38, [], 'ArrowUp');
+  }
+
+  function pressArrowDown() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 40, [], 'ArrowDown');
+  }
+
+  function pressHome() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 36, [], 'Home');
+  }
+
+  function pressEnd() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 35, [], 'End');
+  }
+
+  function pointerEvent(eventType, ratio) {
+    const rect = crSlider.$.barContainer.getBoundingClientRect();
+    crSlider.dispatchEvent(new PointerEvent(eventType, {
+      buttons: 1,
+      pointerId: 1,
+      clientX: rect.left + (ratio * rect.width),
+    }));
+  }
+
+  function pointerDown(ratio) {
+    pointerEvent('pointerdown', ratio);
+  }
+
+  function pointerMove(ratio) {
+    pointerEvent('pointermove', ratio);
+  }
+
+  function pointerUp(ratio) {
+    pointerEvent('pointerup', ratio);
+  }
+
+  // Ensure that value-changed event bubbles, since users of cr-slider rely on
+  // such event.
+  test('value-changed bubbles', function() {
+    const whenFired = test_util.eventToPromise('value-changed', crSlider);
+    crSlider.value = 50;
     return whenFired;
   });
+
+  test('key events', () => {
+    crSlider.value = 0;
+    pressArrowRight();
+    assertEquals(1, crSlider.value);
+    pressPageUp();
+    assertEquals(2, crSlider.value);
+    pressArrowUp();
+    assertEquals(3, crSlider.value);
+    pressHome();
+    assertEquals(0, crSlider.value);
+    pressArrowLeft();
+    assertEquals(0, crSlider.value);
+    pressArrowDown();
+    assertEquals(0, crSlider.value);
+    pressPageDown();
+    assertEquals(0, crSlider.value);
+    pressEnd();
+    assertEquals(100, crSlider.value);
+    pressArrowRight();
+    assertEquals(100, crSlider.value);
+    pressPageUp();
+    assertEquals(100, crSlider.value);
+    pressArrowUp();
+    assertEquals(100, crSlider.value);
+    pressArrowLeft();
+    assertEquals(99, crSlider.value);
+    pressArrowDown();
+    assertEquals(98, crSlider.value);
+    pressPageDown();
+    assertEquals(97, crSlider.value);
+  });
+
+  test('mouse events', () => {
+    crSlider.value = 0;
+    pointerMove(.25);
+    assertEquals(0, crSlider.value);
+    pointerDown(.5);
+    assertEquals(50, crSlider.value);
+    assertEquals(5, crSlider.draggingEventTracker_.listeners_.length);
+    pointerMove(.75);
+    assertEquals(75, crSlider.value);
+    pointerMove(-1);
+    assertEquals(0, crSlider.value);
+    pointerMove(2);
+    assertEquals(100, crSlider.value);
+    pointerUp(0);
+    assertEquals(100, crSlider.value);
+    assertEquals(0, crSlider.draggingEventTracker_.listeners_.length);
+    pointerMove(.25);
+    assertEquals(100, crSlider.value);
+  });
+
+  test('snaps to closest value', () => {
+    crSlider.snaps = true;
+    pointerDown(.501);
+    assertEquals(50, crSlider.value);
+    pointerMove(.505);
+    assertEquals(51, crSlider.value);
+  });
+
+  test('markers', () => {
+    crSlider.value = 0;
+    assertTrue(crSlider.$.markers.hidden);
+    crSlider.markerCount = 10;
+    assertFalse(crSlider.$.markers.hidden);
+    Polymer.dom.flush();
+    const markers = Array.from(crSlider.root.querySelectorAll('#markers div'));
+    assertEquals(9, markers.length);
+    markers.forEach((marker, i) => {
+      assertTrue(marker.classList.contains('inactive-marker'));
+    });
+    crSlider.value = 100;
+    markers.forEach(marker => {
+      assertTrue(marker.classList.contains('active-marker'));
+    });
+    crSlider.value = 50;
+    markers.slice(0, 5).forEach(marker => {
+      assertTrue(marker.classList.contains('active-marker'));
+    });
+    markers.slice(5).forEach(marker => {
+      assertTrue(marker.classList.contains('inactive-marker'));
+    });
+  });
+
+  test('ticks and aria', () => {
+    crSlider.value = 1.5;
+    crSlider.ticks = [1, 2, 4, 8];
+    assertEquals('1', crSlider.getAttribute('aria-valuemin'));
+    assertEquals('8', crSlider.getAttribute('aria-valuemax'));
+    assertEquals('4', crSlider.getAttribute('aria-valuetext'));
+    assertEquals('4', crSlider.getAttribute('aria-valuenow'));
+    assertEquals(2, crSlider.value);
+    crSlider.value = 100;
+    assertEquals(3, crSlider.value);
+    assertEquals('8', crSlider.getAttribute('aria-valuetext'));
+    assertEquals('8', crSlider.getAttribute('aria-valuenow'));
+    crSlider.ticks = [
+      {
+        value: 10,
+        ariaValue: 1,
+        label: 'First',
+      },
+      {
+        value: 20,
+        label: 'Second',
+      },
+      {
+        value: 30,
+        ariaValue: 3,
+        label: 'Third',
+      },
+    ];
+    assertEquals('1', crSlider.getAttribute('aria-valuemin'));
+    assertEquals('3', crSlider.getAttribute('aria-valuemax'));
+    assertEquals('Third', crSlider.getAttribute('aria-valuetext'));
+    assertEquals('3', crSlider.getAttribute('aria-valuenow'));
+    crSlider.value = 1;
+    assertEquals('Second', crSlider.getAttribute('aria-valuetext'));
+    assertEquals('20', crSlider.getAttribute('aria-valuenow'));
+  });
 });
diff --git a/chrome/test/data/webui/multidevice_setup/integration_test.js b/chrome/test/data/webui/multidevice_setup/integration_test.js
index 3842aba..46409f25 100644
--- a/chrome/test/data/webui/multidevice_setup/integration_test.js
+++ b/chrome/test/data/webui/multidevice_setup/integration_test.js
@@ -4,6 +4,47 @@
 
 /** @fileoverview Suite of integration tests for MultiDevice setup WebUI. */
 cr.define('multidevice_setup', () => {
+  /** @implements {multidevice_setup.MultiDeviceSetupDelegate} */
+  class FakeDelegate {
+    constructor() {
+      /** @private {boolean} */
+      this.isPasswordRequiredToSetHost_ = true;
+
+      /** @private {boolean} */
+      this.shouldSetHostSucceed_ = true;
+    }
+
+    set isPasswordRequired(isPasswordRequired) {
+      this.isPasswordRequiredToSetHost_ = isPasswordRequired;
+    }
+
+    /** @override */
+    isPasswordRequiredToSetHost() {
+      return this.isPasswordRequiredToSetHost_;
+    }
+
+    set shouldSetHostSucceed(shouldSetHostSucceed) {
+      this.shouldSetHostSucceed_ = shouldSetHostSucceed;
+    }
+
+    /** @override */
+    setHostDevice(hostDeviceId, opt_authToken) {
+      return new Promise((resolve) => {
+        resolve({success: this.shouldSetHostSucceed_});
+      });
+    }
+
+    set shouldExitSetupFlowAfterHostSet(shouldExitSetupFlowAfterHostSet) {
+      this.shouldExitSetupFlowAfterSettingHost_ =
+          shouldExitSetupFlowAfterHostSet;
+    }
+
+    /** @override */
+    shouldExitSetupFlowAfterSettingHost() {
+      return this.shouldExitSetupFlowAfterSettingHost_;
+    }
+  }
+
   function registerIntegrationTests() {
     suite('MultiDeviceSetup', () => {
       /**
@@ -30,8 +71,8 @@
 
       setup(() => {
         multiDeviceSetupElement = document.createElement('multidevice-setup');
+        multiDeviceSetupElement.delegate = new FakeDelegate();
         multiDeviceSetupElement.multideviceSetup_ = new FakeMojoService();
-        multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.POST_OOBE;
 
         document.body.appendChild(multiDeviceSetupElement);
         forwardButton = multiDeviceSetupElement.$$('button-bar').$$('#forward');
@@ -39,6 +80,13 @@
             multiDeviceSetupElement.$$('button-bar').$$('#backward');
       });
 
+      /** @param {boolean} isOobeMode */
+      function setMode(isOobeMode) {
+        multiDeviceSetupElement.delegate.isPasswordRequired = !isOobeMode;
+        multiDeviceSetupElement.delegate.shouldExitSetupFlowAfterHostSet =
+            isOobeMode;
+      }
+
       /** @param {string} visiblePageName */
       function setVisiblePage(visiblePageName) {
         multiDeviceSetupElement.visiblePageName_ = visiblePageName;
@@ -65,9 +113,9 @@
           done();
         });
 
+        setMode(true /* isOobeMode */);
         setVisiblePage(START);
-        multiDeviceSetupElement.multideviceSetup_.shouldSetHostSucceed = true;
-        multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.OOBE;
+        multiDeviceSetupElement.delegate.shouldSetHostSucceed = true;
 
         backwardButton.click();
       });
@@ -76,16 +124,13 @@
           'StartSetupPage forward button sets host in background and ' +
               'goes to PasswordPage (OOBE).',
           done => {
-            multiDeviceSetupElement.addEventListener(
-                'visible-page-name_-changed', () => {
-                  if (multiDeviceSetupElement.visiblePageName_ == PASSWORD)
-                    done();
-                });
+            multiDeviceSetupElement.addEventListener('setup-exited', () => {
+              done();
+            });
 
+            setMode(true /* isOobeMode */);
             setVisiblePage(START);
-            multiDeviceSetupElement.multideviceSetup_.shouldSetHostSucceed =
-                true;
-            multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.OOBE;
+            multiDeviceSetupElement.delegate.shouldSetHostSucceed = true;
 
             multiDeviceSetupElement.async(() => {
               forwardButton.click();
@@ -97,9 +142,9 @@
       test('StartSetupPage backward button closes UI (post-OOBE)', done => {
         multiDeviceSetupElement.addEventListener('setup-exited', () => done());
 
+        setMode(false /* isOobeMode */);
         setVisiblePage(START);
-        multiDeviceSetupElement.multideviceSetup_.shouldSetHostSucceed = true;
-        multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.POST_OOBE;
+        multiDeviceSetupElement.delegate.shouldSetHostSucceed = true;
 
         backwardButton.click();
       });
@@ -107,9 +152,9 @@
       test('PasswordPage backward button closes UI (post-OOBE)', done => {
         multiDeviceSetupElement.addEventListener('setup-exited', () => done());
 
+        setMode(false /* isOobeMode */);
         setVisiblePage(PASSWORD);
-        multiDeviceSetupElement.multideviceSetup_.shouldSetHostSucceed = true;
-        multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.POST_OOBE;
+        multiDeviceSetupElement.delegate.shouldSetHostSucceed = true;
 
         backwardButton.click();
       });
@@ -124,10 +169,9 @@
                     done();
                 });
 
+            setMode(false /* isOobeMode */);
             setVisiblePage(PASSWORD);
-            multiDeviceSetupElement.multideviceSetup_.shouldSetHostSucceed =
-                true;
-            multiDeviceSetupElement.uiMode = multidevice_setup.UiMode.POST_OOBE;
+            multiDeviceSetupElement.delegate.shouldSetHostSucceed = true;
 
             multiDeviceSetupElement.async(() => {
               forwardButton.click();
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 1e61aa8..e2d6ec1 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -2186,30 +2186,6 @@
 TEST_F('CrSettingsOnStartupPageTest', 'All', function() {
   mocha.run();
 });
-GEN('#if defined(OS_CHROMEOS)');
-
-/**
- * @constructor
- * @extends {CrSettingsBrowserTest}
- */
-function CrSettingsDisplaySizeSliderTest() {}
-
-CrSettingsDisplaySizeSliderTest.prototype = {
-  __proto__: CrSettingsBrowserTest.prototype,
-
-  /** @override */
-  browsePreload: 'chrome://settings/device_page/display_size_slider.html',
-
-  /** @override */
-  extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
-    'display_size_slider_test.js',
-  ]),
-};
-
-TEST_F('CrSettingsDisplaySizeSliderTest', 'All', function() {
-  mocha.run();
-});
-GEN('#endif  // defined(OS_CHROMEOS)');
 
 /**
  * Test fixture for FindShortcutBehavior.
diff --git a/chrome/test/data/webui/settings/device_page_tests.js b/chrome/test/data/webui/settings/device_page_tests.js
index 3e2fd9f..41c61eb 100644
--- a/chrome/test/data/webui/settings/device_page_tests.js
+++ b/chrome/test/data/webui/settings/device_page_tests.js
@@ -530,7 +530,7 @@
         const slider = assert(pointersPage.$$('#mouse settings-slider'));
         expectEquals(4, slider.pref.value);
         MockInteractions.pressAndReleaseKeyOn(
-            slider.$$('cr-slider').$$('#slider'), 37 /* left */);
+            slider.$$('cr-slider'), 37, [], 'ArrowLeft');
         expectEquals(3, devicePage.prefs.settings.mouse.sensitivity2.value);
 
         pointersPage.set('prefs.settings.mouse.sensitivity2.value', 5);
@@ -546,7 +546,7 @@
         const slider = assert(pointersPage.$$('#touchpad settings-slider'));
         expectEquals(3, slider.pref.value);
         MockInteractions.pressAndReleaseKeyOn(
-            slider.$$('cr-slider').$$('#slider'), 39 /* right */);
+            slider.$$('cr-slider'), 39 /* right */, [], 'ArrowRight');
         expectEquals(4, devicePage.prefs.settings.touchpad.sensitivity2.value);
 
         pointersPage.set('prefs.settings.touchpad.sensitivity2.value', 2);
@@ -661,13 +661,11 @@
             // Test interaction with the settings-slider's underlying
             // paper-slider.
             MockInteractions.pressAndReleaseKeyOn(
-                keyboardPage.$$('#delaySlider').$$('cr-slider').$$('#slider'),
-                37 /* left */);
+                keyboardPage.$$('#delaySlider').$$('cr-slider'), 37 /* left */,
+                [], 'ArrowLeft');
             MockInteractions.pressAndReleaseKeyOn(
-                keyboardPage.$$('#repeatRateSlider')
-                    .$$('cr-slider')
-                    .$$('#slider'),
-                39 /* right */);
+                keyboardPage.$$('#repeatRateSlider').$$('cr-slider'), 39, [],
+                'ArrowRight');
             const language = devicePage.prefs.settings.language;
             expectEquals(1000, language.xkb_auto_repeat_delay_r2.value);
             expectEquals(300, language.xkb_auto_repeat_interval_r2.value);
diff --git a/chrome/test/data/webui/settings/display_size_slider_test.js b/chrome/test/data/webui/settings/display_size_slider_test.js
deleted file mode 100644
index 2224026..0000000
--- a/chrome/test/data/webui/settings/display_size_slider_test.js
+++ /dev/null
@@ -1,307 +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.
-
-/** @fileoverview Suite of tests for display-size-slider. */
-suite('DisplaySizeSlider', function() {
-  /** @type {!CrSliderElement} */
-  let slider;
-
-  /** @type {!SliderTicks} */
-  let ticks = [];
-
-  setup(function() {
-    PolymerTest.clearBody();
-    const tickValues = [2, 4, 8, 16, 32, 64, 128];
-    ticks = [];
-    for (let i = 0; i < tickValues.length; i++) {
-      ticks.push({value: tickValues[i], label: tickValues[i].toString()});
-    }
-
-    slider = document.createElement('display-size-slider');
-    slider.ticks = ticks;
-    slider.pref = {
-      type: chrome.settingsPrivate.PrefType.NUMBER,
-      value: ticks[3].value,
-    };
-    document.body.appendChild(slider);
-  });
-
-  test('initialization', function() {
-    const selectedIndex = 3;
-
-    assertFalse(slider.disabled);
-    assertFalse(slider.dragging);
-    expectEquals(selectedIndex, slider.index);
-    expectEquals(ticks.length - 1, slider.markers.length);
-    expectEquals(ticks[selectedIndex].value, slider.pref.value);
-  });
-
-  test('check knob position changes', function() {
-    let expectedIndex = 3;
-    expectEquals(expectedIndex, slider.index);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    let expectedLeftPercentage = (expectedIndex * 100) / (ticks.length - 1);
-    expectEquals(expectedLeftPercentage + '%', slider.$.sliderKnob.style.left);
-
-    // Reset the index to the first tick.
-    slider.resetToMinIndex_();
-    expectedIndex = 0;
-    expectEquals(expectedIndex, slider.index);
-    expectEquals('0%', slider.$.sliderKnob.style.left);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    // Decrementing the slider below the lowest index should have no effect.
-    slider.decrement_();
-    expectEquals(expectedIndex, slider.index);
-    expectEquals('0%', slider.$.sliderKnob.style.left);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    // Reset the index to the last tick.
-    slider.resetToMaxIndex_();
-    expectedIndex = ticks.length - 1;
-    expectEquals(expectedIndex, slider.index);
-    expectEquals('100%', slider.$.sliderKnob.style.left);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    // Incrementing the slider when it is already at max should have no effect.
-    slider.increment_();
-    expectEquals(expectedIndex, slider.index);
-    expectEquals('100%', slider.$.sliderKnob.style.left);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    slider.decrement_();
-    expectedIndex -= 1;
-    expectEquals(expectedIndex, slider.index);
-    expectedLeftPercentage = (expectedIndex * 100) / (ticks.length - 1);
-    expectEquals(
-        Math.round(expectedLeftPercentage),
-        Math.round(parseFloat(slider.$.sliderKnob.style.left)));
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-  });
-
-  test('check keyboard events', function() {
-    let expectedIndex = 3;
-    expectEquals(expectedIndex, slider.index);
-
-    // Right keypress should increment the slider by 1.
-    MockInteractions.pressAndReleaseKeyOn(slider, 39 /* right */);
-    expectedIndex += 1;
-    expectEquals(expectedIndex, slider.index);
-
-    // Left keypress should decrement the slider by 1.
-    MockInteractions.pressAndReleaseKeyOn(slider, 37 /* left */);
-    expectedIndex -= 1;
-    expectEquals(expectedIndex, slider.index);
-
-    // Page down should take the slider to the first value.
-    MockInteractions.pressAndReleaseKeyOn(slider, 34 /* page down */);
-    expectedIndex = 0;
-    expectEquals(expectedIndex, slider.index);
-
-    // Page up should take the slider to the last value.
-    MockInteractions.pressAndReleaseKeyOn(slider, 33 /* page up */);
-    expectedIndex = ticks.length - 1;
-    expectEquals(expectedIndex, slider.index);
-
-    // Down keypress should decrement the slider index by 1.
-    MockInteractions.pressAndReleaseKeyOn(slider, 40 /* down */);
-    expectedIndex -= 1;
-    expectEquals(expectedIndex, slider.index);
-
-    // Up keypress should increment the slider index by 1.
-    MockInteractions.pressAndReleaseKeyOn(slider, 38 /* up */);
-    expectedIndex += 1;
-    expectEquals(expectedIndex, slider.index);
-
-    // Home button should take the slider to the first value.
-    MockInteractions.pressAndReleaseKeyOn(slider, 36 /* home */);
-    expectedIndex = 0;
-    expectEquals(expectedIndex, slider.index);
-
-    // End button should take the slider to the last value.
-    MockInteractions.pressAndReleaseKeyOn(slider, 35 /* up */);
-    expectedIndex = ticks.length - 1;
-    expectEquals(expectedIndex, slider.index);
-  });
-
-  test('set pref updates slider', function() {
-    let expectedIndex = 3;
-    expectEquals(expectedIndex, slider.index);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-
-    let newIndex = 4;
-    slider.set('pref.value', ticks[newIndex].value);
-    expectEquals(newIndex, slider.index);
-
-    let expectedLeftPercentage = (newIndex * 100) / (ticks.length - 1);
-    expectEquals(
-        Math.round(expectedLeftPercentage),
-        Math.round(parseFloat(slider.$.sliderKnob.style.left)));
-  });
-
-  test('check label values', function() {
-    expectEquals('none', getComputedStyle(slider.$.labelContainer).display);
-    let hoverClassName = 'hover';
-    slider.$.sliderContainer.classList.add(hoverClassName);
-    expectEquals('block', getComputedStyle(slider.$.labelContainer).display);
-
-    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
-
-    slider.increment_();
-    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
-
-    slider.resetToMaxIndex_();
-    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
-
-    slider.resetToMinIndex_();
-    expectEquals(ticks[slider.index].label, slider.$.labelText.innerText);
-  });
-
-  test('mouse interactions with the slider knobs', function() {
-    // The label should be visible when focused.
-    expectEquals('none', getComputedStyle(slider.$.labelContainer).display);
-    slider.focus();
-    expectEquals('block', getComputedStyle(slider.$.labelContainer).display);
-
-    let dragEventReceived = false;
-    let valueInEvent = null;
-    slider.addEventListener('immediate-value-changed', function(e) {
-      dragEventReceived = true;
-      valueInEvent = e.detail.value;
-    });
-
-    let oldIndex = slider.index;
-    const sliderKnob = slider.$.sliderKnob;
-    // Width of each tick.
-    const tickWidth = slider.$.sliderBar.offsetWidth / (ticks.length - 1);
-    assertFalse(dragEventReceived);
-    MockInteractions.down(sliderKnob);
-    assertFalse(dragEventReceived);
-
-    let currentPos = MockInteractions.middleOfNode(sliderKnob);
-    let nextPos = {x: currentPos.x + tickWidth, y: currentPos.y};
-    MockInteractions.move(sliderKnob, currentPos, nextPos);
-
-    // We moved by 1 tick width, so the slider index must have increased.
-    expectEquals(oldIndex + 1, slider.index);
-
-    // The mouse is still down, so the pref should not be updated.
-    expectEquals(ticks[oldIndex].value, slider.pref.value);
-
-    // The event should be fired while dragging the knob.
-    assertTrue(dragEventReceived);
-    dragEventReceived = false;
-
-    // The value sent by the event must be the current value of the slider.
-    expectEquals(ticks[slider.index].value, valueInEvent);
-
-    MockInteractions.up(sliderKnob);
-
-    // There should be no event fired when the knob is not being dragged.
-    assertFalse(dragEventReceived);
-
-    // Now that the mouse is down, the pref value should be updated.
-    expectEquals(ticks[oldIndex + 1].value, slider.pref.value);
-
-    oldIndex = slider.index;
-    MockInteractions.track(sliderKnob, -3 * tickWidth, 0);
-    expectEquals(oldIndex - 3, slider.index);
-    expectEquals(ticks[oldIndex - 3].value, slider.pref.value);
-
-    // Moving by a very large amount should clamp the value.
-    oldIndex = slider.index;
-    MockInteractions.track(sliderKnob, 100 * tickWidth, 0);
-    expectEquals(ticks.length - 1, slider.index);
-    expectEquals(ticks[ticks.length - 1].value, slider.pref.value);
-
-    // Similarly for the other side.
-    oldIndex = slider.index;
-    MockInteractions.track(sliderKnob, -100 * tickWidth, 0);
-    expectEquals(0, slider.index);
-    expectEquals(ticks[0].value, slider.pref.value);
-  });
-
-  test('mouse interaction with the bar', function() {
-    let dragEventReceived = false;
-    let valueInEvent = null;
-    slider.addEventListener('immediate-value-changed', function(e) {
-      dragEventReceived = true;
-      valueInEvent = e.detail.value;
-    });
-
-    const sliderBar = slider.$.sliderBar;
-    const sliderBarOrigin = {
-      x: sliderBar.getBoundingClientRect().x,
-      y: sliderBar.getBoundingClientRect().y
-    };
-
-    let oldIndex = slider.index;
-    MockInteractions.down(sliderBar, sliderBarOrigin);
-
-    // Mouse down on the left end of the slider bar should move the knob there.
-    expectEquals(0, slider.index);
-    expectEquals(0, Math.round(parseFloat(slider.$.sliderKnob.style.left)));
-
-    // Mouse down must trigger a drag event since we are essentially dragging
-    // the knob.
-    assertTrue(dragEventReceived);
-    expectEquals(ticks[slider.index].value, valueInEvent);
-    dragEventReceived = false;
-
-    // However the pref value should not update until the mouse is released.
-    expectEquals(ticks[oldIndex].value, slider.pref.value);
-
-    // Release mouse to update pref value.
-    MockInteractions.up(sliderBar);
-    expectEquals(ticks[slider.index].value, slider.pref.value);
-
-    const tickWidth =
-        sliderBar.getBoundingClientRect().width / (ticks.length - 1);
-    let expectedIndex = 3;
-    // The knob position for the 3rd index.
-    let sliderBarPos = {
-      x: sliderBarOrigin.x + tickWidth * expectedIndex,
-      y: sliderBarOrigin.y
-    };
-
-    // The slider has not yet started dragging.
-    assertFalse(slider.dragging);
-
-    oldIndex = slider.index;
-    // Clicking at the 3rd index position on the slider bar should update the
-    // knob.
-    MockInteractions.down(sliderBar, sliderBarPos);
-    let expectedLeftPercentage =
-        (tickWidth * expectedIndex * 100) / sliderBar.offsetWidth;
-    expectEquals(
-        Math.round(expectedLeftPercentage),
-        Math.round(parseFloat(slider.$.sliderKnob.style.left)));
-    expectEquals(expectedIndex, slider.index);
-    expectEquals(ticks[oldIndex].value, slider.pref.value);
-
-
-    expectedIndex = 5;
-    const nextSliderBarPos = {
-      x: sliderBarPos.x + tickWidth * (expectedIndex - slider.index),
-      y: sliderBarPos.y
-    };
-    MockInteractions.move(sliderBar, sliderBarPos, nextSliderBarPos);
-    expectEquals(expectedIndex, slider.index);
-    expectedLeftPercentage =
-        (tickWidth * expectedIndex * 100) / sliderBar.offsetWidth;
-    expectEquals(
-        Math.round(expectedLeftPercentage),
-        Math.round(parseFloat(slider.$.sliderKnob.style.left)));
-
-    expectEquals(ticks[oldIndex].value, slider.pref.value);
-
-    assertTrue(dragEventReceived);
-    expectEquals(ticks[slider.index].value, valueInEvent);
-
-    MockInteractions.up(sliderBar);
-    expectEquals(ticks[expectedIndex].value, slider.pref.value);
-  });
-});
diff --git a/chrome/test/data/webui/settings/settings_slider_tests.js b/chrome/test/data/webui/settings/settings_slider_tests.js
index 1512b521..4eb7adc 100644
--- a/chrome/test/data/webui/settings/settings_slider_tests.js
+++ b/chrome/test/data/webui/settings/settings_slider_tests.js
@@ -13,7 +13,7 @@
    */
   let crSlider;
 
-  const tickValues = [2, 4, 8, 16, 32, 64, 128];
+  const ticks = [2, 4, 8, 16, 32, 64, 128];
 
   setup(function() {
     PolymerTest.clearBody();
@@ -26,6 +26,38 @@
     crSlider = slider.$$('cr-slider');
   });
 
+  function pressArrowRight() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 39, [], 'ArrowRight');
+  }
+
+  function pressArrowLeft() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 37, [], 'ArrowLeft');
+  }
+
+  function pressPageUp() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 33, [], 'PageUp');
+  }
+
+  function pressPageDown() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 34, [], 'PageDown');
+  }
+
+  function pressArrowUp() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 38, [], 'ArrowUp');
+  }
+
+  function pressArrowDown() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 40, [], 'ArrowDown');
+  }
+
+  function pressHome() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 36, [], 'Home');
+  }
+
+  function pressEnd() {
+    MockInteractions.pressAndReleaseKeyOn(crSlider, 35, [], 'End');
+  }
+
   test('enforce value', function() {
     // Test that the indicator is not present until after the pref is
     // enforced.
@@ -43,85 +75,106 @@
   });
 
   test('set value', function() {
-    slider.tickValues = tickValues;
+    slider.ticks = ticks;
     slider.set('pref.value', 16);
+    Polymer.dom.flush();
     expectEquals(6, crSlider.max);
     expectEquals(3, crSlider.value);
-    expectEquals(3, crSlider.immediateValue);
 
     // settings-slider only supports snapping to a range of tick values.
     // Setting to an in-between value should snap to an indexed value.
     slider.set('pref.value', 70);
     expectEquals(5, crSlider.value);
-    expectEquals(5, crSlider.immediateValue);
     expectEquals(64, slider.pref.value);
 
     // Setting the value out-of-range should clamp the slider.
     slider.set('pref.value', -100);
     expectEquals(0, crSlider.value);
-    expectEquals(0, crSlider.immediateValue);
     expectEquals(2, slider.pref.value);
   });
 
   test('move slider', function() {
-    slider.tickValues = tickValues;
+    slider.ticks = ticks;
     slider.set('pref.value', 30);
     expectEquals(4, crSlider.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 39 /* right */);
+    pressArrowRight();
     expectEquals(5, crSlider.value);
     expectEquals(64, slider.pref.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 39 /* right */);
+    pressArrowRight();
     expectEquals(6, crSlider.value);
     expectEquals(128, slider.pref.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 39 /* right */);
+    pressArrowRight();
     expectEquals(6, crSlider.value);
     expectEquals(128, slider.pref.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 37 /* left */);
+    pressArrowLeft();
     expectEquals(5, crSlider.value);
     expectEquals(64, slider.pref.value);
-  });
 
-  test('findNearestIndex_', function() {
-    const slider = document.createElement('settings-slider');
-    const testArray = [80, 20, 350, 1000, 200, 100];
-    const testFindNearestIndex = function(expectedIndex, value) {
-      expectEquals(expectedIndex, slider.findNearestIndex_(testArray, value));
-    };
-    testFindNearestIndex(0, 51);
-    testFindNearestIndex(0, 80);
-    testFindNearestIndex(0, 89);
-    testFindNearestIndex(1, -100);
-    testFindNearestIndex(1, 20);
-    testFindNearestIndex(1, 49);
-    testFindNearestIndex(2, 400);
-    testFindNearestIndex(2, 350);
-    testFindNearestIndex(2, 300);
-    testFindNearestIndex(3, 200000);
-    testFindNearestIndex(3, 1000);
-    testFindNearestIndex(3, 700);
-    testFindNearestIndex(4, 220);
-    testFindNearestIndex(4, 200);
-    testFindNearestIndex(4, 151);
-    testFindNearestIndex(5, 149);
-    testFindNearestIndex(5, 100);
-    testFindNearestIndex(5, 91);
+    pressPageUp();
+    expectEquals(6, crSlider.value);
+    expectEquals(128, slider.pref.value);
+
+    pressPageDown();
+    expectEquals(5, crSlider.value);
+    expectEquals(64, slider.pref.value);
+
+    pressHome();
+    expectEquals(0, crSlider.value);
+    expectEquals(2, slider.pref.value);
+
+    pressArrowDown();
+    expectEquals(0, crSlider.value);
+    expectEquals(2, slider.pref.value);
+
+    pressArrowUp();
+    expectEquals(1, crSlider.value);
+    expectEquals(4, slider.pref.value);
+
+    pressEnd();
+    expectEquals(6, crSlider.value);
+    expectEquals(128, slider.pref.value);
   });
 
   test('scaled slider', function() {
-    slider.scale = 10;
     slider.set('pref.value', 2);
-    expectEquals(20, crSlider.value);
+    expectEquals(2, crSlider.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 39 /* right */);
-    expectEquals(21, crSlider.value);
-    expectEquals(2.1, slider.pref.value);
+    slider.scale = 10;
+    slider.max = 4;
+    pressArrowRight();
+    expectEquals(3, crSlider.value);
+    expectEquals(.3, slider.pref.value);
 
-    MockInteractions.pressAndReleaseKeyOn(crSlider.$.slider, 39 /* right */);
-    expectEquals(22, crSlider.value);
-    expectEquals(2.2, slider.pref.value);
+    pressArrowRight();
+    expectEquals(4, crSlider.value);
+    expectEquals(.4, slider.pref.value);
+
+    pressArrowRight();
+    expectEquals(4, crSlider.value);
+    expectEquals(.4, slider.pref.value);
+
+    pressHome();
+    expectEquals(0, crSlider.value);
+    expectEquals(0, slider.pref.value);
+
+    pressEnd();
+    expectEquals(4, crSlider.value);
+    expectEquals(.4, slider.pref.value);
+
+    slider.set('pref.value', .25);
+    expectEquals(2.5, crSlider.value);
+    expectEquals(.25, slider.pref.value);
+
+    pressPageUp();
+    expectEquals(3.5, crSlider.value);
+    expectEquals(.35, slider.pref.value);
+
+    pressPageUp();
+    expectEquals(4, crSlider.value);
+    expectEquals(.4, slider.pref.value);
   });
 });
diff --git a/chromecast/browser/extensions/api/automation_internal/automation_internal_api.cc b/chromecast/browser/extensions/api/automation_internal/automation_internal_api.cc
index 716db2a..25ef92eb 100644
--- a/chromecast/browser/extensions/api/automation_internal/automation_internal_api.cc
+++ b/chromecast/browser/extensions/api/automation_internal/automation_internal_api.cc
@@ -250,8 +250,8 @@
   std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->tree_id));
   if (!rfh)
     return RespondNow(Error("unable to load tab"));
 
@@ -271,7 +271,7 @@
 AutomationInternalPerformActionFunction::ConvertToAXActionData(
     api::automation_internal::PerformAction::Params* params,
     ui::AXActionData* action) {
-  action->target_tree_id = params->args.tree_id;
+  action->target_tree_id = ui::AXTreeID::FromString(params->args.tree_id);
   action->source_extension_id = extension_id();
   action->target_node_id = params->args.automation_node_id;
   int* request_id = params->args.request_id.get();
@@ -424,7 +424,7 @@
   EXTENSION_FUNCTION_VALIDATE(params.get());
   ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
   ui::AXHostDelegate* delegate =
-      registry->GetHostDelegate(params->args.tree_id);
+      registry->GetHostDelegate(ui::AXTreeID::FromString(params->args.tree_id));
   if (delegate) {
 #if defined(USE_AURA)
     ui::AXActionData data;
@@ -439,8 +439,8 @@
               " platform does not support desktop automation"));
 #endif  // defined(USE_AURA)
   }
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->args.tree_id));
   if (!rfh)
     return RespondNow(Error("Ignoring action on destroyed node"));
 
@@ -507,8 +507,8 @@
   std::unique_ptr<Params> params(Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
+  content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID(
+      ui::AXTreeID::FromString(params->args.tree_id));
   if (!rfh) {
     return RespondNow(
         Error("domQuerySelector query sent on non-web or destroyed tree."));
diff --git a/chromecast/browser/extensions/api/tabs/tabs_constants.cc b/chromecast/browser/extensions/api/tabs/tabs_constants.cc
index df10b0b..eeabb9e8 100644
--- a/chromecast/browser/extensions/api/tabs/tabs_constants.cc
+++ b/chromecast/browser/extensions/api/tabs/tabs_constants.cc
@@ -110,8 +110,6 @@
 const char kInvalidWindowTypeError[] = "Invalid value for type";
 const char kInvalidWindowStateError[] = "Invalid value for state";
 const char kScreenshotsDisabled[] = "Taking screenshots has been disabled";
-const char kCannotUpdateMuteDisabled[] =
-    "Failed to update mute state for tab *, --* must be enabled";
 const char kCannotUpdateMuteCaptured[] =
     "Cannot update mute state for tab *, tab has audio or video currently "
     "being captured";
diff --git a/chromecast/browser/extensions/api/tabs/tabs_constants.h b/chromecast/browser/extensions/api/tabs/tabs_constants.h
index 33d0a01..468fb93 100644
--- a/chromecast/browser/extensions/api/tabs/tabs_constants.h
+++ b/chromecast/browser/extensions/api/tabs/tabs_constants.h
@@ -102,7 +102,6 @@
 extern const char kInvalidWindowTypeError[];
 extern const char kInvalidWindowStateError[];
 extern const char kScreenshotsDisabled[];
-extern const char kCannotUpdateMuteDisabled[];
 extern const char kCannotUpdateMuteCaptured[];
 extern const char kCannotDetermineLanguageOfUnloadedTab[];
 extern const char kMissingLockWindowFullscreenPrivatePermission[];
diff --git a/chromecast/common/extensions_api/cast_extension_messages.h b/chromecast/common/extensions_api/cast_extension_messages.h
index d49c2c6..9a851da1 100644
--- a/chromecast/common/extensions_api/cast_extension_messages.h
+++ b/chromecast/common/extensions_api/cast_extension_messages.h
@@ -79,7 +79,7 @@
 
 IPC_STRUCT_BEGIN(ExtensionMsg_AccessibilityEventBundleParams)
   // ID of the accessibility tree that this event applies to.
-  IPC_STRUCT_MEMBER(std::string, tree_id)
+  IPC_STRUCT_MEMBER(ui::AXTreeID, tree_id)
 
   // Zero or more updates to the accessibility tree to apply first.
   IPC_STRUCT_MEMBER(std::vector<ui::AXTreeUpdate>, updates)
@@ -93,7 +93,7 @@
 
 IPC_STRUCT_BEGIN(ExtensionMsg_AccessibilityLocationChangeParams)
   // ID of the accessibility tree that this event applies to.
-  IPC_STRUCT_MEMBER(std::string, tree_id)
+  IPC_STRUCT_MEMBER(ui::AXTreeID, tree_id)
 
   // ID of the object whose location is changing.
   IPC_STRUCT_MEMBER(int, id)
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index 7cfad18e..2155e12 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -206,6 +206,8 @@
     "media_pipeline_backend_for_mixer.h",
     "mixer_input.cc",
     "mixer_input.h",
+    "mixer_pipeline.cc",
+    "mixer_pipeline.h",
     "post_processing_pipeline.h",
     "post_processing_pipeline_impl.cc",
     "post_processing_pipeline_impl.h",
diff --git a/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc b/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc
index 42563d5..dd6dd5f7 100644
--- a/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc
+++ b/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc
@@ -7,6 +7,7 @@
 #include "chromecast/public/cast_media_shlib.h"
 
 #include <string>
+#include <utility>
 
 #include "chromecast/media/cma/backend/stream_mixer.h"
 
@@ -22,8 +23,8 @@
   StreamMixer::Get()->RemoveLoopbackAudioObserver(observer);
 }
 
-void CastMediaShlib::ResetPostProcessors() {
-  StreamMixer::Get()->ResetPostProcessors();
+void CastMediaShlib::ResetPostProcessors(CastMediaShlib::ResultCallback cb) {
+  StreamMixer::Get()->ResetPostProcessors(std::move(cb));
 }
 
 void CastMediaShlib::SetPostProcessorConfig(const std::string& name,
diff --git a/chromecast/media/cma/backend/filter_group.cc b/chromecast/media/cma/backend/filter_group.cc
index a4be583a..c86c59c5 100644
--- a/chromecast/media/cma/backend/filter_group.cc
+++ b/chromecast/media/cma/backend/filter_group.cc
@@ -20,14 +20,13 @@
 
 FilterGroup::FilterGroup(int num_channels,
                          GroupType type,
-                         bool mix_to_mono,
                          const std::string& name,
                          std::unique_ptr<PostProcessingPipeline> pipeline,
-                         const std::unordered_set<std::string>& device_ids,
+                         const base::flat_set<std::string>& device_ids,
                          const std::vector<FilterGroup*>& mixed_inputs)
     : num_channels_(num_channels),
       type_(type),
-      mix_to_mono_(mix_to_mono),
+      mix_to_mono_(false),
       playout_channel_(kChannelAll),
       name_(name),
       device_ids_(device_ids),
@@ -41,10 +40,6 @@
   for (auto* const m : mixed_inputs) {
     DCHECK_EQ(m->GetOutputChannelCount(), num_channels);
   }
-  // Don't need mono mixer if input is single channel.
-  if (num_channels == 1) {
-    mix_to_mono_ = false;
-  }
 }
 
 FilterGroup::~FilterGroup() = default;
@@ -82,6 +77,7 @@
   AudioContentType content_type = static_cast<AudioContentType>(-1);
 
   rendering_delay.delay_microseconds += GetRenderingDelayMicroseconds();
+  rendering_delay_to_output_ = rendering_delay;
 
   // Recursively mix inputs.
   for (auto* filter_group : mixed_inputs_) {
@@ -205,6 +201,11 @@
          output_samples_per_second_;
 }
 
+MediaPipelineBackend::AudioDecoder::RenderingDelay
+FilterGroup::GetRenderingDelayToOutput() {
+  return rendering_delay_to_output_;
+}
+
 int FilterGroup::GetOutputChannelCount() {
   return post_processing_pipeline_->NumOutputChannels();
 }
diff --git a/chromecast/media/cma/backend/filter_group.h b/chromecast/media/cma/backend/filter_group.h
index 0ce8dff..bc90f35 100644
--- a/chromecast/media/cma/backend/filter_group.h
+++ b/chromecast/media/cma/backend/filter_group.h
@@ -9,7 +9,6 @@
 
 #include <memory>
 #include <string>
-#include <unordered_set>
 #include <vector>
 
 #include "base/containers/flat_set.h"
@@ -43,9 +42,6 @@
   //    some features are specific to certain locations:
   //     - mono mixer takes place at the end of kFinalMix.
   //     - channel selection occurs before post-processing in kLinearize.
-  // |mix_to_mono| enables mono mixing in the pipeline. The number of audio
-  //    output channels will be 1 if it is set to true, otherwise it remains
-  //    same as |num_channels|.
   // |name| is used for debug printing
   // |pipeline| - processing pipeline.
   // |device_ids| is a set of strings that is used as a filter to determine
@@ -58,10 +54,9 @@
 
   FilterGroup(int num_channels,
               GroupType type,
-              bool mix_to_mono,
               const std::string& name,
               std::unique_ptr<PostProcessingPipeline> pipeline,
-              const std::unordered_set<std::string>& device_ids,
+              const base::flat_set<std::string>& device_ids,
               const std::vector<FilterGroup*>& mixed_inputs);
 
   ~FilterGroup();
@@ -89,6 +84,11 @@
   // (Not recursive).
   int64_t GetRenderingDelayMicroseconds();
 
+  // Gets the delay of this FilterGroup and all downstream FilterGroups.
+  // Computed recursively when MixAndFilter() is called.
+  MediaPipelineBackend::AudioDecoder::RenderingDelay
+  GetRenderingDelayToOutput();
+
   // Retrieves a pointer to the output buffer. This will crash if called before
   // MixAndFilter(), and the data & memory location may change each time
   // MixAndFilter() is called.
@@ -127,7 +127,7 @@
   bool mix_to_mono_;
   int playout_channel_;
   const std::string name_;
-  const std::unordered_set<std::string> device_ids_;
+  const base::flat_set<std::string> device_ids_;
   std::vector<FilterGroup*> mixed_inputs_;
   base::flat_set<MixerInput*> active_inputs_;
 
@@ -135,6 +135,7 @@
   int frames_zeroed_;
   float last_volume_;
   int64_t delay_frames_;
+  MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay_to_output_;
   AudioContentType content_type_;
 
   // Buffers that hold audio data while it is mixed.
diff --git a/chromecast/media/cma/backend/filter_group_unittest.cc b/chromecast/media/cma/backend/filter_group_unittest.cc
index e9378cf..9ad6560 100644
--- a/chromecast/media/cma/backend/filter_group_unittest.cc
+++ b/chromecast/media/cma/backend/filter_group_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chromecast/media/cma/backend/filter_group.h"
 
+#include "base/containers/flat_set.h"
 #include "base/memory/ptr_util.h"
 #include "chromecast/media/cma/backend/mixer_input.h"
 #include "chromecast/media/cma/backend/mock_mixer_source.h"
@@ -170,10 +171,10 @@
     EXPECT_CALL(*post_processor_, SetContentType(kDefaultContentType));
     EXPECT_CALL(*post_processor_, UpdatePlayoutChannel(kDefaultPlayoutChannel));
     filter_group_ = std::make_unique<FilterGroup>(
-        kNumInputChannels, type, mix_to_mono, "test_filter",
-        std::move(post_processor),
-        std::unordered_set<std::string>() /* device_ids */,
+        kNumInputChannels, type, "test_filter", std::move(post_processor),
+        base::flat_set<std::string>() /* device_ids */,
         std::vector<FilterGroup*>());
+    filter_group_->SetMixToMono(mix_to_mono);
     filter_group_->Initialize(kInputSampleRate);
     filter_group_->AddInput(&input_);
     filter_group_->UpdatePlayoutChannel(kChannelAll);
diff --git a/chromecast/media/cma/backend/mixer_pipeline.cc b/chromecast/media/cma/backend/mixer_pipeline.cc
new file mode 100644
index 0000000..1cc02eb6
--- /dev/null
+++ b/chromecast/media/cma/backend/mixer_pipeline.cc
@@ -0,0 +1,220 @@
+// 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/media/cma/backend/mixer_pipeline.h"
+
+#include <utility>
+
+#include "base/containers/flat_set.h"
+#include "base/logging.h"
+#include "chromecast/media/base/audio_device_ids.h"
+#include "chromecast/media/cma/backend/filter_group.h"
+#include "chromecast/media/cma/backend/post_processing_pipeline_impl.h"
+#include "chromecast/media/cma/backend/post_processing_pipeline_parser.h"
+#include "media/audio/audio_device_description.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+const int kNumInputChannels = 2;
+
+bool IsOutputDeviceId(const std::string& device) {
+  return device == ::media::AudioDeviceDescription::kDefaultDeviceId ||
+         device == ::media::AudioDeviceDescription::kCommunicationsDeviceId ||
+         device == kLocalAudioDeviceId || device == kAlarmAudioDeviceId ||
+         device == kPlatformAudioDeviceId /* e.g. bluetooth and aux */ ||
+         device == kTtsAudioDeviceId || device == kBypassAudioDeviceId;
+}
+
+std::unique_ptr<FilterGroup> CreateFilterGroup(
+    FilterGroup::GroupType type,
+    int input_channels,
+    const std::string& name,
+    const base::ListValue* filter_list,
+    const base::flat_set<std::string>& device_ids,
+    const std::vector<FilterGroup*>& mixed_inputs,
+    PostProcessingPipelineFactory* ppp_factory) {
+  DCHECK(ppp_factory);
+  auto pipeline =
+      ppp_factory->CreatePipeline(name, filter_list, input_channels);
+  return std::make_unique<FilterGroup>(input_channels, type, name,
+                                       std::move(pipeline), device_ids,
+                                       mixed_inputs);
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<MixerPipeline> MixerPipeline::CreateMixerPipeline(
+    PostProcessingPipelineParser* config,
+    PostProcessingPipelineFactory* factory) {
+  std::unique_ptr<MixerPipeline> mixer_pipeline(new MixerPipeline);
+
+  if (mixer_pipeline->BuildPipeline(config, factory)) {
+    return mixer_pipeline;
+  }
+  return nullptr;
+}
+
+MixerPipeline::MixerPipeline() = default;
+MixerPipeline::~MixerPipeline() = default;
+
+bool MixerPipeline::BuildPipeline(PostProcessingPipelineParser* config,
+                                  PostProcessingPipelineFactory* factory) {
+  DCHECK(config);
+  DCHECK(factory);
+  base::flat_set<std::string> used_streams;
+  for (auto& stream_pipeline : config->GetStreamPipelines()) {
+    const auto& device_ids = stream_pipeline.stream_types;
+    for (const std::string& stream_type : device_ids) {
+      if (!IsOutputDeviceId(stream_type)) {
+        LOG(ERROR) << stream_type
+                   << " is not a stream type. Stream types are listed "
+                   << "in chromecast/media/base/audio_device_ids.cc and "
+                   << "media/audio/audio_device_description.cc";
+        return false;
+      }
+      if (!used_streams.insert(stream_type).second) {
+        LOG(ERROR) << "Multiple instances of stream type '" << stream_type
+                   << "' in " << config->GetFilePath() << ".";
+        return false;
+      }
+      filter_groups_.push_back(CreateFilterGroup(
+          FilterGroup::GroupType::kStream, kNumInputChannels,
+          *device_ids.begin() /* name */, stream_pipeline.pipeline, device_ids,
+          std::vector<FilterGroup*>() /* mixed_inputs */, factory));
+      if (device_ids.find(::media::AudioDeviceDescription::kDefaultDeviceId) !=
+          device_ids.end()) {
+        default_stream_group_ = filter_groups_.back().get();
+      }
+    }
+  }
+
+  if (!filter_groups_.empty()) {
+    std::vector<FilterGroup*> filter_group_ptrs(filter_groups_.size());
+    int mix_group_input_channels = filter_groups_[0]->GetOutputChannelCount();
+    for (size_t i = 0; i < filter_groups_.size(); ++i) {
+      if (mix_group_input_channels !=
+          filter_groups_[i]->GetOutputChannelCount()) {
+        LOG(ERROR)
+            << "All output stream mixers must have the same number of channels"
+            << filter_groups_[i]->name() << " has "
+            << filter_groups_[i]->GetOutputChannelCount() << " but others have "
+            << mix_group_input_channels;
+        return false;
+      }
+      filter_group_ptrs[i] = filter_groups_[i].get();
+    }
+
+    filter_groups_.push_back(CreateFilterGroup(
+        FilterGroup::GroupType::kFinalMix, mix_group_input_channels, "mix",
+        config->GetMixPipeline(),
+        base::flat_set<std::string>() /* device_ids */, filter_group_ptrs,
+        factory));
+  } else {
+    // Mix group directly mixes all inputs.
+    std::string kDefaultDeviceId =
+        ::media::AudioDeviceDescription::kDefaultDeviceId;
+    filter_groups_.push_back(CreateFilterGroup(
+        FilterGroup::GroupType::kFinalMix, kNumInputChannels, "mix",
+        config->GetMixPipeline(),
+        base::flat_set<std::string>({kDefaultDeviceId}),
+        std::vector<FilterGroup*>() /* mixed_inputs */, factory));
+    default_stream_group_ = filter_groups_.back().get();
+  }
+
+  loopback_output_group_ = filter_groups_.back().get();
+
+  filter_groups_.push_back(CreateFilterGroup(
+      FilterGroup::GroupType::kLinearize,
+      loopback_output_group_->GetOutputChannelCount(), "linearize",
+      config->GetLinearizePipeline(),
+      base::flat_set<std::string>() /* device_ids */,
+      std::vector<FilterGroup*>({loopback_output_group_}), factory));
+  output_group_ = filter_groups_.back().get();
+
+  LOG(INFO) << "PostProcessor configuration:";
+  if (default_stream_group_ == loopback_output_group_) {
+    LOG(INFO) << "Stream layer: none";
+  } else {
+    LOG(INFO) << "Stream layer: "
+              << default_stream_group_->GetOutputChannelCount() << " channels";
+  }
+  LOG(INFO) << "Mix filter: " << loopback_output_group_->GetOutputChannelCount()
+            << " channels";
+  LOG(INFO) << "Linearize filter: " << output_group_->GetOutputChannelCount()
+            << " channels";
+
+  return true;
+}
+
+void MixerPipeline::Initialize(int output_samples_per_second_) {
+  for (auto&& filter_group : filter_groups_) {
+    filter_group->Initialize(output_samples_per_second_);
+  }
+}
+
+FilterGroup* MixerPipeline::GetInputGroup(const std::string& device_id) {
+  for (auto&& filter_group : filter_groups_) {
+    if (filter_group->CanProcessInput(device_id)) {
+      return filter_group.get();
+      break;
+    }
+  }
+
+  if (default_stream_group_) {
+    return default_stream_group_;
+  }
+
+  NOTREACHED() << "Could not find a filter group to re-attach " << device_id;
+  return nullptr;
+}
+
+void MixerPipeline::MixAndFilter(
+    int frames_per_write,
+    MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay) {
+  output_group_->MixAndFilter(frames_per_write, rendering_delay);
+}
+
+float* MixerPipeline::GetLoopbackOutput() {
+  return loopback_output_group_->GetOutputBuffer();
+}
+
+float* MixerPipeline::GetOutput() {
+  return output_group_->GetOutputBuffer();
+}
+
+int MixerPipeline::GetLoopbackChannelCount() const {
+  return loopback_output_group_->GetOutputChannelCount();
+}
+
+int MixerPipeline::GetOutputChannelCount() const {
+  return output_group_->GetOutputChannelCount();
+}
+
+int64_t MixerPipeline::GetPostLoopbackRenderingDelayMicroseconds() const {
+  return output_group_->GetRenderingDelayMicroseconds();
+}
+
+void MixerPipeline::SetPostProcessorConfig(const std::string& name,
+                                           const std::string& config) {
+  for (auto&& filter_group : filter_groups_) {
+    filter_group->SetPostProcessorConfig(name, config);
+  }
+}
+
+void MixerPipeline::SetMixToMono(bool mix_to_mono) {
+  loopback_output_group_->SetMixToMono(mix_to_mono);
+}
+
+void MixerPipeline::SetPlayoutChannel(int playout_channel) {
+  for (auto& filter_group : filter_groups_) {
+    filter_group->UpdatePlayoutChannel(playout_channel);
+  }
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/cma/backend/mixer_pipeline.h b/chromecast/media/cma/backend/mixer_pipeline.h
new file mode 100644
index 0000000..086949aa
--- /dev/null
+++ b/chromecast/media/cma/backend/mixer_pipeline.h
@@ -0,0 +1,99 @@
+// 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_MEDIA_CMA_BACKEND_MIXER_PIPELINE_H_
+#define CHROMECAST_MEDIA_CMA_BACKEND_MIXER_PIPELINE_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "chromecast/public/media/audio_post_processor_shlib.h"
+#include "chromecast/public/media/media_pipeline_backend.h"
+
+namespace chromecast {
+namespace media {
+
+class FilterGroup;
+class PostProcessingPipelineParser;
+class PostProcessingPipelineFactory;
+
+// Provides mixer and post-processing functionality for StreamMixer.
+// Internally, MixerPipeline is a tree of post processors with two taps -
+// LoopbackOutput and Output. Calling MixAndFilter causes the pipeline to pull
+// data from inputs, mixing and filtering as described in cast_audio.json.
+class MixerPipeline {
+ public:
+  // Attempts to create a pipeline from |config|.
+  // Returns nullptr if config fails to parse.
+  static std::unique_ptr<MixerPipeline> CreateMixerPipeline(
+      PostProcessingPipelineParser* parser,
+      PostProcessingPipelineFactory* factory);
+
+  ~MixerPipeline();
+
+  // Sets the sample rate of all processors.
+  void Initialize(int samples_per_second);
+
+  // Returns the FilterGroup that should process a stream with |device_id| or
+  // |nullptr| if no matching FilterGroup is found.
+  FilterGroup* GetInputGroup(const std::string& device_id);
+
+  // Polls |MixerInput|s for |frames_per_write| frames of audio data, mixes the
+  // inputs, and applies PostProcessors.
+  // |rendering_delay| is the rendering delay of the output device, and is used
+  // to calculate the delay from various points in the pipeline.
+  void MixAndFilter(
+      int frames_per_write,
+      MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay);
+
+  // Returns the output data from the "mix" group.
+  float* GetLoopbackOutput();
+
+  // Returns the output data from the "linearize" group.
+  float* GetOutput();
+
+  // Returns the number of channels that will be present in GetLoopbackOutput().
+  int GetLoopbackChannelCount() const;
+
+  // Returns the number of channels that will be present in GetOutput().
+  int GetOutputChannelCount() const;
+
+  // Attempts to send |config| to PostProcessors with |name|.
+  void SetPostProcessorConfig(const std::string& name,
+                              const std::string& config);
+
+  // Returns the rendering delay between audio coming from GetLoopbackOutput()
+  // and GetOutput(), i.e. the group delay of PostProcessors in "linearize"
+  int64_t GetPostLoopbackRenderingDelayMicroseconds() const;
+
+  // Informs FilterGroups that the output should be mixed to mono.
+  void SetMixToMono(bool mix_to_mono);
+
+  // Informs FilterGroups and PostProcessors which channel will be played out.
+  // |playout_channel| may be |-1| to signal all channels will be played out.
+  void SetPlayoutChannel(int playout_channel);
+
+ private:
+  // External classes should call CreateMixerPipeline.
+  MixerPipeline();
+
+  // Attempts to build a pipeline using |config|. Returns |true| IFF successful.
+  bool BuildPipeline(PostProcessingPipelineParser* config,
+                     PostProcessingPipelineFactory* factory);
+
+  std::vector<std::unique_ptr<FilterGroup>> filter_groups_;
+  FilterGroup* default_stream_group_ = nullptr;
+  FilterGroup* loopback_output_group_ = nullptr;
+  FilterGroup* output_group_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(MixerPipeline);
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_BACKEND_MIXER_PIPELINE_H_
diff --git a/chromecast/media/cma/backend/post_processing_pipeline_parser.cc b/chromecast/media/cma/backend/post_processing_pipeline_parser.cc
index 7bc3fa61..deac7d19 100644
--- a/chromecast/media/cma/backend/post_processing_pipeline_parser.cc
+++ b/chromecast/media/cma/backend/post_processing_pipeline_parser.cc
@@ -4,13 +4,11 @@
 
 #include "chromecast/media/cma/backend/post_processing_pipeline_parser.h"
 
-#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/values.h"
 #include "chromecast/base/serializers.h"
 #include "chromecast/media/base/audio_device_ids.h"
-#include "chromecast/media/cma/backend/cast_audio_json.h"
 #include "media/audio/audio_device_description.h"
 
 namespace chromecast {
@@ -29,7 +27,7 @@
 
 StreamPipelineDescriptor::StreamPipelineDescriptor(
     const base::ListValue* pipeline_in,
-    const std::unordered_set<std::string>& stream_types_in)
+    const base::flat_set<std::string>& stream_types_in)
     : pipeline(pipeline_in), stream_types(stream_types_in) {}
 
 StreamPipelineDescriptor::~StreamPipelineDescriptor() = default;
@@ -39,21 +37,27 @@
     : StreamPipelineDescriptor(other.pipeline, other.stream_types) {}
 
 PostProcessingPipelineParser::PostProcessingPipelineParser(
-    const std::string& json)
-    : postprocessor_config_(nullptr) {
-  base::FilePath path = CastAudioJson::GetFilePath();
-  if (json.empty() && !base::PathExists(path)) {
-    LOG(WARNING) << "Could not open post-processing config in " << path << ".";
+    std::unique_ptr<base::DictionaryValue> config_dict)
+    : file_path_(""), config_dict_(std::move(config_dict)) {
+  CHECK(config_dict_) << "Invalid JSON";
+  if (!config_dict_->GetDictionary(kPostProcessorsKey,
+                                   &postprocessor_config_)) {
+    LOG(WARNING) << "No post-processor config found.";
+  }
+}
+
+PostProcessingPipelineParser::PostProcessingPipelineParser(
+    const base::FilePath& file_path)
+    : file_path_(file_path) {
+  if (!base::PathExists(file_path_)) {
+    LOG(WARNING) << "No post-processing config found at " << file_path_ << ".";
     return;
   }
 
-  if (json.empty()) {
-    config_dict_ = base::DictionaryValue::From(DeserializeJsonFromFile(path));
-  } else {
-    config_dict_ = base::DictionaryValue::From(DeserializeFromJson(json));
-  }
+  config_dict_ =
+      base::DictionaryValue::From(DeserializeJsonFromFile(file_path_));
+  CHECK(config_dict_) << "Invalid JSON in " << file_path_;
 
-  CHECK(config_dict_) << "Invalid JSON in " << path;
   if (!config_dict_->GetDictionary(kPostProcessorsKey,
                                    &postprocessor_config_)) {
     LOG(WARNING) << "No post-processor config found.";
@@ -82,7 +86,7 @@
 
     const base::ListValue* streams_list;
     CHECK(pipeline_description_dict->GetList(kStreamsKey, &streams_list));
-    std::unordered_set<std::string> streams_set;
+    base::flat_set<std::string> streams_set;
     for (size_t stream = 0; stream < streams_list->GetSize(); ++stream) {
       std::string stream_name;
       CHECK(streams_list->GetString(stream, &stream_name));
@@ -109,8 +113,7 @@
   if (!postprocessor_config_ ||
       !postprocessor_config_->GetDictionary(key, &stream_dict)) {
     LOG(WARNING) << "No post-processor description found for \"" << key
-                 << "\" in " << CastAudioJson::GetFilePath()
-                 << ". Using passthrough.";
+                 << "\" in " << file_path_ << ". Using passthrough.";
     return nullptr;
   }
   const base::ListValue* out_list;
@@ -119,5 +122,9 @@
   return out_list;
 }
 
+base::FilePath PostProcessingPipelineParser::GetFilePath() const {
+  return file_path_;
+}
+
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/post_processing_pipeline_parser.h b/chromecast/media/cma/backend/post_processing_pipeline_parser.h
index b66f237..b5aadea 100644
--- a/chromecast/media/cma/backend/post_processing_pipeline_parser.h
+++ b/chromecast/media/cma/backend/post_processing_pipeline_parser.h
@@ -7,9 +7,10 @@
 
 #include <memory>
 #include <string>
-#include <unordered_set>
 #include <vector>
 
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
 #include "base/macros.h"
 
 namespace base {
@@ -29,11 +30,10 @@
   //    "config": "CONFIGURATION_STRING"},
   //    ... ]
   const base::ListValue* pipeline;
-  std::unordered_set<std::string> stream_types;
+  base::flat_set<std::string> stream_types;
 
-  StreamPipelineDescriptor(
-      const base::ListValue* pipeline_in,
-      const std::unordered_set<std::string>& stream_types_in);
+  StreamPipelineDescriptor(const base::ListValue* pipeline_in,
+                           const base::flat_set<std::string>& stream_types_in);
   ~StreamPipelineDescriptor();
   StreamPipelineDescriptor(const StreamPipelineDescriptor& other);
   StreamPipelineDescriptor operator=(const StreamPipelineDescriptor& other) =
@@ -43,9 +43,12 @@
 // Helper class to parse post-processing pipeline descriptor file.
 class PostProcessingPipelineParser {
  public:
-  // |json|, if provided, is used instead of reading from file.
-  // |json| should be provided in tests only.
-  explicit PostProcessingPipelineParser(const std::string& json = "");
+  PostProcessingPipelineParser(const base::FilePath& path);
+
+  // For testing only:
+  PostProcessingPipelineParser(
+      std::unique_ptr<base::DictionaryValue> config_dict);
+
   ~PostProcessingPipelineParser();
 
   std::vector<StreamPipelineDescriptor> GetStreamPipelines();
@@ -55,11 +58,15 @@
   const base::ListValue* GetMixPipeline();
   const base::ListValue* GetLinearizePipeline();
 
+  // Returns the file path used to load this object.
+  base::FilePath GetFilePath() const;
+
  private:
   const base::ListValue* GetPipelineByKey(const std::string& key);
 
+  const base::FilePath file_path_;
   std::unique_ptr<base::DictionaryValue> config_dict_;
-  const base::DictionaryValue* postprocessor_config_;
+  const base::DictionaryValue* postprocessor_config_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(PostProcessingPipelineParser);
 };
diff --git a/chromecast/media/cma/backend/stream_mixer.cc b/chromecast/media/cma/backend/stream_mixer.cc
index 8c1aef84..c4ee0c29 100644
--- a/chromecast/media/cma/backend/stream_mixer.cc
+++ b/chromecast/media/cma/backend/stream_mixer.cc
@@ -19,13 +19,13 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chromecast/base/chromecast_switches.h"
+#include "chromecast/base/serializers.h"
 #include "chromecast/media/base/audio_device_ids.h"
 #include "chromecast/media/cma/backend/audio_output_redirector.h"
 #include "chromecast/media/cma/backend/cast_audio_json.h"
 #include "chromecast/media/cma/backend/filter_group.h"
 #include "chromecast/media/cma/backend/post_processing_pipeline_impl.h"
 #include "chromecast/media/cma/backend/post_processing_pipeline_parser.h"
-#include "chromecast/public/media/audio_post_processor_shlib.h"
 #include "chromecast/public/media/mixer_output_stream.h"
 #include "media/audio/audio_device_description.h"
 
@@ -118,31 +118,6 @@
   task_runner->PostTask(FROM_HERE, std::move(task));
 }
 
-bool IsOutputDeviceId(const std::string& device) {
-  return device == ::media::AudioDeviceDescription::kDefaultDeviceId ||
-         device == ::media::AudioDeviceDescription::kCommunicationsDeviceId ||
-         device == kLocalAudioDeviceId || device == kAlarmAudioDeviceId ||
-         device == kPlatformAudioDeviceId /* e.g. bluetooth and aux */ ||
-         device == kTtsAudioDeviceId || device == kBypassAudioDeviceId;
-}
-
-std::unique_ptr<FilterGroup> CreateFilterGroup(
-    FilterGroup::GroupType type,
-    int input_channels,
-    bool mix_to_mono,
-    const std::string& name,
-    const base::ListValue* filter_list,
-    const std::unordered_set<std::string>& device_ids,
-    const std::vector<FilterGroup*>& mixed_inputs,
-    std::unique_ptr<PostProcessingPipelineFactory>& ppp_factory) {
-  DCHECK(ppp_factory);
-  auto pipeline =
-      ppp_factory->CreatePipeline(name, filter_list, input_channels);
-  return std::make_unique<FilterGroup>(input_channels, type, mix_to_mono, name,
-                                       std::move(pipeline), device_ids,
-                                       mixed_inputs);
-}
-
 int GetFixedSampleRate() {
   int fixed_sample_rate = GetSwitchValueNonNegativeInt(
       switches::kAudioOutputSampleRate, MixerOutputStream::kInvalidSampleRate);
@@ -253,9 +228,8 @@
     LOG(INFO) << "Setting fixed sample rate to " << fixed_sample_rate_;
   }
 
-  // Read post-processing configuration file.
-  PostProcessingPipelineParser pipeline_parser;
-  CreatePostProcessors(&pipeline_parser);
+  CreatePostProcessors([](bool, const std::string&) {},
+                       "" /* override_config */);
 
   // TODO(jyw): command line flag for filter frame alignment.
   DCHECK_EQ(filter_frame_alignment_ & (filter_frame_alignment_ - 1), 0)
@@ -273,11 +247,14 @@
   }
 }
 
-void StreamMixer::ResetPostProcessors() {
-  POST_THROUGH_INPUT_THREAD(&StreamMixer::ResetPostProcessorsOnThread);
+void StreamMixer::ResetPostProcessors(CastMediaShlib::ResultCallback callback) {
+  POST_THROUGH_INPUT_THREAD(&StreamMixer::ResetPostProcessorsOnThread,
+                            std::move(callback), "");
 }
 
-void StreamMixer::ResetPostProcessorsOnThread() {
+void StreamMixer::ResetPostProcessorsOnThread(
+    CastMediaShlib::ResultCallback callback,
+    const std::string& override_config) {
   DCHECK(mixer_task_runner_->BelongsToCurrentThread());
 
   // Detach inputs.
@@ -285,42 +262,61 @@
     input.second->SetFilterGroup(nullptr);
   }
 
-  // Re-create post processors.
-  filter_groups_.clear();
-  default_filter_ = nullptr;
-
-  PostProcessingPipelineParser parser;
-  CreatePostProcessors(&parser);
-
-  if (state_ == kStateRunning) {
-    for (auto&& filter_group : filter_groups_) {
-      filter_group->Initialize(output_samples_per_second_);
-    }
-  }
+  CreatePostProcessors(std::move(callback), override_config);
 
   // Re-attach inputs.
   for (const auto& input : inputs_) {
-    MixerInput::Source* input_source = input.first;
+    FilterGroup* input_group =
+        mixer_pipeline_->GetInputGroup(input.first->device_id());
+    DCHECK(input_group) << "No input group for input.first->device_id()";
+    input.second->SetFilterGroup(input_group);
+  }
+  UpdatePlayoutChannel();
+}
 
-    FilterGroup* input_filter_group = default_filter_;
-    for (auto&& filter_group : filter_groups_) {
-      if (filter_group->CanProcessInput(input_source->device_id())) {
-        input_filter_group = filter_group.get();
-        break;
-      }
-    }
+// May be called on mixer_task_runner_ or from ctor
+void StreamMixer::CreatePostProcessors(CastMediaShlib::ResultCallback callback,
+                                       const std::string& override_config) {
+  // (Re)-create post processors.
+  mixer_pipeline_.reset();
 
-    if (input_filter_group) {
-      LOG(INFO) << "Re-attach input of type " << input_source->device_id()
-                << " to " << input_filter_group->name();
-      input.second->SetFilterGroup(input_filter_group);
-    } else {
-      NOTREACHED() << "Could not find a filter group to re-attach "
-                   << input_source->device_id();
-    }
+  if (!override_config.empty()) {
+    PostProcessingPipelineParser parser(
+        base::DictionaryValue::From(DeserializeFromJson(override_config)));
+    mixer_pipeline_ = MixerPipeline::CreateMixerPipeline(
+        &parser, post_processing_pipeline_factory_.get());
+  } else {
+    PostProcessingPipelineParser parser(CastAudioJson::GetFilePath());
+    mixer_pipeline_ = MixerPipeline::CreateMixerPipeline(
+        &parser, post_processing_pipeline_factory_.get());
   }
 
-  UpdatePlayoutChannel();
+  // Attempt to fall back to built-in cast_audio.json, unless we were reset with
+  // an override config.
+  if ((!mixer_pipeline_ || !PostProcessorsHaveCorrectNumOutputs()) &&
+      override_config.empty()) {
+    LOG(WARNING) << "Invalid cast_audio.json config loaded. Retrying with "
+                    "read-only config";
+    callback(false,
+             "Unable to build pipeline.");  // TODO(bshaya): Send more specific
+                                            // error message.
+    callback = nullptr;
+    PostProcessingPipelineParser parser(CastAudioJson::GetReadOnlyFilePath());
+    mixer_pipeline_.reset();
+    mixer_pipeline_ = MixerPipeline::CreateMixerPipeline(
+        &parser, post_processing_pipeline_factory_.get());
+  }
+
+  CHECK(mixer_pipeline_) << "Unable to load post processor config!";
+  CHECK(PostProcessorsHaveCorrectNumOutputs());
+
+  if (state_ == kStateRunning) {
+    mixer_pipeline_->Initialize(output_samples_per_second_);
+  }
+
+  if (callback) {
+    callback(true, "");
+  }
 }
 
 void StreamMixer::ResetPostProcessorsForTest(
@@ -328,11 +324,9 @@
     const std::string& pipeline_json) {
   DCHECK(mixer_task_runner_->BelongsToCurrentThread());
   LOG(INFO) << __FUNCTION__ << " disregard previous PostProcessor messages.";
-  filter_groups_.clear();
-  default_filter_ = nullptr;
+  mixer_pipeline_.reset();
   post_processing_pipeline_factory_ = std::move(pipeline_factory);
-  PostProcessingPipelineParser parser(pipeline_json);
-  CreatePostProcessors(&parser);
+  ResetPostProcessorsOnThread([](bool, const std::string&) {}, pipeline_json);
 }
 
 void StreamMixer::SetNumOutputChannelsForTest(int num_output_channels) {
@@ -340,86 +334,6 @@
   num_output_channels_ = num_output_channels;
 }
 
-void StreamMixer::CreatePostProcessors(
-    PostProcessingPipelineParser* pipeline_parser) {
-  std::unordered_set<std::string> used_streams;
-  for (auto& stream_pipeline : pipeline_parser->GetStreamPipelines()) {
-    const auto& device_ids = stream_pipeline.stream_types;
-    for (const std::string& stream_type : device_ids) {
-      CHECK(IsOutputDeviceId(stream_type))
-          << stream_type << " is not a stream type. Stream types are listed "
-          << "in chromecast/media/base/audio_device_ids.cc and "
-          << "media/audio/audio_device_description.cc";
-      CHECK(used_streams.insert(stream_type).second)
-          << "Multiple instances of stream type '" << stream_type << "' in "
-          << CastAudioJson::GetFilePath() << ".";
-    }
-    filter_groups_.push_back(CreateFilterGroup(
-        FilterGroup::GroupType::kStream, kNumInputChannels,
-        false /* mono_mixer */, *device_ids.begin() /* name */,
-        stream_pipeline.pipeline, device_ids,
-        std::vector<FilterGroup*>() /* mixed_inputs */,
-        post_processing_pipeline_factory_));
-    if (device_ids.find(::media::AudioDeviceDescription::kDefaultDeviceId) !=
-        device_ids.end()) {
-      default_filter_ = filter_groups_.back().get();
-    }
-  }
-
-  bool enabled_mono_mixer = (num_output_channels_ == 1);
-  if (!filter_groups_.empty()) {
-    std::vector<FilterGroup*> filter_group_ptrs(filter_groups_.size());
-    int mix_group_input_channels = filter_groups_[0]->GetOutputChannelCount();
-    for (size_t i = 0; i < filter_groups_.size(); ++i) {
-      DCHECK_EQ(mix_group_input_channels,
-                filter_groups_[i]->GetOutputChannelCount())
-          << "All output stream mixers must have the same number of channels";
-      filter_group_ptrs[i] = filter_groups_[i].get();
-    }
-
-    // Enable Mono mixer in |mix_filter_| if necessary.
-    filter_groups_.push_back(CreateFilterGroup(
-        FilterGroup::GroupType::kFinalMix, mix_group_input_channels,
-        enabled_mono_mixer, "mix", pipeline_parser->GetMixPipeline(),
-        std::unordered_set<std::string>() /* device_ids */, filter_group_ptrs,
-        post_processing_pipeline_factory_));
-  } else {
-    // Mix group directly mixes all inputs.
-    std::string kDefaultDeviceId =
-        ::media::AudioDeviceDescription::kDefaultDeviceId;
-    filter_groups_.push_back(CreateFilterGroup(
-        FilterGroup::GroupType::kFinalMix, kNumInputChannels,
-        enabled_mono_mixer, "mix", pipeline_parser->GetMixPipeline(),
-        std::unordered_set<std::string>({kDefaultDeviceId}),
-        std::vector<FilterGroup*>() /* mixed_inputs */,
-        post_processing_pipeline_factory_));
-    default_filter_ = filter_groups_.back().get();
-  }
-
-  mix_filter_ = filter_groups_.back().get();
-
-  filter_groups_.push_back(CreateFilterGroup(
-      FilterGroup::GroupType::kLinearize, mix_filter_->GetOutputChannelCount(),
-      false /* mono_mixer */, "linearize",
-      pipeline_parser->GetLinearizePipeline(),
-      std::unordered_set<std::string>() /* device_ids */,
-      std::vector<FilterGroup*>({mix_filter_}),
-      post_processing_pipeline_factory_));
-  linearize_filter_ = filter_groups_.back().get();
-
-  LOG(INFO) << "PostProcessor configuration:";
-  if (default_filter_ == mix_filter_) {
-    LOG(INFO) << "Stream layer: none";
-  } else {
-    LOG(INFO) << "Stream layer: " << default_filter_->GetOutputChannelCount()
-              << " channels";
-  }
-  LOG(INFO) << "Mix filter: " << mix_filter_->GetOutputChannelCount()
-            << " channels";
-  LOG(INFO) << "Linearize filter: "
-            << linearize_filter_->GetOutputChannelCount() << " channels";
-}
-
 StreamMixer::~StreamMixer() {
   VLOG(1) << __func__;
   if (shim_thread_) {
@@ -489,12 +403,8 @@
       output_->OptimalWriteFramesCount() & ~(filter_frame_alignment_ - 1);
   CHECK_GT(frames_per_write_, 0);
 
-  ValidatePostProcessors();
-
   // Initialize filters.
-  for (auto&& filter_group : filter_groups_) {
-    filter_group->Initialize(output_samples_per_second_);
-  }
+  mixer_pipeline_->Initialize(output_samples_per_second_);
 
   for (auto& redirector : audio_output_redirectors_) {
     redirector.second->Start(output_samples_per_second_);
@@ -590,25 +500,14 @@
     Start();
   }
 
-  FilterGroup* input_filter_group = default_filter_;
-  for (auto&& filter_group : filter_groups_) {
-    if (filter_group->CanProcessInput(input_source->device_id())) {
-      input_filter_group = filter_group.get();
-      break;
-    }
-  }
-  if (input_filter_group) {
-    LOG(INFO) << "Added input of type " << input_source->device_id() << " to "
-              << input_filter_group->name();
-  } else {
-    NOTREACHED() << "Could not find a filter group for "
-                 << input_source->device_id() << "\n"
-                 << "(consider adding a 'default' processor)";
-  }
+  FilterGroup* input_group =
+      mixer_pipeline_->GetInputGroup(input_source->device_id());
+  DCHECK(input_group) << "Could not find a processor for "
+                      << input_source->device_id();
 
   auto input = std::make_unique<MixerInput>(
       input_source, output_samples_per_second_, frames_per_write_,
-      GetTotalRenderingDelay(input_filter_group), input_filter_group);
+      GetTotalRenderingDelay(input_group), input_group);
   if (state_ != kStateRunning) {
     // Mixer error occurred, signal error.
     MixerInput* input_ptr = input.get();
@@ -670,8 +569,6 @@
 
 void StreamMixer::UpdatePlayoutChannel() {
   DCHECK(mixer_task_runner_->BelongsToCurrentThread());
-  DCHECK(mix_filter_);
-  DCHECK(linearize_filter_);
 
   int playout_channel;
   if (inputs_.empty()) {
@@ -688,11 +585,9 @@
          playout_channel >= 0 && playout_channel < kNumInputChannels);
   LOG(INFO) << "Update playout channel: " << playout_channel;
 
-  mix_filter_->SetMixToMono(num_output_channels_ == 1 &&
-                            playout_channel == kChannelAll);
-  for (auto& filter_group : filter_groups_) {
-    filter_group->UpdatePlayoutChannel(playout_channel);
-  }
+  mixer_pipeline_->SetMixToMono(num_output_channels_ == 1 &&
+                                playout_channel == kChannelAll);
+  mixer_pipeline_->SetPlayoutChannel(playout_channel);
 }
 
 MediaPipelineBackend::AudioDecoder::RenderingDelay
@@ -701,20 +596,12 @@
   if (!output_) {
     return MediaPipelineBackend::AudioDecoder::RenderingDelay();
   }
-  MediaPipelineBackend::AudioDecoder::RenderingDelay delay =
-      output_->GetRenderingDelay();
   if (!filter_group) {
-    return delay;
+    return output_->GetRenderingDelay();
   }
 
-  if (filter_group != mix_filter_) {
-    delay.delay_microseconds += filter_group->GetRenderingDelayMicroseconds();
-  }
-  delay.delay_microseconds += mix_filter_->GetRenderingDelayMicroseconds();
-  delay.delay_microseconds +=
-      linearize_filter_->GetRenderingDelayMicroseconds();
-
-  return delay;
+  // Includes |output_->GetRenderingDelay()|.
+  return filter_group->GetRenderingDelayToOutput();
 }
 
 void StreamMixer::PlaybackLoop() {
@@ -740,15 +627,16 @@
   // Recursively mix and filter each group.
   MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay =
       output_->GetRenderingDelay();
-  linearize_filter_->MixAndFilter(frames_per_write_, rendering_delay);
+  mixer_pipeline_->MixAndFilter(frames_per_write_, rendering_delay);
 
   int64_t expected_playback_time;
   if (rendering_delay.timestamp_microseconds == kNoTimestamp) {
     expected_playback_time = kNoTimestamp;
   } else {
-    expected_playback_time = rendering_delay.timestamp_microseconds +
-                             rendering_delay.delay_microseconds +
-                             linearize_filter_->GetRenderingDelayMicroseconds();
+    expected_playback_time =
+        rendering_delay.timestamp_microseconds +
+        rendering_delay.delay_microseconds +
+        mixer_pipeline_->GetPostLoopbackRenderingDelayMicroseconds();
   }
 
   for (auto& redirector : audio_output_redirectors_) {
@@ -762,10 +650,10 @@
   DCHECK(mixer_task_runner_->BelongsToCurrentThread());
 
   // Downmix reference signal to mono to reduce CPU load.
-  int mix_channel_count = mix_filter_->GetOutputChannelCount();
+  int mix_channel_count = mixer_pipeline_->GetLoopbackChannelCount();
   int loopback_channel_count = mix_channel_count;
 
-  float* mixed_data = mix_filter_->GetOutputBuffer();
+  float* mixed_data = mixer_pipeline_->GetLoopbackOutput();
   if (num_output_channels_ == 1 && mix_channel_count != 1) {
     for (int i = 0; i < frames; ++i) {
       float sum = 0;
@@ -779,6 +667,7 @@
 
   // Hard limit to [1.0, -1.0]
   for (int i = 0; i < frames * loopback_channel_count; ++i) {
+    // TODO(bshaya): Warn about clipping here.
     mixed_data[i] = std::min(1.0f, std::max(-1.0f, mixed_data[i]));
   }
 
@@ -793,8 +682,8 @@
   }
 
   // Drop extra channels from linearize filter if necessary.
-  float* linearized_data = linearize_filter_->GetOutputBuffer();
-  int linearize_channel_count = linearize_filter_->GetOutputChannelCount();
+  float* linearized_data = mixer_pipeline_->GetOutput();
+  int linearize_channel_count = mixer_pipeline_->GetOutputChannelCount();
   if (num_output_channels_ == 1 && linearize_channel_count != 1) {
     for (int i = 0; i < frames; ++i) {
       linearized_data[i] = linearized_data[i * linearize_channel_count];
@@ -1012,28 +901,34 @@
 void StreamMixer::SetPostProcessorConfigOnThread(const std::string& name,
                                                  const std::string& config) {
   DCHECK(mixer_task_runner_->BelongsToCurrentThread());
-  for (auto&& filter_group : filter_groups_) {
-    filter_group->SetPostProcessorConfig(name, config);
-  }
+  mixer_pipeline_->SetPostProcessorConfig(name, config);
 }
 
 void StreamMixer::ValidatePostProcessorsForTest() {
-  ValidatePostProcessors();
+  CHECK(PostProcessorsHaveCorrectNumOutputs());
 }
 
-void StreamMixer::ValidatePostProcessors() {
-  // Ensure filter configuration is viable.
-  // This can't be done in CreatePostProcessors() because it breaks tests.
-  CHECK(num_output_channels_ == 1 ||
-        num_output_channels_ == linearize_filter_->GetOutputChannelCount())
-      << "PostProcessor configuration channel count does not match command line"
-      << " flag: " << linearize_filter_->GetOutputChannelCount() << " vs "
-      << num_output_channels_;
-  int loopback_channel_count =
-      num_output_channels_ == 1 ? 1 : mix_filter_->GetOutputChannelCount();
-  CHECK_LE(loopback_channel_count, 2)
-      << "PostProcessor configuration has " << loopback_channel_count
-      << " channels after 'mix' group, but only 1 or 2 are allowed.";
+bool StreamMixer::PostProcessorsHaveCorrectNumOutputs() {
+  bool correct_num_outputs =
+      num_output_channels_ == 1 ||
+      num_output_channels_ == mixer_pipeline_->GetOutputChannelCount();
+  if (!correct_num_outputs) {
+    LOG(WARNING) << "PostProcessor configuration channel count does not match "
+                 << "command line  flag: "
+                 << mixer_pipeline_->GetOutputChannelCount() << " vs "
+                 << num_output_channels_;
+    return false;
+  }
+  int loopback_channel_count = num_output_channels_ == 1
+                                   ? 1
+                                   : mixer_pipeline_->GetLoopbackChannelCount();
+  if (loopback_channel_count > 2) {
+    LOG(WARNING) << "PostProcessor configuration has " << loopback_channel_count
+                 << " channels after 'mix' group, but only 1 or 2 are allowed.";
+    return false;
+  }
+  return true;
 }
+
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/stream_mixer.h b/chromecast/media/cma/backend/stream_mixer.h
index 02683651..efad3e8 100644
--- a/chromecast/media/cma/backend/stream_mixer.h
+++ b/chromecast/media/cma/backend/stream_mixer.h
@@ -20,6 +20,7 @@
 #include "base/threading/thread.h"
 #include "base/time/time.h"
 #include "chromecast/media/cma/backend/mixer_input.h"
+#include "chromecast/media/cma/backend/mixer_pipeline.h"
 #include "chromecast/public/cast_media_shlib.h"
 #include "chromecast/public/media/external_audio_pipeline_shlib.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
@@ -29,9 +30,7 @@
 namespace media {
 
 class AudioOutputRedirector;
-class FilterGroup;
 class MixerOutputStream;
-class PostProcessingPipelineParser;
 class PostProcessingPipelineFactory;
 
 // Mixer implementation. The mixer has zero or more inputs; these can be added
@@ -118,7 +117,7 @@
   void SetPostProcessorConfig(const std::string& name,
                               const std::string& config);
 
-  void ResetPostProcessors();
+  void ResetPostProcessors(CastMediaShlib::ResultCallback callback);
 
   // Test-only methods.
   StreamMixer(std::unique_ptr<MixerOutputStream> output,
@@ -159,9 +158,11 @@
     bool muted = false;
   };
 
-  void CreatePostProcessors(PostProcessingPipelineParser* pipeline_parser);
-  void ResetPostProcessorsOnThread();
-  void ValidatePostProcessors();
+  void ResetPostProcessorsOnThread(CastMediaShlib::ResultCallback callback,
+                                   const std::string& override_config);
+  void CreatePostProcessors(CastMediaShlib::ResultCallback callback,
+                            const std::string& override_config);
+  bool PostProcessorsHaveCorrectNumOutputs();
   void FinalizeOnMixerThread();
   void Start();
   void Stop();
@@ -171,8 +172,6 @@
   void RemoveInputOnThread(MixerInput::Source* input_source);
   void SetCloseTimeout();
   void UpdatePlayoutChannel();
-  MediaPipelineBackend::AudioDecoder::RenderingDelay GetTotalRenderingDelay(
-      FilterGroup* filter_group);
 
   void PlaybackLoop();
   void WriteOneBuffer();
@@ -211,9 +210,13 @@
                         int length);
   void LoopbackInterrupted();
 
+  MediaPipelineBackend::AudioDecoder::RenderingDelay GetTotalRenderingDelay(
+      FilterGroup* filter_group);
+
   std::unique_ptr<MixerOutputStream> output_;
   std::unique_ptr<PostProcessingPipelineFactory>
       post_processing_pipeline_factory_;
+  std::unique_ptr<MixerPipeline> mixer_pipeline_;
   std::unique_ptr<base::Thread> mixer_thread_;
   scoped_refptr<base::SingleThreadTaskRunner> mixer_task_runner_;
 
@@ -239,11 +242,6 @@
   State state_;
   base::TimeTicks close_timestamp_;
 
-  std::vector<std::unique_ptr<FilterGroup>> filter_groups_;
-  FilterGroup* default_filter_ = nullptr;
-  FilterGroup* mix_filter_ = nullptr;
-  FilterGroup* linearize_filter_ = nullptr;
-
   base::flat_map<MixerInput::Source*, std::unique_ptr<MixerInput>> inputs_;
   base::flat_map<MixerInput::Source*, std::unique_ptr<MixerInput>>
       ignored_inputs_;
diff --git a/chromecast/media/cma/backend/stream_mixer_unittest.cc b/chromecast/media/cma/backend/stream_mixer_unittest.cc
index 87e2bbf..e3ed597e 100644
--- a/chromecast/media/cma/backend/stream_mixer_unittest.cc
+++ b/chromecast/media/cma/backend/stream_mixer_unittest.cc
@@ -1482,12 +1482,9 @@
 
   ::testing::FLAGS_gtest_death_test_style = "threadsafe";
   mixer_->SetNumOutputChannelsForTest(kNumOutputChannels);
-  mixer_->ResetPostProcessorsForTest(
-      std::make_unique<MockPostProcessorFactory>(), config);
-
-  ASSERT_DEATH(mixer_->ValidatePostProcessorsForTest(),
-               DeathRegex("PostProcessor configuration channel count does not "
-                          "match command line flag"));
+  ASSERT_DEATH(mixer_->ResetPostProcessorsForTest(
+                   std::make_unique<MockPostProcessorFactory>(), config),
+               DeathRegex("PostProcessorsHaveCorrectNumOutputs"));
 }
 
 TEST_F(StreamMixerDeathTest, CrashesIfMoreThan2LoopbackChannels) {
@@ -1514,11 +1511,9 @@
   ::testing::FLAGS_gtest_death_test_style = "threadsafe";
   mixer_->SetNumOutputChannelsForTest(kNumOutputChannels);
 
-  mixer_->ResetPostProcessorsForTest(
-      std::make_unique<MockPostProcessorFactory>(), config);
-
-  ASSERT_DEATH(mixer_->ValidatePostProcessorsForTest(),
-               DeathRegex("loopback_channel_count <= 2"));
+  ASSERT_DEATH(mixer_->ResetPostProcessorsForTest(
+                   std::make_unique<MockPostProcessorFactory>(), config),
+               DeathRegex("PostProcessorsHaveCorrectNumOutputs"));
 }
 
 #endif  // GTEST_HAS_DEATH_TEST
diff --git a/chromecast/public/cast_media_shlib.h b/chromecast/public/cast_media_shlib.h
index 6a3439e..fc38d8a 100644
--- a/chromecast/public/cast_media_shlib.h
+++ b/chromecast/public/cast_media_shlib.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <vector>
@@ -38,6 +39,8 @@
 // from other tests.
 class CHROMECAST_EXPORT CastMediaShlib {
  public:
+  using ResultCallback =
+      std::function<void(bool success, const std::string& message)>;
   // Observer for audio loopback data.
   class LoopbackAudioObserver {
    public:
@@ -126,8 +129,10 @@
   static void RemoveLoopbackAudioObserver(LoopbackAudioObserver* observer)
       __attribute__((__weak__));
 
-  // Reset the post processing pipeline.
-  static void ResetPostProcessors() __attribute__((__weak__));
+  // Reset the post processing pipeline. |callback| will be called with
+  // |success| = |true| if the new config loads without error.
+  static void ResetPostProcessors(ResultCallback callback)
+      __attribute__((__weak__));
 
   // Updates all postprocessors with the given |name| to have new configuration
   // |config|.
diff --git a/chromecast/renderer/extensions/automation_internal_custom_bindings.cc b/chromecast/renderer/extensions/automation_internal_custom_bindings.cc
index aff6347..bcd13ef 100644
--- a/chromecast/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chromecast/renderer/extensions/automation_internal_custom_bindings.cc
@@ -107,8 +107,8 @@
 
 ui::AXNode* FindNodeWithChildTreeId(ui::AXNode* node,
                                     ui::AXTreeID child_tree_id) {
-  if (child_tree_id ==
-      node->data().GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId))
+  if (child_tree_id == ui::AXTreeID::FromString(node->data().GetStringAttribute(
+                           ax::mojom::StringAttribute::kChildTreeId)))
     return node;
 
   for (int i = 0; i < node->child_count(); ++i) {
@@ -143,7 +143,8 @@
     if (args.Length() != 1 || !args[0]->IsString())
       ThrowInvalidArgumentsException(automation_bindings_);
 
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
     if (!tree_wrapper)
@@ -190,7 +191,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
 
     AutomationAXTreeWrapper* tree_wrapper =
@@ -244,7 +246,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
     std::string attribute = *v8::String::Utf8Value(isolate, args[2]);
 
@@ -300,7 +303,8 @@
 
     v8::Local<v8::Context> context =
         automation_bindings_->context()->v8_context();
-    ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+    ui::AXTreeID tree_id =
+        ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
     int node_id = args[1]->Int32Value(context).FromMaybe(0);
     int start = args[2]->Int32Value(context).FromMaybe(0);
     int end = args[3]->Int32Value(context).FromMaybe(0);
@@ -1032,7 +1036,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
   if (iter == tree_id_to_tree_wrapper_map_.end())
     return;
@@ -1096,8 +1101,9 @@
     // Try to keep following focus recursively, by letting |tree_id| be the
     // new subtree to search in, while keeping |focus_tree_id| set to the tree
     // where we know we found a focused node.
-    ui::AXTreeID child_tree_id = focus->data().GetStringAttribute(
-        ax::mojom::StringAttribute::kChildTreeId);
+    ui::AXTreeID child_tree_id =
+        ui::AXTreeID::FromString(focus->data().GetStringAttribute(
+            ax::mojom::StringAttribute::kChildTreeId));
 
     AutomationAXTreeWrapper* child_tree_wrapper =
         GetAutomationAXTreeWrapperFromTreeID(child_tree_id);
@@ -1139,7 +1145,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   AutomationAXTreeWrapper* tree_wrapper =
       GetAutomationAXTreeWrapperFromTreeID(tree_id);
   if (!tree_wrapper)
@@ -1150,10 +1157,11 @@
   if (!GetFocusInternal(tree_wrapper, &focused_tree_wrapper, &focused_node))
     return;
 
-  args.GetReturnValue().Set(gin::DataObjectBuilder(GetIsolate())
-                                .Set("treeId", focused_tree_wrapper->tree_id())
-                                .Set("nodeId", focused_node->id())
-                                .Build());
+  args.GetReturnValue().Set(
+      gin::DataObjectBuilder(GetIsolate())
+          .Set("treeId", focused_tree_wrapper->tree_id().ToString())
+          .Set("nodeId", focused_node->id())
+          .Build());
 }
 
 void AutomationInternalCustomBindings::GetHtmlAttributes(
@@ -1162,7 +1170,8 @@
   if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+  ui::AXTreeID tree_id =
+      ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
@@ -1186,7 +1195,8 @@
   if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(isolate, args[0]);
+  ui::AXTreeID tree_id =
+      ui::AXTreeID::FromString(*v8::String::Utf8Value(isolate, args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
@@ -1208,7 +1218,7 @@
   }
 
   AutomationAXTreeWrapper* top_tree_wrapper =
-      GetAutomationAXTreeWrapperFromTreeID(0);
+      GetAutomationAXTreeWrapperFromTreeID(ui::DesktopAXTreeID());
   if (!top_tree_wrapper)
     top_tree_wrapper = tree_wrapper;
   AutomationAXTreeWrapper* focused_tree_wrapper = nullptr;
@@ -1263,8 +1273,9 @@
     ui::AXNode* parent = parent_tree_wrapper->tree()->GetFromId(
         (*in_out_tree_wrapper)->host_node_id());
     if (parent) {
-      ui::AXTreeID parent_child_tree_id = parent->data().GetStringAttribute(
-          ax::mojom::StringAttribute::kChildTreeId);
+      ui::AXTreeID parent_child_tree_id =
+          ui::AXTreeID::FromString(parent->data().GetStringAttribute(
+              ax::mojom::StringAttribute::kChildTreeId));
       if (parent_child_tree_id == (*in_out_tree_wrapper)->tree_id()) {
         *in_out_tree_wrapper = parent_tree_wrapper;
         return parent;
@@ -1326,7 +1337,8 @@
     return;
   }
 
-  ui::AXTreeID tree_id = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
+  ui::AXTreeID tree_id = ui::AXTreeID::FromString(
+      *v8::String::Utf8Value(args.GetIsolate(), args[0]));
   int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   const auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
@@ -1370,7 +1382,7 @@
   if (!tree_wrapper->OnAccessibilityEvents(event_bundle, is_active_profile)) {
     LOG(ERROR) << tree_wrapper->tree()->error();
     base::ListValue args;
-    args.AppendString(tree_id);
+    args.AppendString(tree_id.ToString());
     bindings_system_->DispatchEventInContext(
         "automationInternal.onAccessibilityTreeSerializationError", &args,
         nullptr, context());
@@ -1460,7 +1472,7 @@
 
     base::ListValue args;
     args.AppendInteger(observer.id);
-    args.AppendString(tree_id);
+    args.AppendString(tree_id.ToString());
     args.AppendInteger(node->id());
     args.AppendString(ToString(change_type));
     bindings_system_->DispatchEventInContext("automationInternal.onTreeChange",
@@ -1474,7 +1486,7 @@
     ui::AXEvent& event,
     api::automation::EventType event_type) {
   auto event_params = std::make_unique<base::DictionaryValue>();
-  event_params->SetString("treeID", tree_id);
+  event_params->SetString("treeID", tree_id.ToString());
   event_params->SetInteger("targetID", event.id);
   event_params->SetString("eventType", api::automation::ToString(event_type));
   event_params->SetString("eventFrom", ui::ToString(event.event_from));
@@ -1496,7 +1508,7 @@
   ui::AXTreeID tree_id = iter->second->tree_id();
 
   base::ListValue args;
-  args.AppendString(tree_id);
+  args.AppendString(tree_id.ToString());
   args.AppendInteger(node->id());
   bindings_system_->DispatchEventInContext("automationInternal.onChildTreeID",
                                            &args, nullptr, context());
@@ -1512,7 +1524,7 @@
   ui::AXTreeID tree_id = iter->second->tree_id();
 
   base::ListValue args;
-  args.AppendString(tree_id);
+  args.AppendString(tree_id.ToString());
   {
     auto nodes = std::make_unique<base::ListValue>();
     for (auto id : ids)
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 022b672b..0c6a0ba4 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-11107.0.0
\ No newline at end of file
+11108.0.0
\ No newline at end of file
diff --git a/chromeos/components/proximity_auth/proximity_auth_system.cc b/chromeos/components/proximity_auth/proximity_auth_system.cc
index ea9c61f..a61b2d4 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_system.cc
@@ -190,9 +190,12 @@
                  << account_id.Serialize();
     remote_device_life_cycle_ =
         CreateRemoteDeviceLifeCycle(remote_device, local_device);
-    unlock_manager_->SetRemoteDeviceLifeCycle(remote_device_life_cycle_.get());
     remote_device_life_cycle_->AddObserver(this);
-    remote_device_life_cycle_->Start();
+
+    // UnlockManager listens for Bluetooth power change events, and is therefore
+    // responsible for starting RemoteDeviceLifeCycle when Bluetooth becomes
+    // powered.
+    unlock_manager_->SetRemoteDeviceLifeCycle(remote_device_life_cycle_.get());
   }
 }
 
diff --git a/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc b/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
index 92fed9e..ac13b34 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
@@ -274,7 +274,7 @@
 
   EXPECT_EQ(life_cycle(), unlock_manager_life_cycle);
   EXPECT_TRUE(life_cycle());
-  EXPECT_TRUE(life_cycle()->started());
+  EXPECT_FALSE(life_cycle()->started());
   EXPECT_EQ(kUser1, life_cycle()->GetRemoteDevice().user_id());
 
   EXPECT_CALL(*unlock_manager_, SetRemoteDeviceLifeCycle(nullptr))
diff --git a/chromeos/components/proximity_auth/remote_device_life_cycle_impl.cc b/chromeos/components/proximity_auth/remote_device_life_cycle_impl.cc
index a931dfd..97b932d 100644
--- a/chromeos/components/proximity_auth/remote_device_life_cycle_impl.cc
+++ b/chromeos/components/proximity_auth/remote_device_life_cycle_impl.cc
@@ -203,11 +203,24 @@
     chromeos::secure_channel::mojom::ConnectionAttemptFailureReason reason) {
   DCHECK(base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
 
-  PA_LOG(ERROR) << "Failed to create connection to remote device: "
-                << remote_device_.GetTruncatedDeviceIdForLogs()
-                << ", for reason: " << reason << ". Giving up.";
   connection_attempt_.reset();
-  TransitionToState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
+
+  if (reason == chromeos::secure_channel::mojom::
+                    ConnectionAttemptFailureReason::ADAPTER_DISABLED ||
+      reason == chromeos::secure_channel::mojom::
+                    ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT) {
+    // Transition to state STOPPED, and wait for Bluetooth to become powered.
+    // If it does, UnlockManager will start RemoteDeviceLifeCycle again.
+    PA_LOG(WARNING) << "Life cycle for "
+                    << remote_device_.GetTruncatedDeviceIdForLogs()
+                    << " stopped because Bluetooth is not available.";
+    TransitionToState(RemoteDeviceLifeCycle::State::STOPPED);
+  } else {
+    PA_LOG(ERROR) << "Failed to authenticate with remote device: "
+                  << remote_device_.GetTruncatedDeviceIdForLogs()
+                  << ", for reason: " << reason << ". Giving up.";
+    TransitionToState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
+  }
 }
 
 void RemoteDeviceLifeCycleImpl::OnConnection(
diff --git a/chromeos/components/proximity_auth/remote_device_life_cycle_impl_unittest.cc b/chromeos/components/proximity_auth/remote_device_life_cycle_impl_unittest.cc
index c8ce875..ea0fdbb9 100644
--- a/chromeos/components/proximity_auth/remote_device_life_cycle_impl_unittest.cc
+++ b/chromeos/components/proximity_auth/remote_device_life_cycle_impl_unittest.cc
@@ -249,24 +249,23 @@
               life_cycle_.GetMessenger()->GetChannel());
   }
 
-  void SimulateFailureToAuthenticateConnection() {
+  void SimulateFailureToAuthenticateConnection(
+      chromeos::secure_channel::mojom::ConnectionAttemptFailureReason
+          failure_reason,
+      RemoteDeviceLifeCycle::State expected_life_cycle_state) {
     EXPECT_EQ(RemoteDeviceLifeCycle::State::FINDING_CONNECTION,
               life_cycle_.GetState());
 
-    EXPECT_CALL(*this,
-                OnLifeCycleStateChanged(
-                    RemoteDeviceLifeCycle::State::FINDING_CONNECTION,
-                    RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED));
+    EXPECT_CALL(*this, OnLifeCycleStateChanged(
+                           RemoteDeviceLifeCycle::State::FINDING_CONNECTION,
+                           expected_life_cycle_state));
 
-    fake_connection_attempt_->NotifyConnectionAttemptFailure(
-        chromeos::secure_channel::mojom::ConnectionAttemptFailureReason::
-            AUTHENTICATION_ERROR);
+    fake_connection_attempt_->NotifyConnectionAttemptFailure(failure_reason);
 
     EXPECT_EQ(nullptr, life_cycle_.GetChannel());
     EXPECT_EQ(nullptr, life_cycle_.GetMessenger());
 
-    EXPECT_EQ(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED,
-              life_cycle_.GetState());
+    EXPECT_EQ(expected_life_cycle_state, life_cycle_.GetState());
   }
 
   cryptauth::FakeConnection* OnConnectionFound() {
@@ -340,7 +339,35 @@
   CreateFakeConnectionAttempt();
 
   StartLifeCycle();
-  SimulateFailureToAuthenticateConnection();
+  SimulateFailureToAuthenticateConnection(
+      chromeos::secure_channel::mojom::ConnectionAttemptFailureReason::
+          AUTHENTICATION_ERROR /* failure_reason */,
+      RemoteDeviceLifeCycle::State::
+          AUTHENTICATION_FAILED /* expected_life_cycle_state */);
+}
+
+TEST_F(ProximityAuthRemoteDeviceLifeCycleImplTest,
+       MultiDeviceApiEnabled_Failure_BluetoothNotPresent) {
+  SetMultiDeviceApiEnabled();
+  CreateFakeConnectionAttempt();
+
+  StartLifeCycle();
+  SimulateFailureToAuthenticateConnection(
+      chromeos::secure_channel::mojom::ConnectionAttemptFailureReason::
+          ADAPTER_NOT_PRESENT /* failure_reason */,
+      RemoteDeviceLifeCycle::State::STOPPED /* expected_life_cycle_state */);
+}
+
+TEST_F(ProximityAuthRemoteDeviceLifeCycleImplTest,
+       MultiDeviceApiEnabled_Failure_BluetoothNotPowered) {
+  SetMultiDeviceApiEnabled();
+  CreateFakeConnectionAttempt();
+
+  StartLifeCycle();
+  SimulateFailureToAuthenticateConnection(
+      chromeos::secure_channel::mojom::ConnectionAttemptFailureReason::
+          ADAPTER_DISABLED /* failure_reason */,
+      RemoteDeviceLifeCycle::State::STOPPED /* expected_life_cycle_state */);
 }
 
 TEST_F(ProximityAuthRemoteDeviceLifeCycleImplTest, AuthenticateAndDisconnect) {
diff --git a/chromeos/components/proximity_auth/unlock_manager_impl.cc b/chromeos/components/proximity_auth/unlock_manager_impl.cc
index ac0549f..56b94e2 100644
--- a/chromeos/components/proximity_auth/unlock_manager_impl.cc
+++ b/chromeos/components/proximity_auth/unlock_manager_impl.cc
@@ -103,7 +103,7 @@
 
   DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
 
-  SetWakingUpState(true);
+  SetWakingUpState(true /* is_waking_up */);
 
   if (device::BluetoothAdapterFactory::IsBluetoothSupported()) {
     device::BluetoothAdapterFactory::GetAdapter(
@@ -142,7 +142,8 @@
 
   life_cycle_ = life_cycle;
   if (life_cycle_) {
-    SetWakingUpState(true);
+    AttemptToStartRemoteDeviceLifecycle();
+    SetWakingUpState(true /* is_waking_up */);
   } else {
     proximity_monitor_.reset();
   }
@@ -162,7 +163,7 @@
   }
 
   if (state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED)
-    SetWakingUpState(false);
+    SetWakingUpState(false /* is_waking_up */);
 
   UpdateLockScreen();
 }
@@ -190,13 +191,12 @@
       GetScreenlockStateFromRemoteUpdate(status_update)));
 
   // This also calls |UpdateLockScreen()|
-  SetWakingUpState(false);
+  SetWakingUpState(false /* is_waking_up */);
 }
 
 void UnlockManagerImpl::OnDecryptResponse(const std::string& decrypted_bytes) {
   if (!is_attempting_auth_) {
-    PA_LOG(ERROR) << "Decrypt response received but not attempting "
-                  << "auth.";
+    PA_LOG(ERROR) << "Decrypt response received but not attempting auth.";
     return;
   }
 
@@ -212,8 +212,7 @@
 
 void UnlockManagerImpl::OnUnlockResponse(bool success) {
   if (!is_attempting_auth_) {
-    PA_LOG(ERROR) << "Unlock response received but not attempting "
-                  << "auth.";
+    PA_LOG(ERROR) << "Unlock response received but not attempting auth.";
     return;
   }
 
@@ -248,12 +247,8 @@
 void UnlockManagerImpl::OnFocusedUserChanged(const AccountId& account_id) {}
 
 void UnlockManagerImpl::OnScreenLockedOrUnlocked(bool is_locked) {
-  if (is_locked && bluetooth_adapter_ && bluetooth_adapter_->IsPowered() &&
-      life_cycle_ &&
-      life_cycle_->GetState() ==
-          RemoteDeviceLifeCycle::State::FINDING_CONNECTION) {
-    SetWakingUpState(true);
-  }
+  if (is_locked && IsBluetoothPresentAndPowered() && life_cycle_)
+    SetWakingUpState(true /* is_waking_up */);
 
   is_locked_ = is_locked;
   UpdateProximityMonitorState();
@@ -276,7 +271,22 @@
 }
 
 void UnlockManagerImpl::SuspendDone(const base::TimeDelta& sleep_duration) {
-  SetWakingUpState(true);
+  SetWakingUpState(true /* is_waking_up */);
+}
+
+bool UnlockManagerImpl::IsBluetoothPresentAndPowered() const {
+  return bluetooth_adapter_ && bluetooth_adapter_->IsPresent() &&
+         bluetooth_adapter_->IsPowered();
+}
+
+void UnlockManagerImpl::AttemptToStartRemoteDeviceLifecycle() {
+  if (IsBluetoothPresentAndPowered() && life_cycle_ &&
+      life_cycle_->GetState() == RemoteDeviceLifeCycle::State::STOPPED) {
+    // If Bluetooth is disabled after this, |life_cycle_| will be notified by
+    // SecureChannel that the connection attempt failed. From that point on,
+    // |life_cycle_| will wait to be started again by UnlockManager.
+    life_cycle_->Start();
+  }
 }
 
 void UnlockManagerImpl::OnAuthAttempted(mojom::AuthType auth_type) {
@@ -388,16 +398,13 @@
   if (!life_cycle_)
     return ScreenlockState::INACTIVE;
 
-  RemoteDeviceLifeCycle::State life_cycle_state = life_cycle_->GetState();
-  if (life_cycle_state == RemoteDeviceLifeCycle::State::STOPPED)
-    return ScreenlockState::INACTIVE;
-
-  if (!bluetooth_adapter_ || !bluetooth_adapter_->IsPowered())
+  if (!IsBluetoothPresentAndPowered())
     return ScreenlockState::NO_BLUETOOTH;
 
   if (IsUnlockAllowed())
     return ScreenlockState::AUTHENTICATED;
 
+  RemoteDeviceLifeCycle::State life_cycle_state = life_cycle_->GetState();
   if (life_cycle_state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED)
     return ScreenlockState::PHONE_NOT_AUTHENTICATED;
 
@@ -448,6 +455,8 @@
 }
 
 void UnlockManagerImpl::UpdateLockScreen() {
+  AttemptToStartRemoteDeviceLifecycle();
+
   UpdateProximityMonitorState();
 
   ScreenlockState new_state = GetScreenlockState();
diff --git a/chromeos/components/proximity_auth/unlock_manager_impl.h b/chromeos/components/proximity_auth/unlock_manager_impl.h
index ebc674b..ace4ff8 100644
--- a/chromeos/components/proximity_auth/unlock_manager_impl.h
+++ b/chromeos/components/proximity_auth/unlock_manager_impl.h
@@ -99,6 +99,13 @@
   // chromeos::PowerManagerClient::Observer:
   void SuspendDone(const base::TimeDelta& sleep_duration) override;
 
+  // Returns true if the BluetoothAdapter is present and powered.
+  bool IsBluetoothPresentAndPowered() const;
+
+  // If the RemoteDeviceLifeCycle is available, ensure it is started (but only
+  // if Bluetooth is available).
+  void AttemptToStartRemoteDeviceLifecycle();
+
   // Called when auth is attempted to send the sign-in challenge to the remote
   // device for decryption.
   void SendSignInChallenge();
diff --git a/chromeos/components/proximity_auth/unlock_manager_impl_unittest.cc b/chromeos/components/proximity_auth/unlock_manager_impl_unittest.cc
index 57fd8e3..c4067a5 100644
--- a/chromeos/components/proximity_auth/unlock_manager_impl_unittest.cc
+++ b/chromeos/components/proximity_auth/unlock_manager_impl_unittest.cc
@@ -164,6 +164,7 @@
         bluetooth_adapter_(CreateAndRegisterMockBluetoothAdapter()),
         task_runner_(new base::TestSimpleTaskRunner()),
         thread_task_runner_handle_(task_runner_) {
+    ON_CALL(*bluetooth_adapter_, IsPresent()).WillByDefault(Return(true));
     ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(true));
     ON_CALL(messenger_, SupportsSignIn()).WillByDefault(Return(true));
     ON_CALL(messenger_, GetSecureContext())
@@ -477,6 +478,36 @@
   EXPECT_TRUE(proximity_monitor()->started());
 }
 
+TEST_F(ProximityAuthUnlockManagerImplTest, BluetoothAdapterNotPresent) {
+  ON_CALL(*bluetooth_adapter_, IsPresent()).WillByDefault(Return(false));
+
+  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
+
+  EXPECT_CALL(proximity_auth_client_,
+              UpdateScreenlockState(ScreenlockState::NO_BLUETOOTH));
+
+  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  EXPECT_FALSE(life_cycle_.started());
+}
+
+TEST_F(ProximityAuthUnlockManagerImplTest, BluetoothAdapterPowerChanges) {
+  ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(false));
+
+  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
+
+  EXPECT_CALL(proximity_auth_client_,
+              UpdateScreenlockState(ScreenlockState::NO_BLUETOOTH));
+
+  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  EXPECT_FALSE(life_cycle_.started());
+
+  EXPECT_CALL(proximity_auth_client_,
+              UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+  ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(true));
+  bluetooth_adapter_->NotifyAdapterPoweredChanged(true);
+  EXPECT_TRUE(life_cycle_.started());
+}
+
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_SecureChannelEstablished_RegistersAsObserver) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
@@ -505,17 +536,6 @@
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
-       OnLifeCycleStateChanged_Stopped_UpdatesScreenlockState) {
-  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-  SimulateUserPresentState();
-
-  EXPECT_CALL(proximity_auth_client_,
-              UpdateScreenlockState(ScreenlockState::INACTIVE));
-  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::STOPPED);
-  unlock_manager_->OnLifeCycleStateChanged();
-}
-
-TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_AuthenticationFailed_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   SimulateUserPresentState();
@@ -529,50 +549,24 @@
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_FindingConnection_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
 
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
-  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
-  unlock_manager_->OnLifeCycleStateChanged();
-}
-
-TEST_F(ProximityAuthUnlockManagerImplTest,
-       OnLifeCycleStateChanged_FindingConnection_BluetoothAdapterOff) {
-  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
   unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-
-  EXPECT_CALL(proximity_auth_client_,
-              UpdateScreenlockState(ScreenlockState::NO_BLUETOOTH));
-  ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(false));
-  life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
-  unlock_manager_->OnLifeCycleStateChanged();
 }
 
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnLifeCycleStateChanged_Authenticating_UpdatesScreenlockState) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-
   EXPECT_CALL(proximity_auth_client_,
               UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
+  EXPECT_TRUE(life_cycle_.started());
+
   life_cycle_.ChangeState(RemoteDeviceLifeCycle::State::AUTHENTICATING);
   unlock_manager_->OnLifeCycleStateChanged();
 }
 
-TEST_F(
-    ProximityAuthUnlockManagerImplTest,
-    OnLifeCycleStateChanged_SecureChannelEstablished_UpdatesScreenlockState) {
-  CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
-  unlock_manager_->SetRemoteDeviceLifeCycle(&life_cycle_);
-
-  EXPECT_CALL(proximity_auth_client_,
-              UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
-  life_cycle_.ChangeState(
-      RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
-  unlock_manager_->OnLifeCycleStateChanged();
-}
-
 TEST_F(ProximityAuthUnlockManagerImplTest,
        OnDisconnected_UnregistersAsObserver) {
   CreateUnlockManager(ProximityAuthSystem::SESSION_LOCK);
diff --git a/chromeos/services/multidevice_setup/BUILD.gn b/chromeos/services/multidevice_setup/BUILD.gn
index 94cccf7..8e01084a1 100644
--- a/chromeos/services/multidevice_setup/BUILD.gn
+++ b/chromeos/services/multidevice_setup/BUILD.gn
@@ -14,6 +14,8 @@
     "account_status_change_delegate_notifier.h",
     "account_status_change_delegate_notifier_impl.cc",
     "account_status_change_delegate_notifier_impl.h",
+    "android_sms_app_installing_status_observer.cc",
+    "android_sms_app_installing_status_observer.h",
     "device_reenroller.cc",
     "device_reenroller.h",
     "eligible_host_devices_provider.h",
@@ -118,6 +120,7 @@
 
   sources = [
     "account_status_change_delegate_notifier_impl_unittest.cc",
+    "android_sms_app_installing_status_observer_unittest.cc",
     "device_reenroller_unittest.cc",
     "eligible_host_devices_provider_impl_unittest.cc",
     "feature_state_manager_impl_unittest.cc",
diff --git a/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.cc b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.cc
new file mode 100644
index 0000000..d5e6f8c
--- /dev/null
+++ b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.cc
@@ -0,0 +1,77 @@
+// 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 "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/no_destructor.h"
+#include "chromeos/services/multidevice_setup/host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/cpp/android_sms_app_helper_delegate.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+// static
+AndroidSmsAppInstallingStatusObserver::Factory*
+    AndroidSmsAppInstallingStatusObserver::Factory::test_factory_ = nullptr;
+
+// static
+AndroidSmsAppInstallingStatusObserver::Factory*
+AndroidSmsAppInstallingStatusObserver::Factory::Get() {
+  if (test_factory_)
+    return test_factory_;
+
+  static base::NoDestructor<Factory> factory;
+  return factory.get();
+}
+
+// static
+void AndroidSmsAppInstallingStatusObserver::Factory::SetFactoryForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+AndroidSmsAppInstallingStatusObserver::Factory::~Factory() = default;
+
+std::unique_ptr<AndroidSmsAppInstallingStatusObserver>
+AndroidSmsAppInstallingStatusObserver::Factory::BuildInstance(
+    HostStatusProvider* host_status_provider,
+    std::unique_ptr<AndroidSmsAppHelperDelegate>
+        android_sms_app_helper_delegate) {
+  return base::WrapUnique(new AndroidSmsAppInstallingStatusObserver(
+      host_status_provider, std::move(android_sms_app_helper_delegate)));
+}
+
+AndroidSmsAppInstallingStatusObserver::
+    ~AndroidSmsAppInstallingStatusObserver() {
+  host_status_provider_->RemoveObserver(this);
+}
+
+AndroidSmsAppInstallingStatusObserver::AndroidSmsAppInstallingStatusObserver(
+    HostStatusProvider* host_status_provider,
+    std::unique_ptr<AndroidSmsAppHelperDelegate>
+        android_sms_app_helper_delegate)
+    : host_status_provider_(host_status_provider),
+      android_sms_app_helper_delegate_(
+          std::move(android_sms_app_helper_delegate)) {
+  host_status_provider_->AddObserver(this);
+}
+
+void AndroidSmsAppInstallingStatusObserver::OnHostStatusChange(
+    const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
+  mojom::HostStatus status(host_status_with_device.host_status());
+  if (status ==
+          mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation ||
+      status == mojom::HostStatus::kHostVerified) {
+    // This call is re-entrant. If the app is already installed, it will just
+    // fail silently, which is fine.
+    android_sms_app_helper_delegate_->InstallAndroidSmsApp();
+  }
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h
new file mode 100644
index 0000000..b0f35e6
--- /dev/null
+++ b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h
@@ -0,0 +1,62 @@
+// 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 CHROMEOS_SERVICES_MULTIDEVICE_SETUP_ANDROID_SMS_APP_INSTALLING_STATUS_OBSERVER_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_ANDROID_SMS_APP_INSTALLING_STATUS_OBSERVER_H_
+
+#include <memory>
+
+#include "chromeos/services/multidevice_setup/host_status_provider.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+class AndroidSmsAppHelperDelegate;
+
+// Listens for status changes in multidevice state and installs the Android
+// Messages PWA if needed.
+//
+// TODO(crbug.com/884290): Also observe FeatureStateManager to make sure
+// Messages is supported.
+class AndroidSmsAppInstallingStatusObserver
+    : public HostStatusProvider::Observer {
+ public:
+  class Factory {
+   public:
+    static Factory* Get();
+    static void SetFactoryForTesting(Factory* test_factory);
+    virtual ~Factory();
+    virtual std::unique_ptr<AndroidSmsAppInstallingStatusObserver>
+    BuildInstance(HostStatusProvider* host_status_provider,
+                  std::unique_ptr<AndroidSmsAppHelperDelegate>
+                      android_sms_app_helper_delegate);
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  ~AndroidSmsAppInstallingStatusObserver() override;
+
+ private:
+  AndroidSmsAppInstallingStatusObserver(
+      HostStatusProvider* host_status_provider,
+      std::unique_ptr<AndroidSmsAppHelperDelegate>
+          android_sms_app_helper_delegate);
+
+  // HostStatusProvider::Observer:
+  void OnHostStatusChange(const HostStatusProvider::HostStatusWithDevice&
+                              host_status_with_device) override;
+
+  HostStatusProvider* host_status_provider_;
+  std::unique_ptr<AndroidSmsAppHelperDelegate> android_sms_app_helper_delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(AndroidSmsAppInstallingStatusObserver);
+};
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_ANDROID_SMS_APP_INSTALLING_STATUS_OBSERVER_H_
diff --git a/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer_unittest.cc b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer_unittest.cc
new file mode 100644
index 0000000..a443f31c
--- /dev/null
+++ b/chromeos/services/multidevice_setup/android_sms_app_installing_status_observer_unittest.cc
@@ -0,0 +1,99 @@
+// 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 "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
+
+#include <string>
+
+#include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "components/cryptauth/remote_device_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+namespace {
+
+const char kFakePhoneKey[] = "fake-phone-key";
+const char kFakePhoneName[] = "Phony Phone";
+
+}  // namespace
+
+class MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest
+    : public testing::Test {
+ protected:
+  MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest() = default;
+
+  ~MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest() override =
+      default;
+
+  void SetUp() override {
+    auto fake_android_sms_app_helper_delegate =
+        std::make_unique<FakeAndroidSmsAppHelperDelegate>();
+    fake_android_sms_app_helper_delegate_ =
+        fake_android_sms_app_helper_delegate.get();
+    fake_host_status_provider_ = std::make_unique<FakeHostStatusProvider>();
+    android_sms_app_installing_status_observer_ =
+        AndroidSmsAppInstallingStatusObserver::Factory::Get()->BuildInstance(
+            fake_host_status_provider_.get(),
+            std::move(fake_android_sms_app_helper_delegate));
+
+    EXPECT_FALSE(fake_app_helper_delegate()->HasInstalledApp());
+  }
+
+  void SetHostWithStatus(
+      mojom::HostStatus host_status,
+      const base::Optional<cryptauth::RemoteDeviceRef>& host_device) {
+    fake_host_status_provider_->SetHostWithStatus(host_status, host_device);
+  }
+
+  FakeAndroidSmsAppHelperDelegate* fake_app_helper_delegate() {
+    return fake_android_sms_app_helper_delegate_;
+  }
+
+  cryptauth::RemoteDeviceRef GetFakePhone() {
+    return cryptauth::RemoteDeviceRefBuilder()
+        .SetPublicKey(kFakePhoneKey)
+        .SetName(kFakePhoneName)
+        .Build();
+  }
+
+ private:
+  std::unique_ptr<FakeHostStatusProvider> fake_host_status_provider_;
+  FakeAndroidSmsAppHelperDelegate* fake_android_sms_app_helper_delegate_;
+
+  std::unique_ptr<AndroidSmsAppInstallingStatusObserver>
+      android_sms_app_installing_status_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(
+      MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest);
+};
+
+TEST_F(MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest,
+       InstallsAfterHostPending) {
+  SetHostWithStatus(mojom::HostStatus::kEligibleHostExistsButNoHostSet,
+                    base::nullopt /* host_device */);
+  EXPECT_FALSE(fake_app_helper_delegate()->HasInstalledApp());
+
+  SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      GetFakePhone());
+  EXPECT_TRUE(fake_app_helper_delegate()->HasInstalledApp());
+}
+
+TEST_F(MultiDeviceSetupAndroidSmsAppInstallingStatusObserverTest,
+       InstallsAfterHostVerified) {
+  SetHostWithStatus(mojom::HostStatus::kNoEligibleHosts,
+                    base::nullopt /* host_device */);
+  EXPECT_FALSE(fake_app_helper_delegate()->HasInstalledApp());
+
+  SetHostWithStatus(mojom::HostStatus::kHostVerified, GetFakePhone());
+  EXPECT_TRUE(fake_app_helper_delegate()->HasInstalledApp());
+}
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
index d77350356..4cb15e5f 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
@@ -9,6 +9,7 @@
 #include "base/time/default_clock.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
+#include "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
 #include "chromeos/services/multidevice_setup/device_reenroller.h"
 #include "chromeos/services/multidevice_setup/eligible_host_devices_provider_impl.h"
 #include "chromeos/services/multidevice_setup/feature_state_manager_impl.h"
@@ -74,9 +75,7 @@
     std::unique_ptr<AndroidSmsPairingStateTracker>
         android_sms_pairing_state_tracker,
     const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
-    : android_sms_app_helper_delegate_(
-          std::move(android_sms_app_helper_delegate)),
-      eligible_host_devices_provider_(
+    : eligible_host_devices_provider_(
           EligibleHostDevicesProviderImpl::Factory::Get()->BuildInstance(
               device_sync_client)),
       host_backend_delegate_(
@@ -113,6 +112,10 @@
       device_reenroller_(DeviceReenroller::Factory::Get()->BuildInstance(
           device_sync_client,
           gcm_device_info_provider)),
+      android_sms_app_installing_host_observer_(
+          AndroidSmsAppInstallingStatusObserver::Factory::Get()->BuildInstance(
+              host_status_provider_.get(),
+              std::move(android_sms_app_helper_delegate))),
       auth_token_validator_(auth_token_validator) {
   host_status_provider_->AddObserver(this);
   feature_state_manager_->AddObserver(this);
@@ -302,7 +305,6 @@
     return false;
 
   host_backend_delegate_->AttemptToSetMultiDeviceHostOnBackend(*it);
-  android_sms_app_helper_delegate_->InstallAndroidSmsApp();
 
   return true;
 }
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.h b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
index da730ea..5a4d76b 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
@@ -32,6 +32,7 @@
 
 class AccountStatusChangeDelegateNotifier;
 class AndroidSmsAppHelperDelegate;
+class AndroidSmsAppInstallingStatusObserver;
 class AndroidSmsPairingStateTracker;
 class AuthTokenValidator;
 class DeviceReenroller;
@@ -124,7 +125,6 @@
 
   void FlushForTesting();
 
-  std::unique_ptr<AndroidSmsAppHelperDelegate> android_sms_app_helper_delegate_;
   std::unique_ptr<EligibleHostDevicesProvider> eligible_host_devices_provider_;
   std::unique_ptr<HostBackendDelegate> host_backend_delegate_;
   std::unique_ptr<HostVerifier> host_verifier_;
@@ -133,6 +133,8 @@
   std::unique_ptr<SetupFlowCompletionRecorder> setup_flow_completion_recorder_;
   std::unique_ptr<AccountStatusChangeDelegateNotifier> delegate_notifier_;
   std::unique_ptr<DeviceReenroller> device_reenroller_;
+  std::unique_ptr<AndroidSmsAppInstallingStatusObserver>
+      android_sms_app_installing_host_observer_;
   AuthTokenValidator* auth_token_validator_;
 
   mojo::InterfacePtrSet<mojom::HostStatusObserver> host_status_observers_;
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
index 2942e6d65..f6efaff 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
+#include "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
 #include "chromeos/services/multidevice_setup/device_reenroller.h"
 #include "chromeos/services/multidevice_setup/eligible_host_devices_provider_impl.h"
 #include "chromeos/services/multidevice_setup/fake_account_status_change_delegate.h"
@@ -392,6 +393,39 @@
   DISALLOW_COPY_AND_ASSIGN(FakeDeviceReenrollerFactory);
 };
 
+class FakeAndroidSmsAppInstallingStatusObserverFactory
+    : public AndroidSmsAppInstallingStatusObserver::Factory {
+ public:
+  FakeAndroidSmsAppInstallingStatusObserverFactory(
+      FakeHostStatusProviderFactory* fake_host_status_provider_factory,
+      AndroidSmsAppHelperDelegate* expected_android_sms_app_helper_delegate)
+      : fake_host_status_provider_factory_(fake_host_status_provider_factory),
+        expected_android_sms_app_helper_delegate_(
+            expected_android_sms_app_helper_delegate) {}
+
+  ~FakeAndroidSmsAppInstallingStatusObserverFactory() override = default;
+
+ private:
+  // AndroidSmsAppInstallingStatusObserver::Factory:
+  std::unique_ptr<AndroidSmsAppInstallingStatusObserver> BuildInstance(
+      HostStatusProvider* host_status_provider,
+      std::unique_ptr<AndroidSmsAppHelperDelegate>
+          android_sms_app_helper_delegate) override {
+    EXPECT_EQ(fake_host_status_provider_factory_->instance(),
+              host_status_provider);
+    EXPECT_EQ(expected_android_sms_app_helper_delegate_,
+              android_sms_app_helper_delegate.get());
+    // Only check inputs and return nullptr. We do not want to trigger the
+    // AndroidSmsAppInstallingStatusObserver logic in these unit tests.
+    return nullptr;
+  }
+
+  FakeHostStatusProviderFactory* fake_host_status_provider_factory_;
+  AndroidSmsAppHelperDelegate* expected_android_sms_app_helper_delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeAndroidSmsAppInstallingStatusObserverFactory);
+};
+
 }  // namespace
 
 class MultiDeviceSetupImplTest : public testing::Test {
@@ -412,8 +446,6 @@
 
     auto fake_android_sms_app_helper_delegate =
         std::make_unique<FakeAndroidSmsAppHelperDelegate>();
-    fake_android_sms_app_helper_delegate_ =
-        fake_android_sms_app_helper_delegate.get();
 
     auto fake_android_sms_pairing_state_tracker =
         std::make_unique<FakeAndroidSmsPairingStateTracker>();
@@ -479,6 +511,13 @@
     DeviceReenroller::Factory::SetFactoryForTesting(
         fake_device_reenroller_factory_.get());
 
+    fake_android_sms_app_installing_status_observer_factory_ =
+        std::make_unique<FakeAndroidSmsAppInstallingStatusObserverFactory>(
+            fake_host_status_provider_factory_.get(),
+            fake_android_sms_app_helper_delegate.get());
+    AndroidSmsAppInstallingStatusObserver::Factory::SetFactoryForTesting(
+        fake_android_sms_app_installing_status_observer_factory_.get());
+
     multidevice_setup_ = MultiDeviceSetupImpl::Factory::Get()->BuildInstance(
         test_pref_service_.get(), fake_device_sync_client_.get(),
         fake_auth_token_validator_.get(),
@@ -497,6 +536,8 @@
     AccountStatusChangeDelegateNotifierImpl::Factory::SetFactoryForTesting(
         nullptr);
     DeviceReenroller::Factory::SetFactoryForTesting(nullptr);
+    AndroidSmsAppInstallingStatusObserver::Factory::SetFactoryForTesting(
+        nullptr);
   }
 
   void CallSetAccountStatusChangeDelegate() {
@@ -689,10 +730,6 @@
     return fake_account_status_change_delegate_notifier_factory_->instance();
   }
 
-  FakeAndroidSmsAppHelperDelegate* fake_android_sms_app_helper_delegate() {
-    return fake_android_sms_app_helper_delegate_;
-  }
-
   cryptauth::RemoteDeviceRefList& test_devices() { return test_devices_; }
 
   MultiDeviceSetupBase* multidevice_setup() { return multidevice_setup_.get(); }
@@ -780,7 +817,8 @@
   std::unique_ptr<FakeAccountStatusChangeDelegateNotifierFactory>
       fake_account_status_change_delegate_notifier_factory_;
   std::unique_ptr<FakeDeviceReenrollerFactory> fake_device_reenroller_factory_;
-  FakeAndroidSmsAppHelperDelegate* fake_android_sms_app_helper_delegate_;
+  std::unique_ptr<FakeAndroidSmsAppInstallingStatusObserverFactory>
+      fake_android_sms_app_installing_status_observer_factory_;
   FakeAndroidSmsPairingStateTracker* fake_android_sms_pairing_state_tracker_;
 
   std::unique_ptr<FakeAccountStatusChangeDelegate>
@@ -1058,9 +1096,6 @@
   VerifyCurrentHostStatus(mojom::HostStatus::kHostVerified, test_devices()[0],
                           observer.get(), 3u /* expected_observer_index */);
 
-  // Messages App install should have succeeded.
-  EXPECT_TRUE(fake_android_sms_app_helper_delegate()->HasInstalledApp());
-
   // Remove the host.
   multidevice_setup()->RemoveHostDevice();
   fake_host_verifier()->set_is_host_verified(false);
diff --git a/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.cc b/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.cc
index 8afabd3..290b694c6 100644
--- a/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.cc
+++ b/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.cc
@@ -32,6 +32,11 @@
   return has_launched_;
 }
 
+void FakeAndroidSmsAppHelperDelegate::Reset() {
+  has_installed_ = false;
+  has_launched_ = false;
+}
+
 }  // namespace multidevice_setup
 
 }  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h b/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h
index b586c5865..1312c8f 100644
--- a/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h
+++ b/chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h
@@ -17,6 +17,7 @@
   ~FakeAndroidSmsAppHelperDelegate() override;
   bool HasInstalledApp();
   bool HasLaunchedApp();
+  void Reset();
 
   // AndroidSmsAppHelperDelegate:
   void InstallAndroidSmsApp() override;
diff --git a/components/autofill/content/common/autofill_types.mojom b/components/autofill/content/common/autofill_types.mojom
index 99e63e8..fbc07aea 100644
--- a/components/autofill/content/common/autofill_types.mojom
+++ b/components/autofill/content/common/autofill_types.mojom
@@ -57,6 +57,7 @@
     FRAME_DETACHED,
     DOM_MUTATION_AFTER_XHR,
     PROVISIONALLY_SAVED_FORM_ON_START_PROVISIONAL_LOAD,
+    PROBABLE_FORM_SUBMISSION,
     SUBMISSION_INDICATOR_EVENT_COUNT
 };
 
@@ -70,6 +71,7 @@
 
 // autofill::SubmissionSource
 enum SubmissionSource {
+  NONE,
   SAME_DOCUMENT_NAVIGATION,
   XHR_SUCCEEDED,
   FRAME_DETACHED,
diff --git a/components/autofill/content/common/autofill_types_struct_traits.cc b/components/autofill/content/common/autofill_types_struct_traits.cc
index 554892b5..0dbc248 100644
--- a/components/autofill/content/common/autofill_types_struct_traits.cc
+++ b/components/autofill/content/common/autofill_types_struct_traits.cc
@@ -259,6 +259,10 @@
         SUBMISSION_INDICATOR_EVENT_COUNT:
       return autofill::mojom::PasswordFormSubmissionIndicatorEvent::
           SUBMISSION_INDICATOR_EVENT_COUNT;
+    case autofill::PasswordForm::SubmissionIndicatorEvent::
+        PROBABLE_FORM_SUBMISSION:
+      return autofill::mojom::PasswordFormSubmissionIndicatorEvent::
+          PROBABLE_FORM_SUBMISSION;
   }
 
   NOTREACHED();
@@ -302,6 +306,11 @@
           PROVISIONALLY_SAVED_FORM_ON_START_PROVISIONAL_LOAD;
       return true;
     case autofill::mojom::PasswordFormSubmissionIndicatorEvent::
+        PROBABLE_FORM_SUBMISSION:
+      *output = autofill::PasswordForm::SubmissionIndicatorEvent::
+          PROBABLE_FORM_SUBMISSION;
+      return true;
+    case autofill::mojom::PasswordFormSubmissionIndicatorEvent::
         SUBMISSION_INDICATOR_EVENT_COUNT:
       *output = autofill::PasswordForm::SubmissionIndicatorEvent::
           SUBMISSION_INDICATOR_EVENT_COUNT;
@@ -372,6 +381,8 @@
     autofill::mojom::SubmissionSource,
     autofill::SubmissionSource>::ToMojom(autofill::SubmissionSource input) {
   switch (input) {
+    case autofill::SubmissionSource::NONE:
+      return autofill::mojom::SubmissionSource::NONE;
     case autofill::SubmissionSource::SAME_DOCUMENT_NAVIGATION:
       return autofill::mojom::SubmissionSource::SAME_DOCUMENT_NAVIGATION;
     case autofill::SubmissionSource::XHR_SUCCEEDED:
@@ -386,7 +397,7 @@
       return autofill::mojom::SubmissionSource::FORM_SUBMISSION;
   }
   NOTREACHED();
-  return autofill::mojom::SubmissionSource::FORM_SUBMISSION;
+  return autofill::mojom::SubmissionSource::NONE;
 }
 
 // static
@@ -394,6 +405,9 @@
     FromMojom(autofill::mojom::SubmissionSource input,
               autofill::SubmissionSource* output) {
   switch (input) {
+    case autofill::mojom::SubmissionSource::NONE:
+      *output = autofill::SubmissionSource::NONE;
+      return true;
     case autofill::mojom::SubmissionSource::SAME_DOCUMENT_NAVIGATION:
       *output = autofill::SubmissionSource::SAME_DOCUMENT_NAVIGATION;
       return true;
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index f16e702d..89a08030 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -621,22 +621,6 @@
   return WebInputElement();
 }
 
-PasswordForm::SubmissionIndicatorEvent ToSubmissionIndicatorEvent(
-    SubmissionSource source) {
-  switch (source) {
-    case SubmissionSource::FRAME_DETACHED:
-      return PasswordForm::SubmissionIndicatorEvent::FRAME_DETACHED;
-    case SubmissionSource::SAME_DOCUMENT_NAVIGATION:
-      return PasswordForm::SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
-    case SubmissionSource::XHR_SUCCEEDED:
-      return PasswordForm::SubmissionIndicatorEvent::XHR_SUCCEEDED;
-    case SubmissionSource::DOM_MUTATION_AFTER_XHR:
-      return PasswordForm::SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR;
-    default:
-      return PasswordForm::SubmissionIndicatorEvent::NONE;
-  }
-}
-
 WebInputElement ConvertToWebInput(const WebFormControlElement& element) {
   if (element.IsNull())
     return WebInputElement();
diff --git a/components/autofill/core/browser/autofill_download_manager.cc b/components/autofill/core/browser/autofill_download_manager.cc
index 81e2de0..245ab7a 100644
--- a/components/autofill/core/browser/autofill_download_manager.cc
+++ b/components/autofill/core/browser/autofill_download_manager.cc
@@ -18,6 +18,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/rand_util.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -25,9 +26,14 @@
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/proto/server.pb.h"
+#include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_prefs.h"
 #include "components/autofill/core/common/autofill_switches.h"
+#include "components/autofill/core/common/submission_source.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/variations/net/variations_http_headers.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
@@ -331,6 +337,52 @@
   return out;
 }
 
+// Check for and returns true if |upload_event| is allowed to trigger an upload
+// for |form|. If true, updates |prefs| to track that |upload_event| has been
+// recorded for |form|.
+bool IsUploadAllowed(const FormStructure& form, PrefService* prefs) {
+  if (!prefs ||
+      !base::FeatureList::IsEnabled(features::kAutofillUploadThrottling)) {
+    return true;
+  }
+
+  // If the upload event pref needs to be reset, clear it now.
+  static constexpr base::TimeDelta kResetPeriod = base::TimeDelta::FromDays(28);
+  base::Time now = AutofillClock::Now();
+  base::Time last_reset =
+      prefs->GetTime(prefs::kAutofillUploadEventsLastResetTimestamp);
+  if ((now - last_reset) > kResetPeriod) {
+    prefs->ClearPref(prefs::kAutofillUploadEvents);
+    prefs->SetTime(prefs::kAutofillUploadEventsLastResetTimestamp, now);
+  }
+
+  // Get the key for the upload bucket and extract the current bitfield value.
+  static constexpr size_t kNumUploadBuckets = 1021;
+  std::string key = base::StringPrintf(
+      "%03X", static_cast<int>(form.form_signature() % kNumUploadBuckets));
+  auto* upload_events = prefs->GetDictionary(prefs::kAutofillUploadEvents);
+  auto* found = upload_events->FindKeyOfType(key, base::Value::Type::INTEGER);
+  int value = found ? found->GetInt() : 0;
+
+  // Calculate the mask we expect to be set for the form's upload bucket.
+  const int bit = static_cast<int>(form.submission_source());
+  DCHECK_LE(0, bit);
+  DCHECK_LT(bit, 32);
+  const int mask = (1 << bit);
+
+  // Check if the upload should be allowed and, if so, update the upload event
+  // pref to set the appropriate bit.
+  bool allow_upload = ((value & mask) == 0);
+  if (allow_upload) {
+    DictionaryPrefUpdate update(prefs, prefs::kAutofillUploadEvents);
+    update->SetKey(std::move(key), base::Value(value | mask));
+  }
+
+  // Capture metrics and return.
+  AutofillMetrics::LogUploadEvent(form.submission_source(), allow_upload);
+  return allow_upload;
+}
+
 }  // namespace
 
 struct AutofillDownloadManager::FormRequestData {
@@ -397,8 +449,9 @@
     bool form_was_autofilled,
     const ServerFieldTypeSet& available_field_types,
     const std::string& login_form_signature,
-    bool observed_submission) {
-  if (!IsEnabled())
+    bool observed_submission,
+    PrefService* prefs) {
+  if (!IsEnabled() || !IsUploadAllowed(form, prefs))
     return false;
 
   AutofillUploadContents upload;
diff --git a/components/autofill/core/browser/autofill_download_manager.h b/components/autofill/core/browser/autofill_download_manager.h
index c1b7b97b..b51e3cc 100644
--- a/components/autofill/core/browser/autofill_download_manager.h
+++ b/components/autofill/core/browser/autofill_download_manager.h
@@ -23,6 +23,8 @@
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "url/gurl.h"
 
+class PrefService;
+
 namespace autofill {
 
 class AutofillDriver;
@@ -86,7 +88,8 @@
       bool form_was_autofilled,
       const ServerFieldTypeSet& available_field_types,
       const std::string& login_form_signature,
-      bool observed_submission);
+      bool observed_submission,
+      PrefService* pref_service);
 
   // Returns true if the autofill server communication is enabled.
   bool IsEnabled() const { return autofill_server_url_.is_valid(); }
diff --git a/components/autofill/core/browser/autofill_download_manager_unittest.cc b/components/autofill/core/browser/autofill_download_manager_unittest.cc
index 4b76b58..a7d2e25b 100644
--- a/components/autofill/core/browser/autofill_download_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_download_manager_unittest.cc
@@ -27,12 +27,15 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/autofill/core/browser/autofill_field.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
 #include "components/autofill/core/browser/test_autofill_driver.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_switches.h"
 #include "components/autofill/core/common/form_data.h"
+#include "components/prefs/pref_service.h"
 #include "net/http/http_status_code.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -85,7 +88,8 @@
       : test_shared_loader_factory_(
             base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                 &test_url_loader_factory_)),
-        download_manager_(&driver_, this) {
+        download_manager_(&driver_, this),
+        pref_service_(test::PrefServiceForTesting()) {
     driver_.SetSharedURLLoaderFactory(test_shared_loader_factory_);
   }
 
@@ -143,6 +147,7 @@
   network::TestURLLoaderFactory test_url_loader_factory_;
   TestAutofillDriver driver_;
   AutofillDownloadManager download_manager_;
+  std::unique_ptr<PrefService> pref_service_;
 };
 
 TEST_F(AutofillDownloadManagerTest, QueryAndUploadTest) {
@@ -243,14 +248,17 @@
 
   // Request with id 1.
   EXPECT_TRUE(download_manager_.StartUploadRequest(
-      *(form_structures[0]), true, ServerFieldTypeSet(), std::string(), true));
+      *(form_structures[0]), true, ServerFieldTypeSet(), std::string(), true,
+      pref_service_.get()));
   // Request with id 2.
   EXPECT_TRUE(download_manager_.StartUploadRequest(
-      *(form_structures[1]), false, ServerFieldTypeSet(), std::string(), true));
+      *(form_structures[1]), false, ServerFieldTypeSet(), std::string(), true,
+      pref_service_.get()));
   // Request with id 3. Upload request with a non-empty additional password form
   // signature.
-  EXPECT_TRUE(download_manager_.StartUploadRequest(
-      *(form_structures[2]), false, ServerFieldTypeSet(), "42", true));
+  EXPECT_TRUE(download_manager_.StartUploadRequest(*(form_structures[2]), false,
+                                                   ServerFieldTypeSet(), "42",
+                                                   true, pref_service_.get()));
 
   const char* responses[] = {
       "<autofillqueryresponse>"
@@ -487,10 +495,12 @@
   form.fields.push_back(field);
 
   auto form_structure = std::make_unique<FormStructure>(form);
+  form_structure->set_submission_source(SubmissionSource::FORM_SUBMISSION);
 
   // Request with id 0.
   EXPECT_TRUE(download_manager_.StartUploadRequest(
-      *form_structure, true, ServerFieldTypeSet(), std::string(), true));
+      *form_structure, true, ServerFieldTypeSet(), std::string(), true,
+      pref_service_.get()));
 
   auto* request = test_url_loader_factory_.GetPendingRequest(0);
 
@@ -532,9 +542,11 @@
   responses_.pop_front();
 
   // Validate no retry on sending a bad request.
+  form_structure->set_submission_source(SubmissionSource::XHR_SUCCEEDED);
   base::HistogramTester histogram;
   EXPECT_TRUE(download_manager_.StartUploadRequest(
-      *form_structure, true, ServerFieldTypeSet(), std::string(), true));
+      *form_structure, true, ServerFieldTypeSet(), std::string(), true,
+      pref_service_.get()));
   request = test_url_loader_factory_.GetPendingRequest(2);
   test_url_loader_factory_.SimulateResponseWithoutRemovingFromPendingList(
       request,
@@ -755,8 +767,14 @@
   void SetUp() override {
     testing::TestWithParam<ServerCommuncationMode>::SetUp();
 
-    scoped_feature_list_1_.InitAndEnableFeature(
-        features::kAutofillCacheQueryResponses);
+    pref_service_ = test::PrefServiceForTesting();
+
+    scoped_feature_list_1_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillCacheQueryResponses,
+         features::kAutofillUploadThrottling},
+        // Disabled
+        {});
 
     // Setup the server.
     server_.RegisterRequestHandler(
@@ -870,7 +888,7 @@
     AutofillDownloadManager download_manager(driver_.get(), this);
     bool succeeded = download_manager.StartUploadRequest(
         form, form_was_autofilled, available_field_types, login_form_signature,
-        observed_submission);
+        observed_submission, pref_service_.get());
     if (succeeded)
       run_loop_->Run();
     run_loop_.reset();
@@ -888,6 +906,7 @@
   size_t call_count_ = 0;
   scoped_refptr<network::TestSharedURLLoaderFactory> shared_url_loader_factory_;
   std::unique_ptr<TestAutofillDriver> driver_;
+  std::unique_ptr<PrefService> pref_service_;
 };
 
 }  // namespace
@@ -1043,4 +1062,172 @@
                         AutofillQueryTest,
                         ::testing::Values(FINCHED_URL, COMMAND_LINE_URL));
 
+using AutofillUploadTest = AutofillServerCommunicationTest;
+
+TEST_P(AutofillUploadTest, Throttling) {
+  ASSERT_NE(DISABLED, GetParam());
+
+  FormData form;
+  FormFieldData field;
+
+  field.label = ASCIIToUTF16("First Name:");
+  field.name = ASCIIToUTF16("firstname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Last Name:");
+  field.name = ASCIIToUTF16("lastname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Email:");
+  field.name = ASCIIToUTF16("email");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  AutofillDownloadManager download_manager(driver_.get(), this);
+  FormStructure form_structure(form);
+  for (int i = 0; i < 8; ++i) {
+    SCOPED_TRACE(base::StringPrintf("submission source = %d", i));
+    base::HistogramTester histogram_tester;
+    auto submission_source = static_cast<SubmissionSource>(i);
+    form_structure.set_submission_source(submission_source);
+
+    // The first attempt should succeed.
+    EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+    // The second attempt should always fail.
+    EXPECT_FALSE(SendUploadRequest(form_structure, true, {}, "", true));
+
+    // One upload was not sent.
+    histogram_tester.ExpectBucketCount("Autofill.UploadEvent", 0, 1);
+    histogram_tester.ExpectBucketCount(
+        AutofillMetrics::SubmissionSourceToUploadEventMetric(submission_source),
+        0, 1);
+
+    // One upload was sent.
+    histogram_tester.ExpectBucketCount("Autofill.UploadEvent", 1, 1);
+    histogram_tester.ExpectBucketCount(
+        AutofillMetrics::SubmissionSourceToUploadEventMetric(submission_source),
+        1, 1);
+  }
+}
+
+TEST_P(AutofillUploadTest, ThrottlingDisabled) {
+  ASSERT_NE(DISABLED, GetParam());
+
+  FormData form;
+  FormFieldData field;
+
+  field.label = ASCIIToUTF16("First Name:");
+  field.name = ASCIIToUTF16("firstname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Last Name:");
+  field.name = ASCIIToUTF16("lastname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Email:");
+  field.name = ASCIIToUTF16("email");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  AutofillDownloadManager download_manager(driver_.get(), this);
+  FormStructure form_structure(form);
+
+  base::test::ScopedFeatureList local_feature;
+  local_feature.InitAndDisableFeature(features::kAutofillUploadThrottling);
+
+  for (int i = 0; i < 8; ++i) {
+    SCOPED_TRACE(base::StringPrintf("submission source = %d", i));
+    base::HistogramTester histogram_tester;
+    auto submission_source = static_cast<SubmissionSource>(i);
+    form_structure.set_submission_source(submission_source);
+
+    // The first attempt should succeed.
+    EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+    // The second attempt should also succeed
+    EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+    // The third attempt should also succeed
+    EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+    // No throttling metrics should be logged.
+    EXPECT_TRUE(histogram_tester.GetAllSamples("Autofill.UploadEvent").empty());
+    EXPECT_TRUE(
+        histogram_tester
+            .GetAllSamples(AutofillMetrics::SubmissionSourceToUploadEventMetric(
+                submission_source))
+            .empty());
+  }
+}
+
+TEST_P(AutofillUploadTest, Reset) {
+  ASSERT_NE(DISABLED, GetParam());
+
+  FormData form;
+  FormFieldData field;
+
+  field.label = ASCIIToUTF16("First Name:");
+  field.name = ASCIIToUTF16("firstname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Last Name:");
+  field.name = ASCIIToUTF16("lastname");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  field.label = ASCIIToUTF16("Email:");
+  field.name = ASCIIToUTF16("email");
+  field.form_control_type = "text";
+  form.fields.push_back(field);
+
+  AutofillDownloadManager download_manager(driver_.get(), this);
+  SubmissionSource submission_source = SubmissionSource::FORM_SUBMISSION;
+
+  FormStructure form_structure(form);
+  form_structure.set_submission_source(submission_source);
+
+  base::HistogramTester histogram_tester;
+
+  TestAutofillClock test_clock;
+  test_clock.SetNow(base::Time::Now());
+
+  // The first attempt should succeed.
+  EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+  // Advance the clock, but not past the reset period. The pref won't reset,
+  // so the upload should never be sent.
+  test_clock.Advance(base::TimeDelta::FromDays(27));
+  EXPECT_FALSE(SendUploadRequest(form_structure, true, {}, "", true));
+
+  // Advance the clock beyond the reset period. The pref should bfde reset and
+  // the upload should succeed.
+  test_clock.Advance(base::TimeDelta::FromDays(2));  // Total = 29
+  EXPECT_TRUE(SendUploadRequest(form_structure, true, {}, "", true));
+
+  // One upload was not sent.
+  histogram_tester.ExpectBucketCount("Autofill.UploadEvent", 0, 1);
+  histogram_tester.ExpectBucketCount(
+      AutofillMetrics::SubmissionSourceToUploadEventMetric(submission_source),
+      0, 1);
+
+  // Two uploads were sent.
+  histogram_tester.ExpectBucketCount("Autofill.UploadEvent", 1, 2);
+  histogram_tester.ExpectBucketCount(
+      AutofillMetrics::SubmissionSourceToUploadEventMetric(submission_source),
+      1, 2);
+}
+
+// Note that we omit DEFAULT_URL from the test params. We don't actually want
+// the tests to hit the production server. We also excluded DISABLED, since
+// these tests exercise "enabled" functionality.
+INSTANTIATE_TEST_CASE_P(All,
+                        AutofillUploadTest,
+                        ::testing::Values(FINCHED_URL, COMMAND_LINE_URL));
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index d88f532..aa7aea9 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -303,8 +303,11 @@
                                           SubmissionSource source,
                                           base::TimeTicks timestamp) {
   // TODO(crbug.com/801698): handle PROBABLY_FORM_SUBMITTED.
-  if (source == SubmissionSource::PROBABLY_FORM_SUBMITTED)
+  if (source == SubmissionSource::PROBABLY_FORM_SUBMITTED &&
+      !base::FeatureList::IsEnabled(
+          features::kAutofillSaveOnProbablySubmitted)) {
     return;
+  }
 
   // We will always give Autocomplete a chance to save the data.
   std::unique_ptr<FormStructure> submitted_form = ValidateSubmittedForm(form);
@@ -329,6 +332,7 @@
   if (IsCreditCardAutofillEnabled())
     credit_card_form_event_logger_->OnWillSubmitForm();
 
+  submitted_form->set_submission_source(source);
   MaybeStartVoteUploadProcess(std::move(submitted_form), timestamp,
                               /*observed_submission=*/true);
 
@@ -339,6 +343,8 @@
   if (!submitted_form)
     return;
 
+  submitted_form->set_submission_source(source);
+
   CreditCard credit_card =
       form_data_importer_->ExtractCreditCardFromForm(*submitted_form);
   AutofillMetrics::CardNumberStatus card_number_status =
@@ -1083,7 +1089,8 @@
 
   download_manager_->StartUploadRequest(
       submitted_form, was_autofilled, non_empty_types,
-      /*login_form_signature=*/std::string(), observed_submission);
+      /*login_form_signature=*/std::string(), observed_submission,
+      client_->GetPrefs());
 }
 
 void AutofillManager::Reset() {
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 84db4fc..6962703 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -102,12 +102,13 @@
                               AutofillDownloadManager::Observer* observer)
       : TestAutofillDownloadManager(driver, observer) {}
 
-  MOCK_METHOD5(StartUploadRequest,
+  MOCK_METHOD6(StartUploadRequest,
                bool(const FormStructure&,
                     bool,
                     const ServerFieldTypeSet&,
                     const std::string&,
-                    bool));
+                    bool,
+                    PrefService*));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockAutofillDownloadManager);
@@ -6169,7 +6170,7 @@
   autofill_manager_->SetExpectedObservedSubmission(true);
   autofill_manager_->SetCallParentUploadFormData(true);
   EXPECT_CALL(*download_manager_,
-              StartUploadRequest(_, false, _, std::string(), true));
+              StartUploadRequest(_, false, _, std::string(), true, _));
 
   base::HistogramTester histogram_tester;
   FormSubmitted(form);
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 86b8544..537886bc 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -22,6 +22,7 @@
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/submission_source.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 
 namespace autofill {
@@ -1562,6 +1563,37 @@
 }
 
 // static
+const char* AutofillMetrics::SubmissionSourceToUploadEventMetric(
+    SubmissionSource source) {
+  switch (source) {
+    case SubmissionSource::NONE:
+      return "Autofill.UploadEvent.None";
+    case SubmissionSource::SAME_DOCUMENT_NAVIGATION:
+      return "Autofill.UploadEvent.SameDocumentNavigation";
+    case SubmissionSource::XHR_SUCCEEDED:
+      return "Autofill.UploadEvent.XhrSucceeded";
+    case SubmissionSource::FRAME_DETACHED:
+      return "Autofill.UploadEvent.FrameDetached";
+    case SubmissionSource::DOM_MUTATION_AFTER_XHR:
+      return "Autofill.UploadEvent.DomMutationAfterXhr";
+    case SubmissionSource::PROBABLY_FORM_SUBMITTED:
+      return "Autofill.UploadEvent.ProbablyFormSubmitted";
+    case SubmissionSource::FORM_SUBMISSION:
+      return "Autofill.UploadEvent.FormSubmission";
+  }
+  // Unittests exercise this path, so do not put NOTREACHED() here.
+  return "Autofill.UploadEvent.Unknown";
+}
+
+// static
+void AutofillMetrics::LogUploadEvent(SubmissionSource submission_source,
+                                     bool was_sent) {
+  UMA_HISTOGRAM_BOOLEAN("Autofill.UploadEvent", was_sent);
+  LogUMAHistogramEnumeration(
+      SubmissionSourceToUploadEventMetric(submission_source), was_sent, 2);
+}
+
+// static
 void AutofillMetrics::LogCardUploadDecisionsUkm(ukm::UkmRecorder* ukm_recorder,
                                                 ukm::SourceId source_id,
                                                 const GURL& url,
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 20eb82a8..f0c4b7f 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -27,6 +27,7 @@
 
 class AutofillField;
 class CreditCard;
+enum class SubmissionSource;
 
 class AutofillMetrics {
  public:
@@ -1133,6 +1134,15 @@
   // Logs if there is any suggestions for an autocomplete query.
   static void LogAutocompleteSuggestions(bool has_suggestions);
 
+  // Returns the UMA metric used to track whether or not an upload was sent
+  // after being triggered by |submission_source|. This is exposed for testing.
+  static const char* SubmissionSourceToUploadEventMetric(
+      SubmissionSource submission_source);
+
+  // Logs whether or not an upload |was_sent| after being triggered by a
+  // |submission_source| event.
+  static void LogUploadEvent(SubmissionSource submission_source, bool was_sent);
+
   // Logs the card upload decisions ukm for the specified |url|.
   // |upload_decision_metrics| is a bitmask of |CardUploadDecisionMetric|.
   static void LogCardUploadDecisionsUkm(ukm::UkmRecorder* ukm_recorder,
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 6547e88..bcf37e6 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -438,13 +438,19 @@
   upload->set_autofill_used(form_was_autofilled);
   upload->set_data_present(EncodeFieldTypes(available_field_types));
   upload->set_passwords_revealed(passwords_were_revealed_);
-  if (submission_event_ != PasswordForm::SubmissionIndicatorEvent::NONE) {
-    DCHECK(submission_event_ != PasswordForm::SubmissionIndicatorEvent::
-                                    SUBMISSION_INDICATOR_EVENT_COUNT);
-    upload->set_submission_event(
-        static_cast<AutofillUploadContents_SubmissionIndicatorEvent>(
-            submission_event_));
-  }
+
+  auto triggering_event =
+      (submission_event_ != PasswordForm::SubmissionIndicatorEvent::NONE)
+          ? submission_event_
+          : ToSubmissionIndicatorEvent(submission_source_);
+
+  DCHECK_LT(
+      submission_event_,
+      PasswordForm::SubmissionIndicatorEvent::SUBMISSION_INDICATOR_EVENT_COUNT);
+  upload->set_submission_event(
+      static_cast<AutofillUploadContents_SubmissionIndicatorEvent>(
+          triggering_event));
+
   if (password_attributes_vote_) {
     EncodePasswordAttributesVote(*password_attributes_vote_,
                                  password_length_vote_, upload);
diff --git a/components/autofill/core/browser/form_structure.h b/components/autofill/core/browser/form_structure.h
index 098db543..349c2f98e 100644
--- a/components/autofill/core/browser/form_structure.h
+++ b/components/autofill/core/browser/form_structure.h
@@ -24,6 +24,7 @@
 #include "components/autofill/core/browser/form_types.h"
 #include "components/autofill/core/browser/proto/server.pb.h"
 #include "components/autofill/core/common/password_form.h"
+#include "components/autofill/core/common/submission_source.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -292,6 +293,11 @@
   }
 #endif
 
+  SubmissionSource submission_source() const { return submission_source_; }
+  void set_submission_source(SubmissionSource submission_source) {
+    submission_source_ = submission_source;
+  }
+
   bool operator==(const FormData& form) const;
   bool operator!=(const FormData& form) const;
 
@@ -536,6 +542,8 @@
   // DetermineHeuristicTypes().
   int developer_engagement_metrics_;
 
+  SubmissionSource submission_source_ = SubmissionSource::NONE;
+
   DISALLOW_COPY_AND_ASSIGN(FormStructure);
 };
 
diff --git a/components/autofill/core/browser/form_structure_unittest.cc b/components/autofill/core/browser/form_structure_unittest.cc
index f895284..d9eef3c0 100644
--- a/components/autofill/core/browser/form_structure_unittest.cc
+++ b/components/autofill/core/browser/form_structure_unittest.cc
@@ -2407,6 +2407,8 @@
   upload.set_password_has_numeric(true);
   upload.set_password_length(10u);
   upload.set_action_signature(15724779818122431245U);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, "firstname", "text",
                         nullptr, 3U, 0);
@@ -2723,6 +2725,8 @@
   upload.set_password_has_numeric(true);
   upload.set_password_length(10u);
   upload.set_action_signature(15724779818122431245U);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, "firstname", "text",
                         nullptr, 3U, {0, 2});
@@ -2909,6 +2913,9 @@
   // Adjust the expected proto string.
   upload.set_form_signature(7816485729218079147U);
   upload.set_autofill_used(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_HTML_FORM_SUBMISSION);
+
   // Create an additional 2 fields (total of 7).
   for (int i = 0; i < 2; ++i) {
     test::FillUploadField(upload.add_field(), 509334676U, "address", "text",
@@ -3037,6 +3044,8 @@
   upload.set_action_signature(15724779818122431245U);
   upload.set_login_form_signature(42);
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   AutofillUploadContents::Field* upload_firstname_field = upload.add_field();
   test::FillUploadField(upload_firstname_field, 4224610201U, "firstname", "",
@@ -3132,6 +3141,8 @@
   upload.set_data_present("1440");
   upload.set_action_signature(15724779818122431245U);
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, "firstname", "text",
                         "given-name", 3U);
@@ -3220,6 +3231,8 @@
   upload.set_autofill_used(true);
   upload.set_data_present("1440");
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, nullptr, nullptr,
                         nullptr, 3U);
@@ -3298,6 +3311,8 @@
   upload.set_data_present("1440");
   upload.set_action_signature(15724779818122431245U);
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, "firstname", "text",
                         nullptr, 3U);
@@ -3368,6 +3383,8 @@
   upload.set_data_present("1440");
   upload.set_action_signature(15724779818122431245U);
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 1318412689U, nullptr, "text",
                         nullptr, 3U);
@@ -3435,6 +3452,8 @@
   upload.set_data_present("1440");
   upload.set_action_signature(15724779818122431245U);
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   AutofillUploadContents::Field* firstname_field = upload.add_field();
   test::FillUploadField(firstname_field, 1318412689U, nullptr, "text", nullptr,
@@ -3486,7 +3505,9 @@
   form.fields.push_back(field);
   test::InitializePossibleTypesAndValidities(
       possible_field_types, possible_field_types_validities, {EMAIL_ADDRESS});
+
   form_structure.reset(new FormStructure(form));
+  form_structure->set_submission_source(SubmissionSource::FRAME_DETACHED);
 
   ASSERT_EQ(form_structure->field_count(), possible_field_types.size());
   ASSERT_EQ(form_structure->field_count(),
@@ -3513,6 +3534,8 @@
   upload.set_action_signature(15724779818122431245U);
   upload.set_form_name("myform");
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_FRAME_DETACHED);
 
   test::FillUploadField(upload.add_field(), 1318412689U, nullptr, "text",
                         nullptr, 3U);
@@ -3588,6 +3611,8 @@
   upload.set_data_present("1440");
   upload.set_passwords_revealed(false);
   upload.set_action_signature(15724779818122431245U);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 1318412689U, nullptr, "text",
                         nullptr, 3U);
@@ -3672,6 +3697,8 @@
   upload.set_autofill_used(true);
   upload.set_data_present("1440");
   upload.set_passwords_revealed(false);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_NONE);
 
   test::FillUploadField(upload.add_field(), 3763331450U, nullptr, nullptr,
                         nullptr, 3U);
@@ -3713,6 +3740,7 @@
   form.fields.push_back(field);
 
   FormStructure form_structure(form);
+  form_structure.set_submission_source(SubmissionSource::FORM_SUBMISSION);
 
   std::vector<ServerFieldTypeSet> possible_field_types;
   std::vector<ServerFieldTypeValidityStatesMap> possible_field_types_validities;
@@ -3739,6 +3767,8 @@
   upload.set_data_present("");
   upload.set_passwords_revealed(false);
   upload.set_action_signature(15724779818122431245U);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_HTML_FORM_SUBMISSION);
 
   test::FillUploadField(upload.add_field(), 1089846351U, "first", "text",
                         nullptr, 1U);
@@ -3997,7 +4027,7 @@
                                              {ADDRESS_HOME_LINE1});
 
   form_structure.reset(new FormStructure(form));
-
+  form_structure->set_submission_source(SubmissionSource::XHR_SUCCEEDED);
   for (size_t i = 0; i < form_structure->field_count(); ++i) {
     form_structure->field(i)->set_possible_types(possible_field_types[i]);
     form_structure->field(i)->set_possible_types_validities(
@@ -4013,6 +4043,8 @@
   upload.set_data_present("1440000360000008");
   upload.set_passwords_revealed(false);
   upload.set_action_signature(15724779818122431245U);
+  upload.set_submission_event(
+      AutofillUploadContents_SubmissionIndicatorEvent_XHR_SUCCEEDED);
 
   test::FillUploadField(upload.add_field(), 420638584U, "email", "text",
                         nullptr, 9U);
diff --git a/components/autofill/core/browser/proto/server.proto b/components/autofill/core/browser/proto/server.proto
index 9389048..263fabf 100644
--- a/components/autofill/core/browser/proto/server.proto
+++ b/components/autofill/core/browser/proto/server.proto
@@ -342,6 +342,7 @@
     PROVISIONALLY_SAVED_FORM_ON_START_PROVISIONAL_LOAD = 7;
     DEPRECATED_FILLED_FORM_ON_START_PROVISIONAL_LOAD = 8;            // unused
     DEPRECATED_FILLED_INPUT_ELEMENTS_ON_START_PROVISIONAL_LOAD = 9;  // unused
+    PROBABLE_FORM_SUBMISSION = 10;
   }
 
   // The type of the event that was taken as an indication that the form has
diff --git a/components/autofill/core/browser/test_autofill_clock.cc b/components/autofill/core/browser/test_autofill_clock.cc
index a43489e..97d50fa3c 100644
--- a/components/autofill/core/browser/test_autofill_clock.cc
+++ b/components/autofill/core/browser/test_autofill_clock.cc
@@ -24,4 +24,8 @@
   test_clock_.SetNow(now);
 }
 
+void TestAutofillClock::Advance(base::TimeDelta delta) {
+  test_clock_.Advance(delta);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/test_autofill_clock.h b/components/autofill/core/browser/test_autofill_clock.h
index 71bc882..2b97361 100644
--- a/components/autofill/core/browser/test_autofill_clock.h
+++ b/components/autofill/core/browser/test_autofill_clock.h
@@ -28,6 +28,9 @@
   // Set the time to be returned from AutofillClock::Now() calls.
   void SetNow(base::Time now);
 
+  // Advances the clock by |delta|.
+  void Advance(base::TimeDelta delta);
+
  private:
   base::SimpleTestClock test_clock_;
 
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index a3eed220..bfe67a7 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -158,6 +158,9 @@
     "AutofillRestrictUnownedFieldsToFormlessCheckout",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kAutofillSaveOnProbablySubmitted{
+    "AutofillSaveOnProbablySubmitted", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kAutofillSaveCardDialogUnlabeledExpirationDate{
     "AutofillSaveCardDialogUnlabeledExpirationDate",
     base::FEATURE_ENABLED_BY_DEFAULT};
@@ -223,6 +226,9 @@
 const base::Feature kAutofillSuppressDisusedCreditCards{
     "AutofillSuppressDisusedCreditCards", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kAutofillUploadThrottling{"AutofillUploadThrottling",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kAutofillUpstream{"AutofillUpstream",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 8cb99c0..e0c5658d 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -53,6 +53,7 @@
 extern const base::Feature kAutofillRestrictUnownedFieldsToFormlessCheckout;
 extern const base::Feature kAutofillSaveCardDialogUnlabeledExpirationDate;
 extern const base::Feature kAutofillSaveCardSignInAfterLocalSave;
+extern const base::Feature kAutofillSaveOnProbablySubmitted;
 extern const base::Feature kAutofillScanCardholderName;
 extern const base::Feature kAutofillSendExperimentIdsInPaymentsRPCs;
 extern const base::Feature kAutofillSendOnlyCountryInGetUploadDetails;
@@ -64,6 +65,7 @@
 extern const base::Feature kAutofillSuggestInvalidProfileData;
 extern const base::Feature kAutofillSuppressDisusedAddresses;
 extern const base::Feature kAutofillSuppressDisusedCreditCards;
+extern const base::Feature kAutofillUploadThrottling;
 extern const base::Feature kAutofillUpstream;
 extern const base::Feature kAutofillUpstreamAllowAllEmailDomains;
 extern const base::Feature kAutofillUpstreamAlwaysRequestCardholderName;
diff --git a/components/autofill/core/common/autofill_prefs.cc b/components/autofill/core/common/autofill_prefs.cc
index 5b9c0dcf..1d1d054 100644
--- a/components/autofill/core/common/autofill_prefs.cc
+++ b/components/autofill/core/common/autofill_prefs.cc
@@ -60,6 +60,17 @@
 // Boolean that is true if Autofill is enabled and allowed to save profile data.
 const char kAutofillProfileEnabled[] = "autofill.profile_enabled";
 
+// Dictionary pref used to track which form signature uploads have been
+// performed. Each entry in the dictionary maps a form signature (reduced
+// via a 10-bit modulus) to a integer bit-field where each bit denotes whether
+// or not a given upload event has occurred.
+const char kAutofillUploadEvents[] = "autofill.upload_events";
+
+// The timestamp (seconds since the Epoch UTC) for when the the upload event
+// pref was last reset.
+const char kAutofillUploadEventsLastResetTimestamp[] =
+    "autofill.upload_events_last_reset_timestamp";
+
 // Boolean that's true when Wallet card and address import is enabled by the
 // user.
 const char kAutofillWalletImportEnabled[] = "autofill.wallet_import_enabled";
@@ -106,6 +117,9 @@
   registry->RegisterIntegerPref(
       prefs::kAutofillLastVersionDisusedCreditCardsDeleted, 0);
   registry->RegisterBooleanPref(prefs::kAutofillOrphanRowsRemoved, false);
+  registry->RegisterDictionaryPref(prefs::kAutofillUploadEvents);
+  registry->RegisterTimePref(prefs::kAutofillUploadEventsLastResetTimestamp,
+                             base::Time());
 }
 
 void MigrateDeprecatedAutofillPrefs(PrefService* prefs) {
diff --git a/components/autofill/core/common/autofill_prefs.h b/components/autofill/core/common/autofill_prefs.h
index 4dc9d14..aee9128 100644
--- a/components/autofill/core/common/autofill_prefs.h
+++ b/components/autofill/core/common/autofill_prefs.h
@@ -32,6 +32,8 @@
 extern const char kAutofillOrphanRowsRemoved[];
 // Do not get/set the value of this pref directly. Use provided getter/setter.
 extern const char kAutofillProfileEnabled[];
+extern const char kAutofillUploadEvents[];
+extern const char kAutofillUploadEventsLastResetTimestamp[];
 extern const char kAutofillWalletImportEnabled[];
 extern const char kAutofillWalletImportStorageCheckboxState[];
 extern const char kAutofillProfileValidity[];
diff --git a/components/autofill/core/common/password_form.cc b/components/autofill/core/common/password_form.cc
index 34a9bec..65f8a2d 100644
--- a/components/autofill/core/common/password_form.cc
+++ b/components/autofill/core/common/password_form.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "components/autofill/core/common/password_form.h"
+#include "components/autofill/core/common/submission_source.h"
 
 namespace autofill {
 
@@ -198,6 +199,28 @@
   return base::JoinString(pairs, base::ASCIIToUTF16(", "));
 }
 
+PasswordForm::SubmissionIndicatorEvent ToSubmissionIndicatorEvent(
+    SubmissionSource source) {
+  switch (source) {
+    case SubmissionSource::NONE:
+      return PasswordForm::SubmissionIndicatorEvent::NONE;
+    case SubmissionSource::SAME_DOCUMENT_NAVIGATION:
+      return PasswordForm::SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
+    case SubmissionSource::XHR_SUCCEEDED:
+      return PasswordForm::SubmissionIndicatorEvent::XHR_SUCCEEDED;
+    case SubmissionSource::FRAME_DETACHED:
+      return PasswordForm::SubmissionIndicatorEvent::FRAME_DETACHED;
+    case SubmissionSource::DOM_MUTATION_AFTER_XHR:
+      return PasswordForm::SubmissionIndicatorEvent::DOM_MUTATION_AFTER_XHR;
+    case SubmissionSource::PROBABLY_FORM_SUBMITTED:
+      return PasswordForm::SubmissionIndicatorEvent::PROBABLE_FORM_SUBMISSION;
+    case SubmissionSource::FORM_SUBMISSION:
+      return PasswordForm::SubmissionIndicatorEvent::HTML_FORM_SUBMISSION;
+  }
+  // Unittests exercise this path, so do not put NOTREACHED() here.
+  return PasswordForm::SubmissionIndicatorEvent::NONE;
+}
+
 std::ostream& operator<<(std::ostream& os, const PasswordForm& form) {
   base::DictionaryValue form_json;
   PasswordFormToJSON(form, &form_json);
diff --git a/components/autofill/core/common/password_form.h b/components/autofill/core/common/password_form.h
index 10253567..3f5f51b 100644
--- a/components/autofill/core/common/password_form.h
+++ b/components/autofill/core/common/password_form.h
@@ -18,6 +18,8 @@
 
 namespace autofill {
 
+enum class SubmissionSource;
+
 // Pair of a value and the name of the element that contained this value.
 using ValueElementPair = std::pair<base::string16, base::string16>;
 
@@ -85,6 +87,7 @@
     PROVISIONALLY_SAVED_FORM_ON_START_PROVISIONAL_LOAD,
     DEPRECATED_FILLED_FORM_ON_START_PROVISIONAL_LOAD,            // unused
     DEPRECATED_FILLED_INPUT_ELEMENTS_ON_START_PROVISIONAL_LOAD,  // unused
+    PROBABLE_FORM_SUBMISSION,
     SUBMISSION_INDICATOR_EVENT_COUNT
   };
 
@@ -361,6 +364,9 @@
 base::string16 ValueElementVectorToString(
     const ValueElementVector& value_element_pairs);
 
+PasswordForm::SubmissionIndicatorEvent ToSubmissionIndicatorEvent(
+    SubmissionSource source);
+
 // For testing.
 std::ostream& operator<<(std::ostream& os, const PasswordForm& form);
 std::ostream& operator<<(std::ostream& os, PasswordForm* form);
diff --git a/components/autofill/core/common/submission_source.h b/components/autofill/core/common/submission_source.h
index d6c213b2..5eab05a1 100644
--- a/components/autofill/core/common/submission_source.h
+++ b/components/autofill/core/common/submission_source.h
@@ -9,6 +9,7 @@
 
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.autofill
 enum class SubmissionSource {
+  NONE,                      // No submission signal was detected.
   SAME_DOCUMENT_NAVIGATION,  // The form was removed in same document
                              // navigation.
   XHR_SUCCEEDED,             // The form was removed whem XHR succeeded.
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index 9c93add1..040b934 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -252,6 +252,9 @@
 
   <!-- Autofill Local card migration bubble or dialog -->
   <if expr="not is_ios and not is_android">
+    <message name="IDS_AUTOFILL_GOOGLE_PAY_LOGO_ACCESSIBLE_NAME" desc="The accessible name for the Google Pay logo in the local card migration bubble or dialog.">
+      Google Pay logo
+    </message>
     <message name="IDS_AUTOFILL_LOCAL_CARD_MIGRATION_BUBBLE_TITLE" desc="The title text for a bubble that offers users to start the process of migrating local cards to cloud.">
       Save all your cards in one place?
     </message>
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImpl.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImpl.java
index 0928f3ca..b00f636 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImpl.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImpl.java
@@ -7,6 +7,7 @@
 import android.content.Context;
 import android.os.Build;
 
+import org.chromium.base.CommandLine;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
@@ -21,6 +22,7 @@
  */
 class BackgroundTaskSchedulerImpl implements BackgroundTaskScheduler {
     private static final String TAG = "BkgrdTaskScheduler";
+    private static final String SWITCH_IGNORE_BACKGROUND_TASKS = "ignore-background-tasks";
 
     private final BackgroundTaskSchedulerDelegate mSchedulerDelegate;
 
@@ -31,6 +33,12 @@
 
     @Override
     public boolean schedule(Context context, TaskInfo taskInfo) {
+        if (CommandLine.getInstance().hasSwitch(SWITCH_IGNORE_BACKGROUND_TASKS)) {
+            // When background tasks finish executing, they leave a cached process, which
+            // artificially inflates startup metrics that are based on events near to process
+            // creation.
+            return true;
+        }
         try (TraceEvent te = TraceEvent.scoped(
                      "BackgroundTaskScheduler.schedule", Integer.toString(taskInfo.getTaskId()))) {
             ThreadUtils.assertOnUiThread();
diff --git a/components/heap_profiling/test_driver.cc b/components/heap_profiling/test_driver.cc
index 592467a..a070690 100644
--- a/components/heap_profiling/test_driver.cc
+++ b/components/heap_profiling/test_driver.cc
@@ -901,8 +901,7 @@
   if (IsRecordingAllAllocations()) {
     if (should_validate_dumps) {
       result = ValidateDump(heaps_v2, kMallocAllocSize * kMallocAllocCount,
-                            kMallocAllocCount, "malloc",
-                            HasPseudoFrames() ? kMallocTypeTag : nullptr,
+                            kMallocAllocCount, "malloc", kMallocTypeTag,
                             HasPseudoFrames() ? kMallocEvent : "", thread_name);
       if (!result) {
         LOG(ERROR) << "Failed to validate malloc fixed allocations";
diff --git a/components/image_fetcher/core/DEPS b/components/image_fetcher/core/DEPS
index 9b2c814f..ab5ae2fd 100644
--- a/components/image_fetcher/core/DEPS
+++ b/components/image_fetcher/core/DEPS
@@ -1,4 +1,7 @@
 include_rules = [
   "+components/leveldb_proto",
   "+components/prefs",
+  "+ui/gfx/codec",
+  "+ui/gfx/geomtery",
+  "+ui/gfx/image",
 ]
diff --git a/components/image_fetcher/core/cached_image_fetcher.cc b/components/image_fetcher/core/cached_image_fetcher.cc
index 8c00a300..437b066 100644
--- a/components/image_fetcher/core/cached_image_fetcher.cc
+++ b/components/image_fetcher/core/cached_image_fetcher.cc
@@ -10,24 +10,45 @@
 #include "components/image_fetcher/core/image_decoder.h"
 #include "components/image_fetcher/core/request_metadata.h"
 #include "components/image_fetcher/core/storage/image_cache.h"
+#include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace image_fetcher {
 
 namespace {
 
-// Wrapper to check if callbacks can be called.
-void CallbackIfPresent(ImageDataFetcherCallback data_callback,
-                       ImageFetcherCallback image_callback,
-                       const std::string& id,
-                       const std::string& image_data,
-                       const gfx::Image& image,
-                       const image_fetcher::RequestMetadata& metadata) {
-  if (!data_callback.is_null())
-    std::move(data_callback).Run(image_data, metadata);
-  if (!image_callback.is_null())
-    std::move(image_callback).Run(id, image, metadata);
+void DataCallbackIfPresent(ImageDataFetcherCallback data_callback,
+                           const std::string& image_data,
+                           const image_fetcher::RequestMetadata& metadata) {
+  if (data_callback.is_null()) {
+    return;
+  }
+  std::move(data_callback).Run(image_data, metadata);
+}
+
+void ImageCallbackIfPresent(ImageFetcherCallback image_callback,
+                            const std::string& id,
+                            const gfx::Image& image,
+                            const image_fetcher::RequestMetadata& metadata) {
+  if (image_callback.is_null()) {
+    return;
+  }
+  std::move(image_callback).Run(id, image, metadata);
+}
+
+bool EncodeSkBitmapToPNG(const SkBitmap& bitmap,
+                         std::vector<unsigned char>* dest) {
+  if (!bitmap.readyToDraw() || bitmap.isNull()) {
+    return false;
+  }
+
+  return gfx::PNGCodec::Encode(
+      static_cast<const unsigned char*>(bitmap.getPixels()),
+      gfx::PNGCodec::FORMAT_RGBA, gfx::Size(bitmap.width(), bitmap.height()),
+      static_cast<int>(bitmap.rowBytes()), /* discard_transparency */ false,
+      std::vector<gfx::PNGCodec::Comment>(), dest);
 }
 
 }  // namespace
@@ -51,6 +72,7 @@
 
 void CachedImageFetcher::SetDesiredImageFrameSize(const gfx::Size& size) {
   image_fetcher_->SetDesiredImageFrameSize(size);
+  desired_image_frame_size_ = size;
 }
 
 void CachedImageFetcher::SetImageDownloadLimit(
@@ -89,8 +111,9 @@
     FetchImageFromNetwork(id, image_url, std::move(data_callback),
                           std::move(image_callback), traffic_annotation);
   } else {
+    // TODO(wylieb): On Android, do this in-process.
     GetImageDecoder()->DecodeImage(
-        image_data, gfx::Size(),
+        image_data, desired_image_frame_size_,
         base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromCache,
                             weak_ptr_factory_.GetWeakPtr(), id, image_url,
                             base::Passed(std::move(data_callback)),
@@ -110,10 +133,13 @@
   if (image.IsEmpty()) {
     FetchImageFromNetwork(id, image_url, std::move(data_callback),
                           std::move(image_callback), traffic_annotation);
+    // Decoding error, delete image from cache.
     image_cache_->DeleteImage(image_url.spec());
   } else {
-    CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
-                      image_data, image, RequestMetadata());
+    DataCallbackIfPresent(std::move(data_callback), image_data,
+                          RequestMetadata());
+    ImageCallbackIfPresent(std::move(image_callback), id, image,
+                           RequestMetadata());
   }
 }
 
@@ -123,58 +149,50 @@
     ImageDataFetcherCallback data_callback,
     ImageFetcherCallback image_callback,
     const net::NetworkTrafficAnnotationTag& traffic_annotation) {
-  if (!image_url.is_valid()) {
-    // URL is invalid, return empty image/data.
-    CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
-                      std::string(), gfx::Image(), RequestMetadata());
-    return;
-  }
-
-  image_fetcher_->FetchImageData(
+  // Fetch image data and the image itself. The image data will be stored in
+  // the image cache, and the image will be returned to the caller.
+  image_fetcher_->FetchImageAndData(
       id, image_url,
+      base::BindOnce(&CachedImageFetcher::OnImageDataFetchedFromNetwork,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(data_callback),
+                     image_url),
       base::BindOnce(&CachedImageFetcher::OnImageFetchedFromNetwork,
-                     weak_ptr_factory_.GetWeakPtr(), id, image_url,
-                     std::move(data_callback), std::move(image_callback)),
+                     weak_ptr_factory_.GetWeakPtr(), std::move(image_callback)),
       traffic_annotation);
 }
 
 void CachedImageFetcher::OnImageFetchedFromNetwork(
-    const std::string& id,
-    const GURL& image_url,
-    ImageDataFetcherCallback data_callback,
     ImageFetcherCallback image_callback,
+    const std::string& id,
+    const gfx::Image& image,
+    const RequestMetadata& request_metadata) {
+  // The image has been deocded by the fetcher already, return straight to the
+  // caller.
+  ImageCallbackIfPresent(std::move(image_callback), id, image,
+                         request_metadata);
+}
+
+void CachedImageFetcher::OnImageDataFetchedFromNetwork(
+    ImageDataFetcherCallback data_callback,
+    const GURL& image_url,
     const std::string& image_data,
     const RequestMetadata& request_metadata) {
-  if (image_data.empty()) {
-    // Fetching image failed, return empty image/data.
-    CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
-                      image_data, gfx::Image(), request_metadata);
+  DataCallbackIfPresent(std::move(data_callback), image_data, request_metadata);
+  GetImageDecoder()->DecodeImage(
+      image_data, /* Decoding for cache shouldn't specify size */ gfx::Size(),
+      base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromNetwork,
+                          weak_ptr_factory_.GetWeakPtr(), image_url));
+}
+
+void CachedImageFetcher::OnImageDecodedFromNetwork(const GURL& image_url,
+                                                   const gfx::Image& image) {
+  std::vector<unsigned char> encoded_data;
+  if (!EncodeSkBitmapToPNG(*image.ToSkBitmap(), &encoded_data)) {
     return;
   }
 
-  image_fetcher_->GetImageDecoder()->DecodeImage(
-      image_data, gfx::Size(),
-      base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromNetwork,
-                          weak_ptr_factory_.GetWeakPtr(), id, image_url,
-                          base::Passed(std::move(data_callback)),
-                          base::Passed(std::move(image_callback)), image_data,
-                          request_metadata));
-}
-
-void CachedImageFetcher::OnImageDecodedFromNetwork(
-    const std::string& id,
-    const GURL& image_url,
-    ImageDataFetcherCallback data_callback,
-    ImageFetcherCallback image_callback,
-    const std::string& image_data,
-    const RequestMetadata& request_metadata,
-    const gfx::Image& image) {
-  CallbackIfPresent(std::move(data_callback), std::move(image_callback), id,
-                    image_data, image, request_metadata);
-
-  if (!image.IsEmpty()) {
-    image_cache_->SaveImage(image_url.spec(), image_data);
-  }
+  image_cache_->SaveImage(
+      image_url.spec(), std::string(encoded_data.begin(), encoded_data.end()));
 }
 
 }  // namespace image_fetcher
diff --git a/components/image_fetcher/core/cached_image_fetcher.h b/components/image_fetcher/core/cached_image_fetcher.h
index 410e98d..1b3ebe0 100644
--- a/components/image_fetcher/core/cached_image_fetcher.h
+++ b/components/image_fetcher/core/cached_image_fetcher.h
@@ -20,6 +20,7 @@
 
 namespace gfx {
 class Image;
+class Size;
 }  // namespace gfx
 
 namespace image_fetcher {
@@ -76,23 +77,23 @@
       ImageDataFetcherCallback image_data_callback,
       ImageFetcherCallback image_callback,
       const net::NetworkTrafficAnnotationTag& traffic_annotation);
-  void OnImageFetchedFromNetwork(const std::string& id,
-                                 const GURL& image_url,
-                                 ImageDataFetcherCallback image_data_callback,
-                                 ImageFetcherCallback image_callback,
-                                 const std::string& image_data,
+  void OnImageFetchedFromNetwork(ImageFetcherCallback image_callback,
+                                 const std::string& id,
+                                 const gfx::Image& image,
                                  const RequestMetadata& request_metadata);
-  void OnImageDecodedFromNetwork(const std::string& id,
-                                 const GURL& image_url,
-                                 ImageDataFetcherCallback image_data_callback,
-                                 ImageFetcherCallback image_callback,
-                                 const std::string& image_data,
-                                 const RequestMetadata& request_metadata,
+  void OnImageDataFetchedFromNetwork(
+      ImageDataFetcherCallback image_data_callback,
+      const GURL& image_url,
+      const std::string& image_data,
+      const RequestMetadata& request_metadata);
+  void OnImageDecodedFromNetwork(const GURL& image_url,
                                  const gfx::Image& image);
 
   std::unique_ptr<ImageFetcher> image_fetcher_;
   std::unique_ptr<ImageCache> image_cache_;
 
+  gfx::Size desired_image_frame_size_;
+
   base::WeakPtrFactory<CachedImageFetcher> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(CachedImageFetcher);
diff --git a/components/image_fetcher/core/cached_image_fetcher_unittest.cc b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
index 944919e..76bc141 100644
--- a/components/image_fetcher/core/cached_image_fetcher_unittest.cc
+++ b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
@@ -137,23 +137,12 @@
   return !arg.IsEmpty();
 }
 
-// TODO(wylieb): Rename these tests CachedImageFetcherTest* when ntp_snippets/-
-//               remote/cached_image_fetcher has been migrated.
-TEST_F(ComponentizedCachedImageFetcherTest, FetchEmptyUrl) {
-  base::MockCallback<ImageDataFetcherCallback> data_callback;
-  base::MockCallback<ImageFetcherCallback> image_callback;
-  GURL empty_url = GURL(std::string());
-
-  // Make sure an empty image passed to callback.
-  EXPECT_CALL(data_callback, Run(std::string(), _));
-  EXPECT_CALL(image_callback, Run(std::string(), EmptyImage(), _));
-  cached_image_fetcher()->FetchImageAndData(
-      empty_url.spec(), empty_url, data_callback.Get(), image_callback.Get(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
-
-  RunUntilIdle();
+MATCHER(NonEmptyString, "") {
+  return !arg.empty();
 }
 
+// TODO(wylieb): Rename these tests CachedImageFetcherTest* when ntp_snippets/-
+//               remote/cached_image_fetcher has been migrated.
 TEST_F(ComponentizedCachedImageFetcherTest, FetchImageFromCache) {
   // Save the image in the database.
   image_cache()->SaveImage(kImageUrl.spec(), kImageData);
@@ -179,7 +168,7 @@
     base::MockCallback<ImageDataFetcherCallback> data_callback;
     base::MockCallback<ImageFetcherCallback> image_callback;
 
-    EXPECT_CALL(data_callback, Run(kImageData, _));
+    EXPECT_CALL(data_callback, Run(NonEmptyString(), _));
     EXPECT_CALL(image_callback, Run(kImageUrl.spec(), NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl.spec(), kImageUrl, data_callback.Get(), image_callback.Get(),
@@ -189,7 +178,7 @@
   }
   // Make sure the image data is in the database.
   {
-    EXPECT_CALL(*this, OnImageLoaded(kImageData));
+    EXPECT_CALL(*this, OnImageLoaded(NonEmptyString()));
     image_cache()->LoadImage(
         kImageUrl.spec(),
         base::BindOnce(&ComponentizedCachedImageFetcherTest::OnImageLoaded,
@@ -203,7 +192,7 @@
     base::MockCallback<ImageDataFetcherCallback> data_callback;
     base::MockCallback<ImageFetcherCallback> image_callback;
 
-    EXPECT_CALL(data_callback, Run(kImageData, _));
+    EXPECT_CALL(data_callback, Run(NonEmptyString(), _));
     EXPECT_CALL(image_callback, Run(kImageUrl.spec(), NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl.spec(), kImageUrl, data_callback.Get(), image_callback.Get(),
diff --git a/components/network_session_configurator/browser/network_session_configurator_unittest.cc b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
index 0c8388a5..2fdb96a 100644
--- a/components/network_session_configurator/browser/network_session_configurator_unittest.cc
+++ b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
@@ -734,13 +734,6 @@
   EXPECT_EQ("foo", host_port_pair.host());
 }
 
-TEST_F(NetworkSessionConfiguratorTest, TokenBindingDisabled) {
-  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
-  ParseCommandLineAndFieldTrials(command_line);
-
-  EXPECT_FALSE(params_.enable_token_binding);
-}
-
 TEST_F(NetworkSessionConfiguratorTest, ChannelIDEnabled) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(features::kChannelID);
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 81af9db..c2e9321 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -235,11 +235,15 @@
   if (GetQueryInOmniboxSearchTerms(&display_text_)) {
     // The search query has been inserted into |display_text_|.
     DCHECK(!display_text_.empty());
-  } else if (OmniboxFieldTrial::
-                 IsHideSteadyStateUrlSchemeAndSubdomainsEnabled()) {
-    display_text_ = toolbar_model->GetURLForDisplay();
   } else {
+#if defined(OS_IOS)
+    // iOS is unusual in that it uses a separate LocationView to show the
+    // ToolbarModel's display-only URL. The actual OmniboxViewIOS widget is
+    // hidden in the defocused state, and always contains the URL for editing.
     display_text_ = url_for_editing_;
+#else
+    display_text_ = toolbar_model->GetURLForDisplay();
+#endif
   }
 
   // When there's new permanent text, and the user isn't interacting with the
diff --git a/components/omnibox/browser/omnibox_edit_model_unittest.cc b/components/omnibox/browser/omnibox_edit_model_unittest.cc
index 864f3ee..5546b1a5 100644
--- a/components/omnibox/browser/omnibox_edit_model_unittest.cc
+++ b/components/omnibox/browser/omnibox_edit_model_unittest.cc
@@ -58,7 +58,6 @@
     const bool write_url;
     const char* expected_url;
 
-    bool steady_state_elisions_on = false;
     const char* url_for_display = "";
   } input[] = {
       // Test that http:// is inserted if all text is selected.
@@ -103,7 +102,8 @@
        "http://a.de/"},
 
       // Makes sure intranet urls get 'http://' prefixed to them.
-      {"b/foo", 0, "", false, "b/foo", "http://b/foo", true, "http://b/foo"},
+      {"b/foo", 0, "", false, "b/foo", "http://b/foo", true, "http://b/foo",
+       "b/foo"},
 
       // Verifies a search term 'foo' doesn't end up with http.
       {"www.google.com/search?", 0, "", false, "foo", "foo", false, ""},
@@ -126,19 +126,10 @@
 
       // Steady State Elisions test for re-adding an elided 'https://'.
       {"https://a.de/b", 0, "", false, "a.de/b", "https://a.de/b", true,
-       "https://a.de/b", true, "a.de/b"},
+       "https://a.de/b", "a.de/b"},
   };
 
   for (size_t i = 0; i < arraysize(input); ++i) {
-    base::test::ScopedFeatureList feature_list;
-    if (input[i].steady_state_elisions_on) {
-      feature_list.InitAndEnableFeature(
-          omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains);
-    } else {
-      feature_list.InitAndDisableFeature(
-          omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains);
-    }
-
     toolbar_model()->set_formatted_full_url(
         base::ASCIIToUTF16(input[i].url_for_editing));
     toolbar_model()->set_url_for_display(
@@ -242,41 +233,23 @@
       base::ASCIIToUTF16("https://www.example.com/"));
   toolbar_model()->set_url_for_display(base::ASCIIToUTF16("example.com"));
 
-  // Verify the displayed text with Steady State Elisions Enabled.
+  // Verify we show the display text when there is no Query in Omnibox match.
   {
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitAndDisableFeature(
-        omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains);
     model()->ResetDisplayTexts();
+#if defined(OS_IOS)
+    // iOS OmniboxEditModel always provides the full URL as the OmniboxView
+    // permanent display text.
     EXPECT_EQ(base::ASCIIToUTF16("https://www.example.com/"),
               model()->GetPermanentDisplayText());
-
-    base::string16 search_terms;
-    EXPECT_FALSE(model()->GetQueryInOmniboxSearchTerms(&search_terms));
-    EXPECT_TRUE(search_terms.empty());
-  }
-
-// TODO(tommycli): For now, it's not possible to enable Steady State Elisions
-// in the edit model for iOS.
-#if !defined(OS_IOS)
-  // Verify the displayed text with Steady State Elisions Disabled.
-  {
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitAndEnableFeature(
-        omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains);
-
-    ASSERT_TRUE(
-        OmniboxFieldTrial::IsHideSteadyStateUrlSchemeAndSubdomainsEnabled());
-
-    model()->ResetDisplayTexts();
+#else
     EXPECT_EQ(base::ASCIIToUTF16("example.com"),
               model()->GetPermanentDisplayText());
+#endif
 
     base::string16 search_terms;
     EXPECT_FALSE(model()->GetQueryInOmniboxSearchTerms(&search_terms));
     EXPECT_TRUE(search_terms.empty());
   }
-#endif  // !defined(OS_IOS)
 
   // Verify the displayed text when there is a Query in Omnibox match.
   TestOmniboxClient* client =
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index 675a7a1..45daced6 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -146,12 +146,6 @@
     "OmniboxUIExperimentElideSuggestionUrlAfterHost",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Feature used to hide the scheme and trivial subdomains from steady state
-// URLs displayed in the Omnibox. Hidden portions are restored during editing.
-const base::Feature kUIExperimentHideSteadyStateUrlSchemeAndSubdomains{
-    "OmniboxUIExperimentHideSteadyStateUrlSchemeAndSubdomains",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Feature used to jog the Omnibox textfield to align with the dropdown
 // suggestions text when the popup is opened. When this feature is disabled, the
 // textfield is always aligned with the suggestions text, and a separator fills
@@ -773,12 +767,6 @@
   return omnibox::pedal_suggestion_mode.Get();
 }
 
-bool OmniboxFieldTrial::IsHideSteadyStateUrlSchemeAndSubdomainsEnabled() {
-  return base::FeatureList::IsEnabled(
-             omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains) ||
-         base::FeatureList::IsEnabled(features::kExperimentalUi);
-}
-
 bool OmniboxFieldTrial::IsJogTextfieldOnPopupEnabled() {
   return ui::MaterialDesignController::IsRefreshUi() &&
          base::FeatureList::IsEnabled(
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index d0c98da..e24290b 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -39,7 +39,6 @@
 extern const base::Feature kDisplayTitleForCurrentUrl;
 extern const base::Feature kQueryInOmnibox;
 extern const base::Feature kUIExperimentElideSuggestionUrlAfterHost;
-extern const base::Feature kUIExperimentHideSteadyStateUrlSchemeAndSubdomains;
 extern const base::Feature kUIExperimentJogTextfieldOnPopup;
 extern const base::Feature kUIExperimentMaxAutocompleteMatches;
 extern const base::Feature kUIExperimentShowSuggestionFavicons;
@@ -439,10 +438,6 @@
   // Returns the #omnibox-pedal-suggestions feature's mode parameter as enum.
   static PedalSuggestionMode GetPedalSuggestionMode();
 
-  // Returns true if either the steady-state elision flag or the
-  // #upcoming-ui-features flag is enabled.
-  static bool IsHideSteadyStateUrlSchemeAndSubdomainsEnabled();
-
   // Returns true if the jog textfield flag and refresh UI are both enabled.
   static bool IsJogTextfieldOnPopupEnabled();
 
diff --git a/components/password_manager/core/browser/password_form_manager_unittest.cc b/components/password_manager/core/browser/password_form_manager_unittest.cc
index 9e9534ae..c9872b0 100644
--- a/components/password_manager/core/browser/password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_form_manager_unittest.cc
@@ -210,12 +210,13 @@
       autofill::AutofillDownloadManager::Observer* observer)
       : AutofillDownloadManager(driver, observer) {}
 
-  MOCK_METHOD5(StartUploadRequest,
+  MOCK_METHOD6(StartUploadRequest,
                bool(const autofill::FormStructure&,
                     bool,
                     const autofill::ServerFieldTypeSet&,
                     const std::string&,
-                    bool));
+                    bool,
+                    PrefService*));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockAutofillDownloadManager);
@@ -466,7 +467,7 @@
                                                Field::CREDENTIALS_REUSED}})),
                                      HasPasswordAttributesVote(false)),
                                false, expected_available_field_types,
-                               expected_login_signature, true));
+                               expected_login_signature, true, nullptr));
       } else {
         EXPECT_CALL(
             *client()->mock_driver()->mock_autofill_download_manager(),
@@ -475,18 +476,18 @@
                                      HasGenerationVote(expect_generation_vote),
                                      HasPasswordAttributesVote(false)),
                                false, expected_available_field_types,
-                               expected_login_signature, true));
+                               expected_login_signature, true, nullptr));
       }
     } else {
       EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-                  StartUploadRequest(_, _, _, _, _))
+                  StartUploadRequest(_, _, _, _, _, _))
           .Times(0);
     }
     if (times_used == 0) {
       // First login vote.
-      EXPECT_CALL(
-          *client()->mock_driver()->mock_autofill_download_manager(),
-          StartUploadRequest(SignatureIsSameAs(form_to_save), _, _, _, _));
+      EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                  StartUploadRequest(SignatureIsSameAs(form_to_save), _, _, _,
+                                     _, nullptr));
     }
     form_manager.ProvisionallySave(form_to_save);
     form_manager.Save();
@@ -576,7 +577,7 @@
       // An unrelated vote that the credentials were used for the first time.
       EXPECT_CALL(
           *client()->mock_driver()->mock_autofill_download_manager(),
-          StartUploadRequest(SignatureIsSameAs(submitted_form), _, _, _, _));
+          StartUploadRequest(SignatureIsSameAs(submitted_form), _, _, _, _, _));
     }
     EXPECT_CALL(
         *client()->mock_driver()->mock_autofill_download_manager(),
@@ -585,7 +586,7 @@
                   UploadedAutofillTypesAre(expected_types),
                   HasGenerationVote(false), HasPasswordAttributesVote(false)),
             false, expected_available_field_types, expected_login_signature,
-            true));
+            true, nullptr));
 
     switch (field_type) {
       case autofill::NEW_PASSWORD:
@@ -702,13 +703,13 @@
         expected_generation_types;
     expected_generation_types[generation_element] = expected_generation_type;
 
-    EXPECT_CALL(
-        *client()->mock_driver()->mock_autofill_download_manager(),
-        StartUploadRequest(
-            AllOf(SignatureIsSameAs(submitted_form),
-                  UploadedGenerationTypesAre(expected_generation_types,
+    EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                StartUploadRequest(AllOf(SignatureIsSameAs(submitted_form),
+                                         UploadedGenerationTypesAre(
+                                             expected_generation_types,
                                              generated_password_changed)),
-            false, expected_available_field_types, std::string(), true));
+                                   false, expected_available_field_types,
+                                   std::string(), true, nullptr));
     base::HistogramTester histogram_tester;
 
     form_manager.ProvisionallySave(submitted_form);
@@ -811,13 +812,13 @@
         expected_generation_types;
     expected_generation_types[generation_element] = expected_generation_type;
 
-    EXPECT_CALL(
-        *client()->mock_driver()->mock_autofill_download_manager(),
-        StartUploadRequest(
-            AllOf(SignatureIsSameAs(submitted_form),
-                  UploadedGenerationTypesAre(expected_generation_types,
+    EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                StartUploadRequest(AllOf(SignatureIsSameAs(submitted_form),
+                                         UploadedGenerationTypesAre(
+                                             expected_generation_types,
                                              generated_password_changed)),
-            false, expected_available_field_types, std::string(), true));
+                                   false, expected_available_field_types,
+                                   std::string(), true, nullptr));
     form_manager->ProvisionallySave(submitted_form);
     switch (interaction) {
       case SAVE:
@@ -926,7 +927,7 @@
     form_manager.Init(nullptr);
 
     EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-                StartUploadRequest(_, _, _, _, _))
+                StartUploadRequest(_, _, _, _, _, _))
         .Times(::testing::AnyNumber());
 
     PasswordForm http_stored_form = *saved_match();
@@ -1191,9 +1192,9 @@
   autofill::ServerFieldTypeSet expected_available_field_types;
   expected_available_field_types.insert(autofill::ACCOUNT_CREATION_PASSWORD);
   expected_available_field_types.insert(autofill::USERNAME);
-  EXPECT_CALL(
-      *client()->mock_driver()->mock_autofill_download_manager(),
-      StartUploadRequest(_, false, expected_available_field_types, _, true));
+  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+              StartUploadRequest(_, false, expected_available_field_types, _,
+                                 true, nullptr));
   EXPECT_CALL(MockFormSaver::Get(form_manager()), Save(_, _))
       .WillOnce(SaveArg<0>(&actual_saved_form));
   form_manager()->Save();
@@ -1430,7 +1431,7 @@
   EXPECT_CALL(MockFormSaver::Get(form_manager()), Update(_, _, _, nullptr))
       .WillOnce(SaveArg<0>(&saved_result));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   form_manager()->Save();
   // Should be only one password stored, and should not have
@@ -1924,7 +1925,7 @@
       .WillOnce(testing::DoAll(SaveArg<0>(&saved_result),
                                SaveArgPointee<2>(&credentials_to_update)));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   form_manager()->Save();
 
@@ -1958,9 +1959,9 @@
 
   autofill::ServerFieldTypeSet expected_available_field_types;
   expected_available_field_types.insert(autofill::PASSWORD);
-  EXPECT_CALL(
-      *client()->mock_driver()->mock_autofill_download_manager(),
-      StartUploadRequest(_, false, expected_available_field_types, _, true));
+  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+              StartUploadRequest(_, false, expected_available_field_types, _,
+                                 true, nullptr));
   form_manager.ProvisionallySave(form_to_save);
   form_manager.Save();
 }
@@ -1978,8 +1979,9 @@
   autofill::ServerFieldTypeSet expected_available_field_types;
   expected_available_field_types.insert(autofill::USERNAME);
   expected_available_field_types.insert(autofill::PASSWORD);
-  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, _, expected_available_field_types, _, true))
+  EXPECT_CALL(
+      *client()->mock_driver()->mock_autofill_download_manager(),
+      StartUploadRequest(_, _, expected_available_field_types, _, true, _))
       .Times(0);
   blacklist_form_manager.PermanentlyBlacklist();
 }
@@ -2342,7 +2344,7 @@
             AllOf(SignatureIsSameAs(observed),
                   UploadedAutofillTypesAre(expected_types),
                   HasGenerationVote(false), VoteTypesAre(expected_vote_types)),
-            _, Contains(autofill::USERNAME), _, _));
+            _, Contains(autofill::USERNAME), _, _, nullptr));
     form_manager.Save();
 
     // Check what is saved.
@@ -2418,7 +2420,7 @@
             AllOf(SignatureIsSameAs(expected_pending),
                   UploadedAutofillTypesAre(expected_types),
                   HasGenerationVote(false), VoteTypesAre(expected_vote_types)),
-            _, Contains(autofill::USERNAME), _, _));
+            _, Contains(autofill::USERNAME), _, _, nullptr));
     form_manager()->Save();
   }
 }
@@ -2477,9 +2479,9 @@
                 Save(_, ElementsAre(Pair(saved_match()->username_value,
                                          Pointee(*saved_match())))))
         .WillOnce(SaveArg<0>(&actual_saved_form));
-    EXPECT_CALL(
-        *client()->mock_driver()->mock_autofill_download_manager(),
-        StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _, _));
+    EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _,
+                                   _, nullptr));
     form_manager.Save();
 
     // Can't verify |date_created |, so ignore it in form comparison.
@@ -2519,9 +2521,9 @@
   PasswordForm saved_result;
   EXPECT_CALL(MockFormSaver::Get(&form_manager), Save(_, IsEmpty()))
       .WillOnce(SaveArg<0>(&saved_result));
-  EXPECT_CALL(
-      *client()->mock_driver()->mock_autofill_download_manager(),
-      StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _, _));
+  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+              StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _, _,
+                                 nullptr));
   form_manager.Save();
 
   EXPECT_TRUE(saved_result.username_value.empty());
@@ -2557,8 +2559,9 @@
                                        Pointee(*psl_saved_match())))))
       .WillOnce(SaveArg<0>(&saved_result));
   // As the credential is re-used successfully, expect a username vote.
-  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, _, Contains(autofill::USERNAME), _, _));
+  EXPECT_CALL(
+      *client()->mock_driver()->mock_autofill_download_manager(),
+      StartUploadRequest(_, _, Contains(autofill::USERNAME), _, _, nullptr));
   form_manager()->Save();
 
   // Check what is saved.
@@ -2648,7 +2651,7 @@
                               UploadedAutofillTypesAre(expected_types),
                               HasGenerationVote(false),
                               PasswordsWereRevealed(has_passwords_revealed)),
-                        _, Contains(autofill::PASSWORD), _, _));
+                        _, Contains(autofill::PASSWORD), _, _, nullptr));
         form_manager.Save();
 
         EXPECT_EQ(ASCIIToUTF16("p4ssword"), saved_result.password_value);
@@ -2864,7 +2867,7 @@
       .WillOnce(testing::DoAll(SaveArg<0>(&new_credentials),
                                SaveArgPointee<2>(&credentials_to_update)));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   form_manager.Save();
 
@@ -2916,7 +2919,7 @@
               Update(_, _, Pointee(IsEmpty()), nullptr))
       .WillOnce(testing::SaveArg<0>(&new_credentials));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   form_manager.Save();
 
@@ -2957,7 +2960,7 @@
               Update(_, _, Pointee(IsEmpty()), nullptr))
       .WillOnce(testing::SaveArg<0>(&new_credentials));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   form_manager.Save();
 
@@ -2993,8 +2996,9 @@
   EXPECT_CALL(MockFormSaver::Get(&form_manager), Save(_, _))
       .WillOnce(testing::SaveArg<0>(&new_credentials));
   // As the username is re-used, expect a username vote.
-  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, _, Contains(autofill::USERNAME), _, _));
+  EXPECT_CALL(
+      *client()->mock_driver()->mock_autofill_download_manager(),
+      StartUploadRequest(_, _, Contains(autofill::USERNAME), _, _, nullptr));
   form_manager.Save();
 
   EXPECT_EQ(credentials.password_value, new_credentials.password_value);
@@ -3168,16 +3172,16 @@
       autofill::FormStructure(saved_match()->form_data).FormSignatureAsStr();
 
   // First login vote.
-  EXPECT_CALL(
-      *client()->mock_driver()->mock_autofill_download_manager(),
-      StartUploadRequest(SignatureIsSameAs(submitted_form), _, _, _, _));
+  EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+              StartUploadRequest(SignatureIsSameAs(submitted_form), _, _, _, _,
+                                 nullptr));
   // Password change vote.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
               StartUploadRequest(AllOf(UploadedAutofillTypesAre(expected_types),
                                        HasGenerationVote(false),
                                        SignatureIsSameAs(*observed_form())),
                                  false, expected_available_field_types,
-                                 expected_login_signature, true));
+                                 expected_login_signature, true, nullptr));
 
   EXPECT_CALL(MockFormSaver::Get(&form_manager), Update(_, _, _, _));
   form_manager.Update(*saved_match());
@@ -3280,7 +3284,7 @@
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
               StartUploadRequest(
                   UploadedFieldPropertiesMasksAre(expected_field_properties),
-                  false, _, _, true));
+                  false, _, _, true, nullptr));
   form_manager.ProvisionallySave(submitted_form);
   form_manager.Save();
 }
@@ -3345,7 +3349,7 @@
       .WillOnce(testing::DoAll(SaveArg<0>(&new_credentials),
                                SaveArgPointee<2>(&credentials_to_update)));
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
   form_manager()->Save();
 
   EXPECT_TRUE(new_credentials.skip_zero_click);
@@ -3371,7 +3375,7 @@
 
   EXPECT_FALSE(form_manager()->IsNewLogin());
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, false, _, _, true));
+              StartUploadRequest(_, false, _, _, true, nullptr));
 
   base::UserActionTester tester;
   EXPECT_EQ(0, tester.GetActionCount("PasswordManager_LoginFollowingAutofill"));
@@ -3534,9 +3538,9 @@
       InSequence s;
       autofill::ServerFieldTypeSet field_types;
       field_types.insert(autofill::PASSWORD);
-      EXPECT_CALL(
-          *client()->mock_driver()->mock_autofill_download_manager(),
-          StartUploadRequest(_, false, field_types, std::string(), true));
+      EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                  StartUploadRequest(_, false, field_types, std::string(), true,
+                                     nullptr));
 
       EXPECT_CALL(
           *client()->mock_driver()->mock_autofill_download_manager(),
@@ -3545,7 +3549,7 @@
                                    HasGenerationVote(false),
                                    VoteTypesAre(expected_vote_types)),
                              false, expected_available_field_types,
-                             expected_login_signature, true));
+                             expected_login_signature, true, nullptr));
       form_manager.Save();
   }
 }
@@ -3565,7 +3569,7 @@
 
   // Don't expect any vote uploads.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(_, _, _, _, _))
+              StartUploadRequest(_, _, _, _, _, _))
       .Times(0);
 
   form_manager()->Save();
@@ -3610,10 +3614,10 @@
                                         Pointee(*saved_match()))),
                        Pointee(IsEmpty()), nullptr));
     EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-                StartUploadRequest(_, _, _, _, _));
-    EXPECT_CALL(
-        *client()->mock_driver()->mock_autofill_download_manager(),
-        StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _, _));
+                StartUploadRequest(_, _, _, _, _, nullptr));
+    EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
+                StartUploadRequest(_, _, Not(Contains(autofill::USERNAME)), _,
+                                   _, nullptr));
     form_manager()->Save();
   }
 }
@@ -4249,7 +4253,7 @@
     EXPECT_CALL(
         *client()->mock_driver()->mock_autofill_download_manager(),
         StartUploadRequest(SignatureIsSameAs(*test_case.stored_creds.front()),
-                           _, _, _, _))
+                           _, _, _, _, _))
         .Times(AtMost(1));
     // First login vote
     EXPECT_CALL(
@@ -4265,7 +4269,7 @@
             _,
             autofill::ServerFieldTypeSet(
                 {autofill::USERNAME, autofill::PASSWORD}),
-            _, true));
+            _, true, nullptr));
 
     form_manager()->Save();
 
@@ -4312,7 +4316,7 @@
     form_manager()->ProvisionallySave(submitted_form);
 
     EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-                StartUploadRequest(_, _, _, _, _))
+                StartUploadRequest(_, _, _, _, _, _))
         .Times(0);
 
     form_manager()->Save();
@@ -4357,7 +4361,7 @@
   // Unrelated vote
   EXPECT_CALL(
       *client()->mock_driver()->mock_autofill_download_manager(),
-      StartUploadRequest(SignatureIsSameAs(different_password), _, _, _, _))
+      StartUploadRequest(SignatureIsSameAs(different_password), _, _, _, _, _))
       .Times(AtMost(1));
   // First login vote
   EXPECT_CALL(
@@ -4370,7 +4374,8 @@
                      {ASCIIToUTF16("petname"), autofill::UNKNOWN_TYPE}})),
                 UploadedFieldPropertiesMasksAre(expected_field_properties),
                 VoteTypesAre(expected_vote_types)),
-          _, autofill::ServerFieldTypeSet({autofill::USERNAME}), _, true));
+          _, autofill::ServerFieldTypeSet({autofill::USERNAME}), _, true,
+          nullptr));
 
   form_manager()->Save();
 }
@@ -4419,7 +4424,8 @@
 
   // Unrelated vote.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _))
+              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _,
+                                 nullptr))
       .Times(1);
   // First login vote.
   EXPECT_CALL(
@@ -4435,7 +4441,7 @@
           _,
           autofill::ServerFieldTypeSet(
               {autofill::USERNAME, autofill::PASSWORD}),
-          _, true));
+          _, true, nullptr));
 
   form_manager()->Save();
 }
@@ -4479,7 +4485,8 @@
 
   // Unrelated vote.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _))
+              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _,
+                                 nullptr))
       .Times(1);
   // First login vote.
   EXPECT_CALL(
@@ -4495,7 +4502,7 @@
           _,
           autofill::ServerFieldTypeSet(
               {autofill::USERNAME, autofill::PASSWORD}),
-          _, true));
+          _, true, nullptr));
 
   form_manager()->Save();
 }
@@ -4546,7 +4553,8 @@
 
   // Unrelated vote.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _))
+              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _,
+                                 nullptr))
       .Times(1);
   // First login vote.
   EXPECT_CALL(
@@ -4556,7 +4564,8 @@
                 UploadedAutofillTypesAre(expected_votes),
                 UploadedFieldPropertiesMasksAre(expected_field_properties),
                 VoteTypesAre(expected_vote_types)),
-          _, autofill::ServerFieldTypeSet({autofill::PASSWORD}), _, true));
+          _, autofill::ServerFieldTypeSet({autofill::PASSWORD}), _, true,
+          nullptr));
 
   form_manager()->Save();
 }
@@ -4657,7 +4666,8 @@
 
   // Unrelated vote.
   EXPECT_CALL(*client()->mock_driver()->mock_autofill_download_manager(),
-              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _))
+              StartUploadRequest(SignatureIsSameAs(*saved_match()), _, _, _, _,
+                                 nullptr))
       .Times(1);
   // First login vote.
   EXPECT_CALL(
@@ -4670,7 +4680,7 @@
           _,
           autofill::ServerFieldTypeSet(
               {autofill::USERNAME, autofill::PASSWORD}),
-          _, true));
+          _, true, nullptr));
 
   form_manager()->Save();
 }
@@ -4693,7 +4703,7 @@
   EXPECT_CALL(
       *client()->mock_driver()->mock_autofill_download_manager(),
       StartUploadRequest(HasPasswordAttributesVote(true /* is_vote_expected */),
-                         _, _, _, _));
+                         _, _, _, _, nullptr));
   form_manager.Save();
 }
 
diff --git a/components/password_manager/core/browser/votes_uploader.cc b/components/password_manager/core/browser/votes_uploader.cc
index a06414a..b465722 100644
--- a/components/password_manager/core/browser/votes_uploader.cc
+++ b/components/password_manager/core/browser/votes_uploader.cc
@@ -306,7 +306,8 @@
 
   bool success = autofill_manager->download_manager()->StartUploadRequest(
       form_structure, false /* was_autofilled */, available_field_types,
-      login_form_signature, true /* observed_submission */);
+      login_form_signature, true /* observed_submission */,
+      nullptr /* prefs */);
 
   UMA_HISTOGRAM_BOOLEAN("PasswordGeneration.UploadStarted", success);
   return success;
@@ -354,7 +355,7 @@
 
   autofill_manager->download_manager()->StartUploadRequest(
       form_structure, false /* was_autofilled */, available_field_types,
-      std::string(), true /* observed_submission */);
+      std::string(), true /* observed_submission */, nullptr /* prefs */);
 }
 
 void VotesUploader::AddGeneratedVote(FormStructure* form_structure) {
diff --git a/components/password_manager/core/browser/votes_uploader_unittest.cc b/components/password_manager/core/browser/votes_uploader_unittest.cc
index ccad826d..ac451aa3 100644
--- a/components/password_manager/core/browser/votes_uploader_unittest.cc
+++ b/components/password_manager/core/browser/votes_uploader_unittest.cc
@@ -51,12 +51,13 @@
       autofill::AutofillDownloadManager::Observer* observer)
       : AutofillDownloadManager(driver, observer) {}
 
-  MOCK_METHOD5(StartUploadRequest,
+  MOCK_METHOD6(StartUploadRequest,
                bool(const FormStructure&,
                     bool,
                     const ServerFieldTypeSet&,
                     const std::string&,
-                    bool));
+                    bool,
+                    PrefService*));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockAutofillDownloadManager);
@@ -105,7 +106,8 @@
     EXPECT_CALL(client_, GetAutofillManagerForMainFrame())
         .WillRepeatedly(Return(&mock_autofill_manager_));
 
-    ON_CALL(*mock_autofill_download_manager_, StartUploadRequest(_, _, _, _, _))
+    ON_CALL(*mock_autofill_download_manager_,
+            StartUploadRequest(_, _, _, _, _, _))
         .WillByDefault(Return(true));
 
     // Create |fields| in |form_to_upload_| and |submitted_form_|. Only |name|
@@ -156,12 +158,13 @@
   PasswordForm::SubmissionIndicatorEvent expected_submission_event =
       PasswordForm::SubmissionIndicatorEvent::HTML_FORM_SUBMISSION;
 
-  EXPECT_CALL(*mock_autofill_download_manager_,
-              StartUploadRequest(
-                  AllOf(SignatureIsSameAs(form_to_upload_),
-                        UploadedAutofillTypesAre(expected_types),
-                        SubmissionEventIsSameAs(expected_submission_event)),
-                  false, expected_field_types, login_form_signature_, true));
+  EXPECT_CALL(
+      *mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(form_to_upload_),
+                UploadedAutofillTypesAre(expected_types),
+                SubmissionEventIsSameAs(expected_submission_event)),
+          false, expected_field_types, login_form_signature_, true, nullptr));
 
   EXPECT_TRUE(votes_uploader.UploadPasswordVote(
       form_to_upload_, submitted_form_, NEW_PASSWORD, login_form_signature_));
@@ -184,7 +187,8 @@
   EXPECT_CALL(*mock_autofill_download_manager_,
               StartUploadRequest(
                   SubmissionEventIsSameAs(expected_submission_event), false,
-                  expected_field_types, login_form_signature_, true));
+                  expected_field_types, login_form_signature_, true,
+                  /* pref_service= */ nullptr));
 
   EXPECT_TRUE(votes_uploader.UploadPasswordVote(
       form_to_upload_, submitted_form_, PASSWORD, login_form_signature_));
diff --git a/components/resources/components_resources.grd b/components/resources/components_resources.grd
index b1359f5..4623e36e 100644
--- a/components/resources/components_resources.grd
+++ b/components/resources/components_resources.grd
@@ -16,11 +16,6 @@
       <part file="net_log_resources.grdp" />
       <part file="neterror_resources.grdp" />
       <part file="ntp_tiles_resources.grdp" />
-      <if expr="is_win">
-        <part file="nux_email.grdp" />
-        <part file="nux_google_apps.grdp" />
-        <part file="nux_set_as_default.grdp" />
-      </if>
       <part file="offline_pages_resources.grdp" />
       <part file="password_manager_internals_resources.grdp" />
       <part file="printing_resources.grdp" />
diff --git a/components/resources/nux_email.grdp b/components/resources/nux_email.grdp
deleted file mode 100644
index 437612f..0000000
--- a/components/resources/nux_email.grdp
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <include name="IDR_NUX_EMAIL_AOL_1X" file="../nux/email/resources/aol_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_AOL_2X" file="../nux/email/resources/aol_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_CHOOSER_HTML" file="../nux/email/resources/email_chooser.html" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_CHOOSER_JS" file="../nux/email/resources/email_chooser.js" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_GMAIL_1X" file="../nux/email/resources/gmail_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_GMAIL_2X" file="../nux/email/resources/gmail_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_HTML" file="../nux/email/resources/nux_email.html" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_ICLOUD_1X" file="../nux/email/resources/icloud_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_ICLOUD_2X" file="../nux/email/resources/icloud_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_JS" file="../nux/email/resources/nux_email.js" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_OUTLOOK_1X" file="../nux/email/resources/outlook_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_OUTLOOK_2X" file="../nux/email/resources/outlook_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_PROXY_HTML" file="../nux/email/resources/nux_email_proxy.html" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_PROXY_JS" file="../nux/email/resources/nux_email_proxy.js" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_YAHOO_1X" file="../nux/email/resources/yahoo_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_EMAIL_YAHOO_2X" file="../nux/email/resources/yahoo_2x.png" type="BINDATA" />
-</grit-part>
diff --git a/components/resources/nux_google_apps.grdp b/components/resources/nux_google_apps.grdp
deleted file mode 100644
index 5e74f85..0000000
--- a/components/resources/nux_google_apps.grdp
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <include name="IDR_NUX_GOOGLE_APPS_HTML" file="../nux/google_apps/resources/nux_google_apps.html" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_JS" file="../nux/google_apps/resources/nux_google_apps.js" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_PROXY_HTML" file="../nux/google_apps/resources/nux_google_apps_proxy.html" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_PROXY_JS" file="../nux/google_apps/resources/nux_google_apps_proxy.js" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_CHOOSER_HTML" file="../nux/google_apps/resources/apps_chooser.html" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_CHOOSER_JS" file="../nux/google_apps/resources/apps_chooser.js" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_1X" file="../nux/google_apps/resources/chrome_store_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_CHROME_STORE_2X" file="../nux/google_apps/resources/chrome_store_24dp_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_GMAIL_1X" file="../nux/google_apps/resources/gmail_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_GMAIL_2X" file="../nux/google_apps/resources/gmail_24dp_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_MAPS_1X" file="../nux/google_apps/resources/maps_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_MAPS_2X" file="../nux/google_apps/resources/maps_24dp_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_NEWS_1X" file="../nux/google_apps/resources/news_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_NEWS_2X" file="../nux/google_apps/resources/news_24dp_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_1X" file="../nux/google_apps/resources/translate_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_TRANSLATE_2X" file="../nux/google_apps/resources/translate_24dp_2x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_1X" file="../nux/google_apps/resources/youtube_24dp_1x.png" type="BINDATA" />
-  <include name="IDR_NUX_GOOGLE_APPS_YOUTUBE_2X" file="../nux/google_apps/resources/youtube_24dp_2x.png" type="BINDATA" />
-</grit-part>
diff --git a/components/resources/nux_set_as_default.grdp b/components/resources/nux_set_as_default.grdp
deleted file mode 100644
index e37a8fd..0000000
--- a/components/resources/nux_set_as_default.grdp
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <include name="IDR_NUX_SET_AS_DEFAULT_HTML" file="../nux/set_as_default/resources/nux_set_as_default.html" type="BINDATA" />
-  <include name="IDR_NUX_SET_AS_DEFAULT_JS" file="../nux/set_as_default/resources/nux_set_as_default.js" type="BINDATA" />
-  <include name="IDR_NUX_SET_AS_DEFAULT_PROXY_HTML" file="../nux/set_as_default/resources/nux_set_as_default_proxy.html" type="BINDATA" />
-  <include name="IDR_NUX_SET_AS_DEFAULT_PROXY_JS" file="../nux/set_as_default/resources/nux_set_as_default_proxy.js" type="BINDATA" />
-</grit-part>
diff --git a/components/safe_browsing/password_protection/password_protection_service.cc b/components/safe_browsing/password_protection/password_protection_service.cc
index d899d51..b6b2985f 100644
--- a/components/safe_browsing/password_protection/password_protection_service.cc
+++ b/components/safe_browsing/password_protection/password_protection_service.cc
@@ -676,7 +676,7 @@
     int* out_verdict_received_time,
     LoginReputationClientResponse* out_verdict) {
   std::string serialized_verdict_proto;
-  if (!verdict_entry || !out_verdict)
+  if (!verdict_entry || !verdict_entry->is_dict() || !out_verdict)
     return false;
   base::Value* cache_creation_time_value =
       verdict_entry->FindKey(kCacheCreationTime);
diff --git a/components/services/heap_profiling/public/cpp/allocator_shim.cc b/components/services/heap_profiling/public/cpp/allocator_shim.cc
index ef76601..7c36719 100644
--- a/components/services/heap_profiling/public/cpp/allocator_shim.cc
+++ b/components/services/heap_profiling/public/cpp/allocator_shim.cc
@@ -140,8 +140,7 @@
 class ScopedAllowAlloc {
  public:
   ScopedAllowAlloc()
-      : allowed_(LIKELY(CanEnterAllocatorShim()) &&
-                 LIKELY(!base::ThreadLocalStorage::HasBeenDestroyed())) {
+      : allowed_(LIKELY(CanEnterAllocatorShim()) && !HasTLSBeenDestroyed()) {
     if (allowed_)
       SetEnteringAllocatorShim(true);
   }
@@ -151,6 +150,10 @@
   }
   explicit operator bool() const { return allowed_; }
 
+  static inline bool HasTLSBeenDestroyed() {
+    return UNLIKELY(base::ThreadLocalStorage::HasBeenDestroyed());
+  }
+
  private:
   const bool allowed_;
 };
@@ -735,7 +738,8 @@
       break;
     case mojom::StackMode::NATIVE_WITH_THREAD_NAMES:
     case mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES:
-      AllocationContextTracker::SetCaptureMode(CaptureMode::DISABLED);
+      // This would track task contexts only.
+      AllocationContextTracker::SetCaptureMode(CaptureMode::NATIVE_STACK);
       break;
   }
 
@@ -778,6 +782,9 @@
 
 void SerializeFramesFromAllocationContext(FrameSerializer* serializer,
                                           const char** context) {
+  // Allocation context is tracked in TLS. Return nothing if TLS was destroyed.
+  if (ScopedAllowAlloc::HasTLSBeenDestroyed())
+    return;
   auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
   if (!tracker)
     return;
@@ -791,7 +798,8 @@
     *context = allocation_context.type_name;
 }
 
-void SerializeFramesFromBacktrace(FrameSerializer* serializer) {
+void SerializeFramesFromBacktrace(FrameSerializer* serializer,
+                                  const char** context) {
   // Skip 3 top frames related to the profiler itself, e.g.:
   //   base::debug::StackTrace::StackTrace
   //   heap_profiling::RecordAndSendAlloc
@@ -824,6 +832,13 @@
     const char* thread_name = GetOrSetThreadName();
     serializer->AddCString(thread_name);
   }
+
+  if (!*context && !ScopedAllowAlloc::HasTLSBeenDestroyed()) {
+    const auto* tracker =
+        AllocationContextTracker::GetInstanceForCurrentThread();
+    if (tracker)
+      *context = tracker->TaskContext();
+  }
 }
 
 void AllocatorShimLogAlloc(AllocatorType type,
@@ -895,7 +910,7 @@
       capture_mode == CaptureMode::MIXED_STACK) {
     SerializeFramesFromAllocationContext(&serializer, &context);
   } else {
-    SerializeFramesFromBacktrace(&serializer);
+    SerializeFramesFromBacktrace(&serializer, &context);
   }
 
   size_t context_len = context ? strnlen(context, kMaxContextLen) : 0;
diff --git a/components/services/heap_profiling/public/cpp/allocator_shim.h b/components/services/heap_profiling/public/cpp/allocator_shim.h
index f4931e4..c7582e4 100644
--- a/components/services/heap_profiling/public/cpp/allocator_shim.h
+++ b/components/services/heap_profiling/public/cpp/allocator_shim.h
@@ -60,13 +60,15 @@
                             mojom::ProfilingParamsPtr params);
 
 // Creates allocation info record, populates it with current call stack,
-// thread name, allocator type and sends out to the client.
+// thread name, allocator type and sends out to the client. Safe to call this
+// method after TLS is destroyed.
 void RecordAndSendAlloc(AllocatorType type,
                         void* address,
                         size_t sz,
                         const char* context);
 
-// Creates the record for free operation and sends it out to the client.
+// Creates the record for free operation and sends it out to the client. Safe
+// to call this method after TLS is destroyed.
 void RecordAndSendFree(void* address);
 
 // Exists for testing only.
diff --git a/components/signin/core/browser/fake_signin_manager.cc b/components/signin/core/browser/fake_signin_manager.cc
index 916756a..022c2a5 100644
--- a/components/signin/core/browser/fake_signin_manager.cc
+++ b/components/signin/core/browser/fake_signin_manager.cc
@@ -103,7 +103,7 @@
 }
 
 void FakeSigninManager::ForceSignOut() {
-  ProhibitSignout(false);
+  // SigninClients should always allow sign-out for SIGNOUT_TEST.
   SignOut(signin_metrics::SIGNOUT_TEST,
           signin_metrics::SignoutDelete::IGNORE_METRIC);
 }
@@ -113,10 +113,11 @@
     observer.GoogleSigninFailed(error);
 }
 
-void FakeSigninManager::DoSignOut(
+void FakeSigninManager::OnSignoutDecisionReached(
     signin_metrics::ProfileSignout signout_source_metric,
     signin_metrics::SignoutDelete signout_delete_metric,
-    RemoveAccountsOption remove_option) {
+    RemoveAccountsOption remove_option,
+    SigninClient::SignoutDecision signout_decision) {
   if (!IsAuthenticated()) {
     if (AuthInProgress()) {
       // If the user is in the process of signing in, then treat a call to
@@ -133,8 +134,11 @@
     return;
   }
 
-  if (IsSignoutProhibited())
+  // TODO(crbug.com/887756): Consider moving this higher up, or document why
+  // the above blocks are exempt from the |signout_decision| early return.
+  if (signout_decision == SigninClient::SignoutDecision::DISALLOW_SIGNOUT)
     return;
+
   set_auth_in_progress(std::string());
   set_password(std::string());
   AccountInfo account_info = GetAuthenticatedAccountInfo();
diff --git a/components/signin/core/browser/fake_signin_manager.h b/components/signin/core/browser/fake_signin_manager.h
index 41dc74c..a7314d2 100644
--- a/components/signin/core/browser/fake_signin_manager.h
+++ b/components/signin/core/browser/fake_signin_manager.h
@@ -76,9 +76,11 @@
   void CompletePendingSignin() override;
 
  protected:
-  void DoSignOut(signin_metrics::ProfileSignout signout_source_metric,
-                 signin_metrics::SignoutDelete signout_delete_metric,
-                 RemoveAccountsOption remove_option) override;
+  void OnSignoutDecisionReached(
+      signin_metrics::ProfileSignout signout_source_metric,
+      signin_metrics::SignoutDelete signout_delete_metric,
+      RemoveAccountsOption remove_option,
+      SigninClient::SignoutDecision signout_decision) override;
 
   // Username specified in StartSignInWithRefreshToken() call.
   std::string username_;
diff --git a/components/signin/core/browser/signin_client.cc b/components/signin/core/browser/signin_client.cc
index a17afa9..b6741ad 100644
--- a/components/signin/core/browser/signin_client.cc
+++ b/components/signin/core/browser/signin_client.cc
@@ -5,9 +5,10 @@
 #include "components/signin/core/browser/signin_client.h"
 
 void SigninClient::PreSignOut(
-    const base::Callback<void()>& sign_out,
+    base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
     signin_metrics::ProfileSignout signout_source_metric) {
-  sign_out.Run();
+  // Allow sign out to continue.
+  std::move(on_signout_decision_reached).Run(SignoutDecision::ALLOW_SIGNOUT);
 }
 
 void SigninClient::PreGaiaLogout(base::OnceClosure callback) {
diff --git a/components/signin/core/browser/signin_client.h b/components/signin/core/browser/signin_client.h
index dec60e6..7ded61b 100644
--- a/components/signin/core/browser/signin_client.h
+++ b/components/signin/core/browser/signin_client.h
@@ -36,12 +36,10 @@
 // embedder.
 class SigninClient : public KeyedService {
  public:
-  ~SigninClient() override = default;
+  // Argument to PreSignOut() callback, indicating client decision.
+  enum class SignoutDecision { ALLOW_SIGNOUT, DISALLOW_SIGNOUT };
 
-  // Called before Google signout started, call |sign_out| to start the sign out
-  // process.
-  virtual void PreSignOut(const base::Callback<void()>& sign_out,
-                          signin_metrics::ProfileSignout signout_source_metric);
+  ~SigninClient() override = default;
 
   // Perform Chrome-specific sign out. This happens when user signs out.
   virtual void OnSignedOut() = 0;
@@ -77,6 +75,14 @@
                             const std::string& username,
                             const std::string& password) {}
 
+  // Called before Google sign-out started. Implementers must run the
+  // |on_signout_decision_reached|, passing a SignoutDecision to allow/disallow
+  // sign-out to continue. When to disallow sign-out is implementation specific.
+  // Sign-out is always allowed by default.
+  virtual void PreSignOut(
+      base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
+      signin_metrics::ProfileSignout signout_source_metric);
+
   // Called before calling the GAIA logout endpoint.
   // For iOS, cookies should be cleaned up.
   virtual void PreGaiaLogout(base::OnceClosure callback);
diff --git a/components/signin/core/browser/signin_manager.cc b/components/signin/core/browser/signin_manager.cc
index c27e9797..945a0d7 100644
--- a/components/signin/core/browser/signin_manager.cc
+++ b/components/signin/core/browser/signin_manager.cc
@@ -15,7 +15,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
-#include "components/signin/core/browser/signin_client.h"
 #include "components/signin/core/browser/signin_internals_util.h"
 #include "components/signin/core/browser/signin_metrics.h"
 #include "components/signin/core/browser/signin_pref_names.h"
@@ -37,7 +36,6 @@
     : SigninManagerBase(client,
                         account_tracker_service,
                         signin_error_controller),
-      prohibit_signout_(false),
       type_(SIGNIN_TYPE_NONE),
       client_(client),
       token_service_(token_service),
@@ -185,15 +183,17 @@
     signin_metrics::SignoutDelete signout_delete_metric,
     RemoveAccountsOption remove_option) {
   client_->PreSignOut(
-      base::Bind(&SigninManager::DoSignOut, base::Unretained(this),
-                 signout_source_metric, signout_delete_metric, remove_option),
+      base::BindOnce(&SigninManager::OnSignoutDecisionReached,
+                     base::Unretained(this), signout_source_metric,
+                     signout_delete_metric, remove_option),
       signout_source_metric);
 }
 
-void SigninManager::DoSignOut(
+void SigninManager::OnSignoutDecisionReached(
     signin_metrics::ProfileSignout signout_source_metric,
     signin_metrics::SignoutDelete signout_delete_metric,
-    RemoveAccountsOption remove_option) {
+    RemoveAccountsOption remove_option,
+    SigninClient::SignoutDecision signout_decision) {
   DCHECK(IsInitialized());
 
   signin_metrics::LogSignout(signout_source_metric, signout_delete_metric);
@@ -213,8 +213,10 @@
     return;
   }
 
-  if (IsSignoutProhibited()) {
-    DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
+  // TODO(crbug.com/887756): Consider moving this higher up, or document why
+  // the above blocks are exempt from the |signout_decision| early return.
+  if (signout_decision == SigninClient::SignoutDecision::DISALLOW_SIGNOUT) {
+    DVLOG(1) << "Ignoring attempt to sign out while signout disallowed";
     return;
   }
 
@@ -504,9 +506,3 @@
     token_service_->RemoveObserver(this);
   }
 }
-
-void SigninManager::ProhibitSignout(bool prohibit_signout) {
-  prohibit_signout_ = prohibit_signout;
-}
-
-bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }
diff --git a/components/signin/core/browser/signin_manager.h b/components/signin/core/browser/signin_manager.h
index 25f7920..0c9559a 100644
--- a/components/signin/core/browser/signin_manager.h
+++ b/components/signin/core/browser/signin_manager.h
@@ -38,6 +38,7 @@
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/profile_management_switches.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_client.h"
 #include "components/signin/core/browser/signin_internals_util.h"
 #include "components/signin/core/browser/signin_manager_base.h"
 #include "components/signin/core/browser/signin_metrics.h"
@@ -185,22 +186,13 @@
   // a new account).
   static void DisableOneClickSignIn(PrefService* prefs);
 
-  // Tells the SigninManager whether to prohibit signout for this profile.
-  // If |prohibit_signout| is true, then signout will be prohibited.
-  void ProhibitSignout(bool prohibit_signout);
-
-  // If true, signout is prohibited for this profile (calls to SignOut() are
-  // ignored).
-  bool IsSignoutProhibited() const;
-
  protected:
-  // Flag saying whether signing out is allowed.
-  bool prohibit_signout_;
-
   // The sign out process which is started by SigninClient::PreSignOut()
-  virtual void DoSignOut(signin_metrics::ProfileSignout signout_source_metric,
-                         signin_metrics::SignoutDelete signout_delete_metric,
-                         RemoveAccountsOption remove_option);
+  virtual void OnSignoutDecisionReached(
+      signin_metrics::ProfileSignout signout_source_metric,
+      signin_metrics::SignoutDelete signout_delete_metric,
+      RemoveAccountsOption remove_option,
+      SigninClient::SignoutDecision signout_decision);
 
  private:
   enum SigninType {
diff --git a/components/signin/core/browser/signin_manager_unittest.cc b/components/signin/core/browser/signin_manager_unittest.cc
index c47fefa..2a2b0b4d 100644
--- a/components/signin/core/browser/signin_manager_unittest.cc
+++ b/components/signin/core/browser/signin_manager_unittest.cc
@@ -335,11 +335,11 @@
   EXPECT_TRUE(manager_->GetAuthenticatedAccountId().empty());
 
   manager_->SetAuthenticatedAccountInfo("gaia_id", "user@gmail.com");
-  manager_->ProhibitSignout(true);
+  signin_client()->set_is_signout_allowed(false);
   manager_->SignOut(signin_metrics::SIGNOUT_TEST,
                     signin_metrics::SignoutDelete::IGNORE_METRIC);
   EXPECT_TRUE(manager_->IsAuthenticated());
-  manager_->ProhibitSignout(false);
+  signin_client()->set_is_signout_allowed(true);
   manager_->SignOut(signin_metrics::SIGNOUT_TEST,
                     signin_metrics::SignoutDelete::IGNORE_METRIC);
   EXPECT_FALSE(manager_->IsAuthenticated());
diff --git a/components/signin/core/browser/signin_metrics.h b/components/signin/core/browser/signin_metrics.h
index d1649df..a0ca17ba 100644
--- a/components/signin/core/browser/signin_metrics.h
+++ b/components/signin/core/browser/signin_metrics.h
@@ -69,7 +69,7 @@
   HISTOGRAM_REJECTED,
   // The user pressed the X button to dismiss the infobar this time.
   HISTOGRAM_DISMISSED,
-  // The user completely ignored the infoar.  Either they navigated away, or
+  // The user completely ignored the infobar.  Either they navigated away, or
   // they used the page as is.
   HISTOGRAM_IGNORED,
   // The user clicked on the learn more link in the infobar.
diff --git a/components/signin/core/browser/test_signin_client.cc b/components/signin/core/browser/test_signin_client.cc
index b52e930..3a0cb77 100644
--- a/components/signin/core/browser/test_signin_client.cc
+++ b/components/signin/core/browser/test_signin_client.cc
@@ -49,7 +49,8 @@
               &test_url_loader_factory_)),
       pref_service_(pref_service),
       are_signin_cookies_allowed_(true),
-      network_calls_delayed_(false) {}
+      network_calls_delayed_(false),
+      is_signout_allowed_(true) {}
 
 TestSigninClient::~TestSigninClient() {}
 
@@ -75,6 +76,14 @@
   signed_in_password_ = password;
 }
 
+void TestSigninClient::PreSignOut(
+    base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
+    signin_metrics::ProfileSignout signout_source_metric) {
+  std::move(on_signout_decision_reached)
+      .Run(is_signout_allowed_ ? SignoutDecision::ALLOW_SIGNOUT
+                               : SignoutDecision::DISALLOW_SIGNOUT);
+}
+
 scoped_refptr<network::SharedURLLoaderFactory>
 TestSigninClient::GetURLLoaderFactory() {
   return shared_factory_;
diff --git a/components/signin/core/browser/test_signin_client.h b/components/signin/core/browser/test_signin_client.h
index 5b7ba67c..077588b 100644
--- a/components/signin/core/browser/test_signin_client.h
+++ b/components/signin/core/browser/test_signin_client.h
@@ -46,6 +46,12 @@
                     const std::string& username,
                     const std::string& password) override;
 
+  // Allow or disallow continuation of sign-out depending on value of
+  // |is_signout_allowed_|;
+  void PreSignOut(
+      base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
+      signin_metrics::ProfileSignout signout_source_metric) override;
+
   std::string get_signed_in_password() { return signed_in_password_; }
 
   // Returns the empty string.
@@ -65,6 +71,8 @@
     are_signin_cookies_allowed_ = value;
   }
 
+  void set_is_signout_allowed(bool value) { is_signout_allowed_ = value; }
+
   // When |value| is true, network calls posted through DelayNetworkCall() are
   // delayed indefinitely.
   // When |value| is false, all pending calls are unblocked, and new calls are
@@ -95,6 +103,7 @@
   std::unique_ptr<network::mojom::CookieManager> cookie_manager_;
   bool are_signin_cookies_allowed_;
   bool network_calls_delayed_;
+  bool is_signout_allowed_;
   std::vector<base::OnceClosure> delayed_network_calls_;
 
   // Pointer to be filled by PostSignedIn.
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index 6a03ed5..4eba01d 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -29,6 +29,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/console_message_level.h"
 #include "net/base/net_errors.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 namespace subresource_filter {
 
@@ -122,8 +123,10 @@
       transferred_ad_frame || base::ContainsKey(ad_frames_, frame_host);
   DCHECK(!is_ad_subframe || !navigation_handle->IsInMainFrame());
 
-  frame_host->Send(new SubresourceFilterMsg_ActivateForNextCommittedLoad(
-      frame_host->GetRoutingID(), filter->activation_state(), is_ad_subframe));
+  mojom::SubresourceFilterAgentAssociatedPtr agent;
+  frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
+  agent->ActivateForNextCommittedLoad(filter->activation_state().Clone(),
+                                      is_ad_subframe);
 }
 
 void ContentSubresourceFilterThrottleManager::DidFinishNavigation(
@@ -196,18 +199,6 @@
   statistics_->OnDidFinishLoad();
 }
 
-bool ContentSubresourceFilterThrottleManager::OnMessageReceived(
-    const IPC::Message& message,
-    content::RenderFrameHost* render_frame_host) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(ContentSubresourceFilterThrottleManager, message)
-    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_DocumentLoadStatistics,
-                        OnDocumentLoadStatistics)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
-}
-
 // Sets the desired page-level |activation_state| for the currently ongoing
 // page load, identified by its main-frame |navigation_handle|. If this method
 // is not called for a main-frame navigation, the default behavior is no
@@ -382,12 +373,6 @@
   }
 }
 
-void ContentSubresourceFilterThrottleManager::OnDocumentLoadStatistics(
-    const DocumentLoadStatistics& statistics) {
-  if (statistics_)
-    statistics_->OnDocumentLoadStatistics(statistics);
-}
-
 void ContentSubresourceFilterThrottleManager::OnFrameIsAdSubframe(
     content::RenderFrameHost* render_frame_host) {
   DCHECK(render_frame_host);
@@ -405,6 +390,12 @@
   OnFrameIsAdSubframe(binding_.GetCurrentTargetFrame());
 }
 
+void ContentSubresourceFilterThrottleManager::SetDocumentLoadStatistics(
+    mojom::DocumentLoadStatisticsPtr statistics) {
+  if (statistics_)
+    statistics_->OnDocumentLoadStatistics(*statistics);
+}
+
 void ContentSubresourceFilterThrottleManager::MaybeActivateSubframeSpecialUrls(
     content::NavigationHandle* navigation_handle) {
   if (navigation_handle->IsInMainFrame())
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
index 3d94617..9bb8cf1 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
@@ -28,10 +28,6 @@
 class RenderFrameHost;
 }  // namespace content
 
-namespace IPC {
-class Message;
-}  // namespace IPC
-
 namespace subresource_filter {
 
 class AsyncDocumentSubresourceFilter;
@@ -39,7 +35,6 @@
 class PageLoadStatistics;
 class SubresourceFilterObserverManager;
 class SubresourceFilterClient;
-struct DocumentLoadStatistics;
 
 // The ContentSubresourceFilterThrottleManager manages NavigationThrottles in
 // order to calculate frame activation states and subframe navigation filtering,
@@ -96,8 +91,6 @@
       content::NavigationHandle* navigation_handle) override;
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override;
-  bool OnMessageReceived(const IPC::Message& message,
-                         content::RenderFrameHost* render_frame_host) override;
 
   // SubresourceFilterObserver:
   void OnSubresourceFilterGoingAway() override;
@@ -137,8 +130,6 @@
   VerifiedRuleset::Handle* EnsureRulesetHandle();
   void DestroyRulesetHandleIfNoLongerUsed();
 
-  void OnDocumentLoadStatistics(const DocumentLoadStatistics& statistics);
-
   // Registers |render_frame_host| as an ad frame. If the frame later moves to
   // a new process its RenderHost will be told that it's an ad.
   void OnFrameIsAdSubframe(content::RenderFrameHost* render_frame_host);
@@ -146,6 +137,8 @@
   // mojom::SubresourceFilterHost:
   void DidDisallowFirstSubresource() override;
   void FrameIsAdSubframe() override;
+  void SetDocumentLoadStatistics(
+      mojom::DocumentLoadStatisticsPtr statistics) override;
 
   // Adds the navigation's RenderFrameHost to activated_frame_hosts_ if it is a
   // special navigation which did not go through navigation throttles and its
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
index d994a51d..f6c8551 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
@@ -34,6 +34,7 @@
 #include "content/public/test/test_utils.h"
 #include "net/base/net_errors.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "url/url_constants.h"
 
 namespace subresource_filter {
@@ -54,6 +55,44 @@
   WILL_PROCESS_RESPONSE,
 };
 
+class FakeSubresourceFilterAgent : public mojom::SubresourceFilterAgent {
+ public:
+  FakeSubresourceFilterAgent() : binding_(this) {}
+  ~FakeSubresourceFilterAgent() override = default;
+
+  void OnSubresourceFilterAgentRequest(
+      mojo::ScopedInterfaceEndpointHandle handle) {
+    binding_.Bind(
+        mojo::AssociatedInterfaceRequest<mojom::SubresourceFilterAgent>(
+            std::move(handle)));
+  }
+
+  // mojom::SubresourceFilterAgent:
+  void ActivateForNextCommittedLoad(mojom::ActivationStatePtr activation_state,
+                                    bool is_ad_subframe) override {
+    last_activation_ = std::move(activation_state);
+    is_ad_subframe_ = is_ad_subframe;
+  }
+
+  // These methods reset state back to default when they are called.
+  bool LastAdSubframe() {
+    bool is_ad_subframe = is_ad_subframe_;
+    is_ad_subframe_ = false;
+    return is_ad_subframe;
+  }
+  bool LastActivated() {
+    bool activated = last_activation_ && last_activation_->activation_level !=
+                                             mojom::ActivationLevel::kDisabled;
+    last_activation_.reset();
+    return activated;
+  }
+
+ private:
+  mojom::ActivationStatePtr last_activation_;
+  bool is_ad_subframe_ = false;
+  mojo::AssociatedBinding<mojom::SubresourceFilterAgent> binding_;
+};
+
 // Simple throttle that sends page-level activation to the manager for a
 // specific set of URLs.
 class MockPageStateActivationThrottle : public content::NavigationThrottle {
@@ -124,6 +163,9 @@
   // content::RenderViewHostTestHarness:
   void SetUp() override {
     content::RenderViewHostTestHarness::SetUp();
+    content::WebContents* web_contents =
+        RenderViewHostTestHarness::web_contents();
+    CreateAgentForHost(web_contents->GetMainFrame());
 
     NavigateAndCommit(GURL("https://example.first"));
 
@@ -147,9 +189,8 @@
 
     throttle_manager_ =
         std::make_unique<ContentSubresourceFilterThrottleManager>(
-            this, dealer_handle_.get(),
-            RenderViewHostTestHarness::web_contents());
-    Observe(RenderViewHostTestHarness::web_contents());
+            this, dealer_handle_.get(), web_contents);
+    Observe(web_contents);
   }
 
   void TearDown() override {
@@ -162,21 +203,13 @@
   void ExpectActivationSignalForFrame(content::RenderFrameHost* rfh,
                                       bool expect_activation,
                                       bool expect_is_ad_subframe = false) {
-    content::MockRenderProcessHost* render_process_host =
-        static_cast<content::MockRenderProcessHost*>(rfh->GetProcess());
-    const IPC::Message* message =
-        render_process_host->sink().GetFirstMessageMatching(
-            SubresourceFilterMsg_ActivateForNextCommittedLoad::ID);
-    ASSERT_EQ(expect_activation, !!message);
-    if (expect_activation) {
-      std::tuple<mojom::ActivationState, bool> args;
-      SubresourceFilterMsg_ActivateForNextCommittedLoad::Read(message, &args);
-      mojom::ActivationLevel level = std::get<0>(args).activation_level;
-      EXPECT_NE(mojom::ActivationLevel::kDisabled, level);
-      bool is_ad_subframe = std::get<1>(args);
-      EXPECT_EQ(expect_is_ad_subframe, is_ad_subframe);
-    }
-    render_process_host->sink().ClearMessages();
+    // In some cases we need to verify that messages were _not_ sent, in which
+    // case using a Wait() idiom would cause hangs. RunUntilIdle instead to
+    // ensure mojo calls make it to the fake agent.
+    base::RunLoop().RunUntilIdle();
+    FakeSubresourceFilterAgent* agent = agent_map_[rfh].get();
+    EXPECT_EQ(expect_activation, agent->LastActivated());
+    EXPECT_EQ(expect_is_ad_subframe, agent->LastAdSubframe());
   }
 
   // Helper methods:
@@ -262,6 +295,14 @@
 
  protected:
   // content::WebContentsObserver
+  void RenderFrameCreated(content::RenderFrameHost* new_host) override {
+    CreateAgentForHost(new_host);
+  }
+
+  void FrameDeleted(content::RenderFrameHost* host) override {
+    agent_map_.erase(host);
+  }
+
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override {
     if (navigation_handle->IsSameDocument())
@@ -282,6 +323,16 @@
     }
   }
 
+  void CreateAgentForHost(content::RenderFrameHost* host) {
+    auto new_agent = std::make_unique<FakeSubresourceFilterAgent>();
+    host->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+        mojom::SubresourceFilterAgent::Name_,
+        base::BindRepeating(
+            &FakeSubresourceFilterAgent::OnSubresourceFilterAgentRequest,
+            base::Unretained(new_agent.get())));
+    agent_map_[host] = std::move(new_agent);
+  }
+
   // SubresourceFilterClient:
   void ShowNotification() override { ++disallowed_notification_count_; }
   mojom::ActivationLevel OnPageActivationComputed(
@@ -303,6 +354,10 @@
 
   std::unique_ptr<ContentSubresourceFilterThrottleManager> throttle_manager_;
 
+  std::map<content::RenderFrameHost*,
+           std::unique_ptr<FakeSubresourceFilterAgent>>
+      agent_map_;
+
   std::unique_ptr<content::NavigationSimulator> navigation_simulator_;
 
   // Incremented on every OnFirstSubresourceLoadDisallowed call.
@@ -311,7 +366,7 @@
   DISALLOW_COPY_AND_ASSIGN(ContentSubresourceFilterThrottleManagerTest);
 };
 
-INSTANTIATE_TEST_CASE_P(PageActivationNotificationTiming,
+INSTANTIATE_TEST_CASE_P(,
                         ContentSubresourceFilterThrottleManagerTest,
                         ::testing::Values(WILL_START_REQUEST,
                                           WILL_PROCESS_RESPONSE));
@@ -739,18 +794,13 @@
                                  false /* is_ad_subframe */);
 
   // A disallowed subframe navigation should be successfully filtered.
-  content::RenderFrameHost* subframe = CreateSubframeWithTestNavigation(
+  CreateSubframeWithTestNavigation(
       GURL("https://www.example.com/disallowed.html"), main_rfh());
 
   SimulateStartAndExpectResult(
       content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE);
 
   EXPECT_EQ(1, disallowed_notification_count());
-
-  // Since the IPC is not actually sent, is_ad_subframe is not really checked
-  // but adding it here since we do tag even if its not a dryrun scenario.
-  ExpectActivationSignalForFrame(subframe, false /* expect_activation */,
-                                 true /* is_ad_subframe */);
 }
 
 // If the RenderFrame determines that the frame is an ad, then any navigation
@@ -848,7 +898,7 @@
   SimulateStartAndExpectResult(content::NavigationThrottle::PROCEED);
   grandchild_frame =
       SimulateCommitAndExpectResult(content::NavigationThrottle::PROCEED);
-  ExpectActivationSignalForFrame(subframe, true /* expect_activation */,
+  ExpectActivationSignalForFrame(grandchild_frame, true /* expect_activation */,
                                  true /* is_ad_subframe */);
   EXPECT_TRUE(
       throttle_manager()->IsFrameTaggedAsAdForTesting(grandchild_frame));
diff --git a/components/subresource_filter/content/browser/page_load_statistics.cc b/components/subresource_filter/content/browser/page_load_statistics.cc
index 9f11748..efaf382b 100644
--- a/components/subresource_filter/content/browser/page_load_statistics.cc
+++ b/components/subresource_filter/content/browser/page_load_statistics.cc
@@ -16,7 +16,7 @@
 PageLoadStatistics::~PageLoadStatistics() {}
 
 void PageLoadStatistics::OnDocumentLoadStatistics(
-    const DocumentLoadStatistics& statistics) {
+    const mojom::DocumentLoadStatistics& statistics) {
   // Note: Chances of overflow are negligible.
   aggregated_document_statistics_.num_loads_total += statistics.num_loads_total;
   aggregated_document_statistics_.num_loads_evaluated +=
diff --git a/components/subresource_filter/content/browser/page_load_statistics.h b/components/subresource_filter/content/browser/page_load_statistics.h
index 33a190d3..d47e6e34 100644
--- a/components/subresource_filter/content/browser/page_load_statistics.h
+++ b/components/subresource_filter/content/browser/page_load_statistics.h
@@ -6,7 +6,6 @@
 #define COMPONENTS_SUBRESOURCE_FILTER_CONTENT_BROWSER_PAGE_LOAD_STATISTICS_H_
 
 #include "base/macros.h"
-#include "components/subresource_filter/core/common/document_load_statistics.h"
 #include "components/subresource_filter/mojom/subresource_filter.mojom.h"
 
 namespace subresource_filter {
@@ -19,7 +18,8 @@
   PageLoadStatistics(const mojom::ActivationState& state);
   ~PageLoadStatistics();
 
-  void OnDocumentLoadStatistics(const DocumentLoadStatistics& statistics);
+  void OnDocumentLoadStatistics(
+      const mojom::DocumentLoadStatistics& statistics);
   void OnDidFinishLoad();
 
  private:
@@ -27,7 +27,7 @@
 
   // Statistics about subresource loads, aggregated across all frames of the
   // current page.
-  DocumentLoadStatistics aggregated_document_statistics_;
+  mojom::DocumentLoadStatistics aggregated_document_statistics_;
 
   DISALLOW_COPY_AND_ASSIGN(PageLoadStatistics);
 };
diff --git a/components/subresource_filter/content/common/subresource_filter_messages.h b/components/subresource_filter/content/common/subresource_filter_messages.h
index ed92407..1e3c36d 100644
--- a/components/subresource_filter/content/common/subresource_filter_messages.h
+++ b/components/subresource_filter/content/common/subresource_filter_messages.h
@@ -6,36 +6,13 @@
 // no-include-guard-because-multiply-included
 
 #include "base/time/time.h"
-#include "components/subresource_filter/core/common/document_load_statistics.h"
-#include "components/subresource_filter/mojom/subresource_filter.mojom.h"
 #include "content/public/common/common_param_traits_macros.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_message_macros.h"
 #include "ipc/ipc_platform_file.h"
-#include "url/ipc/url_param_traits.h"
 
 #define IPC_MESSAGE_START SubresourceFilterMsgStart
 
-IPC_ENUM_TRAITS_MAX_VALUE(subresource_filter::mojom::ActivationLevel,
-                          subresource_filter::mojom::ActivationLevel::kMaxValue)
-
-IPC_STRUCT_TRAITS_BEGIN(subresource_filter::mojom::ActivationState)
-  IPC_STRUCT_TRAITS_MEMBER(activation_level)
-  IPC_STRUCT_TRAITS_MEMBER(filtering_disabled_for_document)
-  IPC_STRUCT_TRAITS_MEMBER(generic_blocking_rules_disabled)
-  IPC_STRUCT_TRAITS_MEMBER(measure_performance)
-  IPC_STRUCT_TRAITS_MEMBER(enable_logging)
-IPC_STRUCT_TRAITS_END()
-
-IPC_STRUCT_TRAITS_BEGIN(subresource_filter::DocumentLoadStatistics)
-  IPC_STRUCT_TRAITS_MEMBER(num_loads_total)
-  IPC_STRUCT_TRAITS_MEMBER(num_loads_evaluated)
-  IPC_STRUCT_TRAITS_MEMBER(num_loads_matching_rules)
-  IPC_STRUCT_TRAITS_MEMBER(num_loads_disallowed)
-  IPC_STRUCT_TRAITS_MEMBER(evaluation_total_wall_duration)
-  IPC_STRUCT_TRAITS_MEMBER(evaluation_total_cpu_duration)
-IPC_STRUCT_TRAITS_END()
-
 // ----------------------------------------------------------------------------
 // Messages sent from the browser to the renderer.
 // ----------------------------------------------------------------------------
@@ -45,34 +22,3 @@
 // subsequent document loads that have subresource filtering activated.
 IPC_MESSAGE_CONTROL1(SubresourceFilterMsg_SetRulesetForProcess,
                      IPC::PlatformFileForTransit /* ruleset_file */)
-
-// Instructs the renderer to activate subresource filtering at the specified
-// |activation_state| for the document load committed next in a frame.
-//
-// Without browser-side navigation, the message must arrive just before the
-// provisional load is committed on the renderer side. In practice, it is often
-// sent after the provisional load has already started, but this is not a
-// requirement. The message will have no effect if the provisional load fails.
-//
-// With browser-side navigation enabled, the message must arrive just before
-// mojom::FrameNavigationControl::CommitNavigation.
-//
-// If no message arrives, the default behavior is
-// mojom::ActivationLevel::kDisabled.
-IPC_MESSAGE_ROUTED2(
-    SubresourceFilterMsg_ActivateForNextCommittedLoad,
-    subresource_filter::mojom::ActivationState /* activation_state */,
-    bool /* is_ad_subframe */)
-
-// ----------------------------------------------------------------------------
-// Messages sent from the renderer to the browser.
-// ----------------------------------------------------------------------------
-
-// This is sent to a RenderFrameHost in the browser when a document load is
-// finished, just before the DidFinishLoad message, and contains statistics
-// collected by the DocumentSubresourceFilter up until that point: the number of
-// subresources evaluated/disallowed/etc, and total time spent on evaluating
-// subresource loads in its allowLoad method. The time metrics are equal to zero
-// if performance measurements were disabled for the load.
-IPC_MESSAGE_ROUTED1(SubresourceFilterHostMsg_DocumentLoadStatistics,
-                    subresource_filter::DocumentLoadStatistics /* statistics */)
diff --git a/components/subresource_filter/content/renderer/DEPS b/components/subresource_filter/content/renderer/DEPS
index 717a382..f1783be 100644
--- a/components/subresource_filter/content/renderer/DEPS
+++ b/components/subresource_filter/content/renderer/DEPS
@@ -2,4 +2,5 @@
   "+content/public/renderer",
   "+third_party/blink/public/platform",
   "+third_party/blink/public/web",
+  "+mojo/public/cpp",
 ]
diff --git a/components/subresource_filter/content/renderer/subresource_filter_agent.cc b/components/subresource_filter/content/renderer/subresource_filter_agent.cc
index 34f768d..233172a 100644
--- a/components/subresource_filter/content/renderer/subresource_filter_agent.cc
+++ b/components/subresource_filter/content/renderer/subresource_filter_agent.cc
@@ -16,7 +16,6 @@
 #include "components/subresource_filter/content/common/subresource_filter_utils.h"
 #include "components/subresource_filter/content/renderer/unverified_ruleset_dealer.h"
 #include "components/subresource_filter/content/renderer/web_document_subresource_filter_impl.h"
-#include "components/subresource_filter/core/common/document_load_statistics.h"
 #include "components/subresource_filter/core/common/document_subresource_filter.h"
 #include "components/subresource_filter/core/common/memory_mapped_ruleset.h"
 #include "components/subresource_filter/core/common/scoped_timers.h"
@@ -26,6 +25,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "ipc/ipc_message.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 #include "third_party/blink/public/platform/web_worker_fetch_context.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_document_loader.h"
@@ -41,8 +41,16 @@
     : content::RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<SubresourceFilterAgent>(render_frame),
       ruleset_dealer_(ruleset_dealer),
-      ad_resource_tracker_(std::move(ad_resource_tracker)) {
+      ad_resource_tracker_(std::move(ad_resource_tracker)),
+      binding_(this) {
   DCHECK(ruleset_dealer);
+  // |render_frame| can be nullptr in unit tests.
+  if (render_frame) {
+    render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+        base::BindRepeating(
+            &SubresourceFilterAgent::OnSubresourceFilterAgentRequest,
+            base::Unretained(this)));
+  }
 }
 
 SubresourceFilterAgent::~SubresourceFilterAgent() {
@@ -71,9 +79,8 @@
 }
 
 void SubresourceFilterAgent::SendDocumentLoadStatistics(
-    const DocumentLoadStatistics& statistics) {
-  render_frame()->Send(new SubresourceFilterHostMsg_DocumentLoadStatistics(
-      render_frame()->GetRoutingID(), statistics));
+    const mojom::DocumentLoadStatistics& statistics) {
+  GetSubresourceFilterHost()->SetDocumentLoadStatistics(statistics.Clone());
 }
 
 void SubresourceFilterAgent::SendFrameIsAdSubframe() {
@@ -102,14 +109,6 @@
   return mojom::ActivationState();
 }
 
-void SubresourceFilterAgent::OnActivateForNextCommittedLoad(
-    const mojom::ActivationState& activation_state,
-    bool is_ad_subframe) {
-  activation_state_for_next_commit_ = activation_state;
-  if (is_ad_subframe)
-    SetIsAdSubframe();
-}
-
 void SubresourceFilterAgent::RecordHistogramsOnLoadCommitted(
     const mojom::ActivationState& activation_state) {
   // Note: mojom::ActivationLevel used to be called mojom::ActivationState, the
@@ -180,6 +179,19 @@
   return subresource_filter_host_;
 }
 
+void SubresourceFilterAgent::OnSubresourceFilterAgentRequest(
+    mojom::SubresourceFilterAgentAssociatedRequest request) {
+  binding_.Bind(std::move(request));
+}
+
+void SubresourceFilterAgent::ActivateForNextCommittedLoad(
+    mojom::ActivationStatePtr activation_state,
+    bool is_ad_subframe) {
+  activation_state_for_next_commit_ = *activation_state;
+  if (is_ad_subframe)
+    SetIsAdSubframe();
+}
+
 void SubresourceFilterAgent::OnDestruct() {
   delete this;
 }
@@ -257,16 +269,6 @@
   RecordHistogramsOnLoadFinished();
 }
 
-bool SubresourceFilterAgent::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(SubresourceFilterAgent, message)
-    IPC_MESSAGE_HANDLER(SubresourceFilterMsg_ActivateForNextCommittedLoad,
-                        OnActivateForNextCommittedLoad)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
-}
-
 void SubresourceFilterAgent::WillCreateWorkerFetchContext(
     blink::WebWorkerFetchContext* worker_fetch_context) {
   if (!filter_for_last_committed_load_)
diff --git a/components/subresource_filter/content/renderer/subresource_filter_agent.h b/components/subresource_filter/content/renderer/subresource_filter_agent.h
index 31c9d1745..7d1c9de8 100644
--- a/components/subresource_filter/content/renderer/subresource_filter_agent.h
+++ b/components/subresource_filter/content/renderer/subresource_filter_agent.h
@@ -13,6 +13,7 @@
 #include "components/subresource_filter/mojom/subresource_filter.mojom.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
 #include "url/gurl.h"
 
 namespace blink {
@@ -21,7 +22,6 @@
 
 namespace subresource_filter {
 
-struct DocumentLoadStatistics;
 class UnverifiedRulesetDealer;
 class WebDocumentSubresourceFilterImpl;
 
@@ -32,6 +32,7 @@
 class SubresourceFilterAgent
     : public content::RenderFrameObserver,
       public content::RenderFrameObserverTracker<SubresourceFilterAgent>,
+      public mojom::SubresourceFilterAgent,
       public base::SupportsWeakPtr<SubresourceFilterAgent> {
  public:
   // The |ruleset_dealer| must not be null and must outlive this instance. The
@@ -62,7 +63,7 @@
 
   // Sends statistics about the DocumentSubresourceFilter's work to the browser.
   virtual void SendDocumentLoadStatistics(
-      const DocumentLoadStatistics& statistics);
+      const mojom::DocumentLoadStatistics& statistics);
 
   // Tells the browser that the frame is an ad subframe.
   virtual void SendFrameIsAdSubframe();
@@ -71,15 +72,16 @@
   virtual bool IsAdSubframe();
   virtual void SetIsAdSubframe();
 
+  // mojom::SubresourceFilterAgent:
+  void ActivateForNextCommittedLoad(mojom::ActivationStatePtr activation_state,
+                                    bool is_ad_subframe) override;
+
  private:
   // Assumes that the parent will be in a local frame relative to this one, upon
   // construction.
   static mojom::ActivationState GetParentActivationState(
       content::RenderFrame* render_frame);
 
-  void OnActivateForNextCommittedLoad(
-      const mojom::ActivationState& activation_state,
-      bool is_ad_subframe);
   void RecordHistogramsOnLoadCommitted(
       const mojom::ActivationState& activation_state);
   void RecordHistogramsOnLoadFinished();
@@ -87,6 +89,9 @@
 
   const mojom::SubresourceFilterHostAssociatedPtr& GetSubresourceFilterHost();
 
+  void OnSubresourceFilterAgentRequest(
+      mojom::SubresourceFilterAgentAssociatedRequest request);
+
   // content::RenderFrameObserver:
   void OnDestruct() override;
   void DidCreateNewDocument() override;
@@ -94,7 +99,6 @@
                                 ui::PageTransition transition) override;
   void DidFailProvisionalLoad(const blink::WebURLError& error) override;
   void DidFinishLoad() override;
-  bool OnMessageReceived(const IPC::Message& message) override;
   void WillCreateWorkerFetchContext(blink::WebWorkerFetchContext*) override;
 
   // Owned by the ChromeContentRendererClient and outlives us.
@@ -109,6 +113,8 @@
   // to legacy IPC messages.
   mojom::SubresourceFilterHostAssociatedPtr subresource_filter_host_;
 
+  mojo::AssociatedBinding<mojom::SubresourceFilterAgent> binding_;
+
   // If a document has been created for this frame before. The first document
   // for a new local subframe should be about:blank.
   bool first_document_ = true;
diff --git a/components/subresource_filter/content/renderer/subresource_filter_agent_unittest.cc b/components/subresource_filter/content/renderer/subresource_filter_agent_unittest.cc
index 439c08d..24a3f86 100644
--- a/components/subresource_filter/content/renderer/subresource_filter_agent_unittest.cc
+++ b/components/subresource_filter/content/renderer/subresource_filter_agent_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "components/subresource_filter/content/common/subresource_filter_messages.h"
 #include "components/subresource_filter/content/renderer/unverified_ruleset_dealer.h"
-#include "components/subresource_filter/core/common/document_load_statistics.h"
 #include "components/subresource_filter/core/common/memory_mapped_ruleset.h"
 #include "components/subresource_filter/core/common/scoped_timers.h"
 #include "components/subresource_filter/core/common/test_ruleset_creator.h"
@@ -52,7 +51,8 @@
   MOCK_METHOD0(GetDocumentURL, GURL());
   MOCK_METHOD0(OnSetSubresourceFilterForCommittedLoadCalled, void());
   MOCK_METHOD0(SignalFirstSubresourceDisallowedForCommittedLoad, void());
-  MOCK_METHOD1(SendDocumentLoadStatistics, void(const DocumentLoadStatistics&));
+  MOCK_METHOD1(SendDocumentLoadStatistics,
+               void(const mojom::DocumentLoadStatistics&));
   MOCK_METHOD0(SendFrameIsAdSubframe, void());
 
   bool IsMainFrame() override { return true; }
@@ -78,6 +78,8 @@
     return std::move(last_injected_filter_);
   }
 
+  using SubresourceFilterAgent::ActivateForNextCommittedLoad;
+
  private:
   std::unique_ptr<blink::WebDocumentSubresourceFilter> last_injected_filter_;
   bool is_ad_subframe_ = false;
@@ -157,9 +159,7 @@
   void StartLoadAndSetActivationState(mojom::ActivationState state,
                                       bool is_ad_subframe = false) {
     agent_as_rfo()->DidStartProvisionalLoad(nullptr, true);
-    EXPECT_TRUE(agent_as_rfo()->OnMessageReceived(
-        SubresourceFilterMsg_ActivateForNextCommittedLoad(0, state,
-                                                          is_ad_subframe)));
+    agent()->ActivateForNextCommittedLoad(state.Clone(), is_ad_subframe);
     agent_as_rfo()->DidCommitProvisionalLoad(
         false /* is_same_document_navigation */, ui::PAGE_TRANSITION_LINK);
   }
@@ -462,12 +462,11 @@
       SetTestRulesetToDisallowURLsWithPathSuffix(kTestBothURLsPathSuffix));
   ExpectNoSubresourceFilterGetsInjected();
   agent_as_rfo()->DidStartProvisionalLoad(nullptr, true);
-  mojom::ActivationState state;
-  state.activation_level = mojom::ActivationLevel::kEnabled;
-  state.measure_performance = true;
-  EXPECT_TRUE(agent_as_rfo()->OnMessageReceived(
-      SubresourceFilterMsg_ActivateForNextCommittedLoad(
-          0, state, false /* is_ad_subframe */)));
+  mojom::ActivationStatePtr state = mojom::ActivationState::New();
+  state->activation_level = mojom::ActivationLevel::kEnabled;
+  state->measure_performance = true;
+  agent()->ActivateForNextCommittedLoad(std::move(state),
+                                        false /* is_ad_subframe */);
   agent_as_rfo()->DidFailProvisionalLoad(
       blink::WebURLError(net::ERR_FAILED, blink::WebURL()));
   agent_as_rfo()->DidStartProvisionalLoad(nullptr, true);
diff --git a/components/subresource_filter/core/common/BUILD.gn b/components/subresource_filter/core/common/BUILD.gn
index 32a20df..6acaad4 100644
--- a/components/subresource_filter/core/common/BUILD.gn
+++ b/components/subresource_filter/core/common/BUILD.gn
@@ -13,7 +13,6 @@
     "activation_scope.h",
     "common_features.cc",
     "common_features.h",
-    "document_load_statistics.h",
     "document_subresource_filter.cc",
     "document_subresource_filter.h",
     "first_party_origin.cc",
diff --git a/components/subresource_filter/core/common/document_load_statistics.h b/components/subresource_filter/core/common/document_load_statistics.h
deleted file mode 100644
index b6857693..0000000
--- a/components/subresource_filter/core/common/document_load_statistics.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_DOCUMENT_LOAD_STATISTICS_H_
-#define COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_DOCUMENT_LOAD_STATISTICS_H_
-
-#include <stdint.h>
-
-#include "base/time/time.h"
-
-namespace subresource_filter {
-
-// Contains statistics and performance metrics collected by the
-// DocumentSubresourceFilter.
-struct DocumentLoadStatistics {
-  // The number of subresource loads that went through the
-  // DocumentSubresouceFilter filtering methods.
-  int32_t num_loads_total = 0;
-
-  // Statistics on the number of subresource loads that were evaluated, were
-  // matched by filtering rules, and were disallowed, respectively, during the
-  // lifetime of a DocumentSubresourceFilter.
-  int32_t num_loads_evaluated = 0;
-  int32_t num_loads_matching_rules = 0;
-  int32_t num_loads_disallowed = 0;
-
-  // Total time spent in GetLoadPolicy() calls evaluating subresource loads.
-  base::TimeDelta evaluation_total_wall_duration;
-  base::TimeDelta evaluation_total_cpu_duration;
-};
-
-}  // namespace subresource_filter
-
-#endif  // COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_DOCUMENT_LOAD_STATISTICS_H_
diff --git a/components/subresource_filter/core/common/document_subresource_filter.h b/components/subresource_filter/core/common/document_subresource_filter.h
index 79fdf40..19e2fa0d 100644
--- a/components/subresource_filter/core/common/document_subresource_filter.h
+++ b/components/subresource_filter/core/common/document_subresource_filter.h
@@ -12,7 +12,6 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "components/subresource_filter/core/common/document_load_statistics.h"
 #include "components/subresource_filter/core/common/indexed_ruleset.h"
 #include "components/subresource_filter/core/common/load_policy.h"
 #include "components/subresource_filter/mojom/subresource_filter.mojom.h"
@@ -46,12 +45,14 @@
   const mojom::ActivationState& activation_state() const {
     return activation_state_;
   }
-  const DocumentLoadStatistics& statistics() const { return statistics_; }
+  const mojom::DocumentLoadStatistics& statistics() const {
+    return statistics_;
+  }
 
   // WARNING: This is only to allow DocumentSubresourceFilter's wrappers to
   // modify the |statistics|.
   // TODO(pkalinnikov): Find a better way to achieve this.
-  DocumentLoadStatistics& statistics() { return statistics_; }
+  mojom::DocumentLoadStatistics& statistics() { return statistics_; }
 
   LoadPolicy GetLoadPolicy(
       const GURL& subresource_url,
@@ -77,7 +78,7 @@
   // Equals nullptr iff |activation_state_.filtering_disabled_for_document|.
   std::unique_ptr<FirstPartyOrigin> document_origin_;
 
-  DocumentLoadStatistics statistics_;
+  mojom::DocumentLoadStatistics statistics_;
 
   DISALLOW_COPY_AND_ASSIGN(DocumentSubresourceFilter);
 };
diff --git a/components/subresource_filter/mojom/subresource_filter.mojom b/components/subresource_filter/mojom/subresource_filter.mojom
index 3cc02d7..d8753722 100644
--- a/components/subresource_filter/mojom/subresource_filter.mojom
+++ b/components/subresource_filter/mojom/subresource_filter.mojom
@@ -4,6 +4,8 @@
 
 module subresource_filter.mojom;
 
+import "mojo/public/mojom/base/time.mojom";
+
 enum ActivationLevel {
   kDisabled,
   kDryRun,
@@ -37,6 +39,25 @@
   bool enable_logging = false;
 };
 
+// Contains statistics and performance metrics collected by the
+// DocumentSubresourceFilter.
+struct DocumentLoadStatistics {
+  // The number of subresource loads that went through the
+  // DocumentSubresouceFilter filtering methods.
+  int32 num_loads_total = 0;
+
+  // Statistics on the number of subresource loads that were evaluated, were
+  // matched by filtering rules, and were disallowed, respectively, during the
+  // lifetime of a DocumentSubresourceFilter.
+  int32 num_loads_evaluated = 0;
+  int32 num_loads_matching_rules = 0;
+  int32 num_loads_disallowed = 0;
+
+  // Total time spent in GetLoadPolicy() calls evaluating subresource loads.
+  mojo_base.mojom.TimeDelta evaluation_total_wall_duration;
+  mojo_base.mojom.TimeDelta evaluation_total_cpu_duration;
+};
+
 interface SubresourceFilterHost {
   // Called the first time a subresource load is disallowed for the most
   // recently committed document load in a frame. It is used to trigger a UI
@@ -49,5 +70,24 @@
   // an ad via SubresourceFilterMsg_ActivateForNextCommittedLoad, at
   // ReadyToCommitNavigation time.
   FrameIsAdSubframe();
+
+  // This is sent to a RenderFrameHost in the browser when a document load is
+  // finished, just before the DidFinishLoad message, and contains statistics
+  // collected by the DocumentSubresourceFilter up until that point: the number
+  // of subresources evaluated/disallowed/etc, and total time spent on
+  // evaluating subresource loads in its allowLoad method. The time metrics are
+  // equal to zero if performance measurements were disabled for the load.
+  SetDocumentLoadStatistics(DocumentLoadStatistics statistics);
+};
+
+interface SubresourceFilterAgent {
+  // Instructs the renderer to activate subresource filtering at the specified
+  // |activation_state| for the document load committed next in a frame.
+  //
+  // Most be called just before mojom::FrameNavigationControl::CommitNavigation,
+  // at ReadyToCommitNavigation time. If not called, the default behavior is
+  // mojom::ActivationLevel::kDisabled.
+  ActivateForNextCommittedLoad(ActivationState activation_state,
+                               bool is_ad_subframe);
 };
 
diff --git a/components/toolbar/BUILD.gn b/components/toolbar/BUILD.gn
index 339de16..2492cc25 100644
--- a/components/toolbar/BUILD.gn
+++ b/components/toolbar/BUILD.gn
@@ -106,6 +106,7 @@
   deps = [
     ":toolbar",
     "//base",
+    "//base/test:test_support",
     "//testing/gtest",
     "//url",
   ]
diff --git a/components/toolbar/toolbar_field_trial.cc b/components/toolbar/toolbar_field_trial.cc
index 4b03ab2..a8c412a 100644
--- a/components/toolbar/toolbar_field_trial.cc
+++ b/components/toolbar/toolbar_field_trial.cc
@@ -5,10 +5,40 @@
 #include "components/toolbar/toolbar_field_trial.h"
 
 #include "base/feature_list.h"
+#include "build/build_config.h"
+#include "ui/base/ui_base_features.h"
 
 namespace toolbar {
 namespace features {
 
+const base::Feature kHideSteadyStateUrlScheme {
+  "OmniboxUIExperimentHideSteadyStateUrlScheme",
+#if defined(OS_IOS)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+const base::Feature kHideSteadyStateUrlTrivialSubdomains {
+  "OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains",
+#if defined(OS_IOS)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+bool IsHideSteadyStateUrlSchemeEnabled() {
+  return base::FeatureList::IsEnabled(kHideSteadyStateUrlScheme) ||
+         base::FeatureList::IsEnabled(::features::kExperimentalUi);
+}
+
+bool IsHideSteadyStateUrlTrivialSubdomainsEnabled() {
+  return base::FeatureList::IsEnabled(kHideSteadyStateUrlTrivialSubdomains) ||
+         base::FeatureList::IsEnabled(::features::kExperimentalUi);
+}
+
 // Features used for EV UI removal experiment (https://crbug.com/803501).
 const base::Feature kSimplifyHttpsIndicator{"SimplifyHttpsIndicator",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/components/toolbar/toolbar_field_trial.h b/components/toolbar/toolbar_field_trial.h
index 4b9dd0a..f0dbf55b 100644
--- a/components/toolbar/toolbar_field_trial.h
+++ b/components/toolbar/toolbar_field_trial.h
@@ -10,6 +10,22 @@
 namespace toolbar {
 namespace features {
 
+// Feature used to hide the scheme from steady state URLs displayed in the
+// toolbar. It is restored during editing.
+extern const base::Feature kHideSteadyStateUrlScheme;
+
+// Feature used to hide trivial subdomains from steady state URLs displayed in
+// the toolbar. It is restored during editing.
+extern const base::Feature kHideSteadyStateUrlTrivialSubdomains;
+
+// Returns true if either the steady-state elision flag for scheme or the
+// #upcoming-ui-features flag is enabled.
+bool IsHideSteadyStateUrlSchemeEnabled();
+
+// Returns true if either the steady-state elision flag for trivial
+// subdomains or the #upcoming-ui-features flag is enabled.
+bool IsHideSteadyStateUrlTrivialSubdomainsEnabled();
+
 // This feature simplifies the security indiciator UI for https:// pages. The
 // exact UI treatment is dependent on the parameter 'treatment' which can have
 // the following value:
diff --git a/components/toolbar/toolbar_model_impl.cc b/components/toolbar/toolbar_model_impl.cc
index f308fbc..4211322 100644
--- a/components/toolbar/toolbar_model_impl.cc
+++ b/components/toolbar/toolbar_model_impl.cc
@@ -44,12 +44,18 @@
 
 base::string16 ToolbarModelImpl::GetURLForDisplay() const {
   url_formatter::FormatUrlTypes format_types =
+      url_formatter::kFormatUrlOmitDefaults;
+
 #if defined(OS_IOS)
-      url_formatter::kFormatUrlTrimAfterHost |
+  format_types |= url_formatter::kFormatUrlTrimAfterHost;
 #endif
-      url_formatter::kFormatUrlOmitDefaults |
-      url_formatter::kFormatUrlOmitHTTPS |
-      url_formatter::kFormatUrlOmitTrivialSubdomains;
+
+  if (toolbar::features::IsHideSteadyStateUrlSchemeEnabled())
+    format_types |= url_formatter::kFormatUrlOmitHTTPS;
+
+  if (toolbar::features::IsHideSteadyStateUrlTrivialSubdomainsEnabled())
+    format_types |= url_formatter::kFormatUrlOmitTrivialSubdomains;
+
   return GetFormattedURL(format_types);
 }
 
diff --git a/components/toolbar/toolbar_model_impl_unittest.cc b/components/toolbar/toolbar_model_impl_unittest.cc
index 4fe8905..443fb464 100644
--- a/components/toolbar/toolbar_model_impl_unittest.cc
+++ b/components/toolbar/toolbar_model_impl_unittest.cc
@@ -5,6 +5,8 @@
 #include "components/toolbar/toolbar_model_impl.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/toolbar/toolbar_field_trial.h"
 #include "components/toolbar/toolbar_model_delegate.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -33,6 +35,12 @@
 
 TEST(ToolbarModelImplTest,
      DisplayUrlAppliesFormattedStringWithEquivalentMeaning) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {toolbar::features::kHideSteadyStateUrlScheme,
+       toolbar::features::kHideSteadyStateUrlTrivialSubdomains},
+      {});
+
   FakeToolbarModelDelegate delegate;
   auto model = std::make_unique<ToolbarModelImpl>(&delegate, 1024);
 
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index eb8d2dc..3d74be7 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -76,6 +76,10 @@
   void SetUpInputField(Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
   void SetUpScrollableInputField(
       Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
+  void SetUpSingleCharInputField(
+      Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
+  void SetUpSingleCharRtlInputField(
+      Microsoft::WRL::ComPtr<IAccessibleText>* input_text);
   void SetUpTextareaField(
       Microsoft::WRL::ComPtr<IAccessibleText>* textarea_text);
   void SetUpSampleParagraph(
@@ -201,6 +205,39 @@
   SetUpInputFieldHelper(input_text);
 }
 
+// Loads a page with an input text field and places a single character in it.
+void AccessibilityWinBrowserTest::SetUpSingleCharInputField(
+    Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
+  ASSERT_NE(nullptr, input_text);
+  LoadInitialAccessibilityTreeFromHtml(std::string(
+      R"HTML(<!DOCTYPE html>
+          <html>
+          <body>
+            <form>
+              <input type="text" id="textField" name="name" value="x">
+            </form>
+          </body>
+          </html>)HTML"));
+  SetUpInputFieldHelper(input_text);
+}
+
+// Loads a page with a right-to-left input text field and places a single
+// character in it.
+void AccessibilityWinBrowserTest::SetUpSingleCharRtlInputField(
+    Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
+  ASSERT_NE(nullptr, input_text);
+  LoadInitialAccessibilityTreeFromHtml(std::string(
+      R"HTML(<!DOCTYPE html>
+          <html>
+          <body>
+            <form>
+              <input type="text" id="textField" name="name" dir="rtl" value="x">
+            </form>
+          </body>
+          </html>)HTML"));
+  SetUpInputFieldHelper(input_text);
+}
+
 void AccessibilityWinBrowserTest::SetUpInputFieldHelper(
     Microsoft::WRL::ComPtr<IAccessibleText>* input_text) {
   Microsoft::WRL::ComPtr<IAccessible> document(GetRendererAccessible());
@@ -1245,6 +1282,15 @@
       EXPECT_LT(1, width) << "at offset " << offset;
       EXPECT_EQ(previous_height, height) << "at offset " << offset;
     }
+
+    // Past end of text.
+    EXPECT_HRESULT_SUCCEEDED(paragraph_text->get_characterExtents(
+        n_characters, coordinate_type, &x, &y, &width, &height));
+    EXPECT_LT(previous_x, x) << "at final offset " << n_characters;
+    EXPECT_EQ(previous_y, y) << "at final offset " << n_characters;
+    // Last character width past end should be 1, the width of a caret.
+    EXPECT_EQ(1, width) << "at final offset " << n_characters;
+    EXPECT_EQ(previous_height, height) << "at final offset " << n_characters;
   }
 }
 
@@ -1376,6 +1422,111 @@
       EXPECT_LT(1, width) << "at offset " << offset;
       EXPECT_EQ(previous_height, height) << "at offset " << offset;
     }
+    // Past end of text.
+    EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
+        n_characters, coordinate_type, &x, &y, &width, &height));
+    EXPECT_LT(previous_x, x) << "at final offset " << n_characters;
+    EXPECT_EQ(previous_y, y) << "at final offset " << n_characters;
+    // Last character width past end should be 1, the width of a caret.
+    EXPECT_EQ(1, width) << "at final offset " << n_characters;
+    EXPECT_EQ(previous_height, height) << "at final offset " << n_characters;
+  }
+}
+
+// TODO(accessibility) Support bounds in an empty text field where there are
+// no child inline text objects, such that the following test passes.
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+                       DISABLED_TestCharacterExtentsInEmptyInputField) {
+  Microsoft::WRL::ComPtr<IAccessibleText> input_text;
+  SetUpSingleCharInputField(&input_text);
+
+  LONG n_characters;
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
+  ASSERT_EQ(1, n_characters);
+
+  // Get the rect for the only character.
+  LONG prev_x, prev_y, prev_width, prev_height;
+  EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
+      0, IA2_COORDTYPE_SCREEN_RELATIVE, &prev_x, &prev_y, &prev_width,
+      &prev_height));
+
+  EXPECT_LT(1, prev_width);
+  EXPECT_LT(1, prev_height);
+
+  // Delete the character in the input field.
+  AccessibilityNotificationWaiter waiter(
+      shell()->web_contents(), ui::kAXModeComplete,
+      ax::mojom::Event::kTextSelectionChanged);
+  ExecuteScript(std::wstring(L"document.querySelector('input').value='';"));
+  waiter.WaitForNotification();
+
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
+  ASSERT_EQ(0, n_characters);
+  LONG caret_offset;
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_caretOffset(&caret_offset));
+  ASSERT_EQ(0, caret_offset);
+
+  // Now that input is completely empty, the position of the caret should be
+  // returned for character 0. The x,y position and height should be the same as
+  // it was as when there was single character, but the width should now be 1.
+  LONG x, y, width, height;
+  for (int offset = IA2_TEXT_OFFSET_CARET; offset <= 0; ++offset) {
+    EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
+        offset, IA2_COORDTYPE_SCREEN_RELATIVE, &x, &y, &width, &height));
+    EXPECT_EQ(prev_x, x);
+    EXPECT_EQ(prev_y, y);
+    EXPECT_EQ(1, width);
+    EXPECT_EQ(prev_height, height);
+  }
+}
+
+// TODO(accessibility) Support bounds in an empty text field where there are
+// no child inline text objects, such that the following test passes.
+IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest,
+                       DISABLED_TestCharacterExtentsInEmptyRtlInputField) {
+  Microsoft::WRL::ComPtr<IAccessibleText> input_text;
+  SetUpSingleCharRtlInputField(&input_text);
+
+  LONG n_characters;
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
+  ASSERT_EQ(1, n_characters);
+
+  // Get the rect for the only character.
+  LONG prev_x, prev_y, prev_width, prev_height;
+  EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
+      0, IA2_COORDTYPE_SCREEN_RELATIVE, &prev_x, &prev_y, &prev_width,
+      &prev_height));
+
+  EXPECT_LT(1, prev_width);
+  EXPECT_LT(1, prev_height);
+
+  // Delete the character in the input field.
+  AccessibilityNotificationWaiter waiter(
+      shell()->web_contents(), ui::kAXModeComplete,
+      ax::mojom::Event::kTextSelectionChanged);
+  ExecuteScript(
+      std::wstring(L"const input = document.querySelector('input');"
+                   "input.value='';"));
+  waiter.WaitForNotification();
+
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_nCharacters(&n_characters));
+  ASSERT_EQ(0, n_characters);
+  LONG caret_offset;
+  ASSERT_HRESULT_SUCCEEDED(input_text->get_caretOffset(&caret_offset));
+  ASSERT_EQ(0, caret_offset);
+
+  // Now that input is completely empty, the position of the caret should be
+  // returned for character 0. The x,y position and height should be the same as
+  // it was as when there was single character, but the width should now be 1.
+  LONG x, y, width, height;
+  for (int offset = IA2_TEXT_OFFSET_CARET; offset <= 0; ++offset) {
+    EXPECT_HRESULT_SUCCEEDED(input_text->get_characterExtents(
+        offset, IA2_COORDTYPE_SCREEN_RELATIVE, &x, &y, &width, &height));
+    // TODO(accessibility) Why do results keep changing on each run?
+    EXPECT_GE(prev_x + prev_width - 1, x);
+    EXPECT_EQ(prev_y, y);
+    EXPECT_EQ(1, width);
+    EXPECT_EQ(prev_height, height);
   }
 }
 
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index b9fe935..400d970d 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -75,9 +75,10 @@
 
 uint32_t BrowserAccessibility::PlatformChildCount() const {
   if (HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
+    AXTreeID child_tree_id = AXTreeID::FromString(
+        GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
     BrowserAccessibilityManager* child_manager =
-        BrowserAccessibilityManager::FromID(
-            GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
+        BrowserAccessibilityManager::FromID(child_tree_id);
     if (child_manager && child_manager->GetRoot()->PlatformGetParent() == this)
       return 1;
 
@@ -128,9 +129,10 @@
 
   if (child_index == 0 &&
       HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
+    AXTreeID child_tree_id = AXTreeID::FromString(
+        GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
     BrowserAccessibilityManager* child_manager =
-        BrowserAccessibilityManager::FromID(
-            GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
+        BrowserAccessibilityManager::FromID(child_tree_id);
     if (child_manager && child_manager->GetRoot()->PlatformGetParent() == this)
       result = child_manager->GetRoot();
   } else {
@@ -380,7 +382,9 @@
       else
         start = 0;
     }
-    return bounds;
+    // When past the end of text, the area will be 0.
+    // In this case, use bounds provided for the caret.
+    return bounds.IsEmpty() ? GetPageBoundsPastEndOfText() : bounds;
   }
 
   int end = start + len;
@@ -492,6 +496,54 @@
   return bounds;
 }
 
+// Get a rect for a 1-width character past the end of text. This is what ATs
+// expect when getting the character extents past the last character in a line,
+// and equals what the caret bounds would be when past the end of the text.
+gfx::Rect BrowserAccessibility::GetPageBoundsPastEndOfText() const {
+  // Step 1: get approximate caret bounds. The thickness may not yet be correct.
+  gfx::Rect bounds;
+  if (InternalChildCount() > 0) {
+    // When past the end of text, use bounds provided by a last child if
+    // available, and then correct for thickness of caret.
+    BrowserAccessibility* child = InternalGetChild(InternalChildCount() - 1);
+    int child_text_len = child->GetText().size();
+    bounds = child->GetPageBoundsForRange(child_text_len, child_text_len);
+    if (bounds.width() == 0 && bounds.height() == 0)
+      return bounds;  // Inline text boxes info not yet available.
+  } else {
+    // TODO(accessibility) Support bounds in an empty text field where there are
+    // no child inline text objects.
+    return bounds;
+  }
+
+  // Step 2: correct for the thickness of the caret.
+  auto text_direction = static_cast<ax::mojom::TextDirection>(
+      GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
+  constexpr int kCaretThickness = 1;
+  switch (text_direction) {
+    case ax::mojom::TextDirection::kNone:
+    case ax::mojom::TextDirection::kLtr: {
+      bounds.set_width(kCaretThickness);
+      break;
+    }
+    case ax::mojom::TextDirection::kRtl: {
+      bounds.set_x(bounds.right() - kCaretThickness);
+      bounds.set_width(kCaretThickness);
+      break;
+    }
+    case ax::mojom::TextDirection::kTtb: {
+      bounds.set_height(kCaretThickness);
+      break;
+    }
+    case ax::mojom::TextDirection::kBtt: {
+      bounds.set_y(bounds.bottom() - kCaretThickness);
+      bounds.set_height(kCaretThickness);
+      break;
+    }
+  }
+  return bounds;
+}
+
 base::string16 BrowserAccessibility::GetValue() const {
   base::string16 value =
       GetString16Attribute(ax::mojom::StringAttribute::kValue);
@@ -772,6 +824,24 @@
   return name;
 }
 
+std::string BrowserAccessibility::GetLiveRegionText() const {
+  if (GetRole() == ax::mojom::Role::kIgnored)
+    return "";
+
+  std::string text = GetStringAttribute(ax::mojom::StringAttribute::kName);
+  if (!text.empty())
+    return text;
+
+  for (size_t i = 0; i < InternalChildCount(); ++i) {
+    BrowserAccessibility* child = InternalGetChild(i);
+    if (!child)
+      continue;
+
+    text += child->GetLiveRegionText();
+  }
+  return text;
+}
+
 std::vector<int> BrowserAccessibility::GetLineStartOffsets() const {
   if (!instance_active())
     return std::vector<int>();
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 494ad2e8..7a4951c 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -320,6 +320,9 @@
   // to compute a name from its descendants.
   std::string ComputeAccessibleNameFromDescendants() const;
 
+  // Get text to announce for a live region change if AT does not implement.
+  std::string GetLiveRegionText() const;
+
   // Creates a text position rooted at this object.
   BrowserAccessibilityPosition::AXPositionInstance CreatePositionAt(
       int offset,
@@ -389,6 +392,8 @@
   // text, depending on the platform.
   base::string16 GetInnerText() const;
 
+  gfx::Rect GetPageBoundsPastEndOfText() const;
+
   // A unique ID, since node IDs are frame-local.
   ui::AXUniqueId unique_id_;
 
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 8c86aa1..5a69a22 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -5,6 +5,7 @@
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 
 #include <stddef.h>
+#include <map>
 #include <utility>
 
 #include "base/debug/crash_logging.h"
@@ -21,7 +22,7 @@
 namespace {
 
 // Map from AXTreeID to BrowserAccessibilityManager
-using AXTreeIDMap = base::hash_map<ui::AXTreeID, BrowserAccessibilityManager*>;
+using AXTreeIDMap = std::map<ui::AXTreeID, BrowserAccessibilityManager*>;
 base::LazyInstance<AXTreeIDMap>::Leaky g_ax_tree_id_map =
     LAZY_INSTANCE_INITIALIZER;
 
@@ -49,8 +50,8 @@
 
   ui::AXTreeUpdate update;
   ui::AXTreeData tree_data;
-  tree_data.tree_id = 1;
-  tree_data.focused_tree_id = 1;
+  tree_data.tree_id = ui::AXTreeID::FromString("1");
+  tree_data.focused_tree_id = ui::AXTreeID::FromString("1");
   update.tree_data = tree_data;
   update.has_tree_data = true;
   update.root_id = node1.id;
@@ -273,8 +274,9 @@
   for (int32_t host_node_id : host_node_ids) {
     BrowserAccessibility* parent_node = parent_manager->GetFromID(host_node_id);
     if (parent_node) {
-      DCHECK_EQ(ax_tree_id_, parent_node->GetStringAttribute(
-                                 ax::mojom::StringAttribute::kChildTreeId));
+      DCHECK_EQ(ax_tree_id_,
+                AXTreeID::FromString(parent_node->GetStringAttribute(
+                    ax::mojom::StringAttribute::kChildTreeId)));
       return parent_node;
     }
   }
@@ -520,9 +522,10 @@
     return GetRoot();
 
   if (obj->HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
+    AXTreeID child_tree_id = AXTreeID::FromString(
+        obj->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
     BrowserAccessibilityManager* child_manager =
-        BrowserAccessibilityManager::FromID(
-            obj->GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
+        BrowserAccessibilityManager::FromID(child_tree_id);
     if (child_manager)
       return child_manager->GetFocusFromThisOrDescendantFrame();
   }
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index 584c954..ace4792 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -182,6 +182,16 @@
   FireNativeMacNotification(mac_notification, node);
 }
 
+void PostAnnouncementNotification(NSString* announcement) {
+  NSDictionary* notification_info = @{
+    NSAccessibilityAnnouncementKey : announcement,
+    NSAccessibilityPriorityKey : @(NSAccessibilityPriorityLow)
+  };
+  NSAccessibilityPostNotificationWithUserInfo(
+      [NSApp mainWindow], NSAccessibilityAnnouncementRequestedNotification,
+      notification_info);
+}
+
 void BrowserAccessibilityManagerMac::FireGeneratedEvent(
     AXEventGenerator::Event event_type,
     BrowserAccessibility* node) {
@@ -315,6 +325,18 @@
         return;
       }
 
+      if (base::mac::IsAtMostOS10_13()) {
+        // Use the announcement API to get around OS <= 10.13 VoiceOver bug
+        // where it stops announcing live regions after the first time focus
+        // leaves any content area.
+        // Unfortunately this produces an annoying boing sound with each live
+        // announcement, but the alternative is almost no live region support.
+        PostAnnouncementNotification(
+            base::SysUTF8ToNSString(node->GetLiveRegionText()));
+        return;
+      }
+
+      // Use native VoiceOver support for live regions.
       base::scoped_nsobject<BrowserAccessibilityCocoa> retained_node(
           [native_node retain]);
       base::PostDelayedTaskWithTraits(
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 8fba1fcf..f95b75a 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -971,6 +971,11 @@
   RunAriaTest(FILE_PATH_LITERAL("table-column-hidden.html"));
 }
 
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+                       AccessibilityLabelWithSelectedAriaOption) {
+  RunAriaTest(FILE_PATH_LITERAL("label-with-selected-option.html"));
+}
+
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityArticle) {
   RunHtmlTest(FILE_PATH_LITERAL("article.html"));
 }
@@ -1849,6 +1854,11 @@
   RunHtmlTest(FILE_PATH_LITERAL("input-image-with-title.html"));
 }
 
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
+                       AccessibilityLabelWithSelectedOption) {
+  RunHtmlTest(FILE_PATH_LITERAL("label-with-selected-option.html"));
+}
+
 //
 // Regression tests. These don't test a specific web platform feature,
 // they test a specific web page that crashed or had some bad behavior
diff --git a/content/browser/appcache/appcache_storage_impl.cc b/content/browser/appcache/appcache_storage_impl.cc
index 1aff407..3d0aee2 100644
--- a/content/browser/appcache/appcache_storage_impl.cc
+++ b/content/browser/appcache/appcache_storage_impl.cc
@@ -684,7 +684,7 @@
   // We have to ask the quota manager for the value.
   storage_->pending_quota_queries_.insert(this);
   quota_manager->GetUsageAndQuota(
-      group_record_.origin.GetURL(), blink::mojom::StorageType::kTemporary,
+      group_record_.origin, blink::mojom::StorageType::kTemporary,
       base::BindOnce(&StoreGroupAndCacheTask::OnQuotaCallback, this));
 }
 
diff --git a/content/browser/appcache/appcache_storage_impl_unittest.cc b/content/browser/appcache/appcache_storage_impl_unittest.cc
index 03237c2..8fe73464 100644
--- a/content/browser/appcache/appcache_storage_impl_unittest.cc
+++ b/content/browser/appcache/appcache_storage_impl_unittest.cc
@@ -254,7 +254,7 @@
                        storage::GetQuotaSettingsFunc()),
           async_(false) {}
 
-    void GetUsageAndQuota(const GURL& origin,
+    void GetUsageAndQuota(const url::Origin& /* origin */,
                           StorageType type,
                           UsageAndQuotaCallback callback) override {
       EXPECT_EQ(StorageType::kTemporary, type);
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index 66462353..1246417f 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -62,7 +62,8 @@
 namespace cache_storage_cache_unittest {
 
 const char kTestData[] = "Hello World";
-const char kOrigin[] = "http://example.com";
+// TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+const url::Origin kOrigin = url::Origin::Create(GURL("http://example.com"));
 const char kCacheName[] = "test_cache";
 const GURL kBodyUrl("http://example.com/body.html");
 const GURL kBodyUrlWithQuery("http://example.com/body.html?query=test");
@@ -387,9 +388,8 @@
     mock_quota_manager_ = new MockQuotaManager(
         is_incognito, temp_dir_path, base::ThreadTaskRunnerHandle::Get().get(),
         quota_policy_.get());
-    mock_quota_manager_->SetQuota(GURL(kOrigin),
-                                  blink::mojom::StorageType::kTemporary,
-                                  1024 * 1024 * 100);
+    mock_quota_manager_->SetQuota(
+        kOrigin, blink::mojom::StorageType::kTemporary, 1024 * 1024 * 100);
 
     quota_manager_proxy_ = new MockQuotaManagerProxy(
         mock_quota_manager_.get(), base::ThreadTaskRunnerHandle::Get().get());
@@ -415,8 +415,7 @@
         MakeRequest(&blob_ptr_));
 
     cache_ = std::make_unique<TestCacheStorageCache>(
-        url::Origin::Create(GURL(kOrigin)), kCacheName, temp_dir_path,
-        nullptr /* CacheStorage */,
+        kOrigin, kCacheName, temp_dir_path, nullptr /* CacheStorage */,
         BrowserContext::GetDefaultStoragePartition(&browser_context_)
             ->GetURLRequestContext(),
         quota_manager_proxy_, blob_storage_context->context()->AsWeakPtr());
@@ -1539,8 +1538,7 @@
 }
 
 TEST_P(CacheStorageCacheTestP, PutWithSideData_QuotaExceeded) {
-  mock_quota_manager_->SetQuota(GURL(kOrigin),
-                                blink::mojom::StorageType::kTemporary,
+  mock_quota_manager_->SetQuota(kOrigin, blink::mojom::StorageType::kTemporary,
                                 expected_blob_data_.size() - 1);
   blink::mojom::FetchAPIResponsePtr response = CreateBlobBodyResponse();
   const std::string expected_side_data = "SideData";
@@ -1555,8 +1553,7 @@
 }
 
 TEST_P(CacheStorageCacheTestP, PutWithSideData_QuotaExceededSkipSideData) {
-  mock_quota_manager_->SetQuota(GURL(kOrigin),
-                                blink::mojom::StorageType::kTemporary,
+  mock_quota_manager_->SetQuota(kOrigin, blink::mojom::StorageType::kTemporary,
                                 expected_blob_data_.size());
   blink::mojom::FetchAPIResponsePtr response = CreateBlobBodyResponse();
   const std::string expected_side_data = "SideData";
@@ -1638,8 +1635,8 @@
 }
 
 TEST_P(CacheStorageCacheTestP, WriteSideData_QuotaExceeded) {
-  mock_quota_manager_->SetQuota(
-      GURL(kOrigin), blink::mojom::StorageType::kTemporary, 1024 * 1023);
+  mock_quota_manager_->SetQuota(kOrigin, blink::mojom::StorageType::kTemporary,
+                                1024 * 1023);
   base::Time response_time(base::Time::Now());
   blink::mojom::FetchAPIResponsePtr response(CreateNoBodyResponse());
   response->response_time = response_time;
@@ -1746,8 +1743,8 @@
 }
 
 TEST_P(CacheStorageCacheTestP, PutObeysQuotaLimits) {
-  mock_quota_manager_->SetQuota(GURL(kOrigin),
-                                blink::mojom::StorageType::kTemporary, 0);
+  mock_quota_manager_->SetQuota(kOrigin, blink::mojom::StorageType::kTemporary,
+                                0);
   EXPECT_FALSE(Put(body_request_, CreateBlobBodyResponse()));
   EXPECT_EQ(CacheStorageError::kErrorQuotaExceeded, callback_error_);
 }
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index c9aa948..9c9f451 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -259,9 +259,9 @@
     mock_quota_manager_ = new MockQuotaManager(
         MemoryOnly(), temp_dir_path, base::ThreadTaskRunnerHandle::Get().get(),
         quota_policy_.get());
-    mock_quota_manager_->SetQuota(origin1_.GetURL(), StorageType::kTemporary,
+    mock_quota_manager_->SetQuota(origin1_, StorageType::kTemporary,
                                   1024 * 1024 * 100);
-    mock_quota_manager_->SetQuota(origin2_.GetURL(), StorageType::kTemporary,
+    mock_quota_manager_->SetQuota(origin2_, StorageType::kTemporary,
                                   1024 * 1024 * 100);
 
     quota_manager_proxy_ = new MockCacheStorageQuotaManagerProxy(
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index 95cd368..e7001506e 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -988,8 +988,8 @@
           request.method, request.url, request.site_for_cookies,
           first_party_url_policy, request.referrer_policy,
           request.referrer.spec(), &headers, headers.response_code(),
-          redirect_url, false /* token_binding_negotiated */,
-          false /* insecure_scheme_was_upgraded */, false /* copy_fragment */));
+          redirect_url, false /* insecure_scheme_was_upgraded */,
+          false /* copy_fragment */));
 
   client_->OnReceiveRedirect(*response_metadata_->redirect_info,
                              response_metadata_->head);
diff --git a/content/browser/devtools/protocol/storage_handler.cc b/content/browser/devtools/protocol/storage_handler.cc
index 2af30aad8..436be9b 100644
--- a/content/browser/devtools/protocol/storage_handler.cc
+++ b/content/browser/devtools/protocol/storage_handler.cc
@@ -86,11 +86,11 @@
 
 void GetUsageAndQuotaOnIOThread(
     storage::QuotaManager* manager,
-    const GURL& url,
+    const url::Origin& origin,
     std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   manager->GetUsageAndQuotaWithBreakdown(
-      url, blink::mojom::StorageType::kTemporary,
+      origin, blink::mojom::StorageType::kTemporary,
       base::BindOnce(&GotUsageAndQuotaDataCallback, std::move(callback)));
 }
 }  // namespace
@@ -335,7 +335,7 @@
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(&GetUsageAndQuotaOnIOThread, base::RetainedRef(manager),
-                     origin_url, std::move(callback)));
+                     url::Origin::Create(origin_url), std::move(callback)));
 }
 
 Response StorageHandler::TrackCacheStorageForOrigin(const std::string& origin) {
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 26385de..aa292b7 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -2371,6 +2371,7 @@
   EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
       shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
   SiteInstance* site_instance1 = shell()->web_contents()->GetSiteInstance();
+  int process1_id = site_instance1->GetProcess()->GetID();
 
   // Open a new tab. Initially it gets a render view in the original tab's
   // current site instance.
@@ -2383,9 +2384,14 @@
   WebContentsImpl* new_web_contents = static_cast<WebContentsImpl*>(
       new_shell->web_contents());
   SiteInstance* site_instance2 = new_web_contents->GetSiteInstance();
+  int process2_id = site_instance2->GetProcess()->GetID();
 
+  // The 2nd WebUI page should swap to a different process (and SiteInstance),
+  // but should stay in the same BrowsingInstance as the 1st WebUI page.
+  EXPECT_NE(process1_id, process2_id);
   EXPECT_NE(site_instance2, site_instance1);
   EXPECT_TRUE(site_instance2->IsRelatedSiteInstance(site_instance1));
+
   RenderViewHost* initial_rvh = new_web_contents->
       GetRenderManagerForTesting()->GetSwappedOutRenderViewHost(site_instance1);
   ASSERT_TRUE(initial_rvh);
diff --git a/content/browser/frame_host/webui_navigation_browsertest.cc b/content/browser/frame_host/webui_navigation_browsertest.cc
index 7add7719..fbf93aa 100644
--- a/content/browser/frame_host/webui_navigation_browsertest.cc
+++ b/content/browser/frame_host/webui_navigation_browsertest.cc
@@ -2,12 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/command_line.h"
+#include "base/macros.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/frame_host/frame_tree_node.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -16,6 +19,7 @@
 #include "content/shell/browser/shell.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/url_constants.h"
 
 namespace content {
 
@@ -219,6 +223,11 @@
   EXPECT_EQ(chrome_url, webui_rfh->GetLastCommittedURL());
   EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
       webui_rfh->GetProcess()->GetID()));
+  EXPECT_EQ(
+      ChildProcessSecurityPolicyImpl::CheckOriginLockResult::HAS_EQUAL_LOCK,
+      ChildProcessSecurityPolicyImpl::GetInstance()->CheckOriginLock(
+          root->current_frame_host()->GetProcess()->GetID(),
+          webui_site_instance->GetSiteURL()));
 
   GURL web_url(embedded_test_server()->GetURL("/title2.html"));
   std::string script =
@@ -233,6 +242,13 @@
   EXPECT_NE(webui_site_instance, root->current_frame_host()->GetSiteInstance());
   EXPECT_FALSE(webui_site_instance->IsRelatedSiteInstance(
       root->current_frame_host()->GetSiteInstance()));
+  EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
+      root->current_frame_host()->GetProcess()->GetID()));
+  EXPECT_NE(
+      ChildProcessSecurityPolicyImpl::CheckOriginLockResult::HAS_EQUAL_LOCK,
+      ChildProcessSecurityPolicyImpl::GetInstance()->CheckOriginLock(
+          root->current_frame_host()->GetProcess()->GetID(),
+          webui_site_instance->GetSiteURL()));
 }
 
 IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
@@ -267,4 +283,37 @@
                                          BINDINGS_POLICY_WEB_UI);
 }
 
+IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
+                       WebUIOriginsRequireDedicatedProcess) {
+  // chrome:// URLs should require a dedicated process.
+  WebContents* web_contents = shell()->web_contents();
+  BrowserContext* browser_context = web_contents->GetBrowserContext();
+  GURL chrome_url = GURL(std::string(kChromeUIScheme) + "://" +
+                         std::string(kChromeUIGpuHost));
+  EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(browser_context,
+                                                                chrome_url));
+
+  // Navigate to a WebUI page.
+  EXPECT_TRUE(NavigateToURL(shell(), chrome_url));
+
+  // Verify that the "hostname" is also part of the site URL.
+  GURL site_url = web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL();
+  EXPECT_EQ(chrome_url, site_url);
+
+  // Ask the page to create a blob URL and return back the blob URL.
+  const char* kScript = R"(
+          var blob = new Blob(['foo'], {type : 'text/html'});
+          var url = URL.createObjectURL(blob);
+          url;
+      )";
+  GURL blob_url(EvalJs(shell(), kScript).ExtractString());
+  EXPECT_EQ(url::kBlobScheme, blob_url.scheme());
+
+  // Verify that the blob also requires a dedicated process and that it would
+  // use the same site url as the original page.
+  EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(browser_context,
+                                                                blob_url));
+  EXPECT_EQ(chrome_url, SiteInstance::GetSiteForURL(browser_context, blob_url));
+}
+
 }  // namespace content
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
index 7f5000f..7b8ccdc9 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
@@ -58,6 +58,11 @@
 namespace content {
 namespace {
 
+// TODO(crbug.com/889590): Replace with common converter.
+url::Origin ToOrigin(const std::string& url) {
+  return url::Origin::Create(GURL(url));
+}
+
 ACTION_TEMPLATE(MoveArg,
                 HAS_1_TEMPLATE_PARAMS(int, k),
                 AND_1_VALUE_PARAMS(out)) {
@@ -174,8 +179,9 @@
                     {BrowserThread::IO})),
             context_impl_,
             ChromeBlobStorageContext::GetFor(&browser_context_))) {
-    quota_manager_->SetQuota(
-        GURL(kOrigin), blink::mojom::StorageType::kTemporary, kTemporaryQuota);
+    quota_manager_->SetQuota(ToOrigin(kOrigin),
+                             blink::mojom::StorageType::kTemporary,
+                             kTemporaryQuota);
   }
 
   void TearDown() override {
@@ -240,7 +246,7 @@
   const char kObjectStoreName[] = "os";
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
 
@@ -322,9 +328,8 @@
   // Open connection 2, but expect that we won't be called back.
   IDBDatabaseAssociatedPtrInfo database_info2;
   IndexedDBDatabaseMetadata metadata2;
-  TestDatabaseConnection connection2(url::Origin::Create(GURL(kOrigin)),
-                                     base::UTF8ToUTF16(kDatabaseName),
-                                     kDBVersion, 0);
+  TestDatabaseConnection connection2(
+      ToOrigin(kOrigin), base::UTF8ToUTF16(kDatabaseName), kDBVersion, 0);
   connection2.Open(idb_mojo_factory_.get());
 
   // Check that we're called in order and the second connection gets it's
@@ -448,7 +453,7 @@
   const int64_t kTransactionId = 1;
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -508,7 +513,7 @@
   const char kObjectStoreName[] = "os";
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -573,7 +578,7 @@
   const int64_t kTransactionId = 1;
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -636,7 +641,7 @@
   const int64_t kTransactionId = 1;
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -699,7 +704,7 @@
   const char kObjectStoreName[] = "os";
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -764,7 +769,7 @@
   const int64_t kTransactionId = 1;
 
   // Open connection.
-  TestDatabaseConnection connection(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection(ToOrigin(kOrigin),
                                     base::UTF8ToUTF16(kDatabaseName),
                                     kDBVersion, kTransactionId);
   IndexedDBDatabaseMetadata metadata;
@@ -838,7 +843,7 @@
   context_impl_->AddObserver(&observer);
 
   // Open connection 1.
-  TestDatabaseConnection connection1(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection1(ToOrigin(kOrigin),
                                      base::UTF8ToUTF16(kDatabaseName),
                                      kDBVersion1, kTransactionId1);
   IndexedDBDatabaseMetadata metadata1;
@@ -943,7 +948,7 @@
   connection2.database->Close();
 
   // Open connection 3.
-  TestDatabaseConnection connection3(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection3(ToOrigin(kOrigin),
                                      base::UTF8ToUTF16(kDatabaseName),
                                      kDBVersion3, kTransactionId3);
   IndexedDBDatabaseMetadata metadata3;
@@ -1072,7 +1077,7 @@
   connection1.database->Close();
 
   // Open connection 2.
-  TestDatabaseConnection connection2(url::Origin::Create(GURL(kOrigin)),
+  TestDatabaseConnection connection2(ToOrigin(kOrigin),
                                      base::UTF8ToUTF16(kDatabaseName),
                                      kDBVersion2, kTransactionId2);
   IndexedDBDatabaseMetadata metadata2;
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 90543ca5..26f09ff 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -962,7 +962,6 @@
     // there likely remains more to be done.
     // a. For subframe navigations, the Origin header may need to be modified
     //    differently?
-    // b. How should redirect_info_.referred_token_binding_host be handled?
 
     bool should_clear_upload = false;
     net::RedirectUtil::UpdateHttpRequest(
diff --git a/content/browser/quota_dispatcher_host.cc b/content/browser/quota_dispatcher_host.cc
index dee8d4e..350bdf3b 100644
--- a/content/browser/quota_dispatcher_host.cc
+++ b/content/browser/quota_dispatcher_host.cc
@@ -95,7 +95,7 @@
     StorageType storage_type,
     QueryStorageUsageAndQuotaCallback callback) {
   quota_manager_->GetUsageAndQuotaForWebApps(
-      origin.GetURL(), storage_type,
+      origin, storage_type,
       base::BindOnce(&QuotaDispatcherHost::DidQueryStorageUsageAndQuota,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -126,13 +126,13 @@
          storage_type == StorageType::kPersistent);
   if (storage_type == StorageType::kPersistent) {
     quota_manager_->GetUsageAndQuotaForWebApps(
-        origin.GetURL(), storage_type,
+        origin, storage_type,
         base::BindOnce(&QuotaDispatcherHost::DidGetPersistentUsageAndQuota,
                        weak_factory_.GetWeakPtr(), origin, storage_type,
                        requested_size, std::move(callback)));
   } else {
     quota_manager_->GetUsageAndQuotaForWebApps(
-        origin.GetURL(), storage_type,
+        origin, storage_type,
         base::BindOnce(&QuotaDispatcherHost::DidGetTemporaryUsageAndQuota,
                        weak_factory_.GetWeakPtr(), requested_size,
                        std::move(callback)));
@@ -166,7 +166,7 @@
   // TODO(nhiroki): The backend should accept uint64_t values.
   int64_t requested_quota_signed =
       base::saturated_cast<int64_t>(requested_quota);
-  if (quota_manager_->IsStorageUnlimited(origin.GetURL(), storage_type) ||
+  if (quota_manager_->IsStorageUnlimited(origin, storage_type) ||
       requested_quota_signed <= current_quota) {
     std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk, current_usage,
                             requested_quota);
diff --git a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc
index d54aa97..c41c809 100644
--- a/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc
+++ b/content/browser/renderer_host/pepper/pepper_file_system_browser_host.cc
@@ -412,7 +412,7 @@
   storage::FileSystemType file_system_type =
       PepperFileSystemTypeToFileSystemType(type_);
   return !quota_manager_proxy->quota_manager()->IsStorageUnlimited(
-      root_url_.GetOrigin(),
+      url::Origin::Create(root_url_),
       storage::FileSystemTypeToQuotaStorageType(file_system_type));
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 95444b0..404a1e7 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -568,7 +568,7 @@
     return manager->GetRoot()->GetNativeViewAccessible();
 #endif
 
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
   return static_cast<gfx::NativeViewAccessible>(nullptr);
 }
 
@@ -1272,7 +1272,7 @@
 }
 
 base::i18n::TextDirection RenderWidgetHostViewAura::GetTextDirection() const {
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
   return base::i18n::UNKNOWN_DIRECTION;
 }
 
@@ -1382,7 +1382,7 @@
 bool RenderWidgetHostViewAura::GetCompositionTextRange(
     gfx::Range* range) const {
   // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
   return false;
 }
 
@@ -1402,13 +1402,13 @@
 
 bool RenderWidgetHostViewAura::SetSelectionRange(const gfx::Range& range) {
   // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
   return false;
 }
 
 bool RenderWidgetHostViewAura::DeleteRange(const gfx::Range& range) {
   // TODO(suzhe): implement this method when fixing http://crbug.com/55130.
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
   return false;
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index 0fdcb47..e2b2712b 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -92,7 +92,6 @@
   // |delegate| should be set at most once.
   CONTENT_EXPORT void SetDelegate(
     NSObject<RenderWidgetHostViewMacDelegate>* delegate);
-  void SetAllowPauseForResizeOrRepaint(bool allow);
 
   // RenderWidgetHostView implementation.
   void InitAsChild(gfx::NativeView parent_view) override;
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index fc8e9dec..d826ceb 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -217,13 +217,9 @@
   // startup raciness and decrease latency.
   needs_begin_frames_ = needs_begin_frames;
   UpdateNeedsBeginFramesInternal();
-  if (features::IsViewsBrowserCocoa())
-    ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
 }
 
 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
-  if (features::IsViewsBrowserCocoa())
-    ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this);
   if (popup_parent_host_view_) {
     DCHECK(!popup_parent_host_view_->popup_child_host_view_ ||
            popup_parent_host_view_->popup_child_host_view_ == this);
@@ -308,11 +304,6 @@
   [cocoa_view() setResponderDelegate:delegate];
 }
 
-void RenderWidgetHostViewMac::SetAllowPauseForResizeOrRepaint(bool allow) {
-  // TODO: Remove SetAllowPauseForResizeOrRepaint and SetAllowOtherViews, since
-  // they aren't used anymore.
-}
-
 ui::TextInputType RenderWidgetHostViewMac::GetTextInputType() {
   if (!GetActiveWidget())
     return ui::TEXT_INPUT_TYPE_NONE;
@@ -409,9 +400,6 @@
     LOG(ERROR) << "Failed to create display link.";
   }
 
-  if (features::IsViewsBrowserCocoa())
-    ui::CATransactionCoordinator::Get().Synchronize();
-
   // During auto-resize it is the responsibility of the caller to ensure that
   // the NSView and RenderWidgetHostImpl are kept in sync.
   if (host()->auto_resize_enabled())
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 5552d4a..473bd8d 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -464,9 +464,8 @@
   // Handle a redirect response. ComputeRedirectInfo returns non-null redirect
   // info if the given response is a redirect.
   base::Optional<net::RedirectInfo> redirect_info =
-      ServiceWorkerLoaderHelpers::ComputeRedirectInfo(
-          resource_request_, response_head_,
-          response_head_.ssl_info->token_binding_negotiated);
+      ServiceWorkerLoaderHelpers::ComputeRedirectInfo(resource_request_,
+                                                      response_head_);
   if (redirect_info) {
     TRACE_EVENT_WITH_FLOW2(
         "ServiceWorker", "ServiceWorkerNavigationLoader::StartResponse", this,
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index f09529d..2f27e67 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -569,18 +569,23 @@
   if (SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
     return true;
 
-  // Error pages in main frames do require isolation, however since this is
-  // missing the context whether this is for a main frame or not, that part
-  // is enforced in RenderFrameHostManager.
-  if (url.SchemeIs(kChromeErrorScheme))
-    return true;
-
   // Always require a dedicated process for isolated origins.
   GURL site_url = SiteInstance::GetSiteForURL(browser_context, url);
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   if (policy->IsIsolatedOrigin(url::Origin::Create(site_url)))
     return true;
 
+  // Error pages in main frames do require isolation, however since this is
+  // missing the context whether this is for a main frame or not, that part
+  // is enforced in RenderFrameHostManager.
+  if (site_url.SchemeIs(kChromeErrorScheme))
+    return true;
+
+  // Isolate kChromeUIScheme pages from one another and from other kinds of
+  // schemes.
+  if (site_url.SchemeIs(content::kChromeUIScheme))
+    return true;
+
   // Let the content embedder enable site isolation for specific URLs. Use the
   // canonical site url for this check, so that schemes with nested origins
   // (blob and filesystem) work properly.
@@ -611,12 +616,6 @@
   if (site_url.SchemeIs(content::kGuestScheme))
     return false;
 
-  // TODO(creis, nick) https://crbug.com/510588 Chrome UI pages use the same
-  // site (chrome://chrome), so they can't be locked because the site being
-  // loaded doesn't match the SiteInstance.
-  if (site_url.SchemeIs(content::kChromeUIScheme))
-    return false;
-
   // TODO(creis, nick): Until we can handle sites with effective URLs at the
   // call sites of ChildProcessSecurityPolicy::CanAccessDataForOrigin, we
   // must give the embedder a chance to exempt some sites to avoid process
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 41f75a3b..1fd745d 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -232,12 +232,12 @@
   // true here also implies that |site_url| requires a dedicated process.
   // However, the converse does not hold: this might still return false for
   // certain special cases where an origin lock can't be applied even when
-  // |site_url| requires a dedicated process (e.g., with
-  // --site-per-process).  Examples of those cases include <webview> guests,
-  // WebUI, single-process mode, or extensions where a process is currently
-  // allowed to be reused for different extensions.  Most of these special
-  // cases should eventually be removed, and this function should become
-  // equivalent to DoesSiteRequireDedicatedProcess().
+  // |site_url| requires a dedicated process (e.g., with --site-per-process).
+  // Examples of those cases include <webview> guests, single-process mode, or
+  // extensions where a process is currently allowed to be reused for different
+  // extensions.  Most of these special cases should eventually be removed, and
+  // this function should become equivalent to
+  // DoesSiteRequireDedicatedProcess().
   static bool ShouldLockToOrigin(BrowserContext* browser_context,
                                  GURL site_url);
 
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index ec3fa59..412ca32b 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -676,79 +676,6 @@
   DrainMessageLoop();
 }
 
-static scoped_refptr<SiteInstanceImpl> CreateSiteInstance(
-    BrowserContext* browser_context,
-    const GURL& url) {
-  return SiteInstanceImpl::CreateForURL(browser_context, url);
-}
-
-// Test to ensure that pages that require certain privileges are grouped
-// in processes with similar pages.
-// TODO(nasko): Remove. See https://crbug.com/847127.
-TEST_F(SiteInstanceTest, ProcessSharingByType) {
-  // This test shouldn't run with --site-per-process mode, which prohibits
-  // the renderer process reuse this test explicitly exercises.
-  if (AreAllSitesIsolatedForTesting())
-    return;
-
-  ChildProcessSecurityPolicyImpl* policy =
-      ChildProcessSecurityPolicyImpl::GetInstance();
-
-  // Make a bunch of mock renderers so that we hit the limit.
-  std::unique_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
-  std::vector<std::unique_ptr<MockRenderProcessHost>> hosts;
-  for (size_t i = 0; i < kMaxRendererProcessCount; ++i) {
-    hosts.push_back(
-        std::make_unique<MockRenderProcessHost>(browser_context.get()));
-    hosts[i]->SetIsUsed();
-  }
-
-  // On Android by default the number of renderer hosts is unlimited and process
-  // sharing doesn't happen. We set the override so that the test can run
-  // everywhere.
-  RenderProcessHost::SetMaxRendererProcessCount(kMaxRendererProcessCount);
-
-  // Create some extension instances and make sure they share a process.
-  scoped_refptr<SiteInstanceImpl> extension1_instance(
-      CreateSiteInstance(browser_context.get(),
-          GURL(kPrivilegedScheme + std::string("://foo/bar"))));
-  set_privileged_process_id(extension1_instance->GetProcess()->GetID());
-
-  scoped_refptr<SiteInstanceImpl> extension2_instance(
-      CreateSiteInstance(browser_context.get(),
-          GURL(kPrivilegedScheme + std::string("://baz/bar"))));
-
-  std::unique_ptr<RenderProcessHost> extension_host(
-      extension1_instance->GetProcess());
-  EXPECT_EQ(extension1_instance->GetProcess(),
-            extension2_instance->GetProcess());
-
-  // Create some WebUI instances and make sure they share a process.
-  scoped_refptr<SiteInstanceImpl> webui1_instance(
-      CreateSiteInstance(browser_context.get(), GetWebUIURL(kChromeUIGpuHost)));
-  policy->GrantWebUIBindings(webui1_instance->GetProcess()->GetID(),
-                             BINDINGS_POLICY_WEB_UI);
-
-  scoped_refptr<SiteInstanceImpl> webui2_instance(CreateSiteInstance(
-      browser_context.get(), GetWebUIURL(kChromeUIMediaInternalsHost)));
-
-  std::unique_ptr<RenderProcessHost> dom_host(webui1_instance->GetProcess());
-  EXPECT_EQ(webui1_instance->GetProcess(), webui2_instance->GetProcess());
-
-  // Make sure none of differing privilege processes are mixed.
-  EXPECT_NE(extension1_instance->GetProcess(), webui1_instance->GetProcess());
-
-  for (size_t i = 0; i < kMaxRendererProcessCount; ++i) {
-    EXPECT_NE(extension1_instance->GetProcess(), hosts[i].get());
-    EXPECT_NE(webui1_instance->GetProcess(), hosts[i].get());
-  }
-
-  DrainMessageLoop();
-
-  // Disable the process limit override.
-  RenderProcessHost::SetMaxRendererProcessCount(0u);
-}
-
 // Test to ensure that HasWrongProcessForURL behaves properly for different
 // types of URLs.
 TEST_F(SiteInstanceTest, HasWrongProcessForURL) {
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index ab06c5eb..4887d13 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -16,6 +16,7 @@
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/location.h"
+#include "base/optional.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
@@ -106,7 +107,7 @@
   }
 }
 
-void OnQuotaManagedOriginDeleted(const GURL& origin,
+void OnQuotaManagedOriginDeleted(const url::Origin& origin,
                                  blink::mojom::StorageType type,
                                  size_t* deletion_task_count,
                                  base::OnceClosure callback,
@@ -353,15 +354,19 @@
 // Most of the operations in this class are done on IO thread.
 class StoragePartitionImpl::QuotaManagedDataDeletionHelper {
  public:
-  QuotaManagedDataDeletionHelper(uint32_t remove_mask,
-                                 uint32_t quota_storage_remove_mask,
-                                 const GURL& storage_origin,
-                                 base::OnceClosure callback)
+  QuotaManagedDataDeletionHelper(
+      uint32_t remove_mask,
+      uint32_t quota_storage_remove_mask,
+      const base::Optional<url::Origin>& storage_origin,
+      base::OnceClosure callback)
       : remove_mask_(remove_mask),
         quota_storage_remove_mask_(quota_storage_remove_mask),
         storage_origin_(storage_origin),
         callback_(std::move(callback)),
-        task_count_(0) {}
+        task_count_(0) {
+    DCHECK(!storage_origin_.has_value() ||
+           !storage_origin_->GetURL().is_empty());
+  }
 
   void IncrementTaskCountOnIO();
   void DecrementTaskCountOnIO();
@@ -379,14 +384,14 @@
           special_storage_policy,
       const StoragePartition::OriginMatcherFunction& origin_matcher,
       base::OnceClosure callback,
-      const std::set<GURL>& origins,
+      const std::set<url::Origin>& origins,
       blink::mojom::StorageType quota_storage_type);
 
  private:
   // All of these data are accessed on IO thread.
   uint32_t remove_mask_;
   uint32_t quota_storage_remove_mask_;
-  GURL storage_origin_;
+  base::Optional<url::Origin> storage_origin_;
   base::OnceClosure callback_;
   int task_count_;
 
@@ -488,7 +493,10 @@
 
   StoragePartitionImpl::QuotaManagedDataDeletionHelper* helper =
       new StoragePartitionImpl::QuotaManagedDataDeletionHelper(
-          remove_mask_, quota_storage_remove_mask_, storage_origin,
+          remove_mask_, quota_storage_remove_mask_,
+          storage_origin.is_empty()
+              ? base::nullopt
+              : base::make_optional(url::Origin::Create(storage_origin)),
           std::move(callback));
   helper->ClearDataOnIOThread(quota_manager, begin, special_storage_policy,
                               origin_matcher);
@@ -1009,7 +1017,7 @@
             special_storage_policy,
         const StoragePartition::OriginMatcherFunction& origin_matcher,
         base::OnceClosure callback,
-        const std::set<GURL>& origins,
+        const std::set<url::Origin>& origins,
         blink::mojom::StorageType quota_storage_type) {
   // The QuotaManager manages all storage other than cookies, LocalStorage,
   // and SessionStorage. This loop wipes out most HTML5 storage for the given
@@ -1027,23 +1035,22 @@
 
   size_t* deletion_task_count = new size_t(0u);
   (*deletion_task_count)++;
-  for (std::set<GURL>::const_iterator origin = origins.begin();
-       origin != origins.end(); ++origin) {
+  for (const auto& origin : origins) {
     // TODO(mkwst): Clean this up, it's slow. http://crbug.com/130746
-    if (!storage_origin_.is_empty() && origin->GetOrigin() != storage_origin_)
+    if (storage_origin_.has_value() && origin != *storage_origin_)
       continue;
 
     if (!origin_matcher.is_null() &&
-        !origin_matcher.Run(*origin, special_storage_policy.get())) {
+        !origin_matcher.Run(origin.GetURL(), special_storage_policy.get())) {
       continue;
     }
 
     (*deletion_task_count)++;
     quota_manager->DeleteOriginData(
-        *origin, quota_storage_type,
+        origin, quota_storage_type,
         StoragePartitionImpl::GenerateQuotaClientMask(remove_mask_),
-        base::BindOnce(&OnQuotaManagedOriginDeleted, origin->GetOrigin(),
-                       quota_storage_type, deletion_task_count, completion));
+        base::BindOnce(&OnQuotaManagedOriginDeleted, origin, quota_storage_type,
+                       deletion_task_count, completion));
   }
   (*deletion_task_count)--;
 
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index 3f2be88..03a95d8 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -71,10 +71,12 @@
 const char kClearKeyCdmPluginId[] = "application_x-ppapi-clearkey-cdm";
 #endif  // BUILDFLAG(ENABLE_PLUGINS)
 
-const GURL kOrigin1(kTestOrigin1);
-const GURL kOrigin2(kTestOrigin2);
-const GURL kOrigin3(kTestOrigin3);
-const GURL kOriginDevTools(kTestOriginDevTools);
+// TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
+const url::Origin kOrigin2 = url::Origin::Create(GURL(kTestOrigin2));
+const url::Origin kOrigin3 = url::Origin::Create(GURL(kTestOrigin3));
+const url::Origin kOriginDevTools =
+    url::Origin::Create(GURL(kTestOriginDevTools));
 const GURL kResourceURL(kTestURL);
 
 const blink::mojom::StorageType kTemporary =
@@ -138,7 +140,7 @@
   bool ContainsCookie() {
     get_cookie_success_ = false;
     cookie_store_->GetCookieListWithOptionsAsync(
-        kOrigin1, net::CookieOptions(),
+        kOrigin1.GetURL(), net::CookieOptions(),
         base::BindOnce(&RemoveCookieTester::GetCookieListCallback,
                        base::Unretained(this)));
     await_completion_.BlockUntilNotified();
@@ -147,7 +149,7 @@
 
   void AddCookie() {
     cookie_store_->SetCookieWithOptionsAsync(
-        kOrigin1, "A=1", net::CookieOptions(),
+        kOrigin1.GetURL(), "A=1", net::CookieOptions(),
         base::BindOnce(&RemoveCookieTester::SetCookieCallback,
                        base::Unretained(this)));
     await_completion_.BlockUntilNotified();
@@ -190,11 +192,11 @@
   }
 
   // Returns true, if the given origin URL exists.
-  bool DOMStorageExistsForOrigin(const GURL& origin) {
+  bool DOMStorageExistsForOrigin(const url::Origin& origin) {
     GetLocalStorageUsage();
     await_completion_.BlockUntilNotified();
     for (size_t i = 0; i < infos_.size(); ++i) {
-      if (origin == infos_[i].origin)
+      if (origin == url::Origin::Create(infos_[i].origin))
         return true;
     }
     return false;
@@ -215,21 +217,21 @@
     base::Time now = base::Time::Now();
     data.set_last_modified(now.ToInternalValue());
     data.set_size_bytes(16);
-    mock_data_[CreateMetaDataKey(url::Origin::Create(kOrigin1))] =
+    mock_data_[CreateMetaDataKey(kOrigin1)] =
         leveldb::StdStringToUint8Vector(data.SerializeAsString());
-    mock_data_[CreateDataKey(url::Origin::Create(kOrigin1))] = {};
+    mock_data_[CreateDataKey(kOrigin1)] = {};
 
     base::Time one_day_ago = now - base::TimeDelta::FromDays(1);
     data.set_last_modified(one_day_ago.ToInternalValue());
-    mock_data_[CreateMetaDataKey(url::Origin::Create(kOrigin2))] =
+    mock_data_[CreateMetaDataKey(kOrigin2)] =
         leveldb::StdStringToUint8Vector(data.SerializeAsString());
-    mock_data_[CreateDataKey(url::Origin::Create(kOrigin2))] = {};
+    mock_data_[CreateDataKey(kOrigin2)] = {};
 
     base::Time sixty_days_ago = now - base::TimeDelta::FromDays(60);
     data.set_last_modified(sixty_days_ago.ToInternalValue());
-    mock_data_[CreateMetaDataKey(url::Origin::Create(kOrigin3))] =
+    mock_data_[CreateMetaDataKey(kOrigin3)] =
         leveldb::StdStringToUint8Vector(data.SerializeAsString());
-    mock_data_[CreateDataKey(url::Origin::Create(kOrigin3))] = {};
+    mock_data_[CreateDataKey(kOrigin3)] = {};
   }
 
  private:
@@ -341,18 +343,18 @@
     // Create a PluginPrivateFileSystem for ClearKey and add a single file
     // with a timestamp of 1 day ago.
     std::string clearkey_fsid =
-        CreateFileSystem(kClearKeyCdmPluginId, kOrigin1);
-    clearkey_file_ = CreateFile(kOrigin1, clearkey_fsid, "foo");
+        CreateFileSystem(kClearKeyCdmPluginId, kOrigin1.GetURL());
+    clearkey_file_ = CreateFile(kOrigin1.GetURL(), clearkey_fsid, "foo");
     SetFileTimestamp(clearkey_file_, ten_days_ago);
 
     // Create a second PluginPrivateFileSystem for Widevine and add two files
     // with different times.
     std::string widevine_fsid =
-        CreateFileSystem(kWidevineCdmPluginId, kOrigin2);
+        CreateFileSystem(kWidevineCdmPluginId, kOrigin2.GetURL());
     storage::FileSystemURL widevine_file1 =
-        CreateFile(kOrigin2, widevine_fsid, "bar1");
+        CreateFile(kOrigin2.GetURL(), widevine_fsid, "bar1");
     storage::FileSystemURL widevine_file2 =
-        CreateFile(kOrigin2, widevine_fsid, "bar2");
+        CreateFile(kOrigin2.GetURL(), widevine_fsid, "bar2");
     SetFileTimestamp(widevine_file1, now);
     SetFileTimestamp(widevine_file2, sixty_days_ago);
   }
@@ -360,7 +362,7 @@
   void DeleteClearKeyTestData() { DeleteFile(clearkey_file_); }
 
   // Returns true, if the given origin exists in a PluginPrivateFileSystem.
-  bool DataExistsForOrigin(const GURL& origin) {
+  bool DataExistsForOrigin(const url::Origin& origin) {
     AwaitCompletionHelper await_completion;
     bool data_exists_for_origin = false;
     filesystem_context_->default_file_task_runner()->PostTask(
@@ -508,7 +510,7 @@
   // If |origin| exists in the PluginPrivateFileSystem, set
   // |data_exists_for_origin| to true, false otherwise.
   void CheckIfDataExistsForOriginOnFileTaskRunner(
-      const GURL& origin,
+      const url::Origin& origin,
       bool* data_exists_for_origin,
       AwaitCompletionHelper* await_completion) {
     storage::FileSystemBackend* backend =
@@ -520,7 +522,7 @@
     std::set<GURL> origins;
     quota_util->GetOriginsForTypeOnFileTaskRunner(
         storage::kFileSystemTypePluginPrivate, &origins);
-    *data_exists_for_origin = origins.find(origin) != origins.end();
+    *data_exists_for_origin = origins.find(origin.GetURL()) != origins.end();
 
     // AwaitCompletionHelper and MessageLoop don't work on a
     // SequencedTaskRunner, so post a task on the IO thread.
@@ -800,7 +802,7 @@
 void PopulateTestQuotaManagedPersistentData(MockQuotaManager* manager) {
   manager->AddOrigin(kOrigin2, kPersistent, kClientFile, base::Time());
   manager->AddOrigin(kOrigin3, kPersistent, kClientFile,
-      base::Time::Now() - base::TimeDelta::FromDays(1));
+                     base::Time::Now() - base::TimeDelta::FromDays(1));
 
   EXPECT_FALSE(manager->OriginHasData(kOrigin1, kPersistent, kClientFile));
   EXPECT_TRUE(manager->OriginHasData(kOrigin2, kPersistent, kClientFile));
@@ -810,7 +812,7 @@
 void PopulateTestQuotaManagedTemporaryData(MockQuotaManager* manager) {
   manager->AddOrigin(kOrigin1, kTemporary, kClientFile, base::Time::Now());
   manager->AddOrigin(kOrigin3, kTemporary, kClientFile,
-      base::Time::Now() - base::TimeDelta::FromDays(1));
+                     base::Time::Now() - base::TimeDelta::FromDays(1));
 
   EXPECT_TRUE(manager->OriginHasData(kOrigin1, kTemporary, kClientFile));
   EXPECT_FALSE(manager->OriginHasData(kOrigin2, kTemporary, kClientFile));
@@ -844,18 +846,18 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyTemporary) {
@@ -871,18 +873,18 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyPersistent) {
@@ -898,18 +900,18 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNeither) {
@@ -923,18 +925,18 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverSpecificOrigin) {
@@ -947,22 +949,22 @@
 
   base::RunLoop run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&ClearQuotaDataForOrigin, partition, kOrigin1,
-                                base::Time(), &run_loop));
+      FROM_HERE, base::BindOnce(&ClearQuotaDataForOrigin, partition,
+                                kOrigin1.GetURL(), base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastHour) {
@@ -981,18 +983,18 @@
                      &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastWeek) {
@@ -1010,25 +1012,25 @@
                      &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedUnprotectedOrigins) {
   // Protect kOrigin1.
   scoped_refptr<MockSpecialStoragePolicy> mock_policy =
       new MockSpecialStoragePolicy;
-  mock_policy->AddProtected(kOrigin1.GetOrigin());
+  mock_policy->AddProtected(kOrigin1.GetURL());
 
   PopulateTestQuotaManagedData(GetMockManager());
 
@@ -1042,29 +1044,29 @@
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, GURL(),
-                     base::Bind(&DoesOriginMatchForUnprotectedWeb),
+                     base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
                      base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedSpecificOrigin) {
   // Protect kOrigin1.
   scoped_refptr<MockSpecialStoragePolicy> mock_policy =
       new MockSpecialStoragePolicy;
-  mock_policy->AddProtected(kOrigin1.GetOrigin());
+  mock_policy->AddProtected(kOrigin1.GetURL());
 
   PopulateTestQuotaManagedData(GetMockManager());
 
@@ -1078,30 +1080,31 @@
   base::RunLoop run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, kOrigin1,
-                     base::Bind(&DoesOriginMatchForUnprotectedWeb),
+      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
+                     kOrigin1.GetURL(),
+                     base::BindRepeating(&DoesOriginMatchForUnprotectedWeb),
                      base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_TRUE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedOrigins) {
   // Protect kOrigin1.
   scoped_refptr<MockSpecialStoragePolicy> mock_policy =
       new MockSpecialStoragePolicy;
-  mock_policy->AddProtected(kOrigin1.GetOrigin());
+  mock_policy->AddProtected(kOrigin1.GetURL());
 
   PopulateTestQuotaManagedData(GetMockManager());
 
@@ -1114,24 +1117,24 @@
   partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::BindOnce(
-          &ClearQuotaDataWithOriginMatcher, partition, GURL(),
-          base::Bind(&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
-          base::Time(), &run_loop));
+      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, GURL(),
+                     base::BindRepeating(
+                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
+                     base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kTemporary,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin1, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin2, kPersistent,
-      kClientFile));
-  EXPECT_FALSE(GetMockManager()->OriginHasData(kOrigin3, kPersistent,
-      kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kTemporary, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin1, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin2, kPersistent, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->OriginHasData(kOrigin3, kPersistent, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedIgnoreDevTools) {
@@ -1143,16 +1146,17 @@
   partition->OverrideQuotaManagerForTesting(
       GetMockManager());
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
-                                GURL(), base::Bind(&DoesOriginMatchUnprotected),
-                                base::Time(), &run_loop));
+      FROM_HERE,
+      base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition, GURL(),
+                     base::BindRepeating(&DoesOriginMatchUnprotected),
+                     base::Time(), &run_loop));
   run_loop.Run();
 
   // Check that devtools data isn't removed.
   EXPECT_TRUE(GetMockManager()->OriginHasData(kOriginDevTools, kTemporary,
-      kClientFile));
+                                              kClientFile));
   EXPECT_TRUE(GetMockManager()->OriginHasData(kOriginDevTools, kPersistent,
-      kClientFile));
+                                              kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveCookieForever) {
@@ -1216,42 +1220,7 @@
   // Protect kOrigin1.
   scoped_refptr<MockSpecialStoragePolicy> mock_policy =
       new MockSpecialStoragePolicy;
-  mock_policy->AddProtected(kOrigin1.GetOrigin());
-
-  RemoveLocalStorageTester tester(browser_context());
-
-  tester.AddDOMStorageTestData();
-  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
-  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin2));
-  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
-
-  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
-      BrowserContext::GetDefaultStoragePartition(browser_context()));
-  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
-
-  base::RunLoop run_loop;
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ClearStuff,
-                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
-                     partition, base::Time(), base::Time::Max(),
-                     base::Bind(&DoesOriginMatchForUnprotectedWeb), &run_loop));
-  run_loop.Run();
-  // ClearData only guarantees that tasks to delete data are scheduled when its
-  // callback is invoked. It doesn't guarantee data has actually been cleared.
-  // So run all scheduled tasks to make sure data is cleared.
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
-  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
-  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
-}
-
-TEST_F(StoragePartitionImplTest, RemoveProtectedLocalStorageForever) {
-  // Protect kOrigin1.
-  scoped_refptr<MockSpecialStoragePolicy> mock_policy =
-      new MockSpecialStoragePolicy;
-  mock_policy->AddProtected(kOrigin1.GetOrigin());
+  mock_policy->AddProtected(kOrigin1.GetURL());
 
   RemoveLocalStorageTester tester(browser_context());
 
@@ -1270,8 +1239,44 @@
       base::BindOnce(
           &ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
           partition, base::Time(), base::Time::Max(),
-          base::Bind(&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
-          &run_loop));
+          base::BindRepeating(&DoesOriginMatchForUnprotectedWeb), &run_loop));
+  run_loop.Run();
+  // ClearData only guarantees that tasks to delete data are scheduled when its
+  // callback is invoked. It doesn't guarantee data has actually been cleared.
+  // So run all scheduled tasks to make sure data is cleared.
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
+  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin2));
+  EXPECT_FALSE(tester.DOMStorageExistsForOrigin(kOrigin3));
+}
+
+TEST_F(StoragePartitionImplTest, RemoveProtectedLocalStorageForever) {
+  // Protect kOrigin1.
+  scoped_refptr<MockSpecialStoragePolicy> mock_policy =
+      new MockSpecialStoragePolicy;
+  mock_policy->AddProtected(kOrigin1.GetURL());
+
+  RemoveLocalStorageTester tester(browser_context());
+
+  tester.AddDOMStorageTestData();
+  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin1));
+  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin2));
+  EXPECT_TRUE(tester.DOMStorageExistsForOrigin(kOrigin3));
+
+  StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
+      BrowserContext::GetDefaultStoragePartition(browser_context()));
+  partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
+
+  base::RunLoop run_loop;
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ClearStuff,
+                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
+                     partition, base::Time(), base::Time::Max(),
+                     base::BindRepeating(
+                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
+                     &run_loop));
   run_loop.Run();
   // ClearData only guarantees that tasks to delete data are scheduled when its
   // callback is invoked. It doesn't guarantee data has actually been cleared.
@@ -1300,11 +1305,12 @@
   base::RunLoop run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::BindOnce(
-          &ClearStuff, StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
-          partition, a_week_ago, base::Time::Max(),
-          base::Bind(&DoesOriginMatchForBothProtectedAndUnprotectedWeb),
-          &run_loop));
+      base::BindOnce(&ClearStuff,
+                     StoragePartitionImpl::REMOVE_DATA_MASK_LOCAL_STORAGE,
+                     partition, a_week_ago, base::Time::Max(),
+                     base::BindRepeating(
+                         &DoesOriginMatchForBothProtectedAndUnprotectedWeb),
+                     &run_loop));
   run_loop.Run();
   // ClearData only guarantees that tasks to delete data are scheduled when its
   // callback is invoked. It doesn't guarantee data has actually been cleared.
@@ -1329,7 +1335,7 @@
 
   RemoveCodeCacheTester tester(partition->GetGeneratedCodeCacheContext());
 
-  url::Origin origin = url::Origin::Create(kOrigin1);
+  url::Origin origin = kOrigin1;
   std::string data("SomeData");
   tester.AddEntry(kResourceURL, origin, data);
   EXPECT_TRUE(tester.ContainsEntry(kResourceURL, origin));
@@ -1436,8 +1442,9 @@
 
   base::RunLoop run_loop;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&ClearPluginPrivateData, partition, kOrigin1,
-                                base::Time(), base::Time::Max(), &run_loop));
+      FROM_HERE,
+      base::BindOnce(&ClearPluginPrivateData, partition, kOrigin1.GetURL(),
+                     base::Time(), base::Time::Max(), &run_loop));
   run_loop.Run();
 
   // Only Origin1 should be deleted.
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index e71ddb1..a1990c56 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -655,7 +655,7 @@
           record_mode);
     case BackgroundTracingConfigImpl::CategoryPreset::BENCHMARK_GPU:
       return TraceConfig(
-          "benchmark,toplevel,gpu,base,mojom,"
+          "benchmark,toplevel,gpu,base,mojom,ipc,"
           "disabled-by-default-system_stats,disabled-by-default-cpu_profiler",
           record_mode);
     case BackgroundTracingConfigImpl::CategoryPreset::BENCHMARK_IPC:
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index d545411..169b1d8 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -6229,16 +6229,6 @@
   return java_interfaces_.get();
 }
 
-#elif defined(OS_MACOSX)
-
-void WebContentsImpl::SetAllowOtherViews(bool allow) {
-  view_->SetAllowOtherViews(allow);
-}
-
-bool WebContentsImpl::GetAllowOtherViews() {
-  return view_->GetAllowOtherViews();
-}
-
 #endif
 
 bool WebContentsImpl::CompletedFirstVisuallyNonEmptyPaint() const {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 511f9db..44f9ba81 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -464,9 +464,6 @@
   void ActivateNearestFindResult(float x, float y) override;
   void RequestFindMatchRects(int current_version) override;
   service_manager::InterfaceProvider* GetJavaInterfaces() override;
-#elif defined(OS_MACOSX)
-  void SetAllowOtherViews(bool allow) override;
-  bool GetAllowOtherViews() override;
 #endif
 
   bool HasRecentInteractiveInputEvent() const override;
diff --git a/content/browser/web_contents/web_contents_view.h b/content/browser/web_contents/web_contents_view.h
index 281a3d6..5426d60 100644
--- a/content/browser/web_contents/web_contents_view.h
+++ b/content/browser/web_contents/web_contents_view.h
@@ -123,13 +123,6 @@
   virtual void SetOverscrollControllerEnabled(bool enabled) = 0;
 
 #if defined(OS_MACOSX)
-  // Allowing other views disables optimizations which assume that only a single
-  // WebContents is present.
-  virtual void SetAllowOtherViews(bool allow) = 0;
-
-  // Returns true if other views are allowed, false otherwise.
-  virtual bool GetAllowOtherViews() const = 0;
-
   // If we close the tab while a UI control is in an event-tracking
   // loop, the control may message freed objects and crash.
   // WebContents::Close() calls IsEventTracking(), and if it returns
diff --git a/content/browser/web_contents/web_contents_view_child_frame.cc b/content/browser/web_contents/web_contents_view_child_frame.cc
index 015812b..7b339bf 100644
--- a/content/browser/web_contents/web_contents_view_child_frame.cc
+++ b/content/browser/web_contents/web_contents_view_child_frame.cc
@@ -117,15 +117,6 @@
 void WebContentsViewChildFrame::CloseTabAfterEventTracking() {
   NOTREACHED();
 }
-
-void WebContentsViewChildFrame::SetAllowOtherViews(bool allow) {
-  NOTREACHED();
-}
-
-bool WebContentsViewChildFrame::GetAllowOtherViews() const {
-  NOTREACHED();
-  return false;
-}
 #endif
 
 void WebContentsViewChildFrame::RestoreFocus() {
diff --git a/content/browser/web_contents/web_contents_view_child_frame.h b/content/browser/web_contents/web_contents_view_child_frame.h
index 7f7107e..e82cced 100644
--- a/content/browser/web_contents/web_contents_view_child_frame.h
+++ b/content/browser/web_contents/web_contents_view_child_frame.h
@@ -50,8 +50,6 @@
                              RenderViewHost* new_host) override;
   void SetOverscrollControllerEnabled(bool enabled) override;
 #if defined(OS_MACOSX)
-  void SetAllowOtherViews(bool allow) override;
-  bool GetAllowOtherViews() const override;
   bool IsEventTracking() const override;
   void CloseTabAfterEventTracking() override;
 #endif
diff --git a/content/browser/web_contents/web_contents_view_guest.cc b/content/browser/web_contents/web_contents_view_guest.cc
index 65c709c..5de4d7c 100644
--- a/content/browser/web_contents/web_contents_view_guest.cc
+++ b/content/browser/web_contents/web_contents_view_guest.cc
@@ -117,16 +117,6 @@
   return gfx::Rect(size_);
 }
 
-#if defined(OS_MACOSX)
-void WebContentsViewGuest::SetAllowOtherViews(bool allow) {
-  platform_view_->SetAllowOtherViews(allow);
-}
-
-bool WebContentsViewGuest::GetAllowOtherViews() const {
-  return platform_view_->GetAllowOtherViews();
-}
-#endif
-
 void WebContentsViewGuest::CreateView(const gfx::Size& initial_size,
                                       gfx::NativeView context) {
   platform_view_->CreateView(initial_size, context);
diff --git a/content/browser/web_contents/web_contents_view_guest.h b/content/browser/web_contents/web_contents_view_guest.h
index b9691d9..1f0e6616 100644
--- a/content/browser/web_contents/web_contents_view_guest.h
+++ b/content/browser/web_contents/web_contents_view_guest.h
@@ -68,8 +68,6 @@
                              RenderViewHost* new_host) override;
   void SetOverscrollControllerEnabled(bool enabled) override;
 #if defined(OS_MACOSX)
-  void SetAllowOtherViews(bool allow) override;
-  bool GetAllowOtherViews() const override;
   bool IsEventTracking() const override;
   void CloseTabAfterEventTracking() override;
 #endif
diff --git a/content/browser/web_contents/web_contents_view_mac.h b/content/browser/web_contents/web_contents_view_mac.h
index 21b40228..0b37270 100644
--- a/content/browser/web_contents/web_contents_view_mac.h
+++ b/content/browser/web_contents/web_contents_view_mac.h
@@ -91,8 +91,6 @@
   void FocusThroughTabTraversal(bool reverse) override;
   DropData* GetDropData() const override;
   gfx::Rect GetViewBounds() const override;
-  void SetAllowOtherViews(bool allow) override;
-  bool GetAllowOtherViews() const override;
   void CreateView(const gfx::Size& initial_size,
                   gfx::NativeView context) override;
   RenderWidgetHostViewBase* CreateViewForWidget(
@@ -165,9 +163,6 @@
   // Our optional delegate.
   std::unique_ptr<WebContentsViewDelegate> delegate_;
 
-  // Whether to allow other views.
-  bool allow_other_views_;
-
   // This contains all RenderWidgetHostViewMacs that have been added as child
   // NSViews to this NSView. Note that this list may contain RWHVMacs besides
   // just |web_contents_->GetRenderWidgetHostView()|. The only time that the
diff --git a/content/browser/web_contents/web_contents_view_mac.mm b/content/browser/web_contents/web_contents_view_mac.mm
index cb6b58f..325c9c8 100644
--- a/content/browser/web_contents/web_contents_view_mac.mm
+++ b/content/browser/web_contents/web_contents_view_mac.mm
@@ -105,10 +105,7 @@
 
 WebContentsViewMac::WebContentsViewMac(WebContentsImpl* web_contents,
                                        WebContentsViewDelegate* delegate)
-    : web_contents_(web_contents),
-      delegate_(delegate),
-      allow_other_views_(false) {
-}
+    : web_contents_(web_contents), delegate_(delegate) {}
 
 WebContentsViewMac::~WebContentsViewMac() {
   // This handles the case where a renderer close call was deferred
@@ -322,21 +319,6 @@
   return gfx::ScreenRectFromNSRect(window_bounds);
 }
 
-void WebContentsViewMac::SetAllowOtherViews(bool allow) {
-  if (allow_other_views_ == allow)
-    return;
-
-  allow_other_views_ = allow;
-  RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
-      web_contents_->GetRenderWidgetHostView());
-  if (view)
-    view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
-}
-
-bool WebContentsViewMac::GetAllowOtherViews() const {
-  return allow_other_views_;
-}
-
 void WebContentsViewMac::CreateView(
     const gfx::Size& initial_size, gfx::NativeView context) {
   WebContentsViewCocoa* view =
@@ -369,7 +351,6 @@
 
     view->SetDelegate(rw_delegate.get());
   }
-  view->SetAllowPauseForResizeOrRepaint(!allow_other_views_);
 
   // Add the RenderWidgetHostView to the ui::Layer heirarchy.
   child_views_.push_back(view->GetWeakPtr());
diff --git a/content/common/common_param_traits_unittest.cc b/content/common/common_param_traits_unittest.cc
index 7c395a8..cb95c23f 100644
--- a/content/common/common_param_traits_unittest.cc
+++ b/content/common/common_param_traits_unittest.cc
@@ -173,8 +173,6 @@
   in.pkp_bypassed = true;
   in.client_cert_sent = true;
   in.channel_id_sent = true;
-  in.token_binding_negotiated = true;
-  in.token_binding_key_param = net::TB_PARAM_ECDSAP256;
   in.handshake_type = net::SSLInfo::HANDSHAKE_FULL;
   const net::SHA256HashValue kCertPublicKeyHashValue = {{0x01, 0x02}};
   in.public_key_hashes.push_back(net::HashValue(kCertPublicKeyHashValue));
@@ -218,8 +216,6 @@
   ASSERT_EQ(in.pkp_bypassed, out.pkp_bypassed);
   ASSERT_EQ(in.client_cert_sent, out.client_cert_sent);
   ASSERT_EQ(in.channel_id_sent, out.channel_id_sent);
-  ASSERT_EQ(in.token_binding_negotiated, out.token_binding_negotiated);
-  ASSERT_EQ(in.token_binding_key_param, out.token_binding_key_param);
   ASSERT_EQ(in.handshake_type, out.handshake_type);
   ASSERT_EQ(in.public_key_hashes, out.public_key_hashes);
   ASSERT_EQ(in.pinning_failure_log, out.pinning_failure_log);
diff --git a/content/common/service_worker/service_worker_loader_helpers.cc b/content/common/service_worker/service_worker_loader_helpers.cc
index 4b365157..d422ca9 100644
--- a/content/common/service_worker/service_worker_loader_helpers.cc
+++ b/content/common/service_worker/service_worker_loader_helpers.cc
@@ -157,8 +157,7 @@
 base::Optional<net::RedirectInfo>
 ServiceWorkerLoaderHelpers::ComputeRedirectInfo(
     const network::ResourceRequest& original_request,
-    const network::ResourceResponseHead& response_head,
-    bool token_binding_negotiated) {
+    const network::ResourceResponseHead& response_head) {
   std::string new_location;
   if (!response_head.headers->IsRedirect(&new_location))
     return base::nullopt;
@@ -175,8 +174,7 @@
       original_request.referrer_policy,
       network::ComputeReferrer(original_request.referrer),
       response_head.headers.get(), response_head.headers->response_code(),
-      original_request.url.Resolve(new_location), false,
-      token_binding_negotiated);
+      original_request.url.Resolve(new_location), false);
 }
 
 int ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
diff --git a/content/common/service_worker/service_worker_loader_helpers.h b/content/common/service_worker/service_worker_loader_helpers.h
index 3026f7a8..60da247c 100644
--- a/content/common/service_worker/service_worker_loader_helpers.h
+++ b/content/common/service_worker/service_worker_loader_helpers.h
@@ -38,8 +38,7 @@
   // Otherwise returns base::nullopt.
   static base::Optional<net::RedirectInfo> ComputeRedirectInfo(
       const network::ResourceRequest& original_request,
-      const network::ResourceResponseHead& response_head,
-      bool token_binding_negotiated);
+      const network::ResourceResponseHead& response_head);
 
   // Reads |blob| using the range in |headers| (if any), writing into
   // |handle_out|. Calls |on_blob_read_complete| when done or if an error
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 1b12b44..f26f7e5 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -912,13 +912,6 @@
   // scoped to this WebContents. This provides access to interfaces implemented
   // in Java in the browser process to C++ code in the browser process.
   virtual service_manager::InterfaceProvider* GetJavaInterfaces() = 0;
-#elif defined(OS_MACOSX)
-  // Allowing other views disables optimizations which assume that only a single
-  // WebContents is present.
-  virtual void SetAllowOtherViews(bool allow) = 0;
-
-  // Returns true if other views are allowed, false otherwise.
-  virtual bool GetAllowOtherViews() = 0;
 #endif  // OS_ANDROID
 
   // Returns true if the WebContents has completed its first meaningful paint
diff --git a/content/public/common/common_param_traits.cc b/content/public/common/common_param_traits.cc
index 539c53e..1d92adb8 100644
--- a/content/public/common/common_param_traits.cc
+++ b/content/public/common/common_param_traits.cc
@@ -36,6 +36,24 @@
   l->append(")");
 }
 
+void ParamTraits<ui::AXTreeID>::Write(base::Pickle* m, const param_type& p) {
+  WriteParam(m, p.ToString());
+}
+
+bool ParamTraits<ui::AXTreeID>::Read(const base::Pickle* m,
+                                     base::PickleIterator* iter,
+                                     param_type* r) {
+  std::string value;
+  if (!ReadParam(m, iter, &value))
+    return false;
+  *r = ui::AXTreeID::FromString(value);
+  return true;
+}
+
+void ParamTraits<ui::AXTreeID>::Log(const param_type& p, std::string* l) {
+  l->append("<ui::AXTreeID>");
+}
+
 }  // namespace IPC
 
 // Generate param traits write methods.
diff --git a/content/public/common/common_param_traits.h b/content/public/common/common_param_traits.h
index 366da86..5c9f82f 100644
--- a/content/public/common/common_param_traits.h
+++ b/content/public/common/common_param_traits.h
@@ -23,6 +23,7 @@
 #include "content/common/content_export.h"
 #include "content/public/common/common_param_traits_macros.h"
 #include "ipc/ipc_message_utils.h"
+#include "ui/accessibility/ax_tree_id.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/surface/transport_dib.h"
 #include "url/ipc/url_param_traits.h"
@@ -80,6 +81,16 @@
   }
 };
 
+template <>
+struct CONTENT_EXPORT ParamTraits<ui::AXTreeID> {
+  typedef ui::AXTreeID param_type;
+  static void Write(base::Pickle* m, const param_type& p);
+  static bool Read(const base::Pickle* m,
+                   base::PickleIterator* iter,
+                   param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
 }  // namespace IPC
 
 #endif  // CONTENT_PUBLIC_COMMON_COMMON_PARAM_TRAITS_H_
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index 0b8cfb84..d3346e3 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -438,7 +438,7 @@
   // Handle a redirect response. ComputeRedirectInfo returns non-null redirect
   // info if the given response is a redirect.
   redirect_info_ = ServiceWorkerLoaderHelpers::ComputeRedirectInfo(
-      resource_request_, response_head_, false /* token_binding_negotiated */);
+      resource_request_, response_head_);
   if (redirect_info_) {
     if (redirect_limit_-- == 0) {
       CommitCompleted(net::ERR_TOO_MANY_REDIRECTS);
diff --git a/content/test/data/accessibility/aria/label-with-selected-option-expected-blink.txt b/content/test/data/accessibility/aria/label-with-selected-option-expected-blink.txt
new file mode 100644
index 0000000..6af34113
--- /dev/null
+++ b/content/test/data/accessibility/aria/label-with-selected-option-expected-blink.txt
@@ -0,0 +1,9 @@
+rootWebArea
+++checkBox name='Test 1: Flash the screen 2 times.' checkedState=false
+++checkBox name='Test 2: Flash the screen 2 times.' checkedState=false
+++textField
+++checkBox name='Test 3: Flash the screen two times.' checkedState=false
+++checkBox name='Test 4: Flash the screen two times.' checkedState=false
+++textField
+++checkBox name='Test 5: Flash the screen two times.' checkedState=false
+++comboBoxMenuButton name='two'
diff --git a/content/test/data/accessibility/aria/label-with-selected-option.html b/content/test/data/accessibility/aria/label-with-selected-option.html
new file mode 100644
index 0000000..638d24c5
--- /dev/null
+++ b/content/test/data/accessibility/aria/label-with-selected-option.html
@@ -0,0 +1,67 @@
+<html>
+<body>
+  <div>
+    <input type="checkbox" id="test1" />
+    <label for="test1">Test 1: Flash the screen
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="false">1</li>
+        <li role="option" aria-selected="true">2</li>
+        <li role="option">3</li>
+      </ul>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test2" />
+    <label for="test2">Test 2: Flash the screen
+      <div role="combobox">
+        <div role="textbox"></div>
+        <ul role="listbox" style="list-style-type: none;">
+          <li role="option" aria-selected="false">1</li>
+          <li role="option" aria-selected="true">2</li>
+          <li role="option" >3</li>
+        </ul>
+      </div>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test3" />
+    <label for="test3">Test 3: Flash the screen
+      <ul role="listbox" style="list-style-type: none;">
+        <li role="option" aria-selected="false">1</li>
+        <li role="option" aria-selected="true" aria-label="two">2</li>
+        <li role="option">3</li>
+      </ul>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test4" />
+    <label for="test4">Test 4: Flash the screen
+      <div role="combobox">
+        <div role="textbox"></div>
+        <ul role="listbox" style="list-style-type: none;">
+          <li role="option" aria-selected="false">1</li>
+          <li role="option" aria-selected="true" aria-label="two">2</li>
+          <li role="option">3</li>
+      </ul>
+      </div>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test5" />
+    <label for="test5">Test 5: Flash the screen
+      <div role="combobox" aria-haspopup="true" tabindex="0">
+        <ul role="listbox" style="list-style-type: none;">
+          <li role="option" aria-selected="false">1</li>
+          <li role="option" aria-selected="true" aria-label="two">2</li>
+          <li role="option">3</li>
+      </ul>
+      </div>
+      times.
+    </label>
+  </div>
+</body>
+</html>
diff --git a/content/test/data/accessibility/html/label-with-selected-option-expected-blink.txt b/content/test/data/accessibility/html/label-with-selected-option-expected-blink.txt
new file mode 100644
index 0000000..a9906a5b
--- /dev/null
+++ b/content/test/data/accessibility/html/label-with-selected-option-expected-blink.txt
@@ -0,0 +1,33 @@
+rootWebArea
+++genericContainer
+++++checkBox name='Test 1: Flash the screen 2 times.' checkedState=false
+++++popUpButton collapsed value='2'
+++++++menuListPopup invisible
+++++++++menuListOption invisible name='1' selected=false
+++++++++menuListOption name='2' selected=true
+++++++++menuListOption invisible name='3' selected=false
+++genericContainer
+++++checkBox name='Test 2: Flash the screen 2 times.' checkedState=false
+++++listBox activedescendantId=listBoxOption
+++++++listBoxOption name='1' selected=false
+++++++listBoxOption name='2' selected=true
+++++++listBoxOption name='3' selected=false
+++genericContainer
+++++checkBox name='Test 3: Flash the screen two times.' checkedState=false
+++++popUpButton collapsed value='two'
+++++++menuListPopup invisible
+++++++++menuListOption invisible name='1' selected=false
+++++++++menuListOption name='two' selected=true
+++++++++menuListOption invisible name='3' selected=false
+++genericContainer
+++++checkBox name='Test 4: Flash the screen two times.' checkedState=false
+++++listBox activedescendantId=listBoxOption
+++++++listBoxOption name='1' selected=false
+++++++listBoxOption name='two' selected=true
+++++++listBoxOption name='3' selected=false
+++genericContainer
+++++checkBox name='Test 5: Flash the screen two 3 times.' checkedState=false
+++++listBox multiselectable activedescendantId=listBoxOption
+++++++listBoxOption name='1' selected=false
+++++++listBoxOption name='two' selected=true
+++++++listBoxOption name='3' selected=true
diff --git a/content/test/data/accessibility/html/label-with-selected-option.html b/content/test/data/accessibility/html/label-with-selected-option.html
new file mode 100644
index 0000000..ca3696e9
--- /dev/null
+++ b/content/test/data/accessibility/html/label-with-selected-option.html
@@ -0,0 +1,59 @@
+<html>
+<body>
+  <div>
+    <input type="checkbox" id="test1" />
+    <label for="test1" >Test 1: Flash the screen
+      <select size="1">
+        <option>1</option>
+        <option selected>2</option>
+        <option>3</option>
+      </select>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test2" />
+    <label for="test2">Test 2: Flash the screen
+      <select size="3">
+        <option>1</option>
+        <option selected>2</option>
+        <option>3</option>
+      </select>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test3" />
+    <label for="test3">Test 3: Flash the screen
+      <select size="1">
+        <option>1</option>
+        <option selected aria-label="two">2</option>
+        <option>3</option>
+      </select>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test4" />
+    <label for="test4">Test 4: Flash the screen
+      <select size="3">
+        <option>1</option>
+        <option selected aria-label="two">2</option>
+        <option>3</option>
+      </select>
+      times.
+    </label>
+  </div>
+  <div>
+    <input type="checkbox" id="test5" />
+    <label for="test5">Test 5: Flash the screen
+      <select size="3" multiple>
+        <option>1</option>
+        <option selected aria-label="two">2</option>
+        <option selected>3</option>
+      </select>
+      times.
+    </label>
+  </div>
+</body>
+</html>
diff --git a/extensions/browser/api/alarms/alarms_api_unittest.cc b/extensions/browser/api/alarms/alarms_api_unittest.cc
index c15047fe..efe47f5 100644
--- a/extensions/browser/api/alarms/alarms_api_unittest.cc
+++ b/extensions/browser/api/alarms/alarms_api_unittest.cc
@@ -642,7 +642,7 @@
   // Create a new extension, which is packed, and has a granularity of 1 minute.
   // CreateAlarm() uses extension_, so keep a ref of the old one around, and
   // repopulate extension_.
-  scoped_refptr<Extension> extension2(extension_ref());
+  scoped_refptr<const Extension> extension2(extension_ref());
   set_extension(
       utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
 
diff --git a/extensions/browser/api/messaging/message_property_provider.cc b/extensions/browser/api/messaging/message_property_provider.cc
index e7ce840..d49c7f2 100644
--- a/extensions/browser/api/messaging/message_property_provider.cc
+++ b/extensions/browser/api/messaging/message_property_provider.cc
@@ -70,8 +70,7 @@
   net::CompletionCallback net_completion_callback =
       base::Bind(&MessagePropertyProvider::GotChannelID, original_task_runner,
                  base::Owned(output), reply);
-  if (!network_params->enable_token_binding &&
-      !network_params->enable_channel_id) {
+  if (!network_params->enable_channel_id) {
     GotChannelID(original_task_runner, output, reply, net::ERR_FILE_NOT_FOUND);
     return;
   }
diff --git a/extensions/browser/api_unittest.h b/extensions/browser/api_unittest.h
index 485bf98..72c71f8 100644
--- a/extensions/browser/api_unittest.h
+++ b/extensions/browser/api_unittest.h
@@ -39,8 +39,8 @@
 
   content::WebContents* contents() { return contents_.get(); }
   const Extension* extension() const { return extension_.get(); }
-  scoped_refptr<Extension> extension_ref() { return extension_; }
-  void set_extension(scoped_refptr<Extension> extension) {
+  scoped_refptr<const Extension> extension_ref() { return extension_; }
+  void set_extension(scoped_refptr<const Extension> extension) {
     extension_ = extension;
   }
 
@@ -92,7 +92,7 @@
   std::unique_ptr<content::WebContents> contents_;
 
   // The Extension used when running API function calls.
-  scoped_refptr<Extension> extension_;
+  scoped_refptr<const Extension> extension_;
 };
 
 }  // namespace extensions
diff --git a/headless/test/test_network_interceptor.cc b/headless/test/test_network_interceptor.cc
index 4112fdd..e8a23e6 100644
--- a/headless/test/test_network_interceptor.cc
+++ b/headless/test/test_network_interceptor.cc
@@ -54,8 +54,7 @@
             UPDATE_FIRST_PARTY_URL_ON_REDIRECT,
         url_request_.referrer_policy, url_request_.referrer.spec(),
         response_->headers.get(), response_->headers->response_code(),
-        url_.Resolve(location), false /* token_binding_negotiated */,
-        false /* insecure_scheme_was_upgraded */, true);
+        url_.Resolve(location), false /* insecure_scheme_was_upgraded */, true);
     network::ResourceResponseHead head;
     head.request_time = base::Time::Now();
     head.response_time = base::Time::Now();
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 2734cfd..416aedb3 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -1592,6 +1592,11 @@
       mixins: "linux"
     }
     builders {
+      name: "ToTAndroidOfficial"
+      mixins: "clang-ci"
+      mixins: "linux"
+    }
+    builders {
       name: "ToTLinux"
       mixins: "clang-ci"
       mixins: "linux"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 68f1c16..576f3c2 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -1926,6 +1926,11 @@
     short_name: "cfi"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/ToTAndroidOfficial"
+    category: "ToT Android"
+    short_name: "off"
+  }
+  builders {
     name: "buildbot/chromium.clang/ToTMac"
     name: "buildbucket/luci.chromium.ci/ToTMac"
     category: "ToT Mac"
@@ -2149,6 +2154,11 @@
     short_name: "cfi"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/ToTAndroidOfficial"
+    category: "ToT Android"
+    short_name: "off"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/ToTMac"
     category: "ToT Mac"
     short_name: "rel"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index 651cbab9..71ff0cc8 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -214,6 +214,7 @@
   triggers: "ToTAndroid64"
   triggers: "ToTAndroidASan"
   triggers: "ToTAndroidCFI"
+  triggers: "ToTAndroidOfficial"
   triggers: "ToTLinux (dbg)"
   triggers: "ToTLinux"
   triggers: "ToTLinuxASan"
@@ -4272,6 +4273,16 @@
 }
 
 job {
+  id: "ToTAndroidOfficial"
+  acl_sets: "default"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "ToTAndroidOfficial"
+  }
+}
+
+job {
   id: "ToTLinux"
   acl_sets: "default"
   buildbucket {
diff --git a/infra/config/global/tricium-prod.cfg b/infra/config/global/tricium-prod.cfg
index cde7591..a19cc2e 100644
--- a/infra/config/global/tricium-prod.cfg
+++ b/infra/config/global/tricium-prod.cfg
@@ -16,6 +16,19 @@
   platform: UBUNTU
 }
 
+selections {
+  function: "Pylint"
+  platform: UBUNTU
+  configs {
+    name: "disable"
+    value: "all"
+  }
+  configs {
+    name: "enable"
+    value: "syntax-error,unused-variable,undefined-variable,unused-import"
+  }
+}
+
 repos {
   gerrit_project {
     host: "chromium-review.googlesource.com"
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index fe7f78e7..f4e2d23 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -286,7 +286,20 @@
 
   bundle_deps = [ "//ios/chrome/app/resources" ]
   if (!is_chrome_branded || ios_chrome_app_variants == []) {
+    assert(ios_application_icons_target != "",
+           "ios_application_icons_target must be defined.")
     bundle_deps += [ ios_application_icons_target ]
+
+    if (ios_enable_firebase_sdk) {
+      assert(ios_firebase_resources_target != "",
+             "ios_firebase_resources_target must be defined if Firebase SDK " +
+                 "is enabled.")
+
+      # Empty list assigned to bundle_deps to indicate that this is an
+      # intentional override of bundle_deps.
+      bundle_deps = []
+      bundle_deps = [ ios_firebase_resources_target ]
+    }
   } else {
     variants = ios_chrome_app_variants
   }
diff --git a/ios/third_party/firebase/BUILD.gn b/ios/third_party/firebase/BUILD.gn
index e947febe..9fa73ea2 100644
--- a/ios/third_party/firebase/BUILD.gn
+++ b/ios/third_party/firebase/BUILD.gn
@@ -2,13 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//ios/third_party/firebase/firebase.gni")
-
 source_set("firebase") {
-  assert(
-      ios_firebase_resources_target != "",
-      "ios_firebase_resources_target must be defined if Firebase SDK is enabled.")
-
   # From gn documentation:
   #   https://chromium.googlesource.com/chromium/src/+/master/tools/gn/docs/reference.md#ldflags
   # "ldflags are NOT pushed to dependents, so applying ldflags to source sets
@@ -19,9 +13,6 @@
   # This source_set must be specified as a direct deps of an ios_app_bundle
   # target for ldflags specified in :firebase_config to be applied.
   public_configs = [ ":firebase_config" ]
-  deps = [
-    ios_firebase_resources_target,
-  ]
 }
 
 config("firebase_config") {
diff --git a/ios/web_view/internal/signin/ios_web_view_signin_client.h b/ios/web_view/internal/signin/ios_web_view_signin_client.h
index f5fc8bb..6000bc3 100644
--- a/ios/web_view/internal/signin/ios_web_view_signin_client.h
+++ b/ios/web_view/internal/signin/ios_web_view_signin_client.h
@@ -49,7 +49,7 @@
   void RemoveContentSettingsObserver(
       content_settings::Observer* observer) override;
   void PreSignOut(
-      const base::RepeatingClosure& sign_out,
+      base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
       signin_metrics::ProfileSignout signout_source_metric) override;
   void DelayNetworkCall(const base::Closure& callback) override;
   std::unique_ptr<GaiaAuthFetcher> CreateGaiaAuthFetcher(
diff --git a/ios/web_view/internal/signin/ios_web_view_signin_client.mm b/ios/web_view/internal/signin/ios_web_view_signin_client.mm
index b0ee3092..2f16af00 100644
--- a/ios/web_view/internal/signin/ios_web_view_signin_client.mm
+++ b/ios/web_view/internal/signin/ios_web_view_signin_client.mm
@@ -86,9 +86,9 @@
 }
 
 void IOSWebViewSigninClient::PreSignOut(
-    const base::RepeatingClosure& sign_out,
+    base::OnceCallback<void(SignoutDecision)> on_signout_decision_reached,
     signin_metrics::ProfileSignout signout_source_metric) {
-  sign_out.Run();
+  std::move(on_signout_decision_reached).Run(SignoutDecision::ALLOW_SIGNOUT);
   [sync_controller_ didSignoutWithSourceMetric:signout_source_metric];
 }
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index b7a551a..a714def 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -362,8 +362,6 @@
     "ssl/ssl_private_key.h",
     "ssl/ssl_server_config.cc",
     "ssl/ssl_server_config.h",
-    "ssl/token_binding.cc",
-    "ssl/token_binding.h",
     "third_party/quic/core/quic_error_codes.cc",
     "third_party/quic/core/quic_error_codes.h",
     "third_party/uri_template/uri_template.cc",
diff --git a/net/base/mime_sniffer.cc b/net/base/mime_sniffer.cc
index 3f60f22..ace836a5 100644
--- a/net/base/mime_sniffer.cc
+++ b/net/base/mime_sniffer.cc
@@ -152,7 +152,7 @@
   MAGIC_NUMBER("image/tiff", "II*"),
   MAGIC_NUMBER("image/tiff", "MM\x00*"),
   MAGIC_NUMBER("audio/mpeg", "ID3"),
-  MAGIC_NUMBER("image/webp", "RIFF....WEBPVP8 "),
+  MAGIC_NUMBER("image/webp", "RIFF....WEBPVP"),
   MAGIC_NUMBER("video/webm", "\x1A\x45\xDF\xA3"),
   MAGIC_NUMBER("application/zip", "PK\x03\x04"),
   MAGIC_NUMBER("application/x-rar-compressed", "Rar!\x1A\x07\x00"),
diff --git a/net/base/mime_sniffer_unittest.cc b/net/base/mime_sniffer_unittest.cc
index 5c200eb6..dd417c7 100644
--- a/net/base/mime_sniffer_unittest.cc
+++ b/net/base/mime_sniffer_unittest.cc
@@ -458,6 +458,27 @@
   mime_type.clear();
 }
 
+TEST(MimeSnifferTest, ImageTest) {
+  std::string mime_type;
+  const char kWebPSimpleFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8 ";
+  EXPECT_TRUE(SniffMimeTypeFromLocalData(
+      kWebPSimpleFormat, sizeof(kWebPSimpleFormat) - 1, &mime_type));
+  EXPECT_EQ("image/webp", mime_type);
+  mime_type.clear();
+
+  const char kWebPLosslessFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8L";
+  EXPECT_TRUE(SniffMimeTypeFromLocalData(
+      kWebPLosslessFormat, sizeof(kWebPLosslessFormat) - 1, &mime_type));
+  EXPECT_EQ("image/webp", mime_type);
+  mime_type.clear();
+
+  const char kWebPExtendedFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8X";
+  EXPECT_TRUE(SniffMimeTypeFromLocalData(
+      kWebPExtendedFormat, sizeof(kWebPExtendedFormat) - 1, &mime_type));
+  EXPECT_EQ("image/webp", mime_type);
+  mime_type.clear();
+}
+
 // The tests need char parameters, but the ranges to test include 0xFF, and some
 // platforms have signed chars and are noisy about it. Using an int parameter
 // and casting it to char inside the test case solves both these problems.
diff --git a/net/http/http_basic_stream.cc b/net/http/http_basic_stream.cc
index 2fc34a5..2f677a4 100644
--- a/net/http/http_basic_stream.cc
+++ b/net/http/http_basic_stream.cc
@@ -131,12 +131,6 @@
   return state_.connection()->socket()->GetPeerAddress(endpoint) == OK;
 }
 
-Error HttpBasicStream::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                                TokenBindingType tb_type,
-                                                std::vector<uint8_t>* out) {
-  return parser()->GetTokenBindingSignature(key, tb_type, out);
-}
-
 void HttpBasicStream::Drain(HttpNetworkSession* session) {
   HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(this);
   drainer->Start(session);
diff --git a/net/http/http_basic_stream.h b/net/http/http_basic_stream.h
index 48f79725..accecba 100644
--- a/net/http/http_basic_stream.h
+++ b/net/http/http_basic_stream.h
@@ -83,10 +83,6 @@
 
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
 
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
-
   void Drain(HttpNetworkSession* session) override;
 
   void PopulateNetErrorDetails(NetErrorDetails* details) override;
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc
index 951cad57..5582ea6 100644
--- a/net/http/http_network_session.cc
+++ b/net/http/http_network_session.cc
@@ -143,7 +143,6 @@
       quic_race_cert_verification(false),
       quic_estimate_initial_rtt(false),
       quic_headers_include_h2_stream_dependency(false),
-      enable_token_binding(false),
       enable_channel_id(false),
       http_09_on_non_default_ports_enabled(false),
       disable_idle_sockets_close_on_memory_pressure(false) {
@@ -232,7 +231,6 @@
           params.quic_headers_include_h2_stream_dependency,
           params.quic_connection_options,
           params.quic_client_connection_options,
-          params.enable_token_binding,
           params.enable_channel_id,
           params.quic_enable_socket_recv_optimization),
       spdy_session_pool_(context.host_resolver,
@@ -462,9 +460,6 @@
   } else {
     server_config->channel_id_enabled = params_.enable_channel_id;
     proxy_config->channel_id_enabled = params_.enable_channel_id;
-    if (params_.enable_token_binding && context_.channel_id_service) {
-      server_config->token_binding_params.push_back(TB_PARAM_ECDSAP256);
-    }
   }
 }
 
diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h
index fa0596f..c4319af 100644
--- a/net/http/http_network_session.h
+++ b/net/http/http_network_session.h
@@ -226,8 +226,6 @@
     // If non-empty, QUIC will only be spoken to hosts in this list.
     base::flat_set<std::string> quic_host_whitelist;
 
-    // Enable support for Token Binding.
-    bool enable_token_binding;
     // Enable Channel ID. Channel ID is being deprecated.
     bool enable_channel_id;
 
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 2d0718ae..12b8b561 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -67,7 +67,6 @@
 #include "net/ssl/ssl_connection_status_flags.h"
 #include "net/ssl/ssl_info.h"
 #include "net/ssl/ssl_private_key.h"
-#include "net/ssl/token_binding.h"
 #include "url/gurl.h"
 #include "url/url_canon.h"
 
@@ -636,42 +635,6 @@
   return request_->url.SchemeIsCryptographic();
 }
 
-bool HttpNetworkTransaction::IsTokenBindingEnabled() const {
-  if (!IsSecureRequest())
-    return false;
-  SSLInfo ssl_info;
-  stream_->GetSSLInfo(&ssl_info);
-  return ssl_info.token_binding_negotiated &&
-         ssl_info.token_binding_key_param == TB_PARAM_ECDSAP256 &&
-         session_->context().channel_id_service;
-}
-
-void HttpNetworkTransaction::RecordTokenBindingSupport() const {
-  // This enum is used for an UMA histogram - do not change or re-use values.
-  enum {
-    DISABLED = 0,
-    CLIENT_ONLY = 1,
-    CLIENT_AND_SERVER = 2,
-    CLIENT_NO_CHANNEL_ID_SERVICE = 3,
-    TOKEN_BINDING_SUPPORT_MAX
-  } supported;
-  if (!IsSecureRequest())
-    return;
-  SSLInfo ssl_info;
-  stream_->GetSSLInfo(&ssl_info);
-  if (!session_->params().enable_token_binding) {
-    supported = DISABLED;
-  } else if (!session_->context().channel_id_service) {
-    supported = CLIENT_NO_CHANNEL_ID_SERVICE;
-  } else if (ssl_info.token_binding_negotiated) {
-    supported = CLIENT_AND_SERVER;
-  } else {
-    supported = CLIENT_ONLY;
-  }
-  UMA_HISTOGRAM_ENUMERATION("Net.TokenBinding.Support", supported,
-                            TOKEN_BINDING_SUPPORT_MAX);
-}
-
 bool HttpNetworkTransaction::UsingHttpProxyWithoutTunnel() const {
   return (proxy_info_.is_http() || proxy_info_.is_https() ||
           proxy_info_.is_quic()) &&
@@ -736,20 +699,6 @@
       case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
         rv = DoGenerateServerAuthTokenComplete(rv);
         break;
-      case STATE_GET_PROVIDED_TOKEN_BINDING_KEY:
-        DCHECK_EQ(OK, rv);
-        rv = DoGetProvidedTokenBindingKey();
-        break;
-      case STATE_GET_PROVIDED_TOKEN_BINDING_KEY_COMPLETE:
-        rv = DoGetProvidedTokenBindingKeyComplete(rv);
-        break;
-      case STATE_GET_REFERRED_TOKEN_BINDING_KEY:
-        DCHECK_EQ(OK, rv);
-        rv = DoGetReferredTokenBindingKey();
-        break;
-      case STATE_GET_REFERRED_TOKEN_BINDING_KEY_COMPLETE:
-        rv = DoGetReferredTokenBindingKeyComplete(rv);
-        break;
       case STATE_INIT_REQUEST_BODY:
         DCHECK_EQ(OK, rv);
         rv = DoInitRequestBody();
@@ -991,53 +940,6 @@
 int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) {
   DCHECK_NE(ERR_IO_PENDING, rv);
   if (rv == OK)
-    next_state_ = STATE_GET_PROVIDED_TOKEN_BINDING_KEY;
-  return rv;
-}
-
-int HttpNetworkTransaction::DoGetProvidedTokenBindingKey() {
-  next_state_ = STATE_GET_PROVIDED_TOKEN_BINDING_KEY_COMPLETE;
-  if (!IsTokenBindingEnabled())
-    return OK;
-
-  net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY);
-  ChannelIDService* channel_id_service = session_->context().channel_id_service;
-  return channel_id_service->GetOrCreateChannelID(
-      request_->url.host(), &provided_token_binding_key_, io_callback_,
-      &token_binding_request_);
-}
-
-int HttpNetworkTransaction::DoGetProvidedTokenBindingKeyComplete(int rv) {
-  DCHECK_NE(ERR_IO_PENDING, rv);
-  if (IsTokenBindingEnabled()) {
-    net_log_.EndEventWithNetErrorCode(
-        NetLogEventType::HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY, rv);
-  }
-
-  if (rv == OK)
-    next_state_ = STATE_GET_REFERRED_TOKEN_BINDING_KEY;
-  return rv;
-}
-
-int HttpNetworkTransaction::DoGetReferredTokenBindingKey() {
-  next_state_ = STATE_GET_REFERRED_TOKEN_BINDING_KEY_COMPLETE;
-  if (!IsTokenBindingEnabled() || request_->token_binding_referrer.empty())
-    return OK;
-
-  net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY);
-  ChannelIDService* channel_id_service = session_->context().channel_id_service;
-  return channel_id_service->GetOrCreateChannelID(
-      request_->token_binding_referrer, &referred_token_binding_key_,
-      io_callback_, &token_binding_request_);
-}
-
-int HttpNetworkTransaction::DoGetReferredTokenBindingKeyComplete(int rv) {
-  DCHECK_NE(ERR_IO_PENDING, rv);
-  if (IsTokenBindingEnabled() && !request_->token_binding_referrer.empty()) {
-    net_log_.EndEventWithNetErrorCode(
-        NetLogEventType::HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY, rv);
-  }
-  if (rv == OK)
     next_state_ = STATE_INIT_REQUEST_BODY;
   return rv;
 }
@@ -1075,16 +977,6 @@
     request_headers_.SetHeader(HttpRequestHeaders::kContentLength, "0");
   }
 
-  RecordTokenBindingSupport();
-  if (provided_token_binding_key_) {
-    std::string token_binding_header;
-    int rv = BuildTokenBindingHeader(&token_binding_header);
-    if (rv != OK)
-      return rv;
-    request_headers_.SetHeader(HttpRequestHeaders::kTokenBinding,
-                               token_binding_header);
-  }
-
   // Honor load flags that impact proxy caches.
   if (request_->load_flags & LOAD_BYPASS_CACHE) {
     request_headers_.SetHeader(HttpRequestHeaders::kPragma, "no-cache");
@@ -1111,52 +1003,6 @@
   return OK;
 }
 
-int HttpNetworkTransaction::BuildTokenBindingHeader(std::string* out) {
-  base::TimeTicks start = base::TimeTicks::Now();
-  std::vector<uint8_t> signed_ekm;
-  int rv = stream_->GetTokenBindingSignature(provided_token_binding_key_.get(),
-                                             TokenBindingType::PROVIDED,
-                                             &signed_ekm);
-  if (rv != OK)
-    return rv;
-  std::string provided_token_binding;
-  rv = BuildTokenBinding(TokenBindingType::PROVIDED,
-                         provided_token_binding_key_.get(), signed_ekm,
-                         &provided_token_binding);
-  if (rv != OK)
-    return rv;
-
-  std::vector<base::StringPiece> token_bindings;
-  token_bindings.push_back(provided_token_binding);
-
-  std::string referred_token_binding;
-  if (referred_token_binding_key_) {
-    std::vector<uint8_t> referred_signed_ekm;
-    int rv = stream_->GetTokenBindingSignature(
-        referred_token_binding_key_.get(), TokenBindingType::REFERRED,
-        &referred_signed_ekm);
-    if (rv != OK)
-      return rv;
-    rv = BuildTokenBinding(TokenBindingType::REFERRED,
-                           referred_token_binding_key_.get(),
-                           referred_signed_ekm, &referred_token_binding);
-    if (rv != OK)
-      return rv;
-    token_bindings.push_back(referred_token_binding);
-  }
-  std::string header;
-  rv = BuildTokenBindingMessageFromTokenBindings(token_bindings, &header);
-  if (rv != OK)
-    return rv;
-  base::Base64UrlEncode(header, base::Base64UrlEncodePolicy::OMIT_PADDING, out);
-  base::TimeDelta header_creation_time = base::TimeTicks::Now() - start;
-  UMA_HISTOGRAM_CUSTOM_TIMES("Net.TokenBinding.HeaderCreationTime",
-                             header_creation_time,
-                             base::TimeDelta::FromMilliseconds(1),
-                             base::TimeDelta::FromMinutes(1), 50);
-  return OK;
-}
-
 int HttpNetworkTransaction::DoInitRequestBody() {
   next_state_ = STATE_INIT_REQUEST_BODY_COMPLETE;
   int rv = OK;
@@ -1680,8 +1526,6 @@
   remote_endpoint_ = IPEndPoint();
   net_error_details_.quic_broken = false;
   net_error_details_.quic_connection_error = quic::QUIC_NO_ERROR;
-  provided_token_binding_key_.reset();
-  referred_token_binding_key_.reset();
 }
 
 void HttpNetworkTransaction::CacheNetErrorDetailsAndResetStream() {
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index cd95f6ef..e0105d06 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -33,10 +33,6 @@
 #include "net/ssl/ssl_config_service.h"
 #include "net/websockets/websocket_handshake_stream_base.h"
 
-namespace crypto {
-class ECPrivateKey;
-}
-
 namespace net {
 
 class BidirectionalStreamImpl;
@@ -151,10 +147,6 @@
     STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE,
     STATE_GENERATE_SERVER_AUTH_TOKEN,
     STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE,
-    STATE_GET_PROVIDED_TOKEN_BINDING_KEY,
-    STATE_GET_PROVIDED_TOKEN_BINDING_KEY_COMPLETE,
-    STATE_GET_REFERRED_TOKEN_BINDING_KEY,
-    STATE_GET_REFERRED_TOKEN_BINDING_KEY_COMPLETE,
     STATE_INIT_REQUEST_BODY,
     STATE_INIT_REQUEST_BODY_COMPLETE,
     STATE_BUILD_REQUEST,
@@ -171,8 +163,6 @@
   };
 
   bool IsSecureRequest() const;
-  bool IsTokenBindingEnabled() const;
-  void RecordTokenBindingSupport() const;
 
   // Returns true if the request is using an HTTP(S) proxy without being
   // tunneled via the CONNECT method.
@@ -197,10 +187,6 @@
   int DoGenerateProxyAuthTokenComplete(int result);
   int DoGenerateServerAuthToken();
   int DoGenerateServerAuthTokenComplete(int result);
-  int DoGetProvidedTokenBindingKey();
-  int DoGetProvidedTokenBindingKeyComplete(int result);
-  int DoGetReferredTokenBindingKey();
-  int DoGetReferredTokenBindingKeyComplete(int result);
   int DoInitRequestBody();
   int DoInitRequestBodyComplete(int result);
   int DoBuildRequest();
@@ -215,7 +201,6 @@
   int DoDrainBodyForAuthRestartComplete(int result);
 
   int BuildRequestHeaders(bool using_http_proxy_without_tunnel);
-  int BuildTokenBindingHeader(std::string* out);
 
   // Writes a log message to help debugging in the field when we block a proxy
   // response to a CONNECT request.
@@ -351,13 +336,6 @@
   SSLConfig server_ssl_config_;
   SSLConfig proxy_ssl_config_;
 
-  // Keys to use for signing message in Token Binding header.
-  std::unique_ptr<crypto::ECPrivateKey> provided_token_binding_key_;
-  std::unique_ptr<crypto::ECPrivateKey> referred_token_binding_key_;
-  // Object to manage lookup of |provided_token_binding_key_| and
-  // |referred_token_binding_key_|.
-  ChannelIDService::Request token_binding_request_;
-
   HttpRequestHeaders request_headers_;
 
   // The size in bytes of the buffer we use to drain the response body that
diff --git a/net/http/http_network_transaction_ssl_unittest.cc b/net/http/http_network_transaction_ssl_unittest.cc
index e4e7e08..837fd089 100644
--- a/net/http/http_network_transaction_ssl_unittest.cc
+++ b/net/http/http_network_transaction_ssl_unittest.cc
@@ -39,12 +39,9 @@
 
 namespace {
 
-class TokenBindingSSLConfigService : public SSLConfigService {
+class ChannelIDSSLConfigService : public SSLConfigService {
  public:
-  TokenBindingSSLConfigService() {
-    ssl_config_.token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  }
-  ~TokenBindingSSLConfigService() override = default;
+  ~ChannelIDSSLConfigService() override = default;
 
   void GetSSLConfig(SSLConfig* config) override { *config = ssl_config_; }
 
@@ -64,7 +61,7 @@
   HttpNetworkTransactionSSLTest() = default;
 
   void SetUp() override {
-    ssl_config_service_.reset(new TokenBindingSSLConfigService);
+    ssl_config_service_.reset(new ChannelIDSSLConfigService);
     session_context_.ssl_config_service = ssl_config_service_.get();
 
     auth_handler_factory_.reset(new HttpAuthHandlerMock::Factory());
@@ -125,130 +122,4 @@
   EXPECT_TRUE(trans.server_ssl_config_.channel_id_enabled);
 }
 
-#if !defined(OS_IOS)
-TEST_F(HttpNetworkTransactionSSLTest, TokenBinding) {
-  ChannelIDService channel_id_service(new DefaultChannelIDStore(NULL));
-  session_context_.channel_id_service = &channel_id_service;
-
-  SSLSocketDataProvider ssl_data(ASYNC, OK);
-  ssl_data.ssl_info.token_binding_negotiated = true;
-  ssl_data.ssl_info.token_binding_key_param = TB_PARAM_ECDSAP256;
-  mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data);
-  MockRead mock_reads[] = {MockRead("HTTP/1.1 200 OK\r\n\r\n"),
-                           MockRead(SYNCHRONOUS, OK)};
-  StaticSocketDataProvider data(mock_reads, base::span<MockWrite>());
-  mock_socket_factory_.AddSocketDataProvider(&data);
-
-  HttpNetworkSession session(HttpNetworkSession::Params(), session_context_);
-  HttpNetworkTransaction trans1(DEFAULT_PRIORITY, &session);
-
-  TestCompletionCallback callback;
-  int rv = callback.GetResult(
-      trans1.Start(GetRequestInfo("https://www.example.com/"),
-                   callback.callback(), NetLogWithSource()));
-  EXPECT_THAT(rv, IsOk());
-
-  HttpRequestHeaders headers1;
-  ASSERT_TRUE(trans1.GetFullRequestHeaders(&headers1));
-  std::string token_binding_header1;
-  EXPECT_TRUE(headers1.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                 &token_binding_header1));
-
-  // Send a second request and verify that the token binding header is the same
-  // as in the first request.
-  mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data);
-  StaticSocketDataProvider data2(mock_reads, base::span<MockWrite>());
-  mock_socket_factory_.AddSocketDataProvider(&data2);
-  HttpNetworkTransaction trans2(DEFAULT_PRIORITY, &session);
-
-  rv = callback.GetResult(
-      trans2.Start(GetRequestInfo("https://www.example.com/"),
-                   callback.callback(), NetLogWithSource()));
-  EXPECT_THAT(rv, IsOk());
-
-  HttpRequestHeaders headers2;
-  ASSERT_TRUE(trans2.GetFullRequestHeaders(&headers2));
-  std::string token_binding_header2;
-  EXPECT_TRUE(headers2.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                 &token_binding_header2));
-
-  EXPECT_EQ(token_binding_header1, token_binding_header2);
-}
-
-TEST_F(HttpNetworkTransactionSSLTest, NoTokenBindingOverHttp) {
-  ChannelIDService channel_id_service(new DefaultChannelIDStore(NULL));
-  session_context_.channel_id_service = &channel_id_service;
-
-  SSLSocketDataProvider ssl_data(ASYNC, OK);
-  ssl_data.ssl_info.token_binding_negotiated = true;
-  ssl_data.ssl_info.token_binding_key_param = TB_PARAM_ECDSAP256;
-  mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data);
-  MockRead mock_reads[] = {MockRead("HTTP/1.1 200 OK\r\n\r\n"),
-                           MockRead(SYNCHRONOUS, OK)};
-  StaticSocketDataProvider data(mock_reads, base::span<MockWrite>());
-  mock_socket_factory_.AddSocketDataProvider(&data);
-
-  HttpNetworkSession session(HttpNetworkSession::Params(), session_context_);
-  HttpNetworkTransaction trans(DEFAULT_PRIORITY, &session);
-
-  TestCompletionCallback callback;
-  int rv =
-      callback.GetResult(trans.Start(GetRequestInfo("http://www.example.com/"),
-                                     callback.callback(), NetLogWithSource()));
-  EXPECT_THAT(rv, IsOk());
-
-  HttpRequestHeaders headers;
-  ASSERT_TRUE(trans.GetFullRequestHeaders(&headers));
-  std::string token_binding_header;
-  EXPECT_FALSE(headers.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                 &token_binding_header));
-}
-
-// Regression test for https://crbug.com/667683.
-TEST_F(HttpNetworkTransactionSSLTest, TokenBindingAsync) {
-  // Create a separate thread for ChannelIDService
-  // so that asynchronous Channel ID creation can be delayed.
-  base::Thread channel_id_thread("ThreadForChannelIDService");
-  channel_id_thread.Start();
-  scoped_refptr<base::DeferredSequencedTaskRunner> channel_id_runner =
-      new base::DeferredSequencedTaskRunner(channel_id_thread.task_runner());
-  ChannelIDService channel_id_service(new DefaultChannelIDStore(nullptr));
-  channel_id_service.set_task_runner_for_testing(channel_id_runner);
-  session_context_.channel_id_service = &channel_id_service;
-
-  SSLSocketDataProvider ssl_data(ASYNC, OK);
-  ssl_data.ssl_info.token_binding_negotiated = true;
-  ssl_data.ssl_info.token_binding_key_param = TB_PARAM_ECDSAP256;
-  ssl_data.next_proto = kProtoHTTP2;
-  mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data);
-
-  MockRead reads[] = {MockRead(ASYNC, OK, 0)};
-  StaticSocketDataProvider data(reads, base::span<MockWrite>());
-  mock_socket_factory_.AddSocketDataProvider(&data);
-
-  HttpRequestInfo request_info;
-  request_info.url = GURL("https://www.example.com/");
-  request_info.method = "GET";
-  request_info.token_binding_referrer = "encrypted.example.com";
-  request_info.traffic_annotation =
-      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
-
-  HttpNetworkSession session(HttpNetworkSession::Params(), session_context_);
-  HttpNetworkTransaction trans(DEFAULT_PRIORITY, &session);
-
-  TestCompletionCallback callback;
-  int rv = trans.Start(&request_info, callback.callback(), NetLogWithSource());
-  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
-
-  base::RunLoop().RunUntilIdle();
-
-  // When ChannelIdService calls back to HttpNetworkSession,
-  // SpdyHttpStream should not crash.
-  channel_id_runner->Start();
-
-  rv = callback.WaitForResult();
-  EXPECT_THAT(rv, IsError(ERR_CONNECTION_CLOSED));
-}
-#endif  // !defined(OS_IOS)
-
 }  // namespace net
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 1c7ec47..4d3cb523 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -17159,46 +17159,6 @@
   EXPECT_EQ(CountReadBytes(data_reads), trans.GetTotalReceivedBytes());
 }
 
-#if !defined(OS_IOS)
-TEST_F(HttpNetworkTransactionTest, TokenBindingSpdy) {
-  const std::string https_url = "https://www.example.com";
-  HttpRequestInfo request;
-  request.url = GURL(https_url);
-  request.method = "GET";
-  request.traffic_annotation =
-      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
-
-  SSLSocketDataProvider ssl(ASYNC, OK);
-  ssl.ssl_info.token_binding_negotiated = true;
-  ssl.ssl_info.token_binding_key_param = TB_PARAM_ECDSAP256;
-  ssl.next_proto = kProtoHTTP2;
-  session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
-
-  spdy::SpdySerializedFrame resp(
-      spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
-  spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true));
-  MockRead reads[] = {CreateMockRead(resp), CreateMockRead(body),
-                      MockRead(ASYNC, ERR_IO_PENDING)};
-  StaticSocketDataProvider data(reads, base::span<MockWrite>());
-  session_deps_.socket_factory->AddSocketDataProvider(&data);
-  session_deps_.channel_id_service =
-      std::make_unique<ChannelIDService>(new DefaultChannelIDStore(nullptr));
-  std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
-
-  HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
-  TestCompletionCallback callback;
-  EXPECT_EQ(ERR_IO_PENDING,
-            trans.Start(&request, callback.callback(), NetLogWithSource()));
-
-  RunUntilIdle();
-
-  EXPECT_TRUE(trans.GetResponseInfo()->was_fetched_via_spdy);
-  HttpRequestHeaders headers;
-  ASSERT_TRUE(trans.GetFullRequestHeaders(&headers));
-  EXPECT_TRUE(headers.HasHeader(HttpRequestHeaders::kTokenBinding));
-}
-#endif  // !defined(OS_IOS)
-
 void CheckContentEncodingMatching(SpdySessionDependencies* session_deps,
                                   const std::string& accept_encoding,
                                   const std::string& content_encoding,
diff --git a/net/http/http_proxy_client_socket_wrapper_unittest.cc b/net/http/http_proxy_client_socket_wrapper_unittest.cc
index 18427b1..687ee76 100644
--- a/net/http/http_proxy_client_socket_wrapper_unittest.cc
+++ b/net/http/http_proxy_client_socket_wrapper_unittest.cc
@@ -144,8 +144,7 @@
         kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
         allow_server_migration_, race_cert_verification_, estimate_initial_rtt_,
         client_headers_include_h2_stream_dependency_, connection_options_,
-        client_connection_options_, /*enable_token_binding=*/false,
-        /*enable_channel_id=*/false,
+        client_connection_options_, /*enable_channel_id=*/false,
         /*enable_socket_recv_optimization=*/false));
   }
 
diff --git a/net/http/http_request_headers.cc b/net/http/http_request_headers.cc
index ec77236..d8b9df1 100644
--- a/net/http/http_request_headers.cc
+++ b/net/http/http_request_headers.cc
@@ -42,7 +42,6 @@
 const char HttpRequestHeaders::kReferer[] = "Referer";
 const char HttpRequestHeaders::kSecOriginPolicy[] = "Sec-Origin-Policy";
 const char HttpRequestHeaders::kTransferEncoding[] = "Transfer-Encoding";
-const char HttpRequestHeaders::kTokenBinding[] = "Sec-Token-Binding";
 const char HttpRequestHeaders::kUserAgent[] = "User-Agent";
 
 HttpRequestHeaders::HeaderKeyValuePair::HeaderKeyValuePair() = default;
diff --git a/net/http/http_request_headers.h b/net/http/http_request_headers.h
index acdbc8e..3bb9e43 100644
--- a/net/http/http_request_headers.h
+++ b/net/http/http_request_headers.h
@@ -86,7 +86,6 @@
   static const char kReferer[];
   static const char kSecOriginPolicy[];
   static const char kTransferEncoding[];
-  static const char kTokenBinding[];
   static const char kUserAgent[];
 
   HttpRequestHeaders();
diff --git a/net/http/http_request_info.h b/net/http/http_request_info.h
index 987c0e1..a7fbfe43 100644
--- a/net/http/http_request_info.h
+++ b/net/http/http_request_info.h
@@ -42,10 +42,6 @@
   // tracked by the server (e.g. without channel id).
   PrivacyMode privacy_mode;
 
-  // If present, the host of the referrer whose TokenBindingID should be
-  // included in a referred TokenBinding.
-  std::string token_binding_referrer;
-
   // Tag applied to all sockets used to service request.
   SocketTag socket_tag;
 
diff --git a/net/http/http_response_body_drainer_unittest.cc b/net/http/http_response_body_drainer_unittest.cc
index edf7f42..f21ba7d 100644
--- a/net/http/http_response_body_drainer_unittest.cc
+++ b/net/http/http_response_body_drainer_unittest.cc
@@ -118,12 +118,6 @@
   void GetSSLInfo(SSLInfo* ssl_info) override {}
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {}
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; }
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override {
-    ADD_FAILURE();
-    return ERR_NOT_IMPLEMENTED;
-  }
 
   // Mocked API
   int ReadResponseBody(IOBuffer* buf,
diff --git a/net/http/http_stream.h b/net/http/http_stream.h
index 581a491..96937af 100644
--- a/net/http/http_stream.h
+++ b/net/http/http_stream.h
@@ -23,11 +23,6 @@
 #include "net/base/net_export.h"
 #include "net/base/request_priority.h"
 #include "net/http/http_raw_request_headers.h"
-#include "net/ssl/token_binding.h"
-
-namespace crypto {
-class ECPrivateKey;
-}
 
 namespace net {
 
@@ -169,13 +164,6 @@
   // and does not modify |endpoint| if it is unavailable.
   virtual bool GetRemoteEndpoint(IPEndPoint* endpoint) = 0;
 
-  // Generates the signature used in Token Binding using |*key| and for a Token
-  // Binding of type |tb_type|, putting the signature in |*out|. Returns OK or
-  // ERR_FAILED.
-  virtual Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                         TokenBindingType tb_type,
-                                         std::vector<uint8_t>* out) = 0;
-
   // In the case of an HTTP error or redirect, flush the response body (usually
   // a simple error or "this page has moved") so that we can re-use the
   // underlying connection. This stream is responsible for deleting itself when
diff --git a/net/http/http_stream_factory_unittest.cc b/net/http/http_stream_factory_unittest.cc
index 8459add6c..0f23be7 100644
--- a/net/http/http_stream_factory_unittest.cc
+++ b/net/http/http_stream_factory_unittest.cc
@@ -146,12 +146,6 @@
   void GetSSLInfo(SSLInfo* ssl_info) override {}
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {}
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; }
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override {
-    ADD_FAILURE();
-    return ERR_NOT_IMPLEMENTED;
-  }
   void Drain(HttpNetworkSession* session) override {}
   void PopulateNetErrorDetails(NetErrorDetails* details) override { return; }
   void SetPriority(RequestPriority priority) override {}
diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc
index ce23e57..ec6f480e 100644
--- a/net/http/http_stream_parser.cc
+++ b/net/http/http_stream_parser.cc
@@ -25,7 +25,6 @@
 #include "net/log/net_log_event_type.h"
 #include "net/socket/client_socket_handle.h"
 #include "net/socket/ssl_client_socket.h"
-#include "net/ssl/token_binding.h"
 #include "url/url_canon.h"
 
 namespace net {
@@ -1138,16 +1137,6 @@
   }
 }
 
-Error HttpStreamParser::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                                 TokenBindingType tb_type,
-                                                 std::vector<uint8_t>* out) {
-  if (!request_->url.SchemeIsCryptographic() || !connection_->socket()) {
-    NOTREACHED();
-    return ERR_FAILED;
-  }
-  return connection_->socket()->GetTokenBindingSignature(key, tb_type, out);
-}
-
 int HttpStreamParser::EncodeChunk(const base::StringPiece& payload,
                                   char* output,
                                   size_t output_size) {
diff --git a/net/http/http_stream_parser.h b/net/http/http_stream_parser.h
index 603db77..a70c254 100644
--- a/net/http/http_stream_parser.h
+++ b/net/http/http_stream_parser.h
@@ -21,7 +21,6 @@
 #include "net/base/net_errors.h"
 #include "net/base/net_export.h"
 #include "net/log/net_log_with_source.h"
-#include "net/ssl/token_binding.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace net {
@@ -104,10 +103,6 @@
 
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
 
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out);
-
   // Encodes the given |payload| in the chunked format to |output|.
   // Returns the number of bytes written to |output|. |output_size| should
   // be large enough to store the encoded chunk, which is payload.size() +
diff --git a/net/http/proxy_connect_redirect_http_stream.cc b/net/http/proxy_connect_redirect_http_stream.cc
index 565ae55..99b3ba4 100644
--- a/net/http/proxy_connect_redirect_http_stream.cc
+++ b/net/http/proxy_connect_redirect_http_stream.cc
@@ -108,14 +108,6 @@
   return false;
 }
 
-Error ProxyConnectRedirectHttpStream::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  NOTREACHED();
-  return ERR_NOT_IMPLEMENTED;
-}
-
 void ProxyConnectRedirectHttpStream::Drain(HttpNetworkSession* session) {
   NOTREACHED();
 }
diff --git a/net/http/proxy_connect_redirect_http_stream.h b/net/http/proxy_connect_redirect_http_stream.h
index 2a0ae44..67cc6181 100644
--- a/net/http/proxy_connect_redirect_http_stream.h
+++ b/net/http/proxy_connect_redirect_http_stream.h
@@ -62,9 +62,6 @@
   void GetSSLInfo(SSLInfo* ssl_info) override;
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override;
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   void Drain(HttpNetworkSession* session) override;
   void PopulateNetErrorDetails(NetErrorDetails* details) override;
 
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index c22bf49..437ec84 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -1227,9 +1227,6 @@
 // restarting for authentication, on keep alive connections.
 EVENT_TYPE(HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART)
 
-// Measures the time taken to look up the key used for Token Binding.
-EVENT_TYPE(HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY)
-
 // This event is sent when we try to restart a transaction after an error.
 // The following parameters are attached:
 //   {
diff --git a/net/nqe/network_quality_estimator_params.cc b/net/nqe/network_quality_estimator_params.cc
index 16dda1aa..b0516fd 100644
--- a/net/nqe/network_quality_estimator_params.cc
+++ b/net/nqe/network_quality_estimator_params.cc
@@ -238,6 +238,24 @@
   }
 }
 
+// Typical HTTP RTT value corresponding to a given WebEffectiveConnectionType
+// value. Taken from
+// https://cs.chromium.org/chromium/src/net/nqe/network_quality_estimator_params.cc.
+const base::TimeDelta kTypicalHttpRttEffectiveConnectionType
+    [net::EFFECTIVE_CONNECTION_TYPE_LAST] = {
+        base::TimeDelta::FromMilliseconds(0),
+        base::TimeDelta::FromMilliseconds(0),
+        base::TimeDelta::FromMilliseconds(3600),
+        base::TimeDelta::FromMilliseconds(1800),
+        base::TimeDelta::FromMilliseconds(450),
+        base::TimeDelta::FromMilliseconds(175)};
+
+// Typical downlink throughput (in Mbps) value corresponding to a given
+// WebEffectiveConnectionType value. Taken from
+// https://cs.chromium.org/chromium/src/net/nqe/network_quality_estimator_params.cc.
+const int32_t kTypicalDownlinkKbpsEffectiveConnectionType
+    [net::EFFECTIVE_CONNECTION_TYPE_LAST] = {0, 0, 40, 75, 400, 1600};
+
 // Sets |typical_network_quality| to typical network quality for different
 // effective connection types.
 void ObtainTypicalNetworkQualities(
@@ -257,30 +275,39 @@
           // Set to the 77.5th percentile of 2G RTT observations on Android.
           // This corresponds to the median RTT observation when effective
           // connection type is Slow 2G.
-          base::TimeDelta::FromMilliseconds(3600),
-          base::TimeDelta::FromMilliseconds(3000), 40);
+          kTypicalHttpRttEffectiveConnectionType
+              [EFFECTIVE_CONNECTION_TYPE_SLOW_2G],
+          base::TimeDelta::FromMilliseconds(3000),
+          kTypicalDownlinkKbpsEffectiveConnectionType
+              [EFFECTIVE_CONNECTION_TYPE_SLOW_2G]);
 
   typical_network_quality[EFFECTIVE_CONNECTION_TYPE_2G] =
       nqe::internal::NetworkQuality(
           // Set to the 58th percentile of 2G RTT observations on Android. This
           // corresponds to the median RTT observation when effective connection
           // type is 2G.
-          base::TimeDelta::FromMilliseconds(1800),
-          base::TimeDelta::FromMilliseconds(1500), 75);
+          kTypicalHttpRttEffectiveConnectionType[EFFECTIVE_CONNECTION_TYPE_2G],
+          base::TimeDelta::FromMilliseconds(1500),
+          kTypicalDownlinkKbpsEffectiveConnectionType
+              [EFFECTIVE_CONNECTION_TYPE_2G]);
 
   typical_network_quality[EFFECTIVE_CONNECTION_TYPE_3G] =
       nqe::internal::NetworkQuality(
           // Set to the 75th percentile of 3G RTT observations on Android. This
           // corresponds to the median RTT observation when effective connection
           // type is 3G.
-          base::TimeDelta::FromMilliseconds(450),
-          base::TimeDelta::FromMilliseconds(400), 400);
+          kTypicalHttpRttEffectiveConnectionType[EFFECTIVE_CONNECTION_TYPE_3G],
+          base::TimeDelta::FromMilliseconds(400),
+          kTypicalDownlinkKbpsEffectiveConnectionType
+              [EFFECTIVE_CONNECTION_TYPE_3G]);
 
   // Set to the 25th percentile of 3G RTT observations on Android.
   typical_network_quality[EFFECTIVE_CONNECTION_TYPE_4G] =
-      nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(175),
-                                    base::TimeDelta::FromMilliseconds(125),
-                                    1600);
+      nqe::internal::NetworkQuality(
+          kTypicalHttpRttEffectiveConnectionType[EFFECTIVE_CONNECTION_TYPE_4G],
+          base::TimeDelta::FromMilliseconds(125),
+          kTypicalDownlinkKbpsEffectiveConnectionType
+              [EFFECTIVE_CONNECTION_TYPE_4G]);
 
   static_assert(
       EFFECTIVE_CONNECTION_TYPE_4G + 1 == EFFECTIVE_CONNECTION_TYPE_LAST,
@@ -518,6 +545,18 @@
   return use_small_responses_;
 };
 
+// static
+base::TimeDelta NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
+    EffectiveConnectionType effective_connection_type) {
+  return kTypicalHttpRttEffectiveConnectionType[effective_connection_type];
+}
+
+// static
+int32_t NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(
+    EffectiveConnectionType effective_connection_type) {
+  return kTypicalDownlinkKbpsEffectiveConnectionType[effective_connection_type];
+}
+
 void NetworkQualityEstimatorParams::SetForcedEffectiveConnectionTypeForTesting(
     EffectiveConnectionType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/net/nqe/network_quality_estimator_params.h b/net/nqe/network_quality_estimator_params.h
index 6add5a6..f9762e2 100644
--- a/net/nqe/network_quality_estimator_params.h
+++ b/net/nqe/network_quality_estimator_params.h
@@ -174,6 +174,18 @@
   // network quality. Set to true only for tests.
   bool use_small_responses() const;
 
+  // Returns the typical HTTP RTT that maps to the given
+  // |effective_connection_type|. May return invalid value if
+  // |effective_connection_type| is less than Slow2G or faster than 4G,
+  static base::TimeDelta GetDefaultTypicalHttpRtt(
+      EffectiveConnectionType effective_connection_type);
+
+  // Returns the typical downslink throughput (in kbps) that maps to the given
+  // |effective_connection_type|. May return invalid value if
+  // |effective_connection_type| is less than Slow2G or faster than 4G,
+  static int32_t GetDefaultTypicalDownlinkKbps(
+      EffectiveConnectionType effective_connection_type);
+
   // |use_small_responses| should only be true when testing.
   // Allows the responses smaller than |kMinTransferSizeInBits| to be used for
   // network quality estimation.
diff --git a/net/nqe/network_quality_estimator_params_unittest.cc b/net/nqe/network_quality_estimator_params_unittest.cc
index d9b2ed2..aa6c98bb0 100644
--- a/net/nqe/network_quality_estimator_params_unittest.cc
+++ b/net/nqe/network_quality_estimator_params_unittest.cc
@@ -83,6 +83,12 @@
               params.TypicalNetworkQuality(ect).downstream_throughput_kbps());
     EXPECT_EQ(nqe::internal::INVALID_RTT_THROUGHPUT,
               params.ConnectionThreshold(ect).downstream_throughput_kbps());
+
+    EXPECT_EQ(params.TypicalNetworkQuality(ect).http_rtt(),
+              NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(ect));
+    EXPECT_EQ(
+        params.TypicalNetworkQuality(ect).downstream_throughput_kbps(),
+        NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(ect));
   }
 
   // The typical network quality of 4G connection should be at least as fast
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index bb1bcdea..cafdd106 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -38,7 +38,6 @@
 #include "net/ssl/channel_id_service.h"
 #include "net/ssl/ssl_connection_status_flags.h"
 #include "net/ssl/ssl_info.h"
-#include "net/ssl/token_binding.h"
 #include "net/third_party/quic/core/http/quic_client_promised_info.h"
 #include "net/third_party/quic/core/http/spdy_utils.h"
 #include "net/third_party/quic/platform/api/quic_ptr_util.h"
@@ -57,12 +56,6 @@
 // IP address changes.
 const size_t kMaxReadersPerQuicSession = 5;
 
-// Size of the MRU cache of Token Binding signatures. Since the material being
-// signed is constant and there aren't many keys being used to sign, a fairly
-// small number was chosen, somewhat arbitrarily, and to match
-// SSLClientSocketImpl.
-const size_t kTokenBindingSignatureMapSize = 10;
-
 // Time to wait (in seconds) when no networks are available and
 // migrating sessions need to wait for a new network to connect.
 const size_t kWaitTimeForNewNetworkSecs = 10;
@@ -333,16 +326,6 @@
   return session_->GetConnectTiming();
 }
 
-Error QuicChromiumClientSession::Handle::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  if (!session_)
-    return ERR_CONNECTION_CLOSED;
-
-  return session_->GetTokenBindingSignature(key, tb_type, out);
-}
-
 void QuicChromiumClientSession::Handle::PopulateNetErrorDetails(
     NetErrorDetails* details) const {
   if (session_) {
@@ -740,7 +723,6 @@
                                        net_log_)),
       going_away_(false),
       port_migration_detected_(false),
-      token_binding_signatures_(kTokenBindingSignatureMapSize),
       push_delegate_(push_delegate),
       streams_pushed_count_(0),
       streams_pushed_and_claimed_count_(0),
@@ -1175,40 +1157,9 @@
 
   ssl_info->UpdateCertificateTransparencyInfo(*ct_verify_result_);
 
-  if (crypto_stream_->crypto_negotiated_params().token_binding_key_param ==
-      quic::kTB10) {
-    ssl_info->token_binding_negotiated = true;
-    ssl_info->token_binding_key_param = TB_PARAM_ECDSAP256;
-  }
-
   return true;
 }
 
-Error QuicChromiumClientSession::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  // The same key will be used across multiple requests to sign the same value,
-  // so the signature is cached.
-  std::string raw_public_key;
-  if (!key->ExportRawPublicKey(&raw_public_key))
-    return ERR_FAILED;
-  TokenBindingSignatureMap::iterator it =
-      token_binding_signatures_.Get(std::make_pair(tb_type, raw_public_key));
-  if (it != token_binding_signatures_.end()) {
-    *out = it->second;
-    return OK;
-  }
-
-  std::string key_material;
-  if (!crypto_stream_->ExportTokenBindingKeyingMaterial(&key_material))
-    return ERR_FAILED;
-  if (!CreateTokenBindingSignature(key_material, tb_type, key, out))
-    return ERR_FAILED;
-  token_binding_signatures_.Put(std::make_pair(tb_type, raw_public_key), *out);
-  return OK;
-}
-
 int QuicChromiumClientSession::CryptoConnect(CompletionOnceCallback callback) {
   connect_timing_.connect_start = base::TimeTicks::Now();
   RecordHandshakeState(STATE_STARTED);
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index 1bfd7a8..2a2da167 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -58,10 +58,6 @@
 class SSLInfo;
 class TransportSecurityState;
 
-using TokenBindingSignatureMap =
-    base::MRUCache<std::pair<TokenBindingType, std::string>,
-                   std::vector<uint8_t>>;
-
 namespace test {
 class QuicChromiumClientSessionPeer;
 }  // namespace test
@@ -192,12 +188,6 @@
     // Returns the connection timing for the handshake of this session.
     const LoadTimingInfo::ConnectTiming& GetConnectTiming();
 
-    // Signs the exported keying material used for Token Binding using key
-    // |*key| and puts the signature in |*out|. Returns a net error code.
-    Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                   TokenBindingType tb_type,
-                                   std::vector<uint8_t>* out);
-
     // Returns true if |other| is a handle to the same session as this handle.
     bool SharesSameSession(const Handle& other) const;
 
@@ -514,9 +504,6 @@
   // MultiplexedSession methods:
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
   bool GetSSLInfo(SSLInfo* ssl_info) const override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
 
   // Performs a crypto handshake with the server.
   int CryptoConnect(CompletionOnceCallback callback);
@@ -783,7 +770,6 @@
   bool going_away_;
   // True when the session receives a go away from server due to port migration.
   bool port_migration_detected_;
-  TokenBindingSignatureMap token_binding_signatures_;
   // Not owned. |push_delegate_| outlives the session and handles server pushes
   // received by session.
   ServerPushDelegate* push_delegate_;
diff --git a/net/quic/quic_end_to_end_unittest.cc b/net/quic/quic_end_to_end_unittest.cc
index 675c8e7..8478688 100644
--- a/net/quic/quic_end_to_end_unittest.cc
+++ b/net/quic/quic_end_to_end_unittest.cc
@@ -179,8 +179,6 @@
         quic::test::kInitialStreamFlowControlWindowForTest);
     server_config_.SetInitialSessionFlowControlWindowToSend(
         quic::test::kInitialSessionFlowControlWindowForTest);
-    server_config_options_.token_binding_params =
-        quic::QuicTagVector{quic::kTB10, quic::kP256};
     server_.reset(new QuicSimpleServer(
         quic::test::crypto_test_utils::ProofSourceForTesting(), server_config_,
         server_config_options_, quic::AllSupportedVersions(),
@@ -282,27 +280,6 @@
   CheckResponse(consumer, "HTTP/1.1 200", response);
 }
 
-TEST_P(QuicEndToEndTest, TokenBinding) {
-  // Enable token binding and re-initialize the TestTransactionFactory.
-  session_params_.enable_token_binding = true;
-  transaction_factory_.reset(
-      new TestTransactionFactory(session_params_, session_context_));
-
-  AddToCache(request_.url.PathForRequest(), 200, "OK", kResponseBody);
-
-  TestTransactionConsumer consumer(DEFAULT_PRIORITY,
-                                   transaction_factory_.get());
-  consumer.Start(&request_, NetLogWithSource());
-
-  // Will terminate when the last consumer completes.
-  base::RunLoop().Run();
-
-  CheckResponse(consumer, "HTTP/1.1 200", kResponseBody);
-  HttpRequestHeaders headers;
-  ASSERT_TRUE(consumer.transaction()->GetFullRequestHeaders(&headers));
-  EXPECT_TRUE(headers.HasHeader(HttpRequestHeaders::kTokenBinding));
-}
-
 // crbug.com/559173
 #if defined(THREAD_SANITIZER)
 TEST_P(QuicEndToEndTest, DISABLED_LargePostWithNoPacketLoss) {
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index b79cc201..a24b5a18 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -50,7 +50,6 @@
 #include "net/socket/socket_performance_watcher.h"
 #include "net/socket/socket_performance_watcher_factory.h"
 #include "net/socket/udp_client_socket.h"
-#include "net/ssl/token_binding.h"
 #include "net/third_party/quic/core/crypto/proof_verifier.h"
 #include "net/third_party/quic/core/crypto/quic_random.h"
 #include "net/third_party/quic/core/http/quic_client_promised_info.h"
@@ -380,15 +379,27 @@
   };
 
   void CloseStaleHostConnection() {
+    DVLOG(1) << "Closing connection from stale host.";
     if (session_) {
       QuicChromiumClientSession* session = session_;
       session_ = nullptr;
-      session->CloseSessionOnError(
+      session->CloseSessionOnErrorLater(
           ERR_ABORTED, quic::QUIC_CONNECTION_CANCELLED,
           quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     }
   }
 
+  bool DoesPeerAddressMatchWithFreshAddressList() {
+    std::vector<net::IPEndPoint> endpoints = address_list_.endpoints();
+    IPEndPoint stale_address = session_->peer_address().impl().socket_address();
+
+    if (std::find(endpoints.begin(), endpoints.end(), stale_address) !=
+        endpoints.end()) {
+      return true;
+    }
+    return false;
+  }
+
   IoState io_state_;
   QuicStreamFactory* factory_;
   quic::QuicTransportVersion quic_version_;
@@ -534,11 +545,17 @@
       return;
     } else if (io_state_ != STATE_HOST_VALIDATION) {
       // Case where host resolution returns successfully, but stale connection
-      // hasn't finished yet. Host validation will be handled in
-      // DoValidateHost().
-      // TODO(renjietang): In the future, we can also compare IPs here and if
-      // they don't match, we can close the stale connection early.
-      return;
+      // hasn't finished yet.
+      if (DoesPeerAddressMatchWithFreshAddressList()) {
+        dns_race_ongoing_ = false;
+        return;
+      }
+      net_log_.AddEvent(
+          NetLogEventType::
+              QUIC_STREAM_FACTORY_JOB_STALE_HOST_RESOLUTION_NO_MATCH);
+      CloseStaleHostConnection();
+      dns_race_ongoing_ = false;
+      io_state_ = STATE_RESOLVE_HOST_COMPLETE;
     }
   }
 
@@ -695,11 +712,7 @@
 // This state is reached iff both host resolution and connection from stale dns
 // have finished successfully.
 int QuicStreamFactory::Job::DoValidateHost() {
-  std::vector<net::IPEndPoint> endpoints = address_list_.endpoints();
-  IPEndPoint stale_address = session_->peer_address().impl().socket_address();
-
-  if (std::find(endpoints.begin(), endpoints.end(), stale_address) !=
-      endpoints.end()) {
+  if (DoesPeerAddressMatchWithFreshAddressList()) {
     io_state_ = STATE_CONFIRM_CONNECTION;
     return OK;
   }
@@ -911,7 +924,6 @@
     bool headers_include_h2_stream_dependency,
     const quic::QuicTagVector& connection_options,
     const quic::QuicTagVector& client_connection_options,
-    bool enable_token_binding,
     bool enable_channel_id,
     bool enable_socket_recv_optimization)
     : require_confirmation_(true),
@@ -993,8 +1005,6 @@
     crypto_config_.SetChannelIDSource(
         new ChannelIDSourceChromium(channel_id_service));
   }
-  if (enable_token_binding && channel_id_service)
-    crypto_config_.tb_key_params.push_back(quic::kTB10);
   crypto::EnsureOpenSSLInit();
   bool has_aes_hardware_support = !!EVP_has_aes_hardware();
   UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.PreferAesGcm",
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index 1595fb6..e3e7c69 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -261,7 +261,6 @@
       bool headers_include_h2_stream_dependency,
       const quic::QuicTagVector& connection_options,
       const quic::QuicTagVector& client_connection_options,
-      bool enable_token_binding,
       bool enable_channel_id,
       bool enable_socket_recv_optimization);
   ~QuicStreamFactory() override;
diff --git a/net/quic/quic_stream_factory_fuzzer.cc b/net/quic/quic_stream_factory_fuzzer.cc
index 1b1e7f8d..e82e47b4 100644
--- a/net/quic/quic_stream_factory_fuzzer.cc
+++ b/net/quic/quic_stream_factory_fuzzer.cc
@@ -101,7 +101,6 @@
   bool race_cert_verification = data_provider.ConsumeBool();
   bool estimate_initial_rtt = data_provider.ConsumeBool();
   bool headers_include_h2_stream_dependency = data_provider.ConsumeBool();
-  bool enable_token_binding = data_provider.ConsumeBool();
   bool enable_channel_id = data_provider.ConsumeBool();
   bool enable_socket_recv_optimization = data_provider.ConsumeBool();
   bool race_stale_dns_on_connection = data_provider.ConsumeBool();
@@ -150,8 +149,8 @@
           kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
           allow_server_migration, race_cert_verification, estimate_initial_rtt,
           headers_include_h2_stream_dependency, env->connection_options,
-          env->client_connection_options, enable_token_binding,
-          enable_channel_id, enable_socket_recv_optimization);
+          env->client_connection_options, enable_channel_id,
+          enable_socket_recv_optimization);
 
   QuicStreamRequest request(factory.get());
   TestCompletionCallback callback;
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index ece11002..810f89a 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -299,7 +299,7 @@
         kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
         allow_server_migration_, race_cert_verification_, estimate_initial_rtt_,
         client_headers_include_h2_stream_dependency_, connection_options_,
-        client_connection_options_, /*enable_token_binding*/ false,
+        client_connection_options_,
         /*enable_channel_id*/ false,
         /*enable_socket_recv_optimization*/ false));
   }
@@ -8690,6 +8690,7 @@
   cache->Set(key, entry, base::TimeTicks::Now(), zero);
   // Expire the cache
   cache->OnNetworkChange();
+
   MockQuicData quic_data;
   quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   quic_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
@@ -9128,14 +9129,16 @@
 
   MockQuicData quic_data;
   quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  quic_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_INITIAL);
   quic_data.AddWrite(
       SYNCHRONOUS, client_maker_.MakeConnectionClosePacket(
-                       2, true, quic::QUIC_CONNECTION_CANCELLED, "net error"));
+                       1, true, quic::QUIC_CONNECTION_CANCELLED, "net error"));
   quic_data.AddSocketDataToFactory(socket_factory_.get());
 
   MockQuicData quic_data2;
   quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
+  quic_data2.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   QuicStreamRequest request(factory_.get());
@@ -9147,9 +9150,7 @@
                 failed_on_default_network_callback_, callback_.callback()));
   // Finish dns resolution, but need to wait for stale connection.
   host_resolver_->ResolveAllPending();
-  EXPECT_FALSE(callback_.have_result());
-
-  // Finish stale connection and expect the job to be done.
+  base::RunLoop().RunUntilIdle();
   crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
       quic::QuicSession::HANDSHAKE_CONFIRMED);
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -9271,8 +9272,6 @@
   host_resolver_->ResolveAllPending();
   EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_NAME_NOT_RESOLVED));
 
-  EXPECT_FALSE(HasLiveSession(host_port_pair_));
-
   EXPECT_TRUE(quic_data.AllReadDataConsumed());
   EXPECT_TRUE(quic_data.AllWriteDataConsumed());
 }
@@ -9494,82 +9493,6 @@
 }
 
 // With dns race experiment on, dns resolve async and stale connect async, dns
-// resolve returns fine then preconnect fails
-TEST_P(QuicStreamFactoryTest, ResultAfterDNSRaceResolveAsyncStaleAsyncError) {
-  race_stale_dns_on_connection_ = true;
-  host_resolver_ = std::make_unique<MockCachingHostResolver>();
-  Initialize();
-  ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
-  crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
-
-  // Set asynchronous fresh address in host resolver.
-  host_resolver_->set_ondemand_mode(true);
-  factory_->set_require_confirmation(true);
-  crypto_client_stream_factory_.set_handshake_mode(
-      MockCryptoClientStream::ZERO_RTT);
-  host_resolver_->rules()->AddIPLiteralRule(host_port_pair_.host(),
-                                            kNonCachedIPAddress, "");
-
-  // Set up an address in stale resolver cache.
-  HostCache::Key key(host_port_pair_.host(), ADDRESS_FAMILY_UNSPECIFIED, 0);
-  HostCache::Entry entry(OK,
-                         AddressList::CreateFromIPAddress(kCachedIPAddress, 0),
-                         HostCache::Entry::SOURCE_DNS);
-  base::TimeDelta zero;
-  HostCache* cache = host_resolver_->GetHostCache();
-  cache->Set(key, entry, base::TimeTicks::Now(), zero);
-  // Expire the cache
-  cache->OnNetworkChange();
-
-  MockQuicData quic_data;
-  quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  quic_data.AddSocketDataToFactory(socket_factory_.get());
-
-  MockQuicData quic_data2;
-  quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  quic_data2.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
-  quic_data2.AddSocketDataToFactory(socket_factory_.get());
-
-  QuicStreamRequest request(factory_.get());
-  EXPECT_EQ(ERR_IO_PENDING,
-            request.Request(
-                host_port_pair_, version_, privacy_mode_, DEFAULT_PRIORITY,
-                SocketTag(),
-                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
-                failed_on_default_network_callback_, callback_.callback()));
-
-  // Host resolution finishes but stale connection is still running.
-  host_resolver_->ResolveAllPending();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(callback_.have_result());
-
-  // Kill the session so that crypto connect will return error.
-  QuicChromiumClientSession* session = GetPendingSession(host_port_pair_);
-  session->CloseSessionOnError(ERR_ABORTED, quic::QUIC_INTERNAL_ERROR,
-                               quic::ConnectionCloseBehavior::SILENT_CLOSE);
-  EXPECT_FALSE(callback_.have_result());
-
-  // Send crypto handshake for the new connection and check that it finishes.
-  crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
-      quic::QuicSession::HANDSHAKE_CONFIRMED);
-  EXPECT_THAT(callback_.WaitForResult(), IsOk());
-
-  std::unique_ptr<HttpStream> stream = CreateStream(&request);
-  EXPECT_TRUE(stream.get());
-
-  QuicChromiumClientSession* session2 = GetActiveSession(host_port_pair_);
-
-  EXPECT_EQ(
-      session2->peer_address().impl().socket_address().ToStringWithoutPort(),
-      kNonCachedIPAddress);
-
-  EXPECT_TRUE(quic_data.AllReadDataConsumed());
-  EXPECT_TRUE(quic_data.AllWriteDataConsumed());
-  EXPECT_TRUE(quic_data2.AllReadDataConsumed());
-  EXPECT_TRUE(quic_data2.AllWriteDataConsumed());
-}
-
-// With dns race experiment on, dns resolve async and stale connect async, dns
 // resolve returns error and then preconnect fails.
 TEST_P(QuicStreamFactoryTest,
        ResultAfterDNSRaceResolveAsyncErrorStaleAsyncError) {
diff --git a/net/socket/fuzzed_socket_factory.cc b/net/socket/fuzzed_socket_factory.cc
index 65a11fc..504ee2aa 100644
--- a/net/socket/fuzzed_socket_factory.cc
+++ b/net/socket/fuzzed_socket_factory.cc
@@ -93,13 +93,6 @@
     return nullptr;
   }
 
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override {
-    NOTREACHED();
-    return ERR_UNEXPECTED;
-  }
-
   crypto::ECPrivateKey* GetChannelIDKey() const override {
     NOTREACHED();
     return nullptr;
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index a8492e01..164b9f3 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -1586,13 +1586,6 @@
   return data_->channel_id_service;
 }
 
-Error MockSSLClientSocket::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                                    TokenBindingType tb_type,
-                                                    std::vector<uint8_t>* out) {
-  out->push_back('A');
-  return OK;
-}
-
 int MockSSLClientSocket::ExportKeyingMaterial(const base::StringPiece& label,
                                               bool has_context,
                                               const base::StringPiece& context,
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
index 7851ad9..b7cabb2 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -918,9 +918,6 @@
   bool GetSSLInfo(SSLInfo* ssl_info) override;
   void GetSSLCertRequestInfo(
       SSLCertRequestInfo* cert_request_info) const override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   ChannelIDService* GetChannelIDService() const override;
   crypto::ECPrivateKey* GetChannelIDKey() const override;
   void ApplySocketTag(const SocketTag& tag) override;
diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h
index 0221f8b1..f522db3db 100644
--- a/net/socket/ssl_client_socket.h
+++ b/net/socket/ssl_client_socket.h
@@ -18,7 +18,6 @@
 #include "net/base/net_export.h"
 #include "net/socket/ssl_socket.h"
 #include "net/socket/stream_socket.h"
-#include "net/ssl/token_binding.h"
 
 namespace net {
 
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index bdcbf4b..4bc70a5f1 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -53,7 +53,6 @@
 #include "net/ssl/ssl_info.h"
 #include "net/ssl/ssl_key_logger.h"
 #include "net/ssl/ssl_private_key.h"
-#include "net/ssl/token_binding.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "third_party/boringssl/src/include/openssl/bio.h"
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
@@ -461,7 +460,6 @@
       cert_verifier_(context.cert_verifier),
       cert_transparency_verifier_(context.cert_transparency_verifier),
       channel_id_service_(context.channel_id_service),
-      tb_signature_map_(10),
       transport_(std::move(transport_socket)),
       host_and_port_(host_and_port),
       ssl_config_(ssl_config),
@@ -666,10 +664,6 @@
   ssl_info->client_cert_sent =
       ssl_config_.send_client_cert && ssl_config_.client_cert.get();
   ssl_info->channel_id_sent = channel_id_sent_;
-  ssl_info->token_binding_negotiated =
-      SSL_is_token_binding_negotiated(ssl_.get());
-  ssl_info->token_binding_key_param = static_cast<net::TokenBindingParam>(
-      SSL_get_negotiated_token_binding_param(ssl_.get()));
   ssl_info->pinning_failure_log = pinning_failure_log_;
   ssl_info->ocsp_result = server_cert_verify_result_.ocsp_result;
   ssl_info->is_fatal_cert_error = is_fatal_cert_error_;
@@ -748,39 +742,6 @@
   return channel_id_service_;
 }
 
-Error SSLClientSocketImpl::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                                    TokenBindingType tb_type,
-                                                    std::vector<uint8_t>* out) {
-  // The same key will be used across multiple requests to sign the same value,
-  // so the signature is cached.
-  std::string raw_public_key;
-  if (!key->ExportRawPublicKey(&raw_public_key))
-    return ERR_FAILED;
-  auto it = tb_signature_map_.Get(std::make_pair(tb_type, raw_public_key));
-  if (it != tb_signature_map_.end()) {
-    *out = it->second;
-    return OK;
-  }
-
-  uint8_t tb_ekm_buf[32];
-  static const char kTokenBindingExporterLabel[] = "EXPORTER-Token-Binding";
-  if (!SSL_export_keying_material(ssl_.get(), tb_ekm_buf, sizeof(tb_ekm_buf),
-                                  kTokenBindingExporterLabel,
-                                  strlen(kTokenBindingExporterLabel), nullptr,
-                                  0, false /* no context */)) {
-    return ERR_FAILED;
-  }
-
-  if (!CreateTokenBindingSignature(
-          base::StringPiece(reinterpret_cast<char*>(tb_ekm_buf),
-                            sizeof(tb_ekm_buf)),
-          tb_type, key, out))
-    return ERR_FAILED;
-
-  tb_signature_map_.Put(std::make_pair(tb_type, raw_public_key), *out);
-  return OK;
-}
-
 crypto::ECPrivateKey* SSLClientSocketImpl::GetChannelIDKey() const {
   return channel_id_key_.get();
 }
@@ -976,12 +937,6 @@
     SSL_enable_tls_channel_id(ssl_.get());
   }
 
-  if (!ssl_config_.token_binding_params.empty()) {
-    std::vector<uint8_t> params(ssl_config_.token_binding_params.begin(),
-                                ssl_config_.token_binding_params.end());
-    SSL_set_token_binding_params(ssl_.get(), params.data(), params.size());
-  }
-
   if (!ssl_config_.alpn_protos.empty()) {
     std::vector<uint8_t> wire_protos =
         SerializeNextProtos(ssl_config_.alpn_protos);
@@ -1699,9 +1654,6 @@
 }
 
 bool SSLClientSocketImpl::IsRenegotiationAllowed() const {
-  if (SSL_is_token_binding_negotiated(ssl_.get()))
-    return false;
-
   if (negotiated_protocol_ == kProtoUnknown)
     return ssl_config_.renego_allowed_default;
 
diff --git a/net/socket/ssl_client_socket_impl.h b/net/socket/ssl_client_socket_impl.h
index 14d3e10..186594c1 100644
--- a/net/socket/ssl_client_socket_impl.h
+++ b/net/socket/ssl_client_socket_impl.h
@@ -53,10 +53,6 @@
 class SSLInfo;
 class SSLKeyLogger;
 
-using TokenBindingSignatureMap =
-    base::MRUCache<std::pair<TokenBindingType, std::string>,
-                   std::vector<uint8_t>>;
-
 class SSLClientSocketImpl : public SSLClientSocket,
                             public SocketBIOAdapter::Delegate {
  public:
@@ -107,9 +103,6 @@
   void GetSSLCertRequestInfo(
       SSLCertRequestInfo* cert_request_info) const override;
   ChannelIDService* GetChannelIDService() const override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   crypto::ECPrivateKey* GetChannelIDKey() const override;
 
   void ApplySocketTag(const SocketTag& tag) override;
@@ -219,13 +212,6 @@
                        const void* buf,
                        size_t len);
 
-  int TokenBindingAdd(const uint8_t** out,
-                      size_t* out_len,
-                      int* out_alert_value);
-  int TokenBindingParse(const uint8_t* contents,
-                        size_t contents_len,
-                        int* out_alert_value);
-
   void LogConnectEndEvent(int rv);
 
   // Record whether ALPN was used, and if so, the negotiated protocol,
@@ -287,7 +273,6 @@
 
   // The service for retrieving Channel ID keys.  May be NULL.
   ChannelIDService* channel_id_service_;
-  TokenBindingSignatureMap tb_signature_map_;
 
   // OpenSSL stuff
   bssl::UniquePtr<SSL> ssl_;
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 27ed3ee6..7e9a15c 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -3066,52 +3066,6 @@
   EXPECT_THAT(rv, IsError(ERR_SSL_VERSION_OR_CIPHER_MISMATCH));
 }
 
-TEST_F(SSLClientSocketTest, TokenBindingEnabled) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  ASSERT_TRUE(StartTestServer(ssl_options));
-
-  SSLConfig ssl_config;
-  ssl_config.token_binding_params.push_back(TB_PARAM_ECDSAP256);
-
-  int rv;
-  ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
-  EXPECT_THAT(rv, IsOk());
-  SSLInfo info;
-  EXPECT_TRUE(sock_->GetSSLInfo(&info));
-  EXPECT_TRUE(info.token_binding_negotiated);
-  EXPECT_EQ(TB_PARAM_ECDSAP256, info.token_binding_key_param);
-}
-
-TEST_F(SSLClientSocketTest, TokenBindingFailsWithEmsDisabled) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  ssl_options.disable_extended_master_secret = true;
-  ASSERT_TRUE(StartTestServer(ssl_options));
-
-  SSLConfig ssl_config;
-  ssl_config.token_binding_params.push_back(TB_PARAM_ECDSAP256);
-
-  int rv;
-  ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
-  EXPECT_THAT(rv, IsError(ERR_SSL_PROTOCOL_ERROR));
-}
-
-TEST_F(SSLClientSocketTest, TokenBindingEnabledWithoutServerSupport) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ASSERT_TRUE(StartTestServer(ssl_options));
-
-  SSLConfig ssl_config;
-  ssl_config.token_binding_params.push_back(TB_PARAM_ECDSAP256);
-
-  int rv;
-  ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
-  EXPECT_THAT(rv, IsOk());
-  SSLInfo info;
-  EXPECT_TRUE(sock_->GetSSLInfo(&info));
-  EXPECT_FALSE(info.token_binding_negotiated);
-}
-
 TEST_F(SSLClientSocketFalseStartTest, FalseStartEnabled) {
   // False Start requires ALPN, ECDHE, and an AEAD.
   SpawnedTestServer::SSLOptions server_options;
diff --git a/net/socket/stream_socket.cc b/net/socket/stream_socket.cc
index dd83d61..bf698ba 100644
--- a/net/socket/stream_socket.cc
+++ b/net/socket/stream_socket.cc
@@ -23,13 +23,6 @@
   return nullptr;
 }
 
-Error StreamSocket::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                             TokenBindingType tb_type,
-                                             std::vector<uint8_t>* out) {
-  NOTREACHED();
-  return ERR_NOT_IMPLEMENTED;
-}
-
 crypto::ECPrivateKey* StreamSocket::GetChannelIDKey() const {
   NOTREACHED();
   return nullptr;
diff --git a/net/socket/stream_socket.h b/net/socket/stream_socket.h
index 1c5a6f06..41bb302f5 100644
--- a/net/socket/stream_socket.h
+++ b/net/socket/stream_socket.h
@@ -14,7 +14,6 @@
 #include "net/socket/connection_attempts.h"
 #include "net/socket/next_proto.h"
 #include "net/socket/socket.h"
-#include "net/ssl/token_binding.h"
 
 namespace crypto {
 class ECPrivateKey;
@@ -160,13 +159,6 @@
   // not support SSL.
   virtual ChannelIDService* GetChannelIDService() const;
 
-  // Generates the signature used in Token Binding using key |*key| and for a
-  // Token Binding of type |tb_type|, putting the signature in |*out|. Returns a
-  // net error code.  Must not be called on a socket that does not support SSL.
-  virtual Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                         TokenBindingType tb_type,
-                                         std::vector<uint8_t>* out);
-
   // This method is only for debugging https://crbug.com/548423 and will be
   // removed when that bug is closed. This returns the channel ID key that was
   // used when establishing the connection (or NULL if no channel ID was used).
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 79c32f7..e51998c 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -54,18 +54,6 @@
 
 namespace {
 
-// Creates an address from ip address and port and writes it to |*address|.
-bool CreateUDPAddress(const std::string& ip_str,
-                      uint16_t port,
-                      IPEndPoint* address) {
-  IPAddress ip_address;
-  if (!ip_address.AssignFromIPLiteral(ip_str))
-    return false;
-
-  *address = IPEndPoint(ip_address, port);
-  return true;
-}
-
 class UDPSocketTest : public PlatformTest, public WithScopedTaskEnvironment {
  public:
   UDPSocketTest() : buffer_(base::MakeRefCounted<IOBufferWithSize>(kMaxRead)) {}
@@ -126,15 +114,14 @@
     WriteSocket(socket, msg);
   }
 
-  // And again for a bare socket
-  int SendToSocket(UDPSocket* socket,
-                   std::string msg,
-                   const IPEndPoint& address) {
-    scoped_refptr<StringIOBuffer> io_buffer = new StringIOBuffer(msg);
-    TestCompletionCallback callback;
-    int rv = socket->SendTo(io_buffer.get(), io_buffer->size(), address,
-                            callback.callback());
-    return callback.GetResult(rv);
+  // Creates an address from ip address and port and writes it to |*address|.
+  void CreateUDPAddress(const std::string& ip_str,
+                        uint16_t port,
+                        IPEndPoint* address) {
+    IPAddress ip_address;
+    if (!ip_address.AssignFromIPLiteral(ip_str))
+      return;
+    *address = IPEndPoint(ip_address, port);
   }
 
   // Run unit test for a connection test.
@@ -326,9 +313,9 @@
   std::string first_message("first message"), second_message("second message");
 
   IPEndPoint broadcast_address;
-  ASSERT_TRUE(CreateUDPAddress("255.255.255.255", kPort, &broadcast_address));
+  CreateUDPAddress("255.255.255.255", kPort, &broadcast_address);
   IPEndPoint listen_address;
-  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &listen_address));
+  CreateUDPAddress("0.0.0.0", kPort, &listen_address);
 
   TestNetLog server1_log, server2_log;
   std::unique_ptr<UDPServerSocket> server1(
@@ -597,7 +584,7 @@
 TEST_F(UDPSocketTest, ServerSetDoNotFragment) {
   for (std::string ip : {"127.0.0.1", "::1"}) {
     IPEndPoint bind_address;
-    ASSERT_TRUE(CreateUDPAddress(ip, 0, &bind_address));
+    CreateUDPAddress(ip, 0, &bind_address);
     UDPServerSocket server(nullptr, NetLogSource());
     int rv = server.Listen(bind_address);
     // May fail on IPv6 is IPv6 is not configure
@@ -646,7 +633,7 @@
   const char kGroup[] = "237.132.100.17";
 
   IPEndPoint bind_address;
-  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
+  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
   IPAddress group_ip;
   EXPECT_TRUE(group_ip.AssignFromIPLiteral(kGroup));
 
@@ -678,7 +665,7 @@
 TEST_F(UDPSocketTest, MulticastOptions) {
   const uint16_t kPort = 9999;
   IPEndPoint bind_address;
-  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
+  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
 
   UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // Before binding.
@@ -706,7 +693,7 @@
   IPEndPoint bind_address;
   UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // We need a real IP, but we won't actually send anything to it.
-  ASSERT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
+  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
   int rv = client.Open(bind_address.GetFamily());
   EXPECT_THAT(rv, IsOk());
 
@@ -784,255 +771,120 @@
 namespace {
 
 const HANDLE kFakeHandle = (HANDLE)19;
-const QOS_FLOWID kFakeFlowId1 = (QOS_FLOWID)27;
-const QOS_FLOWID kFakeFlowId2 = (QOS_FLOWID)38;
+const QOS_FLOWID kFakeFlowId = (QOS_FLOWID)27;
 
-class TestUDPSocketWin : public UDPSocketWin {
- public:
-  TestUDPSocketWin(QwaveAPI& qos,
-                   DatagramSocket::BindType bind_type,
-                   net::NetLog* net_log,
-                   const net::NetLogSource& source)
-      : UDPSocketWin(bind_type, net_log, source), qos_(qos) {}
-
-  // Overriding GetQwaveAPI causes the test class to use the injected mock
-  // QwaveAPI instance instead of the singleton.  Ensure close is called in the
-  // child destructor before our mock CloseHandle is uninstalled.
-  ~TestUDPSocketWin() override { UDPSocketWin::Close(); }
-
-  QwaveAPI& GetQwaveAPI() override { return qos_; }
-
- private:
-  QwaveAPI& qos_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestUDPSocketWin);
-};
-
-class MockQwaveAPI : public QwaveAPI {
- public:
-  MOCK_METHOD2(CreateHandle, BOOL(PQOS_VERSION version, PHANDLE handle));
-  MOCK_METHOD1(CloseHandle, BOOL(HANDLE handle));
-
-  MOCK_METHOD6(AddSocketToFlow,
-               BOOL(HANDLE handle,
-                    SOCKET socket,
-                    PSOCKADDR addr,
-                    QOS_TRAFFIC_TYPE traffic_type,
-                    DWORD flags,
-                    PQOS_FLOWID flow_id));
-
-  MOCK_METHOD4(
-      RemoveSocketFromFlow,
-      BOOL(HANDLE handle, SOCKET socket, QOS_FLOWID flow_id, DWORD reserved));
-  MOCK_METHOD7(SetFlow,
-               BOOL(HANDLE handle,
-                    QOS_FLOWID flow_id,
-                    QOS_SET_FLOW op,
-                    ULONG size,
-                    PVOID data,
-                    DWORD reserved,
-                    LPOVERLAPPED overlapped));
-};
-
-std::unique_ptr<UDPSocket> OpenedDscpTestClient(QwaveAPI& qos,
-                                                IPEndPoint bind_address) {
-  auto client = std::make_unique<TestUDPSocketWin>(
-      qos, DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
-  int rv = client->Open(bind_address.GetFamily());
-  EXPECT_THAT(rv, IsOk());
-
-  return client;
+BOOL WINAPI FakeQOSCreateHandleFAIL(PQOS_VERSION version, PHANDLE handle) {
+  EXPECT_EQ(0, version->MinorVersion);
+  EXPECT_EQ(1, version->MajorVersion);
+  SetLastError(ERROR_OPEN_FAILED);
+  return false;
 }
 
-std::unique_ptr<UDPSocket> ConnectedDscpTestClient(QwaveAPI& qos) {
-  IPEndPoint bind_address;
-  // We need a real IP, but we won't actually send anything to it.
-  EXPECT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
-  auto client = OpenedDscpTestClient(qos, bind_address);
-  EXPECT_THAT(client->Connect(bind_address), IsOk());
-  return client;
+BOOL WINAPI FakeQOSCreateHandle(PQOS_VERSION version, PHANDLE handle) {
+  EXPECT_EQ(0, version->MinorVersion);
+  EXPECT_EQ(1, version->MajorVersion);
+  *handle = kFakeHandle;
+  return true;
 }
 
-std::unique_ptr<UDPSocket> UnconnectedDscpTestClient(QwaveAPI& qos) {
-  IPEndPoint bind_address;
-  EXPECT_TRUE(CreateUDPAddress("0.0.0.0", 9999, &bind_address));
-  auto client = OpenedDscpTestClient(qos, bind_address);
-  EXPECT_THAT(client->Bind(bind_address), IsOk());
-  return client;
+BOOL WINAPI FakeQOSCloseHandle(HANDLE handle) {
+  EXPECT_EQ(kFakeHandle, handle);
+  return true;
+}
+
+QOS_TRAFFIC_TYPE g_expected_traffic_type;
+
+BOOL WINAPI FakeQOSAddSocketToFlow(HANDLE handle,
+                                   SOCKET socket,
+                                   PSOCKADDR addr,
+                                   QOS_TRAFFIC_TYPE traffic_type,
+                                   DWORD flags,
+                                   PQOS_FLOWID flow_id) {
+  EXPECT_EQ(kFakeHandle, handle);
+  EXPECT_EQ(NULL, addr);
+  EXPECT_EQ(static_cast<DWORD>(QOS_NON_ADAPTIVE_FLOW), flags);
+  EXPECT_EQ(0u, *flow_id);
+  *flow_id = kFakeFlowId;
+  return true;
+}
+
+BOOL WINAPI FakeQOSRemoveSocketFromFlow(HANDLE handle,
+                                        SOCKET socket,
+                                        QOS_FLOWID flowid,
+                                        DWORD reserved) {
+  EXPECT_EQ(kFakeHandle, handle);
+  EXPECT_EQ(0u, socket);
+  EXPECT_EQ(kFakeFlowId, flowid);
+  EXPECT_EQ(0u, reserved);
+  return true;
+}
+
+DWORD g_expected_dscp;
+
+BOOL WINAPI FakeQOSSetFlow(HANDLE handle,
+                           QOS_FLOWID flow_id,
+                           QOS_SET_FLOW op,
+                           ULONG size,
+                           PVOID data,
+                           DWORD reserved,
+                           LPOVERLAPPED overlapped) {
+  EXPECT_EQ(kFakeHandle, handle);
+  EXPECT_EQ(QOSSetOutgoingDSCPValue, op);
+  EXPECT_EQ(sizeof(DWORD), size);
+  EXPECT_EQ(g_expected_dscp, *reinterpret_cast<DWORD*>(data));
+  EXPECT_EQ(kFakeFlowId, flow_id);
+  EXPECT_EQ(0u, reserved);
+  EXPECT_EQ(NULL, overlapped);
+  return true;
 }
 
 }  // namespace
 
-using ::testing::_;
-using ::testing::Return;
-using ::testing::SetArgPointee;
+// Mock out the Qwave functions and make sure they are
+// called correctly. Must be in net namespace for friendship
+// reasons.
+TEST_F(UDPSocketTest, SetDSCPFake) {
+  // Setup the server to listen.
+  IPEndPoint bind_address;
+  // We need a real IP, but we won't actually send anything to it.
+  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
+  UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
+  int rv = client.SetDiffServCodePoint(DSCP_AF41);
+  EXPECT_THAT(rv, IsError(ERR_SOCKET_NOT_CONNECTED));
 
-TEST_F(UDPSocketTest, SetDSCPNoopIfPassedNoChange) {
-  MockQwaveAPI qos;
-  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
-  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
-}
+  rv = client.Open(bind_address.GetFamily());
+  EXPECT_THAT(rv, IsOk());
 
-TEST_F(UDPSocketTest, SetDSCPFailsIfQOSHandleCanNotBeCreated) {
-  MockQwaveAPI qos;
-  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+  rv = client.Connect(bind_address);
+  EXPECT_THAT(rv, IsOk());
 
-  EXPECT_CALL(qos, CreateHandle(_, _)).WillOnce(Return(false));
-  EXPECT_EQ(ERROR_NOT_SUPPORTED, client->SetDiffServCodePoint(DSCP_AF41));
-}
+  QwaveAPI& qos(QwaveAPI::Get());
+  qos.create_handle_func_ = FakeQOSCreateHandleFAIL;
+  qos.close_handle_func_ = FakeQOSCloseHandle;
+  qos.add_socket_to_flow_func_ = FakeQOSAddSocketToFlow;
+  qos.remove_socket_from_flow_func_ = FakeQOSRemoveSocketFromFlow;
+  qos.set_flow_func_ = FakeQOSSetFlow;
+  qos.qwave_supported_ = true;
 
-MATCHER_P(DscpPointee, dscp, "") {
-  return *(DWORD*)arg == (DWORD)dscp;
-}
-
-TEST_F(UDPSocketTest, SetDSCPCallsQwaveFunctions) {
-  MockQwaveAPI qos;
-  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
-
-  EXPECT_CALL(qos, CreateHandle(_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
-  // AddSocketToFlow also sets flow_id, but we don't use that here
-  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeAudioVideo, _, _))
-      .WillOnce(Return(true));
-  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
-                           DscpPointee(DSCP_AF41), _, _));
-  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
-  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
-}
-
-TEST_F(UDPSocketTest, SecondSetDSCPCallsQwaveFunctions) {
-  MockQwaveAPI qos;
-  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
-
-  EXPECT_CALL(qos, CreateHandle(_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
-
-  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
-  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
-  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
-
-  // New dscp value should reset the flow.
-  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
-  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeBestEffort, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
-  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
-                           DscpPointee(DSCP_DEFAULT), _, _));
-  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
-
-  // Called from DscpManager destructor.
-  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
-  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
-}
-
-// TODO(zstein): Mocking out DscpManager might be simpler here
-// (just verify that DscpManager::Set and DscpManager::PrepareForSend are
-// called).
-TEST_F(UDPSocketTest, SendToCallsQwaveApis) {
-  MockQwaveAPI qos;
-  std::unique_ptr<UDPSocket> client = UnconnectedDscpTestClient(qos);
-
-  EXPECT_CALL(qos, CreateHandle(_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
-  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
-
-  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
-  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
-
-  std::string simple_message("hello world");
-  IPEndPoint server_address(IPAddress::IPv4Localhost(), 9438);
-  int rv = SendToSocket(client.get(), simple_message, server_address);
-  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
-
-  // TODO(zstein): Move to second test case (Qwave APIs called once per address)
-  rv = SendToSocket(client.get(), simple_message, server_address);
-  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
-
-  // TODO(zstein): Move to third test case (Qwave APIs called for each
-  // destination address).
-  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _)).WillOnce(Return(true));
-  IPEndPoint server_address2(IPAddress::IPv4Localhost(), 9439);
-
-  rv = SendToSocket(client.get(), simple_message, server_address2);
-  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
-
-  // Called from DscpManager destructor.
-  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
-  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
-}
-
-class DscpManagerTest : public testing::Test {
- protected:
-  DscpManagerTest() : dscp_manager_(qos_, INVALID_SOCKET, (HANDLE)0) {
-    CreateUDPAddress("1.2.3.4", 9001, &address1_);
-    CreateUDPAddress("1234:5678:90ab:cdef:1234:5678:90ab:cdef", 9002,
-                     &address2_);
-  }
-
-  MockQwaveAPI qos_;
-  DscpManager dscp_manager_;
-
-  IPEndPoint address1_;
-  IPEndPoint address2_;
-};
-
-TEST_F(DscpManagerTest, PrepareForSendIsNoopIfNoSet) {
-  dscp_manager_.PrepareForSend(address1_);
-}
-
-TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisAfterSet) {
-  dscp_manager_.Set(DSCP_CS2);
-
-  // AddSocketToFlow should be called for each address.
-  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)))
-      .WillOnce(Return(true));
-  // SetFlow should only be called when the flow is first created.
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
-  dscp_manager_.PrepareForSend(address1_);
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
-  dscp_manager_.PrepareForSend(address2_);
-
-  // Called from DscpManager destructor.
-  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
-}
-
-TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisOncePerAddress) {
-  dscp_manager_.Set(DSCP_CS2);
-
-  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
-  dscp_manager_.PrepareForSend(address1_);
-  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _)).Times(0);
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
-  dscp_manager_.PrepareForSend(address1_);
-
-  // Called from DscpManager destructor.
-  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
-}
-
-TEST_F(DscpManagerTest, SetDestroysExistingFlowAndResetsPrepareState) {
-  dscp_manager_.Set(DSCP_CS2);
-  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
-  dscp_manager_.PrepareForSend(address1_);
-
-  // Calling Set should destroy the existing flow.
-  // TODO(zstein): Verify that RemoveSocketFromFlow with no address
-  // destroys the flow for all destinations.
-  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, NULL, kFakeFlowId1, _));
-  dscp_manager_.Set(DSCP_CS5);
-
-  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
-  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
-  dscp_manager_.PrepareForSend(address1_);
-
-  // Called from DscpManager destructor.
-  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, kFakeFlowId2, _));
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
+  EXPECT_EQ(ERROR_NOT_SUPPORTED, client.SetDiffServCodePoint(DSCP_AF41));
+  qos.create_handle_func_ = FakeQOSCreateHandle;
+  g_expected_dscp = DSCP_AF41;
+  g_expected_traffic_type = QOSTrafficTypeAudioVideo;
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_AF41), IsOk());
+  g_expected_dscp = DSCP_DEFAULT;
+  g_expected_traffic_type = QOSTrafficTypeBestEffort;
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
+  g_expected_dscp = DSCP_CS2;
+  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_CS2), IsOk());
+  g_expected_dscp = DSCP_CS3;
+  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
+  g_expected_dscp = DSCP_DEFAULT;
+  g_expected_traffic_type = QOSTrafficTypeBestEffort;
+  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
+  client.Close();
 }
 #endif
 
diff --git a/net/socket/udp_socket_win.cc b/net/socket/udp_socket_win.cc
index 9f48f4d..945d9e8b 100644
--- a/net/socket/udp_socket_win.cc
+++ b/net/socket/udp_socket_win.cc
@@ -56,7 +56,7 @@
   void WatchForWrite();
 
   // The UDPSocketWin is going away.
-  void Detach() { socket_ = nullptr; }
+  void Detach() { socket_ = NULL; }
 
   // The separate OVERLAPPED variables for asynchronous operation.
   OVERLAPPED read_overlapped_;
@@ -260,6 +260,7 @@
       recv_from_address_(nullptr),
       net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::UDP_SOCKET)),
       qos_handle_(nullptr),
+      qos_flow_id_(0),
       event_pending_(this) {
   EnsureWinsockInit();
   net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE,
@@ -295,15 +296,12 @@
   if (socket_ == INVALID_SOCKET)
     return;
 
-  if (qos_handle_) {
-    GetQwaveAPI().CloseHandle(qos_handle_);
-    dscp_manager_ = nullptr;
-    qos_handle_ = NULL;
-  }
+  if (qos_handle_)
+    QwaveAPI::Get().CloseHandle(qos_handle_);
 
   // Zero out any pending read/write callback state.
   read_callback_.Reset();
-  recv_from_address_ = nullptr;
+  recv_from_address_ = NULL;
   write_callback_.Reset();
 
   base::TimeTicks start_time = base::TimeTicks::Now();
@@ -327,7 +325,7 @@
 
   if (core_) {
     core_->Detach();
-    core_ = nullptr;
+    core_ = NULL;
   }
 }
 
@@ -380,7 +378,7 @@
 int UDPSocketWin::Read(IOBuffer* buf,
                        int buf_len,
                        CompletionOnceCallback callback) {
-  return RecvFrom(buf, buf_len, nullptr, std::move(callback));
+  return RecvFrom(buf, buf_len, NULL, std::move(callback));
 }
 
 int UDPSocketWin::RecvFrom(IOBuffer* buf,
@@ -417,13 +415,6 @@
                          int buf_len,
                          const IPEndPoint& address,
                          CompletionOnceCallback callback) {
-  if (dscp_manager_) {
-    // Alert DscpManager in case this is a new remote address.  Failure to
-    // apply Dscp code is never fatal.
-    int rv = dscp_manager_->PrepareForSend(address);
-    if (rv != OK)
-      net_log_.AddEventWithNetErrorCode(NetLogEventType::UDP_SEND_ERROR, rv);
-  }
   return SendToOrWrite(buf, buf_len, &address, std::move(callback));
 }
 
@@ -490,10 +481,6 @@
     return MapSystemError(WSAGetLastError());
 
   remote_address_.reset(new IPEndPoint(address));
-
-  if (dscp_manager_)
-    dscp_manager_->PrepareForSend(*remote_address_.get());
-
   return rv;
 }
 
@@ -624,7 +611,7 @@
   int result = ok ? num_bytes : MapSystemError(WSAGetLastError());
   // Convert address.
   IPEndPoint address;
-  IPEndPoint* address_to_log = nullptr;
+  IPEndPoint* address_to_log = NULL;
   if (result >= 0) {
     if (address.FromSockAddr(core_->recv_addr_storage_.addr,
                              core_->recv_addr_storage_.addr_len)) {
@@ -636,8 +623,8 @@
     }
   }
   LogRead(result, core_->read_iobuffer_->data(), address_to_log);
-  core_->read_iobuffer_ = nullptr;
-  recv_from_address_ = nullptr;
+  core_->read_iobuffer_ = NULL;
+  recv_from_address_ = NULL;
   DoReadCallback(result);
 }
 
@@ -650,7 +637,7 @@
   LogWrite(result, core_->write_iobuffer_->data(), send_to_address_.get());
 
   send_to_address_.reset();
-  core_->write_iobuffer_ = nullptr;
+  core_->write_iobuffer_ = NULL;
   DoWriteCallback(result);
 }
 
@@ -704,9 +691,9 @@
                                        recv_from_address_);
   if (rv == ERR_IO_PENDING)
     return;
-  read_iobuffer_ = nullptr;
+  read_iobuffer_ = NULL;
   read_iobuffer_len_ = 0;
-  recv_from_address_ = nullptr;
+  recv_from_address_ = NULL;
   DoReadCallback(rv);
 }
 
@@ -715,7 +702,7 @@
                                      send_to_address_.get());
   if (rv == ERR_IO_PENDING)
     return;
-  write_iobuffer_ = nullptr;
+  write_iobuffer_ = NULL;
   write_iobuffer_len_ = 0;
   send_to_address_.reset();
   DoWriteCallback(rv);
@@ -780,13 +767,13 @@
   CHECK_NE(INVALID_SOCKET, socket_);
   AssertEventNotSignaled(core_->read_overlapped_.hEvent);
   int rv = WSARecvFrom(socket_, &read_buffer, 1, &num, &flags, storage.addr,
-                       &storage.addr_len, &core_->read_overlapped_, nullptr);
+                       &storage.addr_len, &core_->read_overlapped_, NULL);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
       int result = num;
       // Convert address.
       IPEndPoint address_storage;
-      IPEndPoint* address_to_log = nullptr;
+      IPEndPoint* address_to_log = NULL;
       if (result >= 0) {
         if (address_storage.FromSockAddr(core_->recv_addr_storage_.addr,
                                          core_->recv_addr_storage_.addr_len)) {
@@ -804,7 +791,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogRead(result, nullptr, nullptr);
+      LogRead(result, NULL, NULL);
       return result;
     }
   }
@@ -821,12 +808,12 @@
   struct sockaddr* addr = storage.addr;
   // Convert address.
   if (!address) {
-    addr = nullptr;
+    addr = NULL;
     storage.addr_len = 0;
   } else {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, nullptr, nullptr);
+      LogWrite(result, NULL, NULL);
       return result;
     }
   }
@@ -838,8 +825,8 @@
   DWORD flags = 0;
   DWORD num;
   AssertEventNotSignaled(core_->write_overlapped_.hEvent);
-  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags, addr,
-                     storage.addr_len, &core_->write_overlapped_, nullptr);
+  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags,
+                     addr, storage.addr_len, &core_->write_overlapped_, NULL);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
       int result = num;
@@ -850,7 +837,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogWrite(result, nullptr, nullptr);
+      LogWrite(result, NULL, NULL);
       return result;
     }
   }
@@ -879,11 +866,11 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogRead(rv, nullptr, nullptr);
+    LogRead(rv, NULL, NULL);
     return rv;
   }
   IPEndPoint address_storage;
-  IPEndPoint* address_to_log = nullptr;
+  IPEndPoint* address_to_log = NULL;
   if (rv >= 0) {
     if (address_storage.FromSockAddr(storage.addr, storage.addr_len)) {
       if (address)
@@ -907,11 +894,11 @@
   if (address) {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, nullptr, nullptr);
+      LogWrite(result, NULL, NULL);
       return result;
     }
   } else {
-    addr = nullptr;
+    addr = NULL;
     storage.addr_len = 0;
   }
 
@@ -925,7 +912,7 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogWrite(rv, nullptr, nullptr);
+    LogWrite(rv, NULL, NULL);
     return rv;
   }
   LogWrite(rv, buf->data(), address);
@@ -1015,10 +1002,6 @@
   return DoBind(IPEndPoint(address, 0));
 }
 
-QwaveAPI& UDPSocketWin::GetQwaveAPI() {
-  return QwaveAPI::Get();
-}
-
 int UDPSocketWin::JoinGroup(const IPAddress& group_address) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!is_connected())
@@ -1128,7 +1111,28 @@
   return OK;
 }
 
-QOS_TRAFFIC_TYPE DscpToTrafficType(DiffServCodePoint dscp) {
+int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
+  if (dscp == DSCP_NO_CHANGE) {
+    return OK;
+  }
+
+  if (!is_connected())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  QwaveAPI& qos(QwaveAPI::Get());
+
+  if (!qos.qwave_supported())
+    return ERROR_NOT_SUPPORTED;
+
+  if (qos_handle_ == NULL) {
+    QOS_VERSION version;
+    version.MajorVersion = 1;
+    version.MinorVersion = 0;
+    qos.CreateHandle(&version, &qos_handle_);
+    if (qos_handle_ == NULL)
+      return ERROR_NOT_SUPPORTED;
+  }
+
   QOS_TRAFFIC_TYPE traffic_type = QOSTrafficTypeBestEffort;
   switch (dscp) {
     case DSCP_CS0:
@@ -1168,36 +1172,34 @@
       NOTREACHED();
       break;
   }
-  return traffic_type;
-}
-
-int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
-  if (dscp == DSCP_NO_CHANGE)
-    return OK;
-
-  if (!is_connected())
-    return ERR_SOCKET_NOT_CONNECTED;
-
-  QwaveAPI& qos(GetQwaveAPI());
-
-  if (!qos.qwave_supported())
-    return ERROR_NOT_SUPPORTED;
-
-  if (!qos_handle_) {
-    QOS_VERSION version;
-    version.MajorVersion = 1;
-    version.MinorVersion = 0;
-    qos.CreateHandle(&version, &qos_handle_);
-    if (!qos_handle_)
-      return ERROR_NOT_SUPPORTED;
+  if (qos_flow_id_ != 0) {
+    qos.RemoveSocketFromFlow(qos_handle_, NULL, qos_flow_id_, 0);
+    qos_flow_id_ = 0;
   }
-
-  if (!dscp_manager_)
-    dscp_manager_ = std::make_unique<DscpManager>(qos, socket_, qos_handle_);
-
-  dscp_manager_->Set(dscp);
-  if (remote_address_)
-    return dscp_manager_->PrepareForSend(*remote_address_.get());
+  if (!qos.AddSocketToFlow(qos_handle_,
+                           socket_,
+                           NULL,
+                           traffic_type,
+                           QOS_NON_ADAPTIVE_FLOW,
+                           &qos_flow_id_)) {
+    DWORD err = GetLastError();
+    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
+      qos.CloseHandle(qos_handle_);
+      qos_flow_id_ = 0;
+      qos_handle_ = 0;
+    }
+    return MapSystemError(err);
+  }
+  // This requires admin rights, and may fail, if so we ignore it
+  // as AddSocketToFlow should still do *approximately* the right thing.
+  DWORD buf = dscp;
+  qos.SetFlow(qos_handle_,
+              qos_flow_id_,
+              QOSSetOutgoingDSCPValue,
+              sizeof(buf),
+              &buf,
+              0,
+              NULL);
 
   return OK;
 }
@@ -1248,70 +1250,5 @@
   NOTIMPLEMENTED();
   return result;
 }
-DscpManager::DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle)
-    : qos_(qos), socket_(socket), qos_handle_(qos_handle) {}
-
-DscpManager::~DscpManager() {
-  if (flow_id_ != 0)
-    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
-}
-
-void DscpManager::Set(DiffServCodePoint dscp) {
-  if (dscp == DSCP_NO_CHANGE || dscp == dscp_value_)
-    return;
-
-  dscp_value_ = dscp;
-  // TODO(zstein): We could reuse the flow when the value changes
-  // by calling QOSSetFlow with the new traffic type and dscp value.
-  if (flow_id_ != 0) {
-    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
-    configured_.clear();
-    flow_id_ = 0;
-  }
-}
-
-int DscpManager::PrepareForSend(const IPEndPoint& remote_address) {
-  if (dscp_value_ == DSCP_NO_CHANGE) {
-    // No DSCP value has been set.
-    return OK;
-  }
-
-  if (configured_.find(remote_address) != configured_.end())
-    return OK;
-
-  SockaddrStorage storage;
-  if (!remote_address.ToSockAddr(storage.addr, &storage.addr_len))
-    return ERR_ADDRESS_INVALID;
-
-  // We won't try again if we get an error.
-  configured_.emplace(remote_address);
-
-  // We don't need to call SetFlow if we already have a qos flow.
-  bool new_flow = flow_id_ == 0;
-
-  const QOS_TRAFFIC_TYPE traffic_type = DscpToTrafficType(dscp_value_);
-
-  if (!qos_.AddSocketToFlow(qos_handle_, socket_, storage.addr, traffic_type,
-                            QOS_NON_ADAPTIVE_FLOW, &flow_id_)) {
-    DWORD err = GetLastError();
-    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
-      qos_.CloseHandle(qos_handle_);
-      flow_id_ = 0;
-      qos_handle_ = 0;
-      dscp_value_ = DSCP_NO_CHANGE;
-    }
-    return MapSystemError(err);
-  }
-
-  if (new_flow) {
-    DWORD buf = dscp_value_;
-    // This requires admin rights, and may fail, if so we ignore it
-    // as AddSocketToFlow should still do *approximately* the right thing.
-    qos_.SetFlow(qos_handle_, flow_id_, QOSSetOutgoingDSCPValue, sizeof(buf),
-                 &buf, 0, nullptr);
-  }
-
-  return OK;
-}
 
 }  // namespace net
diff --git a/net/socket/udp_socket_win.h b/net/socket/udp_socket_win.h
index 8240fc86..81cd245f 100644
--- a/net/socket/udp_socket_win.h
+++ b/net/socket/udp_socket_win.h
@@ -37,110 +37,6 @@
 struct NetLogSource;
 class SocketTag;
 
-// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
-// library for setting packet priorities (and other things). Unfortunately,
-// Microsoft has decided that setting the DSCP bits with setsockopt() no
-// longer works, so we have to use this API instead.
-// This class is meant to be used as a singleton. It exposes a few dynamically
-// loaded functions and a bool called "qwave_supported".
-class NET_EXPORT QwaveAPI {
-  typedef BOOL(WINAPI* CreateHandleFn)(PQOS_VERSION, PHANDLE);
-  typedef BOOL(WINAPI* CloseHandleFn)(HANDLE);
-  typedef BOOL(WINAPI* AddSocketToFlowFn)(HANDLE,
-                                          SOCKET,
-                                          PSOCKADDR,
-                                          QOS_TRAFFIC_TYPE,
-                                          DWORD,
-                                          PQOS_FLOWID);
-  typedef BOOL(WINAPI* RemoveSocketFromFlowFn)(HANDLE,
-                                               SOCKET,
-                                               QOS_FLOWID,
-                                               DWORD);
-  typedef BOOL(WINAPI* SetFlowFn)(HANDLE,
-                                  QOS_FLOWID,
-                                  QOS_SET_FLOW,
-                                  ULONG,
-                                  PVOID,
-                                  DWORD,
-                                  LPOVERLAPPED);
-
- public:
-  QwaveAPI();
-  virtual ~QwaveAPI() = default;
-
-  static QwaveAPI& Get();
-
-  virtual bool qwave_supported() const;
-  virtual BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
-  virtual BOOL CloseHandle(HANDLE handle);
-  virtual BOOL AddSocketToFlow(HANDLE handle,
-                               SOCKET socket,
-                               PSOCKADDR addr,
-                               QOS_TRAFFIC_TYPE traffic_type,
-                               DWORD flags,
-                               PQOS_FLOWID flow_id);
-  virtual BOOL RemoveSocketFromFlow(HANDLE handle,
-                                    SOCKET socket,
-                                    QOS_FLOWID flow_id,
-                                    DWORD reserved);
-  virtual BOOL SetFlow(HANDLE handle,
-                       QOS_FLOWID flow_id,
-                       QOS_SET_FLOW op,
-                       ULONG size,
-                       PVOID data,
-                       DWORD reserved,
-                       LPOVERLAPPED overlapped);
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
-
-  bool qwave_supported_;
-  CreateHandleFn create_handle_func_;
-  CloseHandleFn close_handle_func_;
-  AddSocketToFlowFn add_socket_to_flow_func_;
-  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
-  SetFlowFn set_flow_func_;
-
-  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
-};
-
-//-----------------------------------------------------------------------------
-
-// Helper for maintaining the state that (unlike a blanket socket option), DSCP
-// values are set per-remote endpoint instead of just per-socket on Windows.
-// The implementation creates a single QWAVE 'flow' for the socket, and adds
-// all encountered remote addresses to that flow.  Flows are the minimum
-// manageable unit within the QWAVE API.  See
-// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/qos2/
-// for Microsoft's documentation.
-class NET_EXPORT DscpManager {
- public:
-  DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle);
-  ~DscpManager();
-
-  // Remembers the latest |dscp| so PrepareToSend can add remote addresses to
-  // the qos flow. Destroys the old flow if it exists and |dscp| changes.
-  void Set(DiffServCodePoint dscp);
-
-  // Constructs a qos flow for the latest set DSCP value if we don't already
-  // have one. Adds |remote_address| to the qos flow if it hasn't been added
-  // already. Does nothing if no DSCP value has been Set.
-  int PrepareForSend(const IPEndPoint& remote_address);
-
- private:
-  QwaveAPI& qos_;
-  SOCKET socket_;
-  HANDLE qos_handle_;
-
-  DiffServCodePoint dscp_value_ = DSCP_NO_CHANGE;
-  // The remote addresses currently in the flow.
-  std::set<IPEndPoint> configured_;
-  // 0 means no flow has been constructed.
-  QOS_FLOWID flow_id_ = 0;
-};
-
-//-----------------------------------------------------------------------------
-
 class NET_EXPORT UDPSocketWin : public base::win::ObjectWatcher::Delegate {
  public:
   UDPSocketWin(DatagramSocket::BindType bind_type,
@@ -389,11 +285,6 @@
   // Binds to a random port on |address|.
   int RandomBind(const IPAddress& address);
 
-  // This is provided to allow QwaveAPI mocking in tests. |UDPSocketWin| method
-  // implementations should call |GetQwaveAPI()| instead of |QwaveAPI::Get()|
-  // directly.
-  virtual QwaveAPI& GetQwaveAPI();
-
   SOCKET socket_;
   int addr_family_;
   bool is_connected_;
@@ -455,9 +346,7 @@
 
   // QWAVE data. Used to set DSCP bits on outgoing packets.
   HANDLE qos_handle_;
-
-  // Maintains remote addresses for QWAVE qos management.
-  std::unique_ptr<DscpManager> dscp_manager_;
+  QOS_FLOWID qos_flow_id_;
 
   THREAD_CHECKER(thread_checker_);
 
@@ -470,6 +359,60 @@
 
 //-----------------------------------------------------------------------------
 
+// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
+// library for setting packet priorities (and other things). Unfortunately,
+// Microsoft has decided that setting the DSCP bits with setsockopt() no
+// longer works, so we have to use this API instead.
+// This class is meant to be used as a singleton. It exposes a few dynamically
+// loaded functions and a bool called "qwave_supported".
+class NET_EXPORT QwaveAPI {
+  typedef BOOL (WINAPI *CreateHandleFn)(PQOS_VERSION, PHANDLE);
+  typedef BOOL (WINAPI *CloseHandleFn)(HANDLE);
+  typedef BOOL (WINAPI *AddSocketToFlowFn)(
+      HANDLE, SOCKET, PSOCKADDR, QOS_TRAFFIC_TYPE, DWORD, PQOS_FLOWID);
+  typedef BOOL (WINAPI *RemoveSocketFromFlowFn)(
+      HANDLE, SOCKET, QOS_FLOWID, DWORD);
+  typedef BOOL (WINAPI *SetFlowFn)(
+      HANDLE, QOS_FLOWID, QOS_SET_FLOW, ULONG, PVOID, DWORD, LPOVERLAPPED);
+
+ public:
+  QwaveAPI();
+
+  static QwaveAPI& Get();
+
+  bool qwave_supported() const;
+  BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
+  BOOL CloseHandle(HANDLE handle);
+  BOOL AddSocketToFlow(HANDLE handle,
+                       SOCKET socket,
+                       PSOCKADDR addr,
+                       QOS_TRAFFIC_TYPE traffic_type,
+                       DWORD flags,
+                       PQOS_FLOWID flow_id);
+  BOOL RemoveSocketFromFlow(HANDLE handle,
+                            SOCKET socket,
+                            QOS_FLOWID flow_id,
+                            DWORD reserved);
+  BOOL SetFlow(HANDLE handle,
+               QOS_FLOWID flow_id,
+               QOS_SET_FLOW op,
+               ULONG size,
+               PVOID data,
+               DWORD reserved,
+               LPOVERLAPPED overlapped);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
+
+  bool qwave_supported_;
+  CreateHandleFn create_handle_func_;
+  CloseHandleFn close_handle_func_;
+  AddSocketToFlowFn add_socket_to_flow_func_;
+  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
+  SetFlowFn set_flow_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
+};
 
 
 }  // namespace net
diff --git a/net/spdy/multiplexed_http_stream.cc b/net/spdy/multiplexed_http_stream.cc
index a11ef9b6f..cdfb865 100644
--- a/net/spdy/multiplexed_http_stream.cc
+++ b/net/spdy/multiplexed_http_stream.cc
@@ -37,13 +37,6 @@
   NOTREACHED();
 }
 
-Error MultiplexedHttpStream::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  return session_->GetTokenBindingSignature(key, tb_type, out);
-}
-
 void MultiplexedHttpStream::Drain(HttpNetworkSession* session) {
   NOTREACHED();
   Close(false);
diff --git a/net/spdy/multiplexed_http_stream.h b/net/spdy/multiplexed_http_stream.h
index f731ff8..f2c34fb7 100644
--- a/net/spdy/multiplexed_http_stream.h
+++ b/net/spdy/multiplexed_http_stream.h
@@ -26,9 +26,6 @@
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
   void GetSSLInfo(SSLInfo* ssl_info) override;
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   void Drain(HttpNetworkSession* session) override;
   HttpStream* RenewStreamForAuth() override;
   void SetConnectionReused() override;
diff --git a/net/spdy/multiplexed_session.cc b/net/spdy/multiplexed_session.cc
index 6429cfc..61086319 100644
--- a/net/spdy/multiplexed_session.cc
+++ b/net/spdy/multiplexed_session.cc
@@ -33,13 +33,4 @@
   has_ssl_info_ = session_->GetSSLInfo(&ssl_info_);
 }
 
-Error MultiplexedSessionHandle::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  if (!session_)
-    return ERR_CONNECTION_CLOSED;
-  return session_->GetTokenBindingSignature(key, tb_type, out);
-}
-
 }  // namespace net
diff --git a/net/spdy/multiplexed_session.h b/net/spdy/multiplexed_session.h
index aaeb292..79ac978 100644
--- a/net/spdy/multiplexed_session.h
+++ b/net/spdy/multiplexed_session.h
@@ -11,11 +11,6 @@
 #include "net/base/net_errors.h"
 #include "net/http/http_stream.h"
 #include "net/ssl/ssl_info.h"
-#include "net/ssl/token_binding.h"
-
-namespace crypto {
-class ECPrivateKey;
-}  // namespace crypto
 
 namespace net {
 
@@ -31,13 +26,6 @@
   // any. Returns true and fills in |endpoint| if it is available; returns false
   // and does not modify |endpoint| if it is unavailable.
   virtual bool GetRemoteEndpoint(IPEndPoint* endpoint) = 0;
-
-  // Generates the signature used in Token Binding using key |*key| and for a
-  // Token Binding of type |tb_type|, putting the signature in |*out|. Returns a
-  // net error code.
-  virtual Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                         TokenBindingType tb_type,
-                                         std::vector<uint8_t>* out) = 0;
 };
 
 // A handle to a multiplexed session which will be valid even after the
@@ -58,13 +46,6 @@
   // Caches SSL info from the underlying session.
   void SaveSSLInfo();
 
-  // Generates the signature used in Token Binding using key |*key| and for a
-  // Token Binding of type |tb_type|, putting the signature in |*out|. Returns a
-  // net error code.
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out);
-
  private:
   base::WeakPtr<MultiplexedSession> session_;
   SSLInfo ssl_info_;
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index b8d54afa..5f38d336 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -1275,12 +1275,6 @@
   return connection_->socket()->GetSSLInfo(ssl_info);
 }
 
-Error SpdySession::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                            TokenBindingType tb_type,
-                                            std::vector<uint8_t>* out) {
-  return connection_->socket()->GetTokenBindingSignature(key, tb_type, out);
-}
-
 bool SpdySession::WasAlpnNegotiated() const {
   return connection_->socket()->WasAlpnNegotiated();
 }
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
index b4bae19..97f7f58 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -54,10 +54,6 @@
 #include "url/gurl.h"
 #include "url/scheme_host_port.h"
 
-namespace crypto {
-class ECPrivateKey;
-}
-
 namespace net {
 
 namespace test {
@@ -428,9 +424,6 @@
   // MultiplexedSession methods:
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
   bool GetSSLInfo(SSLInfo* ssl_info) const override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
 
   // Returns true if ALPN was negotiated for the underlying socket.
   bool WasAlpnNegotiated() const;
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index edfada1e..ca4b101 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -754,12 +754,6 @@
   return session_->GetSSLInfo(ssl_info);
 }
 
-Error SpdyStream::GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                           TokenBindingType tb_type,
-                                           std::vector<uint8_t>* out) const {
-  return session_->GetTokenBindingSignature(key, tb_type, out);
-}
-
 bool SpdyStream::WasAlpnNegotiated() const {
   return session_->WasAlpnNegotiated();
 }
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index 29abeaac..9718976 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -25,17 +25,12 @@
 #include "net/socket/ssl_client_socket.h"
 #include "net/spdy/spdy_buffer.h"
 #include "net/ssl/ssl_client_cert_type.h"
-#include "net/ssl/token_binding.h"
 #include "net/third_party/spdy/core/spdy_framer.h"
 #include "net/third_party/spdy/core/spdy_header_block.h"
 #include "net/third_party/spdy/core/spdy_protocol.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "url/gurl.h"
 
-namespace crypto {
-class ECPrivateKey;
-}
-
 namespace net {
 
 class IPEndPoint;
@@ -339,13 +334,6 @@
   // Fills SSL info in |ssl_info| and returns true when SSL is in use.
   bool GetSSLInfo(SSLInfo* ssl_info) const;
 
-  // Generates the signature used in Token Binding using |*key| and for a Token
-  // Binding of type |tb_type|, putting the signature in |*out|. Returns OK or
-  // ERR_FAILED.
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) const;
-
   // Returns true if ALPN was negotiated for the underlying socket.
   bool WasAlpnNegotiated() const;
 
diff --git a/net/ssl/ssl_config.h b/net/ssl/ssl_config.h
index 7dab7ea..4c84056 100644
--- a/net/ssl/ssl_config.h
+++ b/net/ssl/ssl_config.h
@@ -29,12 +29,6 @@
   SSL_PROTOCOL_VERSION_TLS1_3 = 0x0304,
 };
 
-enum TokenBindingParam {
-  TB_PARAM_RSA2048_PKCS15 = 0,
-  TB_PARAM_RSA2048_PSS = 1,
-  TB_PARAM_ECDSAP256 = 2,
-};
-
 enum TLS13Variant {
   kTLS13VariantDraft23,
   kTLS13VariantFinal,
@@ -109,10 +103,6 @@
 
   bool channel_id_enabled;   // True if TLS channel ID extension is enabled.
 
-  // List of Token Binding key parameters supported by the client. If empty,
-  // Token Binding will be disabled, even if token_binding_enabled is true.
-  std::vector<TokenBindingParam> token_binding_params;
-
   bool false_start_enabled;  // True if we'll use TLS False Start.
 
   // If true, causes only ECDHE cipher suites to be enabled.
diff --git a/net/ssl/ssl_info.h b/net/ssl/ssl_info.h
index 1d0a90ce..696e921 100644
--- a/net/ssl/ssl_info.h
+++ b/net/ssl/ssl_info.h
@@ -99,15 +99,6 @@
   // True if a channel ID was sent to the server.
   bool channel_id_sent = false;
 
-  // True if Token Binding was negotiated with the server and we agreed on a
-  // version and key params.
-  bool token_binding_negotiated = false;
-
-  // Only valid if |token_binding_negotiated| is true. Contains the key param
-  // negotiated by the client and server in the Token Binding Negotiation TLS
-  // extension.
-  TokenBindingParam token_binding_key_param = TB_PARAM_ECDSAP256;
-
   // True if data was received over early data on the server. This field is only
   // set for server sockets.
   bool early_data_received = false;
diff --git a/net/ssl/token_binding.cc b/net/ssl/token_binding.cc
deleted file mode 100644
index d7f0ae7..0000000
--- a/net/ssl/token_binding.cc
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2015 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 "net/ssl/token_binding.h"
-
-#include "base/stl_util.h"
-#include "crypto/ec_private_key.h"
-#include "net/base/net_errors.h"
-#include "net/ssl/ssl_config.h"
-#include "third_party/boringssl/src/include/openssl/bn.h"
-#include "third_party/boringssl/src/include/openssl/bytestring.h"
-#include "third_party/boringssl/src/include/openssl/ec.h"
-#include "third_party/boringssl/src/include/openssl/ec_key.h"
-#include "third_party/boringssl/src/include/openssl/ecdsa.h"
-#include "third_party/boringssl/src/include/openssl/evp.h"
-#include "third_party/boringssl/src/include/openssl/mem.h"
-
-namespace net {
-
-namespace {
-
-const size_t kUncompressedPointLen = 65;
-
-bool BuildTokenBindingID(crypto::ECPrivateKey* key, CBB* out) {
-  EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key->key());
-  DCHECK(ec_key);
-
-  uint8_t point_buf[kUncompressedPointLen];
-  if (EC_POINT_point2oct(
-          EC_KEY_get0_group(ec_key), EC_KEY_get0_public_key(ec_key),
-          POINT_CONVERSION_UNCOMPRESSED, point_buf, kUncompressedPointLen,
-          NULL) != kUncompressedPointLen) {
-    return false;
-  }
-  CBB public_key, ec_point;
-  return CBB_add_u8(out, TB_PARAM_ECDSAP256) &&
-         CBB_add_u16_length_prefixed(out, &public_key) &&
-         CBB_add_u8_length_prefixed(&public_key, &ec_point) &&
-         CBB_add_bytes(&ec_point, point_buf + 1, kUncompressedPointLen - 1) &&
-         CBB_flush(out);
-}
-
-bool ECDSA_SIGToRaw(ECDSA_SIG* ec_sig, EC_KEY* ec, std::vector<uint8_t>* out) {
-  const EC_GROUP* group = EC_KEY_get0_group(ec);
-  const BIGNUM* order = EC_GROUP_get0_order(group);
-  size_t len = BN_num_bytes(order);
-  out->resize(2 * len);
-  if (!BN_bn2bin_padded(out->data(), len, ec_sig->r) ||
-      !BN_bn2bin_padded(out->data() + len, len, ec_sig->s)) {
-    return false;
-  }
-  return true;
-}
-
-ECDSA_SIG* RawToECDSA_SIG(EC_KEY* ec, base::StringPiece sig) {
-  bssl::UniquePtr<ECDSA_SIG> raw_sig(ECDSA_SIG_new());
-  const EC_GROUP* group = EC_KEY_get0_group(ec);
-  const BIGNUM* order = EC_GROUP_get0_order(group);
-  size_t group_size = BN_num_bytes(order);
-  if (sig.size() != group_size * 2)
-    return nullptr;
-  const uint8_t* sigp = reinterpret_cast<const uint8_t*>(sig.data());
-  if (!BN_bin2bn(sigp, group_size, raw_sig->r) ||
-      !BN_bin2bn(sigp + group_size, group_size, raw_sig->s)) {
-    return nullptr;
-  }
-  return raw_sig.release();
-}
-
-}  // namespace
-
-bool CreateTokenBindingSignature(base::StringPiece ekm,
-                                 TokenBindingType type,
-                                 crypto::ECPrivateKey* key,
-                                 std::vector<uint8_t>* out) {
-  bssl::ScopedEVP_MD_CTX digest_ctx;
-  uint8_t tb_type = static_cast<uint8_t>(type);
-  uint8_t key_type = static_cast<uint8_t>(TB_PARAM_ECDSAP256);
-  uint8_t digest[EVP_MAX_MD_SIZE];
-  unsigned int digest_len;
-  if (!EVP_DigestInit(digest_ctx.get(), EVP_sha256()) ||
-      !EVP_DigestUpdate(digest_ctx.get(), &tb_type, 1) ||
-      !EVP_DigestUpdate(digest_ctx.get(), &key_type, 1) ||
-      !EVP_DigestUpdate(digest_ctx.get(), ekm.data(), ekm.size()) ||
-      !EVP_DigestFinal_ex(digest_ctx.get(), digest, &digest_len)) {
-    return false;
-  }
-  EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key->key());
-  if (!ec_key)
-    return false;
-  bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_do_sign(digest, digest_len, ec_key));
-  if (!sig)
-    return false;
-  return ECDSA_SIGToRaw(sig.get(), ec_key, out);
-}
-
-Error BuildTokenBindingMessageFromTokenBindings(
-    const std::vector<base::StringPiece>& token_bindings,
-    std::string* out) {
-  CBB tb_message, child;
-  if (!CBB_init(&tb_message, 0) ||
-      !CBB_add_u16_length_prefixed(&tb_message, &child)) {
-    CBB_cleanup(&tb_message);
-    return ERR_FAILED;
-  }
-  for (const base::StringPiece& token_binding : token_bindings) {
-    if (!CBB_add_bytes(&child,
-                       reinterpret_cast<const uint8_t*>(token_binding.data()),
-                       token_binding.size())) {
-      CBB_cleanup(&tb_message);
-      return ERR_FAILED;
-    }
-  }
-
-  uint8_t* out_data;
-  size_t out_len;
-  if (!CBB_finish(&tb_message, &out_data, &out_len)) {
-    CBB_cleanup(&tb_message);
-    return ERR_FAILED;
-  }
-  out->assign(reinterpret_cast<char*>(out_data), out_len);
-  OPENSSL_free(out_data);
-  return OK;
-}
-
-Error BuildTokenBinding(TokenBindingType type,
-                        crypto::ECPrivateKey* key,
-                        const std::vector<uint8_t>& signed_ekm,
-                        std::string* out) {
-  uint8_t* out_data;
-  size_t out_len;
-  CBB token_binding;
-  if (!CBB_init(&token_binding, 0) ||
-      !CBB_add_u8(&token_binding, static_cast<uint8_t>(type)) ||
-      !BuildTokenBindingID(key, &token_binding) ||
-      !CBB_add_u16(&token_binding, signed_ekm.size()) ||
-      !CBB_add_bytes(&token_binding, signed_ekm.data(), signed_ekm.size()) ||
-      // 0-length extensions
-      !CBB_add_u16(&token_binding, 0) ||
-      !CBB_finish(&token_binding, &out_data, &out_len)) {
-    CBB_cleanup(&token_binding);
-    return ERR_FAILED;
-  }
-  out->assign(reinterpret_cast<char*>(out_data), out_len);
-  OPENSSL_free(out_data);
-  return OK;
-}
-
-TokenBinding::TokenBinding() = default;
-
-bool ParseTokenBindingMessage(base::StringPiece token_binding_message,
-                              std::vector<TokenBinding>* token_bindings) {
-  CBS tb_message, tb, public_key, ec_point, signature, extensions;
-  uint8_t tb_type, tb_param;
-  CBS_init(&tb_message,
-           reinterpret_cast<const uint8_t*>(token_binding_message.data()),
-           token_binding_message.size());
-  if (!CBS_get_u16_length_prefixed(&tb_message, &tb))
-    return false;
-  while (CBS_len(&tb)) {
-    if (!CBS_get_u8(&tb, &tb_type) || !CBS_get_u8(&tb, &tb_param) ||
-        !CBS_get_u16_length_prefixed(&tb, &public_key) ||
-        !CBS_get_u8_length_prefixed(&public_key, &ec_point) ||
-        CBS_len(&public_key) != 0 ||
-        !CBS_get_u16_length_prefixed(&tb, &signature) ||
-        !CBS_get_u16_length_prefixed(&tb, &extensions) ||
-        tb_param != TB_PARAM_ECDSAP256 ||
-        (TokenBindingType(tb_type) != TokenBindingType::PROVIDED &&
-         TokenBindingType(tb_type) != TokenBindingType::REFERRED)) {
-      return false;
-    }
-
-    TokenBinding token_binding;
-    token_binding.type = TokenBindingType(tb_type);
-    token_binding.ec_point = std::string(
-        reinterpret_cast<const char*>(CBS_data(&ec_point)), CBS_len(&ec_point));
-    token_binding.signature =
-        std::string(reinterpret_cast<const char*>(CBS_data(&signature)),
-                    CBS_len(&signature));
-    token_bindings->push_back(token_binding);
-  }
-  return true;
-}
-
-bool VerifyTokenBindingSignature(base::StringPiece ec_point,
-                                 base::StringPiece signature,
-                                 TokenBindingType type,
-                                 base::StringPiece ekm) {
-  if (ec_point.size() != kUncompressedPointLen - 1)
-    return false;
-  uint8_t x9_62_ec_point[kUncompressedPointLen];
-  x9_62_ec_point[0] = 4;
-  memcpy(x9_62_ec_point + 1, ec_point.data(), kUncompressedPointLen - 1);
-  bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
-  EC_KEY* keyp = key.get();
-  bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(EC_KEY_get0_group(keyp)));
-  if (!EC_POINT_oct2point(EC_KEY_get0_group(keyp), pub_key.get(),
-                          x9_62_ec_point, kUncompressedPointLen, nullptr) ||
-      !EC_KEY_set_public_key(keyp, pub_key.get())) {
-    return false;
-  }
-
-  bssl::ScopedEVP_MD_CTX digest_ctx;
-  uint8_t tb_type = static_cast<uint8_t>(type);
-  uint8_t key_type = static_cast<uint8_t>(TB_PARAM_ECDSAP256);
-  uint8_t digest[EVP_MAX_MD_SIZE];
-  unsigned int digest_len;
-  if (!EVP_DigestInit(digest_ctx.get(), EVP_sha256()) ||
-      !EVP_DigestUpdate(digest_ctx.get(), &tb_type, 1) ||
-      !EVP_DigestUpdate(digest_ctx.get(), &key_type, 1) ||
-      !EVP_DigestUpdate(digest_ctx.get(), ekm.data(), ekm.size()) ||
-      !EVP_DigestFinal_ex(digest_ctx.get(), digest, &digest_len)) {
-    return false;
-  }
-
-  bssl::UniquePtr<ECDSA_SIG> sig(RawToECDSA_SIG(keyp, signature));
-  if (!sig)
-    return false;
-  return !!ECDSA_do_verify(digest, digest_len, sig.get(), keyp);
-}
-
-}  // namespace net
diff --git a/net/ssl/token_binding.h b/net/ssl/token_binding.h
deleted file mode 100644
index 445e4d6f..0000000
--- a/net/ssl/token_binding.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2015 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 NET_SSL_TOKEN_BINDING_H_
-#define NET_SSL_TOKEN_BINDING_H_
-
-#include <string>
-#include <vector>
-
-#include "base/strings/string_piece.h"
-#include "net/base/net_errors.h"
-#include "net/base/net_export.h"
-
-namespace crypto {
-class ECPrivateKey;
-}
-
-namespace net {
-
-enum class TokenBindingType {
-  PROVIDED = 0,
-  REFERRED = 1,
-};
-
-// Takes an exported keying material value |ekm| from the TLS layer, the type of
-// Token Binding |type|, and a token binding key |key| and concatenates the
-// Token Binding type, key type, and ekm. This concatenation is signed with
-// |key| in accordance with section 3.3 of draft-ietf-tokbind-protocol-10, with
-// the signature written to |*out|. Returns true on success or false if there's
-// an error in the signing operations.
-bool CreateTokenBindingSignature(base::StringPiece ekm,
-                                 TokenBindingType type,
-                                 crypto::ECPrivateKey* key,
-                                 std::vector<uint8_t>* out);
-
-// Given a vector of serialized TokenBinding structs (as defined in
-// draft-ietf-tokbind-protocol-04), this function combines them to form the
-// serialized TokenBindingMessage struct in |*out|. This function returns a net
-// error.
-//
-// struct {
-//     TokenBinding tokenbindings<0..2^16-1>;
-// } TokenBindingMessage;
-Error BuildTokenBindingMessageFromTokenBindings(
-    const std::vector<base::StringPiece>& token_bindings,
-    std::string* out);
-
-// Builds a TokenBinding struct of type |type| with a TokenBindingID created
-// from |*key| and a signature of |ekm| using |*key| to sign.
-//
-// enum {
-//     rsa2048_pkcs1.5(0), rsa2048_pss(1), ecdsap256(2), (255)
-// } TokenBindingKeyParameters;
-//
-// struct {
-//     opaque modulus<1..2^16-1>;
-//     opaque publicexponent<1..2^8-1>;
-// } RSAPublicKey;
-//
-// struct {
-//     opaque point <1..2^8-1>;
-// } ECPoint;
-//
-// enum {
-//     provided_token_binding(0), referred_token_binding(1), (255)
-// } TokenBindingType;
-//
-// struct {
-//     TokenBindingType tokenbinding_type;
-//     TokenBindingKeyParameters key_parameters;
-//     select (key_parameters) {
-//         case rsa2048_pkcs1.5:
-//         case rsa2048_pss:
-//             RSAPublicKey rsapubkey;
-//         case ecdsap256:
-//             ECPoint point;
-//     }
-// } TokenBindingID;
-//
-// struct {
-//     TokenBindingID tokenbindingid;
-//     opaque signature<0..2^16-1>;// Signature over the exported keying
-//                                 // material value
-//     Extension extensions<0..2^16-1>;
-// } TokenBinding;
-Error BuildTokenBinding(TokenBindingType type,
-                        crypto::ECPrivateKey* key,
-                        const std::vector<uint8_t>& ekm,
-                        std::string* out);
-
-// Represents a parsed TokenBinding from a TokenBindingMessage.
-struct TokenBinding {
-  TokenBinding();
-
-  TokenBindingType type;
-  std::string ec_point;
-  std::string signature;
-};
-
-// Given a TokenBindingMessage, parses the TokenBinding structs from it, putting
-// them into |*token_bindings|. If there is an error parsing the
-// TokenBindingMessage or the key parameter for any TokenBinding in the
-// TokenBindingMessage is not ecdsap256, then this function returns false.
-NET_EXPORT_PRIVATE bool ParseTokenBindingMessage(
-    base::StringPiece token_binding_message,
-    std::vector<TokenBinding>* token_bindings);
-
-// Takes an ECPoint |ec_point| from a TokenBindingID, |signature| from a
-// TokenBinding, and a Token Binding type |type| and verifies that |signature|
-// is the signature of |ekm| using |ec_point| as the public key. Returns true if
-// the signature verifies and false if it doesn't or some other error occurs in
-// verification. This function is only provided for testing.
-NET_EXPORT_PRIVATE bool VerifyTokenBindingSignature(base::StringPiece ec_point,
-                                                    base::StringPiece signature,
-                                                    TokenBindingType type,
-                                                    base::StringPiece ekm);
-
-}  // namespace net
-
-#endif  // NET_SSL_TOKEN_BINDING_H_
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
index fb1163e..3797c91 100644
--- a/net/test/spawned_test_server/base_test_server.cc
+++ b/net/test/spawned_test_server/base_test_server.cc
@@ -114,15 +114,6 @@
   return true;
 }
 
-std::unique_ptr<base::ListValue> GetTokenBindingParams(
-    std::vector<int> params) {
-  std::unique_ptr<base::ListValue> values(new base::ListValue());
-  for (int param : params) {
-    values->AppendInteger(param);
-  }
-  return values;
-}
-
 std::string OCSPStatusToString(
     const BaseTestServer::SSLOptions::OCSPStatus& ocsp_status) {
   switch (ocsp_status) {
@@ -719,13 +710,6 @@
       arguments->Set("disable-extended-master-secret",
                      std::make_unique<base::Value>());
     }
-    if (!ssl_options_.supported_token_binding_params.empty()) {
-      std::unique_ptr<base::ListValue> token_binding_params(
-          new base::ListValue());
-      arguments->Set(
-          "token-binding-params",
-          GetTokenBindingParams(ssl_options_.supported_token_binding_params));
-    }
   }
 
   return GenerateAdditionalArguments(arguments);
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index c5b71a5..da11ca28ba 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -342,9 +342,6 @@
 
     // If true, disables extended master secret tls extension.
     bool disable_extended_master_secret = false;
-
-    // List of token binding params that the server supports and will negotiate.
-    std::vector<int> supported_token_binding_params;
   };
 
   // Initialize a TestServer.
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index cf55990..648082e 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -161,8 +161,7 @@
                npn_protocols, record_resume_info, tls_intolerant,
                tls_intolerance_type, signed_cert_timestamps,
                fallback_scsv_enabled, ocsp_response,
-               alert_after_handshake, disable_channel_id, disable_ems,
-               token_binding_params):
+               alert_after_handshake, disable_channel_id, disable_ems):
     self.cert_chain = tlslite.api.X509CertChain()
     self.cert_chain.parsePemList(pem_cert_and_key)
     # Force using only python implementation - otherwise behavior is different
@@ -209,8 +208,6 @@
       self.ssl_handshake_settings.enableChannelID = False
     if disable_ems:
       self.ssl_handshake_settings.enableExtendedMasterSecret = False
-    self.ssl_handshake_settings.supportedTokenBindingParams = \
-        token_binding_params
     self.ssl_handshake_settings.alpnProtos=alpn_protocols;
 
     if record_resume_info:
@@ -344,8 +341,6 @@
       self.GetSSLSessionCacheHandler,
       self.SSLManySmallRecords,
       self.GetChannelID,
-      self.GetTokenBindingEKM,
-      self.ForwardTokenBindingHeader,
       self.GetClientCert,
       self.ClientCipherListHandler,
       self.CloseSocketHandler,
@@ -1522,41 +1517,6 @@
     self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
     return True
 
-  def GetTokenBindingEKM(self):
-    """Send a reply containing the EKM value for token binding from the TLS
-    layer."""
-
-    if not self._ShouldHandleRequest('/tokbind-ekm'):
-      return False
-
-    ekm = self.server.tlsConnection.exportKeyingMaterial(
-        "EXPORTER-Token-Binding", "", False, 32)
-    self.send_response(200)
-    self.send_header('Content-Type', 'application/octet-stream')
-    self.end_headers()
-    self.wfile.write(ekm)
-    return True
-
-  def ForwardTokenBindingHeader(self):
-    """Send a redirect that sets the Include-Referred-Token-Binding-ID
-    header."""
-
-    test_name = '/forward-tokbind'
-    if not self._ShouldHandleRequest(test_name):
-      return False
-
-    query_char = self.path.find('?')
-    if query_char < 0 or len(self.path) <= query_char + 1:
-      self.sendRedirectHelp(test_name)
-      return True
-    dest = urllib.unquote(self.path[query_char + 1:])
-
-    self.send_response(302)
-    self.send_header('Location', dest)
-    self.send_header('Include-Referred-Token-Binding-ID', 'true')
-    self.end_headers()
-    return True
-
   def GetClientCert(self):
     """Send a reply whether a client certificate was provided."""
 
@@ -2118,8 +2078,7 @@
                              stapled_ocsp_response,
                              self.options.alert_after_handshake,
                              self.options.disable_channel_id,
-                             self.options.disable_extended_master_secret,
-                             self.options.token_binding_params)
+                             self.options.disable_extended_master_secret)
         print 'HTTPS server started on https://%s:%d...' % \
             (host, server.server_port)
       else:
@@ -2419,8 +2378,6 @@
     self.option_parser.add_option('--disable-channel-id', action='store_true')
     self.option_parser.add_option('--disable-extended-master-secret',
                                   action='store_true')
-    self.option_parser.add_option('--token-binding-params', action='append',
-                                  default=[], type='int')
     self.option_parser.add_option('--redirect-connect-to-localhost',
                                   dest='redirect_connect_to_localhost',
                                   default=False, action='store_true',
diff --git a/net/url_request/redirect_info.cc b/net/url_request/redirect_info.cc
index 0287bb4..f7075073 100644
--- a/net/url_request/redirect_info.cc
+++ b/net/url_request/redirect_info.cc
@@ -124,7 +124,6 @@
     int http_status_code,
     const GURL& new_location,
     bool insecure_scheme_was_upgraded,
-    bool token_binding_negotiated,
     bool copy_fragment) {
   DCHECK(!response_headers ||
          response_headers->response_code() == http_status_code);
@@ -171,15 +170,6 @@
                                               redirect_info.new_url)
           .spec();
 
-  if (response_headers) {
-    std::string include_referer;
-    response_headers->GetNormalizedHeader("include-referred-token-binding-id",
-                                          &include_referer);
-    include_referer = base::ToLowerASCII(include_referer);
-    if (include_referer == "true" && token_binding_negotiated)
-      redirect_info.referred_token_binding_host = original_url.host();
-  }
-
   return redirect_info;
 }
 
diff --git a/net/url_request/redirect_info.h b/net/url_request/redirect_info.h
index 57d1ec13..d461115 100644
--- a/net/url_request/redirect_info.h
+++ b/net/url_request/redirect_info.h
@@ -42,8 +42,6 @@
       const GURL& new_location,
       // Whether the URL was upgraded to HTTPS due to upgrade-insecure-requests.
       bool insecure_scheme_was_upgraded,
-      // Whether Token Binding of TLS was negotiated.
-      bool token_binding_negotiated,
       // This method copies the URL fragment of the original URL to the new URL
       // by default. Set false only when the network delegate has set the
       // desired redirect URL (with or without fragment), so it must not be
@@ -75,11 +73,6 @@
   // The new referrer policy that should be obeyed if there are
   // subsequent redirects.
   URLRequest::ReferrerPolicy new_referrer_policy;
-
-  // The hostname of the referrer if it asked the client to include a referred
-  // Token Binding when following the redirect; otherwise this is the empty
-  // string.
-  std::string referred_token_binding_host;
 };
 
 }  // namespace net
diff --git a/net/url_request/redirect_info_unittest.cc b/net/url_request/redirect_info_unittest.cc
index e5edd55..01a307e6 100644
--- a/net/url_request/redirect_info_unittest.cc
+++ b/net/url_request/redirect_info_unittest.cc
@@ -37,7 +37,6 @@
   const std::string kOriginalReferrer = "";
   const GURL kNewLocation = GURL("https://foo.test/redirected");
   const bool kInsecureSchemeWasUpgraded = false;
-  const bool kTokenBindingNegotiated = false;
   const bool kCopyFragment = true;
 
   for (const auto& test : kTests) {
@@ -50,7 +49,7 @@
         kOriginalFirstPartyUrlPolicy, kOriginalReferrerPolicy,
         kOriginalReferrer, nullptr /* response_headers */,
         test.http_status_code, kNewLocation, kInsecureSchemeWasUpgraded,
-        kTokenBindingNegotiated, kCopyFragment);
+        kCopyFragment);
 
     EXPECT_EQ(test.expected_new_method, redirect_info.new_method);
     EXPECT_EQ(test.http_status_code, redirect_info.status_code);
@@ -89,7 +88,6 @@
   const std::string kOriginalReferrer = "";
   const int kHttpStatusCode = 301;
   const bool kInsecureSchemeWasUpgraded = false;
-  const bool kTokenBindingNegotiated = false;
 
   for (const auto& test : kTests) {
     SCOPED_TRACE(::testing::Message()
@@ -102,7 +100,7 @@
         kOriginalFirstPartyUrlPolicy, kOriginalReferrerPolicy,
         kOriginalReferrer, nullptr /* response_headers */, kHttpStatusCode,
         GURL(test.new_location), kInsecureSchemeWasUpgraded,
-        kTokenBindingNegotiated, test.copy_fragment);
+        test.copy_fragment);
 
     EXPECT_EQ(GURL(test.expected_new_url), redirect_info.new_url);
   }
@@ -128,7 +126,6 @@
   const GURL kNewLocation = GURL("https://foo.test/redirected");
   const bool kInsecureSchemeWasUpgraded = false;
   const int kHttpStatusCode = 301;
-  const bool kTokenBindingNegotiated = false;
   const bool kCopyFragment = true;
 
   for (const auto& test : kTests) {
@@ -140,8 +137,7 @@
         KOriginalMethod, kOriginalUrl, kOriginalSiteForCookies,
         test.original_first_party_url_policy, kOriginalReferrerPolicy,
         kOriginalReferrer, nullptr /* response_headers */, kHttpStatusCode,
-        kNewLocation, kInsecureSchemeWasUpgraded, kTokenBindingNegotiated,
-        kCopyFragment);
+        kNewLocation, kInsecureSchemeWasUpgraded, kCopyFragment);
 
     EXPECT_EQ(GURL(test.expected_new_site_for_cookies),
               redirect_info.new_site_for_cookies);
@@ -426,7 +422,6 @@
   const URLRequest::FirstPartyURLPolicy kOriginalFirstPartyUrlPolicy =
       net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL;
   const bool kInsecureSchemeWasUpgraded = false;
-  const bool kTokenBindingNegotiated = false;
   const bool kCopyFragment = true;
 
   for (const auto& test : kTests) {
@@ -456,7 +451,7 @@
         kOriginalFirstPartyUrlPolicy, test.original_referrer_policy,
         test.original_referrer, response_headers.get(),
         response_headers->response_code(), new_location,
-        kInsecureSchemeWasUpgraded, kTokenBindingNegotiated, kCopyFragment);
+        kInsecureSchemeWasUpgraded, kCopyFragment);
 
     EXPECT_EQ(test.expected_new_referrer_policy,
               redirect_info.new_referrer_policy);
@@ -464,61 +459,5 @@
   }
 }
 
-TEST(RedirectInfoTest, ReferredTokenBinding) {
-  struct TestCase {
-    bool token_binding_negotiated;
-    const char* response_headers;
-    const char* expected_referred_token_binding_host;
-  };
-  const TestCase kTests[] = {
-      {true, "", ""},
-      {true, "Include-Referred-Token-Binding-ID: true", "foo.test"},
-      {true, "Include-Referred-Token-Binding-ID: bar", ""},
-      {false, "", ""},
-      {false, "Include-Referred-Token-Binding-ID: true", ""},
-      {false, "Include-Referred-Token-Binding-ID: bar", ""},
-  };
-
-  const std::string KOriginalMethod = "GET";
-  const GURL kriginalUrl = GURL("https://foo.test/");
-  const GURL kOriginalSiteForCookies = GURL("https://foo.test/");
-  const URLRequest::FirstPartyURLPolicy kOriginalFirstPartyUrlPolicy =
-      net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL;
-  const URLRequest::ReferrerPolicy kOriginalReferrerPolicy =
-      net::URLRequest::NEVER_CLEAR_REFERRER;
-  const std::string kOriginalReferrer = "";
-  const GURL kNewLocation = GURL("https://bar.test/redirected");
-  const bool kInsecureSchemeWasUpgraded = false;
-  const bool kCopyFragment = true;
-
-  for (const auto& test : kTests) {
-    SCOPED_TRACE(::testing::Message()
-                 << "token_binding_negotiated: "
-                 << test.token_binding_negotiated
-                 << " response_headers:" << test.response_headers);
-
-    std::string response_header_text =
-        "HTTP/1.1 302 Redirect\nLocation: " + kNewLocation.spec() + "\n" +
-        std::string(test.response_headers);
-    std::string raw_headers = HttpUtil::AssembleRawHeaders(
-        response_header_text.c_str(),
-        static_cast<int>(response_header_text.length()));
-    auto response_headers =
-        base::MakeRefCounted<HttpResponseHeaders>(raw_headers);
-    EXPECT_EQ(302, response_headers->response_code());
-
-    RedirectInfo redirect_info = RedirectInfo::ComputeRedirectInfo(
-        KOriginalMethod, kriginalUrl, kOriginalSiteForCookies,
-        kOriginalFirstPartyUrlPolicy, kOriginalReferrerPolicy,
-        kOriginalReferrer, response_headers.get(),
-        response_headers->response_code(), kNewLocation,
-        kInsecureSchemeWasUpgraded, test.token_binding_negotiated,
-        kCopyFragment);
-
-    EXPECT_EQ(test.expected_referred_token_binding_host,
-              redirect_info.referred_token_binding_host);
-  }
-}
-
 }  // namespace
 }  // namespace net
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 825a96b3..ef2c226 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -963,7 +963,6 @@
   referrer_ = redirect_info.new_referrer;
   referrer_policy_ = redirect_info.new_referrer_policy;
   site_for_cookies_ = redirect_info.new_site_for_cookies;
-  token_binding_referrer_ = redirect_info.referred_token_binding_host;
 
   url_chain_.push_back(redirect_info.new_url);
   --redirect_limit_;
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index a64498d..e6efafc6 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -351,13 +351,6 @@
   ReferrerPolicy referrer_policy() const { return referrer_policy_; }
   void set_referrer_policy(ReferrerPolicy referrer_policy);
 
-  // If this request should include a referred Token Binding, this returns the
-  // hostname of the referrer that indicated this request should include a
-  // referred Token Binding. Otherwise, this returns the empty string.
-  const std::string& token_binding_referrer() const {
-    return token_binding_referrer_;
-  }
-
   // Sets the delegate of the request.  This is only to allow creating a request
   // before creating its delegate.  |delegate| must be non-NULL and the request
   // must not yet have a Delegate set.
@@ -860,7 +853,6 @@
   std::string method_;  // "GET", "POST", etc. Should be all uppercase.
   std::string referrer_;
   ReferrerPolicy referrer_policy_;
-  std::string token_binding_referrer_;
   FirstPartyURLPolicy first_party_url_policy_;
   HttpRequestHeaders extra_request_headers_;
   int load_flags_;  // Flags indicating the request type for the load;
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 729775e..7c98db2 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -441,8 +441,6 @@
                                           referrer.spec());
   }
 
-  request_info_.token_binding_referrer = request_->token_binding_referrer();
-
   // This should be kept in sync with the corresponding code in
   // URLRequest::GetUserAgent.
   request_info_.extra_headers.SetHeaderIfMissing(
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 5b27df0e..f30efb45 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -430,7 +430,6 @@
         request_->first_party_url_policy(), request_->referrer_policy(),
         request_->referrer(), request_->response_headers(), http_status_code,
         new_location, insecure_scheme_was_upgraded,
-        request_->ssl_info().token_binding_negotiated,
         CopyFragmentOnRedirect(new_location));
     bool defer_redirect = false;
     request_->NotifyReceivedRedirect(redirect_info, &defer_redirect);
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 19b71c4..ac22382 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -102,7 +102,6 @@
 #include "net/ssl/ssl_connection_status_flags.h"
 #include "net/ssl/ssl_private_key.h"
 #include "net/ssl/ssl_server_config.h"
-#include "net/ssl/token_binding.h"
 #include "net/test/cert_test_util.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -4151,9 +4150,8 @@
 
 class TestSSLConfigService : public SSLConfigService {
  public:
-  explicit TestSSLConfigService(bool token_binding_enabled)
-      : token_binding_enabled_(token_binding_enabled),
-        min_version_(kDefaultSSLVersionMin),
+  TestSSLConfigService()
+      : min_version_(kDefaultSSLVersionMin),
         max_version_(kDefaultSSLVersionMax) {}
   ~TestSSLConfigService() override = default;
 
@@ -4165,9 +4163,6 @@
     *config = SSLConfig();
     config->version_min = min_version_;
     config->version_max = max_version_;
-    if (token_binding_enabled_) {
-      config->token_binding_params.push_back(TB_PARAM_ECDSAP256);
-    }
   }
 
   bool CanShareConnectionWithClientCerts(
@@ -4176,213 +4171,12 @@
   }
 
  private:
-  const bool token_binding_enabled_;
   uint16_t min_version_;
   uint16_t max_version_;
 };
 
 }  // namespace
 
-// TODO(svaldez): Update tests to use EmbeddedTestServer.
-#if !defined(OS_IOS)
-class TokenBindingURLRequestTest : public URLRequestTestHTTP {
- public:
-  TokenBindingURLRequestTest() = default;
-
-  void SetUp() override {
-    ssl_config_service_ = std::make_unique<TestSSLConfigService>(
-        true /* token_binding_enabled */);
-    default_context().set_ssl_config_service(ssl_config_service_.get());
-    channel_id_service_ =
-        std::make_unique<ChannelIDService>(new DefaultChannelIDStore(NULL));
-    default_context().set_channel_id_service(channel_id_service_.get());
-    URLRequestTestHTTP::SetUp();
-  }
-
- protected:
-  std::unique_ptr<TestSSLConfigService> ssl_config_service_;
-  std::unique_ptr<ChannelIDService> channel_id_service_;
-};
-
-TEST_F(TokenBindingURLRequestTest, TokenBindingTest) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  SpawnedTestServer https_test_server(SpawnedTestServer::TYPE_HTTPS,
-                                      ssl_options,
-                                      base::FilePath(kTestFilePath));
-  ASSERT_TRUE(https_test_server.Start());
-
-  TestDelegate d;
-  {
-    std::unique_ptr<URLRequest> r(default_context().CreateRequest(
-        https_test_server.GetURL("tokbind-ekm"), DEFAULT_PRIORITY, &d,
-        TRAFFIC_ANNOTATION_FOR_TESTS));
-    r->Start();
-    EXPECT_TRUE(r->is_pending());
-
-    d.RunUntilComplete();
-
-    EXPECT_EQ(OK, d.request_status());
-
-    HttpRequestHeaders headers;
-    std::string token_binding_header, token_binding_message;
-    EXPECT_TRUE(r->GetFullRequestHeaders(&headers));
-    EXPECT_TRUE(headers.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                  &token_binding_header));
-    EXPECT_TRUE(base::Base64UrlDecode(
-        token_binding_header, base::Base64UrlDecodePolicy::DISALLOW_PADDING,
-        &token_binding_message));
-    std::vector<TokenBinding> token_bindings;
-    ASSERT_TRUE(
-        ParseTokenBindingMessage(token_binding_message, &token_bindings));
-    ASSERT_EQ(1ull, token_bindings.size());
-
-    EXPECT_GT(d.bytes_received(), 0);
-    std::string ekm = d.data_received();
-
-    EXPECT_EQ(TokenBindingType::PROVIDED, token_bindings[0].type);
-    EXPECT_TRUE(VerifyTokenBindingSignature(token_bindings[0].ec_point,
-                                            token_bindings[0].signature,
-                                            TokenBindingType::PROVIDED, ekm));
-  }
-}
-
-TEST_F(TokenBindingURLRequestTest, ForwardTokenBinding) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  SpawnedTestServer https_test_server(SpawnedTestServer::TYPE_HTTPS,
-                                      ssl_options,
-                                      base::FilePath(kTestFilePath));
-  ASSERT_TRUE(https_test_server.Start());
-
-  TestDelegate d;
-  {
-    GURL redirect_url =
-        https_test_server.GetURL("forward-tokbind?/tokbind-ekm");
-    std::unique_ptr<URLRequest> r(default_context().CreateRequest(
-        redirect_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
-    r->Start();
-    EXPECT_TRUE(r->is_pending());
-
-    d.RunUntilComplete();
-
-    EXPECT_EQ(OK, d.request_status());
-
-    HttpRequestHeaders headers;
-    std::string token_binding_header, token_binding_message;
-    EXPECT_TRUE(r->GetFullRequestHeaders(&headers));
-    EXPECT_TRUE(headers.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                  &token_binding_header));
-    EXPECT_TRUE(base::Base64UrlDecode(
-        token_binding_header, base::Base64UrlDecodePolicy::DISALLOW_PADDING,
-        &token_binding_message));
-    std::vector<TokenBinding> token_bindings;
-    ASSERT_TRUE(
-        ParseTokenBindingMessage(token_binding_message, &token_bindings));
-    ASSERT_EQ(2ull, token_bindings.size());
-
-    EXPECT_GT(d.bytes_received(), 0);
-    std::string ekm = d.data_received();
-
-    EXPECT_EQ(TokenBindingType::PROVIDED, token_bindings[0].type);
-    EXPECT_TRUE(VerifyTokenBindingSignature(token_bindings[0].ec_point,
-                                            token_bindings[0].signature,
-                                            TokenBindingType::PROVIDED, ekm));
-    EXPECT_EQ(TokenBindingType::REFERRED, token_bindings[1].type);
-    EXPECT_TRUE(VerifyTokenBindingSignature(token_bindings[1].ec_point,
-                                            token_bindings[1].signature,
-                                            TokenBindingType::REFERRED, ekm));
-  }
-}
-
-// TODO(nharper): Remove this #ifdef and replace SpawnedTestServer with
-// EmbeddedTestServer once crbug.com/599187 is resolved.
-#if !defined(OS_ANDROID)
-TEST_F(TokenBindingURLRequestTest, DontForwardHeaderFromHttp) {
-  SpawnedTestServer http_server(SpawnedTestServer::TYPE_HTTP, base::FilePath());
-  ASSERT_TRUE(http_server.Start());
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  SpawnedTestServer https_test_server(SpawnedTestServer::TYPE_HTTPS,
-                                      ssl_options,
-                                      base::FilePath(kTestFilePath));
-  ASSERT_TRUE(https_test_server.Start());
-
-  TestDelegate d;
-  {
-    GURL redirect_url = http_server.GetURL(
-        "forward-tokbind?" + https_test_server.GetURL("tokbind-ekm").spec());
-    std::unique_ptr<URLRequest> r(default_context().CreateRequest(
-        redirect_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
-    r->Start();
-    EXPECT_TRUE(r->is_pending());
-
-    d.RunUntilComplete();
-
-    EXPECT_EQ(OK, d.request_status());
-
-    HttpRequestHeaders headers;
-    std::string token_binding_header, token_binding_message;
-    EXPECT_TRUE(r->GetFullRequestHeaders(&headers));
-    EXPECT_TRUE(headers.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                  &token_binding_header));
-    EXPECT_TRUE(base::Base64UrlDecode(
-        token_binding_header, base::Base64UrlDecodePolicy::DISALLOW_PADDING,
-        &token_binding_message));
-    std::vector<TokenBinding> token_bindings;
-    ASSERT_TRUE(
-        ParseTokenBindingMessage(token_binding_message, &token_bindings));
-    ASSERT_EQ(1ull, token_bindings.size());
-
-    EXPECT_GT(d.bytes_received(), 0);
-    std::string ekm = d.data_received();
-
-    EXPECT_EQ(TokenBindingType::PROVIDED, token_bindings[0].type);
-    EXPECT_TRUE(VerifyTokenBindingSignature(token_bindings[0].ec_point,
-                                            token_bindings[0].signature,
-                                            TokenBindingType::PROVIDED, ekm));
-  }
-}
-
-// Test that if a server supporting Token Binding redirects (with
-// Include-Referred-Token-Binding-ID) to an https url on a server that does not
-// support Token Binding, then we do not send a Sec-Token-Binding when following
-// the redirect.
-TEST_F(TokenBindingURLRequestTest, ForwardWithoutTokenBinding) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  SpawnedTestServer https_test_server(SpawnedTestServer::TYPE_HTTPS,
-                                      ssl_options,
-                                      base::FilePath(kTestFilePath));
-  ASSERT_TRUE(https_test_server.Start());
-  ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256);
-  SpawnedTestServer token_binding_test_server(SpawnedTestServer::TYPE_HTTPS,
-                                              ssl_options,
-                                              base::FilePath(kTestFilePath));
-  ASSERT_TRUE(token_binding_test_server.Start());
-
-  TestDelegate d;
-  {
-    GURL redirect_url = token_binding_test_server.GetURL(
-        "forward-tokbind?" + https_test_server.GetURL("tokbind-ekm").spec());
-    std::unique_ptr<URLRequest> r(default_context().CreateRequest(
-        redirect_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
-    r->Start();
-    EXPECT_TRUE(r->is_pending());
-
-    d.RunUntilComplete();
-
-    EXPECT_EQ(OK, d.request_status());
-
-    HttpRequestHeaders headers;
-    std::string token_binding_header, token_binding_message;
-    EXPECT_TRUE(r->GetFullRequestHeaders(&headers));
-    EXPECT_FALSE(headers.GetHeader(HttpRequestHeaders::kTokenBinding,
-                                   &token_binding_header));
-  }
-}
-#endif  // !defined(OS_ANDROID)
-#endif  // !defined(OS_IOS)
-
 // In this unit test, we're using the HTTPTestServer as a proxy server and
 // issuing a CONNECT request with the magic host name "www.redirect.com".
 // The EmbeddedTestServer will return a 302 response, which we should not
@@ -10828,8 +10622,7 @@
 class HTTPSFallbackTest : public TestWithScopedTaskEnvironment {
  public:
   HTTPSFallbackTest() : context_(true) {
-    ssl_config_service_ = std::make_unique<TestSSLConfigService>(
-        false /* token binding enabled */);
+    ssl_config_service_ = std::make_unique<TestSSLConfigService>();
     context_.set_ssl_config_service(ssl_config_service_.get());
   }
   ~HTTPSFallbackTest() override = default;
diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc
index 3fcdf38..266f6aa 100644
--- a/net/websockets/websocket_basic_handshake_stream.cc
+++ b/net/websockets/websocket_basic_handshake_stream.cc
@@ -344,16 +344,6 @@
   return;
 }
 
-Error WebSocketBasicHandshakeStream::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  DCHECK(url_.SchemeIsCryptographic());
-
-  return state_.connection()->socket()->GetTokenBindingSignature(key, tb_type,
-                                                                 out);
-}
-
 void WebSocketBasicHandshakeStream::Drain(HttpNetworkSession* session) {
   HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(this);
   drainer->Start(session);
diff --git a/net/websockets/websocket_basic_handshake_stream.h b/net/websockets/websocket_basic_handshake_stream.h
index 27e6f035..ccd371b 100644
--- a/net/websockets/websocket_basic_handshake_stream.h
+++ b/net/websockets/websocket_basic_handshake_stream.h
@@ -74,9 +74,6 @@
   void GetSSLInfo(SSLInfo* ssl_info) override;
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override;
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   void Drain(HttpNetworkSession* session) override;
   void SetPriority(RequestPriority priority) override;
   void PopulateNetErrorDetails(NetErrorDetails* details) override;
diff --git a/net/websockets/websocket_http2_handshake_stream.cc b/net/websockets/websocket_http2_handshake_stream.cc
index 73f0a3a8..9bea1903 100644
--- a/net/websockets/websocket_http2_handshake_stream.cc
+++ b/net/websockets/websocket_http2_handshake_stream.cc
@@ -216,13 +216,6 @@
   return;
 }
 
-Error WebSocketHttp2HandshakeStream::GetTokenBindingSignature(
-    crypto::ECPrivateKey* key,
-    TokenBindingType tb_type,
-    std::vector<uint8_t>* out) {
-  return stream_->GetTokenBindingSignature(key, tb_type, out);
-}
-
 void WebSocketHttp2HandshakeStream::Drain(HttpNetworkSession* session) {
   Close(true /* not_reusable */);
 }
diff --git a/net/websockets/websocket_http2_handshake_stream.h b/net/websockets/websocket_http2_handshake_stream.h
index f576f43..cef1642 100644
--- a/net/websockets/websocket_http2_handshake_stream.h
+++ b/net/websockets/websocket_http2_handshake_stream.h
@@ -18,18 +18,11 @@
 #include "net/base/net_export.h"
 #include "net/base/request_priority.h"
 #include "net/log/net_log_with_source.h"
-#include "net/ssl/token_binding.h"
 #include "net/third_party/spdy/core/spdy_header_block.h"
 #include "net/websockets/websocket_basic_stream_adapters.h"
 #include "net/websockets/websocket_handshake_stream_base.h"
 #include "net/websockets/websocket_stream.h"
 
-namespace crypto {
-
-class ECPrivateKey;
-
-}  // namespace crypto
-
 namespace net {
 
 struct LoadTimingInfo;
@@ -88,9 +81,6 @@
   void GetSSLInfo(SSLInfo* ssl_info) override;
   void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override;
   bool GetRemoteEndpoint(IPEndPoint* endpoint) override;
-  Error GetTokenBindingSignature(crypto::ECPrivateKey* key,
-                                 TokenBindingType tb_type,
-                                 std::vector<uint8_t>* out) override;
   void Drain(HttpNetworkSession* session) override;
   void SetPriority(RequestPriority priority) override;
   void PopulateNetErrorDetails(NetErrorDetails* details) override;
diff --git a/services/network/public/cpp/net_ipc_param_traits.cc b/services/network/public/cpp/net_ipc_param_traits.cc
index 8ca5e2e..79912a5 100644
--- a/services/network/public/cpp/net_ipc_param_traits.cc
+++ b/services/network/public/cpp/net_ipc_param_traits.cc
@@ -290,8 +290,6 @@
   WriteParam(m, p.pkp_bypassed);
   WriteParam(m, p.client_cert_sent);
   WriteParam(m, p.channel_id_sent);
-  WriteParam(m, p.token_binding_negotiated);
-  WriteParam(m, p.token_binding_key_param);
   WriteParam(m, p.handshake_type);
   WriteParam(m, p.public_key_hashes);
   WriteParam(m, p.pinning_failure_log);
@@ -319,8 +317,6 @@
          ReadParam(m, iter, &r->pkp_bypassed) &&
          ReadParam(m, iter, &r->client_cert_sent) &&
          ReadParam(m, iter, &r->channel_id_sent) &&
-         ReadParam(m, iter, &r->token_binding_negotiated) &&
-         ReadParam(m, iter, &r->token_binding_key_param) &&
          ReadParam(m, iter, &r->handshake_type) &&
          ReadParam(m, iter, &r->public_key_hashes) &&
          ReadParam(m, iter, &r->pinning_failure_log) &&
diff --git a/services/network/public/cpp/net_ipc_param_traits.h b/services/network/public/cpp/net_ipc_param_traits.h
index 3beeb69..26b492c 100644
--- a/services/network/public/cpp/net_ipc_param_traits.h
+++ b/services/network/public/cpp/net_ipc_param_traits.h
@@ -216,7 +216,6 @@
 
 IPC_ENUM_TRAITS_MAX_VALUE(net::SSLInfo::HandshakeType,
                           net::SSLInfo::HANDSHAKE_FULL)
-IPC_ENUM_TRAITS_MAX_VALUE(net::TokenBindingParam, net::TB_PARAM_ECDSAP256)
 
 IPC_ENUM_TRAITS_MAX_VALUE(net::URLRequest::ReferrerPolicy,
                           net::URLRequest::MAX_REFERRER_POLICY - 1)
@@ -243,7 +242,6 @@
   IPC_STRUCT_TRAITS_MEMBER(new_referrer)
   IPC_STRUCT_TRAITS_MEMBER(insecure_scheme_was_upgraded)
   IPC_STRUCT_TRAITS_MEMBER(new_referrer_policy)
-  IPC_STRUCT_TRAITS_MEMBER(referred_token_binding_host)
 IPC_STRUCT_TRAITS_END()
 
 IPC_ENUM_TRAITS_MAX_VALUE(net::HttpResponseInfo::ConnectionInfo,
diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
index 48684f6..7b43d21 100644
--- a/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
+++ b/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
@@ -241,13 +241,11 @@
 
     // Grant relatively big quota initially.
     quota_manager_->SetQuota(
-        origin_,
-        storage::FileSystemTypeToQuotaStorageType(src_type_),
-        1024 * 1024);
+        url::Origin::Create(origin_),
+        storage::FileSystemTypeToQuotaStorageType(src_type_), 1024 * 1024);
     quota_manager_->SetQuota(
-        origin_,
-        storage::FileSystemTypeToQuotaStorageType(dest_type_),
-        1024 * 1024);
+        url::Origin::Create(origin_),
+        storage::FileSystemTypeToQuotaStorageType(dest_type_), 1024 * 1024);
   }
 
   int64_t GetSourceUsage() {
@@ -391,7 +389,8 @@
                         int64_t* usage,
                         int64_t* quota) {
     blink::mojom::QuotaStatusCode status =
-        AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(), origin_,
+        AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
+                                              url::Origin::Create(origin_),
                                               type, usage, quota);
     ASSERT_EQ(blink::mojom::QuotaStatusCode::kOk, status);
   }
diff --git a/storage/browser/fileapi/file_system_operation_impl.cc b/storage/browser/fileapi/file_system_operation_impl.cc
index a7bc400..87454ac 100644
--- a/storage/browser/fileapi/file_system_operation_impl.cc
+++ b/storage/browser/fileapi/file_system_operation_impl.cc
@@ -407,7 +407,8 @@
   DCHECK(quota_manager_proxy);
   DCHECK(quota_manager_proxy->quota_manager());
   quota_manager_proxy->quota_manager()->GetUsageAndQuota(
-      url.origin(), FileSystemTypeToQuotaStorageType(url.type()),
+      url::Origin::Create(url.origin()),
+      FileSystemTypeToQuotaStorageType(url.type()),
       base::BindOnce(&FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask,
                      weak_ptr_, task, error_callback));
 }
diff --git a/storage/browser/fileapi/file_system_operation_impl_unittest.cc b/storage/browser/fileapi/file_system_operation_impl_unittest.cc
index bc6eb31..8636aa75 100644
--- a/storage/browser/fileapi/file_system_operation_impl_unittest.cc
+++ b/storage/browser/fileapi/file_system_operation_impl_unittest.cc
@@ -255,7 +255,8 @@
   void GetUsageAndQuota(int64_t* usage, int64_t* quota) {
     blink::mojom::QuotaStatusCode status =
         AsyncFileTestHelper::GetUsageAndQuota(
-            quota_manager_.get(), sandbox_file_system_.origin(),
+            quota_manager_.get(),
+            url::Origin::Create(sandbox_file_system_.origin()),
             sandbox_file_system_.type(), usage, quota);
     scoped_task_environment_.RunUntilIdle();
     ASSERT_EQ(blink::mojom::QuotaStatusCode::kOk, status);
@@ -279,9 +280,9 @@
   void GrantQuotaForCurrentUsage() {
     int64_t usage;
     GetUsageAndQuota(&usage, nullptr);
-    quota_manager()->SetQuota(sandbox_file_system_.origin(),
-                              sandbox_file_system_.storage_type(),
-                              usage);
+    quota_manager()->SetQuota(
+        url::Origin::Create(sandbox_file_system_.origin()),
+        sandbox_file_system_.storage_type(), usage);
   }
 
   int64_t GetUsage() {
@@ -293,9 +294,9 @@
   void AddQuota(int64_t quota_delta) {
     int64_t quota;
     GetUsageAndQuota(nullptr, &quota);
-    quota_manager()->SetQuota(sandbox_file_system_.origin(),
-                              sandbox_file_system_.storage_type(),
-                              quota + quota_delta);
+    quota_manager()->SetQuota(
+        url::Origin::Create(sandbox_file_system_.origin()),
+        sandbox_file_system_.storage_type(), quota + quota_delta);
   }
 
   base::File::Error Move(
diff --git a/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc b/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc
index 70b2bfb5..59ac2a9 100644
--- a/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc
+++ b/storage/browser/fileapi/file_system_operation_impl_write_unittest.cc
@@ -259,8 +259,9 @@
 
 TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) {
   ScopedTextBlob blob(url_request_context(), "blob:success", "Hello, world!\n");
-  quota_manager_->SetQuota(
-      kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10);
+  quota_manager_->SetQuota(url::Origin::Create(kOrigin),
+                           FileSystemTypeToQuotaStorageType(kFileSystemType),
+                           10);
   file_system_context_->operation_runner()->Write(URLForPath(virtual_path_),
                                                   blob.GetBlobDataHandle(), 0,
                                                   RecordWriteCallback());
diff --git a/storage/browser/fileapi/obfuscated_file_util_unittest.cc b/storage/browser/fileapi/obfuscated_file_util_unittest.cc
index 1fecb7a3..fcf0117a 100644
--- a/storage/browser/fileapi/obfuscated_file_util_unittest.cc
+++ b/storage/browser/fileapi/obfuscated_file_util_unittest.cc
@@ -274,12 +274,9 @@
 
   void GetUsageFromQuotaManager() {
     int64_t quota = -1;
-    quota_status_ =
-        AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
-                                              origin(),
-                                              sandbox_file_system_.type(),
-                                              &usage_,
-                                              &quota);
+    quota_status_ = AsyncFileTestHelper::GetUsageAndQuota(
+        quota_manager_.get(), url::Origin::Create(origin()),
+        sandbox_file_system_.type(), &usage_, &quota);
     EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, quota_status_);
   }
 
diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.cc b/storage/browser/fileapi/sandbox_file_stream_writer.cc
index c1b2fff..f6fcd9a 100644
--- a/storage/browser/fileapi/sandbox_file_stream_writer.cc
+++ b/storage/browser/fileapi/sandbox_file_stream_writer.cc
@@ -161,7 +161,8 @@
 
   DCHECK(quota_manager_proxy->quota_manager());
   quota_manager_proxy->quota_manager()->GetUsageAndQuota(
-      url_.origin(), FileSystemTypeToQuotaStorageType(url_.type()),
+      url::Origin::Create(url_.origin()),
+      FileSystemTypeToQuotaStorageType(url_.type()),
       base::BindOnce(&SandboxFileStreamWriter::DidGetUsageAndQuota,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
diff --git a/storage/browser/quota/client_usage_tracker.cc b/storage/browser/quota/client_usage_tracker.cc
index 732fe4a..3aa30e6 100644
--- a/storage/browser/quota/client_usage_tracker.cc
+++ b/storage/browser/quota/client_usage_tracker.cc
@@ -29,7 +29,7 @@
 
 bool EraseOriginFromOriginSet(OriginSetByHost* origins_by_host,
                               const std::string& host,
-                              const GURL& origin) {
+                              const url::Origin& origin) {
   OriginSetByHost::iterator found = origins_by_host->find(host);
   if (found == origins_by_host->end())
     return false;
@@ -44,7 +44,7 @@
 
 bool OriginSetContainsOrigin(const OriginSetByHost& origins,
                              const std::string& host,
-                             const GURL& origin) {
+                             const url::Origin& origin) {
   OriginSetByHost::const_iterator itr = origins.find(host);
   return itr != origins.end() && base::ContainsKey(itr->second, origin);
 }
@@ -105,7 +105,7 @@
 
   for (const auto& host_and_origins : non_cached_limited_origins_by_host_) {
     for (const auto& origin : host_and_origins.second)
-      client_->GetOriginUsage(url::Origin::Create(origin), type_, accumulator);
+      client_->GetOriginUsage(origin, type_, accumulator);
   }
 
   accumulator.Run(global_limited_usage_);
@@ -144,8 +144,9 @@
                      AsWeakPtr(), host));
 }
 
-void ClientUsageTracker::UpdateUsageCache(const GURL& origin, int64_t delta) {
-  std::string host = net::GetHostOrSpecFromURL(origin);
+void ClientUsageTracker::UpdateUsageCache(const url::Origin& origin,
+                                          int64_t delta) {
+  std::string host = net::GetHostOrSpecFromURL(origin.GetURL());
   if (base::ContainsKey(cached_hosts_, host)) {
     if (!IsUsageCacheEnabledForOrigin(origin))
       return;
@@ -192,7 +193,7 @@
 }
 
 void ClientUsageTracker::GetCachedOriginsUsage(
-    std::map<GURL, int64_t>* origin_usage) const {
+    std::map<url::Origin, int64_t>* origin_usage) const {
   DCHECK(origin_usage);
   for (const auto& host_and_usage_map : cached_usage_by_host_) {
     for (const auto& origin_and_usage : host_and_usage_map.second)
@@ -200,7 +201,8 @@
   }
 }
 
-void ClientUsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
+void ClientUsageTracker::GetCachedOrigins(
+    std::set<url::Origin>* origins) const {
   DCHECK(origins);
   for (const auto& host_and_usage_map : cached_usage_by_host_) {
     for (const auto& origin_and_usage : host_and_usage_map.second)
@@ -208,9 +210,9 @@
   }
 }
 
-void ClientUsageTracker::SetUsageCacheEnabled(const GURL& origin,
+void ClientUsageTracker::SetUsageCacheEnabled(const url::Origin& origin,
                                               bool enabled) {
-  std::string host = net::GetHostOrSpecFromURL(origin);
+  std::string host = net::GetHostOrSpecFromURL(origin.GetURL());
   if (!enabled) {
     // Erase |origin| from cache and subtract its usage.
     HostUsageMap::iterator found_host = cached_usage_by_host_.find(host);
@@ -262,7 +264,7 @@
   OriginSetByHost origins_by_host;
   for (const auto& origin : origins) {
     GURL origin_url = origin.GetURL();
-    origins_by_host[net::GetHostOrSpecFromURL(origin_url)].insert(origin_url);
+    origins_by_host[net::GetHostOrSpecFromURL(origin_url)].insert(origin);
   }
 
   AccumulateInfo* info = new AccumulateInfo;
@@ -278,10 +280,7 @@
 
   for (const auto& host_and_origins : origins_by_host) {
     const std::string& host = host_and_origins.first;
-    const std::set<GURL>& origin_urls = host_and_origins.second;
-    std::set<url::Origin> origins;
-    for (const auto& origin_url : origin_urls)
-      origins.insert(url::Origin::Create(origin_url));
+    const std::set<url::Origin>& origins = host_and_origins.second;
     if (host_usage_accumulators_.Add(host, accumulator))
       GetUsageForOrigins(host, origins);
   }
@@ -328,36 +327,37 @@
                           AsWeakPtr(), base::Owned(info), host);
 
   for (const auto& origin : origins) {
-    GURL origin_url = origin.GetURL();
-    DCHECK_EQ(host, net::GetHostOrSpecFromURL(origin_url));
+    DCHECK_EQ(host, net::GetHostOrSpecFromURL(origin.GetURL()));
 
     int64_t origin_usage = 0;
-    if (GetCachedOriginUsage(origin_url, &origin_usage)) {
-      accumulator.Run(origin_url, origin_usage);
+    if (GetCachedOriginUsage(origin, &origin_usage)) {
+      accumulator.Run(origin, origin_usage);
     } else {
       client_->GetOriginUsage(origin, type_,
-                              base::BindOnce(accumulator, origin_url));
+                              base::BindOnce(accumulator, origin));
     }
   }
 
   // Fire the sentinel as we've now called GetOriginUsage for all clients.
-  accumulator.Run(GURL(), 0);
+  accumulator.Run(base::nullopt, 0);
 }
 
-void ClientUsageTracker::AccumulateOriginUsage(AccumulateInfo* info,
-                                               const std::string& host,
-                                               const GURL& origin,
-                                               int64_t usage) {
-  if (!origin.is_empty()) {
+void ClientUsageTracker::AccumulateOriginUsage(
+    AccumulateInfo* info,
+    const std::string& host,
+    const base::Optional<url::Origin>& origin,
+    int64_t usage) {
+  if (origin.has_value()) {
+    DCHECK(!origin->GetURL().is_empty());
     if (usage < 0)
       usage = 0;
 
-    if (IsStorageUnlimited(origin))
+    if (IsStorageUnlimited(*origin))
       info->unlimited_usage += usage;
     else
       info->limited_usage += usage;
-    if (IsUsageCacheEnabledForOrigin(origin))
-      AddCachedOrigin(origin, usage);
+    if (IsUsageCacheEnabledForOrigin(*origin))
+      AddCachedOrigin(*origin, usage);
   }
   if (--info->pending_jobs)
     return;
@@ -367,7 +367,7 @@
       host, info->limited_usage, info->unlimited_usage);
 }
 
-void ClientUsageTracker::DidGetHostUsageAfterUpdate(const GURL& origin,
+void ClientUsageTracker::DidGetHostUsageAfterUpdate(const url::Origin& origin,
                                                     int64_t usage) {
   if (!storage_monitor_)
     return;
@@ -376,11 +376,11 @@
   storage_monitor_->NotifyUsageChange(filter, 0);
 }
 
-void ClientUsageTracker::AddCachedOrigin(const GURL& origin,
+void ClientUsageTracker::AddCachedOrigin(const url::Origin& origin,
                                          int64_t new_usage) {
   DCHECK(IsUsageCacheEnabledForOrigin(origin));
 
-  std::string host = net::GetHostOrSpecFromURL(origin);
+  std::string host = net::GetHostOrSpecFromURL(origin.GetURL());
   int64_t* usage = &cached_usage_by_host_[host][origin];
   int64_t delta = new_usage - *usage;
   *usage = new_usage;
@@ -407,9 +407,9 @@
   return usage;
 }
 
-bool ClientUsageTracker::GetCachedOriginUsage(const GURL& origin,
+bool ClientUsageTracker::GetCachedOriginUsage(const url::Origin& origin,
                                               int64_t* usage) const {
-  std::string host = net::GetHostOrSpecFromURL(origin);
+  std::string host = net::GetHostOrSpecFromURL(origin.GetURL());
   HostUsageMap::const_iterator found_host = cached_usage_by_host_.find(host);
   if (found_host == cached_usage_by_host_.end())
     return false;
@@ -424,42 +424,42 @@
 }
 
 bool ClientUsageTracker::IsUsageCacheEnabledForOrigin(
-    const GURL& origin) const {
-  std::string host = net::GetHostOrSpecFromURL(origin);
+    const url::Origin& origin) const {
+  std::string host = net::GetHostOrSpecFromURL(origin.GetURL());
   return !OriginSetContainsOrigin(non_cached_limited_origins_by_host_,
                                   host, origin) &&
       !OriginSetContainsOrigin(non_cached_unlimited_origins_by_host_,
                                host, origin);
 }
 
-void ClientUsageTracker::OnGranted(const GURL& origin,
-                                   int change_flags) {
+void ClientUsageTracker::OnGranted(const GURL& origin_url, int change_flags) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
+    url::Origin origin = url::Origin::Create(origin_url);
     int64_t usage = 0;
     if (GetCachedOriginUsage(origin, &usage)) {
       global_unlimited_usage_ += usage;
       global_limited_usage_ -= usage;
     }
 
-    std::string host = net::GetHostOrSpecFromURL(origin);
+    std::string host = net::GetHostOrSpecFromURL(origin_url);
     if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_,
                                  host, origin))
       non_cached_unlimited_origins_by_host_[host].insert(origin);
   }
 }
 
-void ClientUsageTracker::OnRevoked(const GURL& origin,
-                                   int change_flags) {
+void ClientUsageTracker::OnRevoked(const GURL& origin_url, int change_flags) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
+    url::Origin origin = url::Origin::Create(origin_url);
     int64_t usage = 0;
     if (GetCachedOriginUsage(origin, &usage)) {
       global_unlimited_usage_ -= usage;
       global_limited_usage_ += usage;
     }
 
-    std::string host = net::GetHostOrSpecFromURL(origin);
+    std::string host = net::GetHostOrSpecFromURL(origin_url);
     if (EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_,
                                  host, origin))
       non_cached_limited_origins_by_host_[host].insert(origin);
@@ -499,11 +499,11 @@
   }
 }
 
-bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const {
+bool ClientUsageTracker::IsStorageUnlimited(const url::Origin& origin) const {
   if (type_ == blink::mojom::StorageType::kSyncable)
     return false;
   return special_storage_policy_.get() &&
-         special_storage_policy_->IsStorageUnlimited(origin);
+         special_storage_policy_->IsStorageUnlimited(origin.GetURL());
 }
 
 }  // namespace storage
diff --git a/storage/browser/quota/client_usage_tracker.h b/storage/browser/quota/client_usage_tracker.h
index 72460d2..5bac56f2 100644
--- a/storage/browser/quota/client_usage_tracker.h
+++ b/storage/browser/quota/client_usage_tracker.h
@@ -39,9 +39,10 @@
       base::RepeatingCallback<void(int64_t limited_usage,
                                    int64_t unlimited_usage)>;
   using OriginUsageAccumulator =
-      base::RepeatingCallback<void(const GURL& origin, int64_t usage)>;
+      base::RepeatingCallback<void(const base::Optional<url::Origin>& origin,
+                                   int64_t usage)>;
   using UsageAccumulator = base::RepeatingCallback<void(int64_t usage)>;
-  using OriginSetByHost = std::map<std::string, std::set<GURL>>;
+  using OriginSetByHost = std::map<std::string, std::set<url::Origin>>;
 
   using HostUsageCallback =
       base::OnceCallback<void(int64_t limited_usage, int64_t unlimited_usage)>;
@@ -56,20 +57,21 @@
   void GetGlobalLimitedUsage(UsageCallback callback);
   void GetGlobalUsage(GlobalUsageCallback callback);
   void GetHostUsage(const std::string& host, UsageCallback callback);
-  void UpdateUsageCache(const GURL& origin, int64_t delta);
+  void UpdateUsageCache(const url::Origin& origin, int64_t delta);
   int64_t GetCachedUsage() const;
   void GetCachedHostsUsage(std::map<std::string, int64_t>* host_usage) const;
-  void GetCachedOriginsUsage(std::map<GURL, int64_t>* origin_usage) const;
-  void GetCachedOrigins(std::set<GURL>* origins) const;
-  bool IsUsageCacheEnabledForOrigin(const GURL& origin) const;
-  void SetUsageCacheEnabled(const GURL& origin, bool enabled);
+  void GetCachedOriginsUsage(
+      std::map<url::Origin, int64_t>* origin_usage) const;
+  void GetCachedOrigins(std::set<url::Origin>* origins) const;
+  bool IsUsageCacheEnabledForOrigin(const url::Origin& origin) const;
+  void SetUsageCacheEnabled(const url::Origin& origin, bool enabled);
 
  private:
   using HostUsageAccumulatorMap =
       CallbackQueueMap<HostUsageCallback, std::string, int64_t, int64_t>;
 
   using HostSet = std::set<std::string>;
-  using UsageMap = std::map<GURL, int64_t>;
+  using UsageMap = std::map<url::Origin, int64_t>;
   using HostUsageMap = std::map<std::string, UsageMap>;
 
   struct AccumulateInfo {
@@ -95,28 +97,28 @@
                           const std::set<url::Origin>& origins);
   void AccumulateOriginUsage(AccumulateInfo* info,
                              const std::string& host,
-                             const GURL& origin,
+                             const base::Optional<url::Origin>& origin,
                              int64_t usage);
 
-  void DidGetHostUsageAfterUpdate(const GURL& origin, int64_t usage);
+  void DidGetHostUsageAfterUpdate(const url::Origin& origin, int64_t usage);
 
   // Methods used by our GatherUsage tasks, as a task makes progress
   // origins and hosts are added incrementally to the cache.
-  void AddCachedOrigin(const GURL& origin, int64_t usage);
+  void AddCachedOrigin(const url::Origin& origin, int64_t usage);
   void AddCachedHost(const std::string& host);
 
   int64_t GetCachedHostUsage(const std::string& host) const;
   int64_t GetCachedGlobalUnlimitedUsage();
-  bool GetCachedOriginUsage(const GURL& origin, int64_t* usage) const;
+  bool GetCachedOriginUsage(const url::Origin& origin, int64_t* usage) const;
 
   // SpecialStoragePolicy::Observer overrides
-  void OnGranted(const GURL& origin, int change_flags) override;
-  void OnRevoked(const GURL& origin, int change_flags) override;
+  void OnGranted(const GURL& origin_url, int change_flags) override;
+  void OnRevoked(const GURL& origin_url, int change_flags) override;
   void OnCleared() override;
 
   void UpdateGlobalUsageValue(int64_t* usage_value, int64_t delta);
 
-  bool IsStorageUnlimited(const GURL& origin) const;
+  bool IsStorageUnlimited(const url::Origin& origin) const;
 
   UsageTracker* tracker_;
   QuotaClient* client_;
diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h
index 7a4a4e53..bcf6402a 100644
--- a/storage/browser/quota/quota_callbacks.h
+++ b/storage/browser/quota/quota_callbacks.h
@@ -15,11 +15,14 @@
 
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
+#include "base/optional.h"
 #include "base/stl_util.h"
 #include "storage/browser/quota/quota_client.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
-class GURL;
+namespace url {
+class Origin;
+}
 
 namespace storage {
 
@@ -40,10 +43,11 @@
     base::OnceCallback<void(blink::mojom::QuotaStatusCode, int64_t)>;
 using StatusCallback = base::OnceCallback<void(blink::mojom::QuotaStatusCode)>;
 using GetOriginsCallback =
-    base::OnceCallback<void(const std::set<GURL>& origins,
+    base::OnceCallback<void(const std::set<url::Origin>& origins,
                             blink::mojom::StorageType type)>;
 using GetUsageInfoCallback = base::OnceCallback<void(const UsageInfoEntries&)>;
-using GetOriginCallback = base::OnceCallback<void(const GURL&)>;
+using GetOriginCallback =
+    base::OnceCallback<void(const base::Optional<url::Origin>&)>;
 
 // Simple template wrapper for a callback queue.
 template <typename CallbackType, typename... Args>
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 46c0522..0bcc0bc 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -20,6 +20,7 @@
 #include "sql/statement.h"
 #include "sql/transaction.h"
 #include "storage/browser/quota/special_storage_policy.h"
+#include "url/gurl.h"
 
 using blink::mojom::StorageType;
 
@@ -120,7 +121,7 @@
     : type(StorageType::kUnknown), used_count(0) {}
 
 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
-    const GURL& origin,
+    const url::Origin& origin,
     StorageType type,
     int used_count,
     const base::Time& last_access_time,
@@ -129,8 +130,7 @@
       type(type),
       used_count(used_count),
       last_access_time(last_access_time),
-      last_modified_time(last_modified_time) {
-}
+      last_modified_time(last_modified_time) {}
 
 // QuotaDatabase ------------------------------------------------------------
 QuotaDatabase::QuotaDatabase(const base::FilePath& path)
@@ -187,8 +187,9 @@
   return true;
 }
 
-bool QuotaDatabase::SetOriginLastAccessTime(
-    const GURL& origin, StorageType type, base::Time last_access_time) {
+bool QuotaDatabase::SetOriginLastAccessTime(const url::Origin& origin,
+                                            StorageType type,
+                                            base::Time last_access_time) {
   if (!LazyOpen(true))
     return false;
 
@@ -214,7 +215,7 @@
   }
   statement.BindInt(0, entry.used_count);
   statement.BindInt64(1, last_access_time.ToInternalValue());
-  statement.BindString(2, origin.spec());
+  statement.BindString(2, origin.GetURL().spec());
   statement.BindInt(3, static_cast<int>(type));
 
   if (!statement.Run())
@@ -224,8 +225,9 @@
   return true;
 }
 
-bool QuotaDatabase::SetOriginLastModifiedTime(
-    const GURL& origin, StorageType type, base::Time last_modified_time) {
+bool QuotaDatabase::SetOriginLastModifiedTime(const url::Origin& origin,
+                                              StorageType type,
+                                              base::Time last_modified_time) {
   if (!LazyOpen(true))
     return false;
 
@@ -247,7 +249,7 @@
     statement.BindInt64(3, last_modified_time.ToInternalValue());
   }
   statement.BindInt64(0, last_modified_time.ToInternalValue());
-  statement.BindString(1, origin.spec());
+  statement.BindString(1, origin.GetURL().spec());
   statement.BindInt(2, static_cast<int>(type));
 
   if (!statement.Run())
@@ -257,7 +259,7 @@
   return true;
 }
 
-bool QuotaDatabase::GetOriginLastEvictionTime(const GURL& origin,
+bool QuotaDatabase::GetOriginLastEvictionTime(const url::Origin& origin,
                                               StorageType type,
                                               base::Time* last_modified_time) {
   DCHECK(last_modified_time);
@@ -270,7 +272,7 @@
       " WHERE origin = ? AND type = ?";
 
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
-  statement.BindString(0, origin.spec());
+  statement.BindString(0, origin.GetURL().spec());
   statement.BindInt(1, static_cast<int>(type));
 
   if (!statement.Step())
@@ -280,7 +282,7 @@
   return true;
 }
 
-bool QuotaDatabase::SetOriginLastEvictionTime(const GURL& origin,
+bool QuotaDatabase::SetOriginLastEvictionTime(const url::Origin& origin,
                                               StorageType type,
                                               base::Time last_modified_time) {
   if (!LazyOpen(true))
@@ -292,7 +294,7 @@
       " VALUES (?, ?, ?)";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt64(0, last_modified_time.ToInternalValue());
-  statement.BindString(1, origin.spec());
+  statement.BindString(1, origin.GetURL().spec());
   statement.BindInt(2, static_cast<int>(type));
 
   if (!statement.Run())
@@ -302,7 +304,7 @@
   return true;
 }
 
-bool QuotaDatabase::DeleteOriginLastEvictionTime(const GURL& origin,
+bool QuotaDatabase::DeleteOriginLastEvictionTime(const url::Origin& origin,
                                                  StorageType type) {
   if (!LazyOpen(false))
     return false;
@@ -312,7 +314,7 @@
       " WHERE origin = ? AND type = ?";
 
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
-  statement.BindString(0, origin.spec());
+  statement.BindString(0, origin.GetURL().spec());
   statement.BindInt(1, static_cast<int>(type));
 
   if (!statement.Run())
@@ -323,7 +325,8 @@
 }
 
 bool QuotaDatabase::RegisterInitialOriginInfo(
-    const std::set<GURL>& origins, StorageType type) {
+    const std::set<url::Origin>& origins,
+    StorageType type) {
   if (!LazyOpen(true))
     return false;
 
@@ -332,7 +335,7 @@
         "INSERT OR IGNORE INTO OriginInfoTable"
         " (origin, type) VALUES (?, ?)";
     sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
-    statement.BindString(0, origin.spec());
+    statement.BindString(0, origin.GetURL().spec());
     statement.BindInt(1, static_cast<int>(type));
 
     if (!statement.Run())
@@ -343,7 +346,7 @@
   return true;
 }
 
-bool QuotaDatabase::GetOriginInfo(const GURL& origin,
+bool QuotaDatabase::GetOriginInfo(const url::Origin& origin,
                                   StorageType type,
                                   QuotaDatabase::OriginInfoTableEntry* entry) {
   if (!LazyOpen(false))
@@ -353,14 +356,15 @@
       "SELECT * FROM OriginInfoTable"
       " WHERE origin = ? AND type = ?";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
-  statement.BindString(0, origin.spec());
+  statement.BindString(0, origin.GetURL().spec());
   statement.BindInt(1, static_cast<int>(type));
 
   if (!statement.Step())
     return false;
 
+  // TODO(crbug.com/889590): Use helper for url::Origin creation from string.
   *entry = OriginInfoTableEntry(
-      GURL(statement.ColumnString(0)),
+      url::Origin::Create(GURL(statement.ColumnString(0))),
       static_cast<StorageType>(statement.ColumnInt(1)), statement.ColumnInt(2),
       base::Time::FromInternalValue(statement.ColumnInt64(3)),
       base::Time::FromInternalValue(statement.ColumnInt64(4)));
@@ -388,8 +392,8 @@
   return true;
 }
 
-bool QuotaDatabase::DeleteOriginInfo(
-    const GURL& origin, StorageType type) {
+bool QuotaDatabase::DeleteOriginInfo(const url::Origin& origin,
+                                     StorageType type) {
   if (!LazyOpen(false))
     return false;
 
@@ -398,7 +402,7 @@
       " WHERE origin = ? AND type = ?";
 
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
-  statement.BindString(0, origin.spec());
+  statement.BindString(0, origin.GetURL().spec());
   statement.BindInt(1, static_cast<int>(type));
 
   if (!statement.Run())
@@ -422,11 +426,10 @@
   return meta_table_->SetValue(key, value);
 }
 
-bool QuotaDatabase::GetLRUOrigin(
-    StorageType type,
-    const std::set<GURL>& exceptions,
-    SpecialStoragePolicy* special_storage_policy,
-    GURL* origin) {
+bool QuotaDatabase::GetLRUOrigin(StorageType type,
+                                 const std::set<url::Origin>& exceptions,
+                                 SpecialStoragePolicy* special_storage_policy,
+                                 base::Optional<url::Origin>* origin) {
   DCHECK(origin);
   if (!LazyOpen(false))
     return false;
@@ -440,26 +443,28 @@
   statement.BindInt(0, static_cast<int>(type));
 
   while (statement.Step()) {
-    GURL url(statement.ColumnString(0));
-    if (base::ContainsKey(exceptions, url))
+    url::Origin read_origin =
+        url::Origin::Create(GURL(statement.ColumnString(0)));
+    if (base::ContainsKey(exceptions, read_origin))
       continue;
 
-    if (special_storage_policy && (
-        special_storage_policy->IsStorageDurable(url) ||
-        special_storage_policy->IsStorageUnlimited(url))) {
+    if (special_storage_policy &&
+        (special_storage_policy->IsStorageDurable(read_origin.GetURL()) ||
+         special_storage_policy->IsStorageUnlimited(read_origin.GetURL()))) {
       continue;
     }
 
-    *origin = url;
+    *origin = read_origin;
     return true;
   }
 
-  *origin = GURL();
+  origin->reset();
   return statement.Succeeded();
 }
 
-bool QuotaDatabase::GetOriginsModifiedSince(
-    StorageType type, std::set<GURL>* origins, base::Time modified_since) {
+bool QuotaDatabase::GetOriginsModifiedSince(StorageType type,
+                                            std::set<url::Origin>* origins,
+                                            base::Time modified_since) {
   DCHECK(origins);
   if (!LazyOpen(false))
     return false;
@@ -473,7 +478,7 @@
 
   origins->clear();
   while (statement.Step())
-    origins->insert(GURL(statement.ColumnString(0)));
+    origins->insert(url::Origin::Create(GURL(statement.ColumnString(0))));
 
   return statement.Succeeded();
 }
@@ -749,11 +754,11 @@
 
   while (statement.Step()) {
     OriginInfoTableEntry entry(
-      GURL(statement.ColumnString(0)),
-      static_cast<StorageType>(statement.ColumnInt(1)),
-      statement.ColumnInt(2),
-      base::Time::FromInternalValue(statement.ColumnInt64(3)),
-      base::Time::FromInternalValue(statement.ColumnInt64(4)));
+        url::Origin::Create(GURL(statement.ColumnString(0))),
+        static_cast<StorageType>(statement.ColumnInt(1)),
+        statement.ColumnInt(2),
+        base::Time::FromInternalValue(statement.ColumnInt64(3)),
+        base::Time::FromInternalValue(statement.ColumnInt64(4)));
 
     if (!callback.Run(entry))
       return true;
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 529387f..e8a1f3ea 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -19,7 +19,7 @@
 #include "base/timer/timer.h"
 #include "storage/browser/storage_browser_export.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 class QuotaDatabaseTest;
@@ -30,8 +30,6 @@
 class MetaTable;
 }
 
-class GURL;
-
 namespace storage {
 
 class SpecialStoragePolicy;
@@ -41,12 +39,12 @@
  public:
   struct STORAGE_EXPORT OriginInfoTableEntry {
     OriginInfoTableEntry();
-    OriginInfoTableEntry(const GURL& origin,
+    OriginInfoTableEntry(const url::Origin& origin,
                          blink::mojom::StorageType type,
                          int used_count,
                          const base::Time& last_access_time,
                          const base::Time& last_modified_time);
-    GURL origin;
+    url::Origin origin;
     blink::mojom::StorageType type;
     int used_count;
     base::Time last_access_time;
@@ -74,41 +72,42 @@
                     int64_t quota);
   bool DeleteHostQuota(const std::string& host, blink::mojom::StorageType type);
 
-  bool SetOriginLastAccessTime(const GURL& origin,
+  bool SetOriginLastAccessTime(const url::Origin& origin,
                                blink::mojom::StorageType type,
                                base::Time last_access_time);
 
-  bool SetOriginLastModifiedTime(const GURL& origin,
+  bool SetOriginLastModifiedTime(const url::Origin& origin,
                                  blink::mojom::StorageType type,
                                  base::Time last_modified_time);
 
   // Gets the time |origin| was last evicted. Returns whether the record could
   // be found.
-  bool GetOriginLastEvictionTime(const GURL& origin,
+  bool GetOriginLastEvictionTime(const url::Origin& origin,
                                  blink::mojom::StorageType type,
                                  base::Time* last_eviction_time);
 
   // Sets the time the origin was last evicted. Returns whether the operation
   // succeeded.
-  bool SetOriginLastEvictionTime(const GURL& origin,
+  bool SetOriginLastEvictionTime(const url::Origin& origin,
                                  blink::mojom::StorageType type,
                                  base::Time last_eviction_time);
-  bool DeleteOriginLastEvictionTime(const GURL& origin,
+  bool DeleteOriginLastEvictionTime(const url::Origin& origin,
                                     blink::mojom::StorageType type);
 
   // Register initial |origins| info |type| to the database.
   // This method is assumed to be called only after the installation or
   // the database schema reset.
-  bool RegisterInitialOriginInfo(const std::set<GURL>& origins,
+  bool RegisterInitialOriginInfo(const std::set<url::Origin>& origins,
                                  blink::mojom::StorageType type);
 
   // Gets the OriginInfoTableEntry for |origin|. Returns whether the record
   // could be found.
-  bool GetOriginInfo(const GURL& origin,
+  bool GetOriginInfo(const url::Origin& origin,
                      blink::mojom::StorageType type,
                      OriginInfoTableEntry* entry);
 
-  bool DeleteOriginInfo(const GURL& origin, blink::mojom::StorageType type);
+  bool DeleteOriginInfo(const url::Origin& origin,
+                        blink::mojom::StorageType type);
 
   bool GetQuotaConfigValue(const char* key, int64_t* value);
   bool SetQuotaConfigValue(const char* key, int64_t value);
@@ -116,16 +115,16 @@
   // Sets |origin| to the least recently used origin of origins not included
   // in |exceptions| and not granted the special unlimited storage right.
   // It returns false when it failed in accessing the database.
-  // |origin| is set to empty when there is no matching origin.
+  // |origin| is set to nullopt when there is no matching origin.
   bool GetLRUOrigin(blink::mojom::StorageType type,
-                    const std::set<GURL>& exceptions,
+                    const std::set<url::Origin>& exceptions,
                     SpecialStoragePolicy* special_storage_policy,
-                    GURL* origin);
+                    base::Optional<url::Origin>* origin);
 
   // Populates |origins| with the ones that have been modified since
   // the |modified_since|. Returns whether the operation succeeded.
   bool GetOriginsModifiedSince(blink::mojom::StorageType type,
-                               std::set<GURL>* origins,
+                               std::set<url::Origin>* origins,
                                base::Time modified_since);
 
   // Returns false if SetOriginDatabaseBootstrapped has never
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index 0e2e5a5..e0de603 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -152,15 +152,16 @@
     QuotaDatabase db(kDbFile);
     ASSERT_TRUE(db.LazyOpen(true));
 
-    std::set<GURL> exceptions;
-    GURL origin;
+    std::set<url::Origin> exceptions;
+    base::Optional<url::Origin> origin;
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_TRUE(origin.is_empty());
+    EXPECT_FALSE(origin.has_value());
 
-    const GURL kOrigin1("http://a/");
-    const GURL kOrigin2("http://b/");
-    const GURL kOrigin3("http://c/");
-    const GURL kOrigin4("http://p/");
+    // TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+    const url::Origin kOrigin1 = url::Origin::Create(GURL("http://a/"));
+    const url::Origin kOrigin2 = url::Origin::Create(GURL("http://b/"));
+    const url::Origin kOrigin3 = url::Origin::Create(GURL("http://c/"));
+    const url::Origin kOrigin4 = url::Origin::Create(GURL("http://p/"));
 
     // Adding three temporary storages, and
     EXPECT_TRUE(db.SetOriginLastAccessTime(kOrigin1, kTemporary,
@@ -175,33 +176,33 @@
                                            base::Time::FromInternalValue(40)));
 
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_EQ(kOrigin1.spec(), origin.spec());
+    EXPECT_EQ(kOrigin1, origin);
 
     // Test that unlimited origins are exluded from eviction, but
     // protected origins are not excluded.
     scoped_refptr<MockSpecialStoragePolicy> policy(
         new MockSpecialStoragePolicy);
-    policy->AddUnlimited(kOrigin1);
-    policy->AddProtected(kOrigin2);
+    policy->AddUnlimited(kOrigin1.GetURL());
+    policy->AddProtected(kOrigin2.GetURL());
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, policy.get(), &origin));
-    EXPECT_EQ(kOrigin2.spec(), origin.spec());
+    EXPECT_EQ(kOrigin2, origin);
 
     // Test that durable origins are excluded from eviction.
-    policy->AddDurable(kOrigin2);
+    policy->AddDurable(kOrigin2.GetURL());
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, policy.get(), &origin));
-    EXPECT_EQ(kOrigin3.spec(), origin.spec());
+    EXPECT_EQ(kOrigin3, origin);
 
     exceptions.insert(kOrigin1);
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_EQ(kOrigin2.spec(), origin.spec());
+    EXPECT_EQ(kOrigin2, origin);
 
     exceptions.insert(kOrigin2);
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_EQ(kOrigin3.spec(), origin.spec());
+    EXPECT_EQ(kOrigin3, origin);
 
     exceptions.insert(kOrigin3);
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_TRUE(origin.is_empty());
+    EXPECT_FALSE(origin.has_value());
 
     EXPECT_TRUE(
         db.SetOriginLastAccessTime(kOrigin1, kTemporary, base::Time::Now()));
@@ -212,25 +213,25 @@
     // Querying again to see if the deletion has worked.
     exceptions.clear();
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_EQ(kOrigin2.spec(), origin.spec());
+    EXPECT_EQ(kOrigin2, origin);
 
     exceptions.insert(kOrigin1);
     exceptions.insert(kOrigin2);
     EXPECT_TRUE(db.GetLRUOrigin(kTemporary, exceptions, nullptr, &origin));
-    EXPECT_TRUE(origin.is_empty());
+    EXPECT_FALSE(origin.has_value());
   }
 
   void OriginLastModifiedSince(const base::FilePath& kDbFile) {
     QuotaDatabase db(kDbFile);
     ASSERT_TRUE(db.LazyOpen(true));
 
-    std::set<GURL> origins;
+    std::set<url::Origin> origins;
     EXPECT_TRUE(db.GetOriginsModifiedSince(kTemporary, &origins, base::Time()));
     EXPECT_TRUE(origins.empty());
 
-    const GURL kOrigin1("http://a/");
-    const GURL kOrigin2("http://b/");
-    const GURL kOrigin3("http://c/");
+    const url::Origin kOrigin1 = url::Origin::Create(GURL("http://a/"));
+    const url::Origin kOrigin2 = url::Origin::Create(GURL("http://b/"));
+    const url::Origin kOrigin3 = url::Origin::Create(GURL("http://c/"));
 
     // Report last mod time for the test origins.
     EXPECT_TRUE(db.SetOriginLastModifiedTime(kOrigin1, kTemporary,
@@ -299,9 +300,9 @@
     QuotaDatabase db(kDbFile);
     ASSERT_TRUE(db.LazyOpen(true));
 
-    const GURL kOrigin1("http://a/");
-    const GURL kOrigin2("http://b/");
-    const GURL kOrigin3("http://c/");
+    const url::Origin kOrigin1 = url::Origin::Create(GURL("http://a/"));
+    const url::Origin kOrigin2 = url::Origin::Create(GURL("http://b/"));
+    const url::Origin kOrigin3 = url::Origin::Create(GURL("http://c/"));
 
     base::Time last_eviction_time;
     EXPECT_FALSE(db.GetOriginLastEvictionTime(kOrigin1, kTemporary,
@@ -343,36 +344,39 @@
     EXPECT_EQ(base::Time(), last_eviction_time);
 
     // Deleting an origin that is not present should not fail.
-    EXPECT_TRUE(db.DeleteOriginLastEvictionTime(GURL("http://notpresent.com"),
-                                                kTemporary));
+    EXPECT_TRUE(db.DeleteOriginLastEvictionTime(
+        url::Origin::Create(GURL("http://notpresent.com")), kTemporary));
   }
 
   void RegisterInitialOriginInfo(const base::FilePath& kDbFile) {
     QuotaDatabase db(kDbFile);
 
-    const GURL kOrigins[] = {
-      GURL("http://a/"),
-      GURL("http://b/"),
-      GURL("http://c/") };
-    std::set<GURL> origins(kOrigins, kOrigins + base::size(kOrigins));
+    const url::Origin kOrigins[] = {url::Origin::Create(GURL("http://a/")),
+                                    url::Origin::Create(GURL("http://b/")),
+                                    url::Origin::Create(GURL("http://c/"))};
+    std::set<url::Origin> origins(kOrigins, kOrigins + base::size(kOrigins));
 
     EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kTemporary));
 
     QuotaDatabase::OriginInfoTableEntry info;
     info.used_count = -1;
-    EXPECT_TRUE(db.GetOriginInfo(GURL("http://a/"), kTemporary, &info));
+    EXPECT_TRUE(db.GetOriginInfo(url::Origin::Create(GURL("http://a/")),
+                                 kTemporary, &info));
     EXPECT_EQ(0, info.used_count);
 
-    EXPECT_TRUE(db.SetOriginLastAccessTime(GURL("http://a/"), kTemporary,
-                                           base::Time::FromDoubleT(1.0)));
+    EXPECT_TRUE(
+        db.SetOriginLastAccessTime(url::Origin::Create(GURL("http://a/")),
+                                   kTemporary, base::Time::FromDoubleT(1.0)));
     info.used_count = -1;
-    EXPECT_TRUE(db.GetOriginInfo(GURL("http://a/"), kTemporary, &info));
+    EXPECT_TRUE(db.GetOriginInfo(url::Origin::Create(GURL("http://a/")),
+                                 kTemporary, &info));
     EXPECT_EQ(1, info.used_count);
 
     EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kTemporary));
 
     info.used_count = -1;
-    EXPECT_TRUE(db.GetOriginInfo(GURL("http://a/"), kTemporary, &info));
+    EXPECT_TRUE(db.GetOriginInfo(url::Origin::Create(GURL("http://a/")),
+                                 kTemporary, &info));
     EXPECT_EQ(1, info.used_count);
   }
 
@@ -414,9 +418,11 @@
     base::Time now(base::Time::Now());
     using Entry = QuotaDatabase::OriginInfoTableEntry;
     Entry kTableEntries[] = {
-        Entry(GURL("http://go/"), kTemporary, 2147483647, now, now),
-        Entry(GURL("http://oo/"), kTemporary, 0, now, now),
-        Entry(GURL("http://gle/"), kTemporary, 1, now, now),
+        Entry(url::Origin::Create(GURL("http://go/")), kTemporary, 2147483647,
+              now, now),
+        Entry(url::Origin::Create(GURL("http://oo/")), kTemporary, 0, now, now),
+        Entry(url::Origin::Create(GURL("http://gle/")), kTemporary, 1, now,
+              now),
     };
     Entry* begin = kTableEntries;
     Entry* end = kTableEntries + base::size(kTableEntries);
@@ -434,7 +440,7 @@
   }
 
   void GetOriginInfo(const base::FilePath& kDbFile) {
-    const GURL kOrigin = GURL("http://go/");
+    const url::Origin kOrigin = url::Origin::Create(GURL("http://go/"));
     using Entry = QuotaDatabase::OriginInfoTableEntry;
     Entry kTableEntries[] = {
         Entry(kOrigin, kTemporary, 100, base::Time(), base::Time())};
@@ -459,7 +465,8 @@
     {
       Entry entry;
       EXPECT_FALSE(
-          db.GetOriginInfo(GURL("http://notpresent.org/"), kTemporary, &entry));
+          db.GetOriginInfo(url::Origin::Create(GURL("http://notpresent.org/")),
+                           kTemporary, &entry));
     }
   }
 
@@ -495,7 +502,7 @@
       statement.Assign(db->GetCachedStatement(SQL_FROM_HERE, kSql));
       ASSERT_TRUE(statement.is_valid());
 
-      statement.BindString(0, itr->origin.spec());
+      statement.BindString(0, itr->origin.GetURL().spec());
       statement.BindInt(1, static_cast<int>(itr->type));
       statement.BindInt(2, itr->used_count);
       statement.BindInt64(3, itr->last_access_time.ToInternalValue());
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 41a92cc..11a057a 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -87,7 +87,7 @@
   return type == StorageType::kTemporary || type == StorageType::kPersistent;
 }
 
-void CountOriginType(const std::set<GURL>& origins,
+void CountOriginType(const std::set<url::Origin>& origins,
                      SpecialStoragePolicy* policy,
                      size_t* protected_origins,
                      size_t* unlimited_origins) {
@@ -98,9 +98,10 @@
   if (!policy)
     return;
   for (const auto& origin : origins) {
-    if (policy->IsStorageProtected(origin))
+    const GURL url = origin.GetURL();
+    if (policy->IsStorageProtected(url))
       ++*protected_origins;
-    if (policy->IsStorageUnlimited(origin))
+    if (policy->IsStorageUnlimited(url))
       ++*unlimited_origins;
   }
 }
@@ -124,16 +125,16 @@
 }
 
 bool GetLRUOriginOnDBThread(StorageType type,
-                            const std::set<GURL>& exceptions,
+                            const std::set<url::Origin>& exceptions,
                             SpecialStoragePolicy* policy,
-                            GURL* url,
+                            base::Optional<url::Origin>* origin,
                             QuotaDatabase* database) {
   DCHECK(database);
-  database->GetLRUOrigin(type, exceptions, policy, url);
+  database->GetLRUOrigin(type, exceptions, policy, origin);
   return true;
 }
 
-bool DeleteOriginInfoOnDBThread(const GURL& origin,
+bool DeleteOriginInfoOnDBThread(const url::Origin& origin,
                                 StorageType type,
                                 bool is_eviction,
                                 QuotaDatabase* database) {
@@ -172,7 +173,7 @@
   return database->SetOriginLastEvictionTime(origin, type, now);
 }
 
-bool BootstrapDatabaseOnDBThread(const std::set<GURL>* origins,
+bool BootstrapDatabaseOnDBThread(const std::set<url::Origin>* origins,
                                  QuotaDatabase* database) {
   DCHECK(database);
   if (database->IsOriginDatabaseBootstrapped())
@@ -186,7 +187,7 @@
   return false;
 }
 
-bool UpdateAccessTimeOnDBThread(const GURL& origin,
+bool UpdateAccessTimeOnDBThread(const url::Origin& origin,
                                 StorageType type,
                                 base::Time accessed_time,
                                 QuotaDatabase* database) {
@@ -194,7 +195,7 @@
   return database->SetOriginLastAccessTime(origin, type, accessed_time);
 }
 
-bool UpdateModifiedTimeOnDBThread(const GURL& origin,
+bool UpdateModifiedTimeOnDBThread(const url::Origin& origin,
                                   StorageType type,
                                   base::Time modified_time,
                                   QuotaDatabase* database) {
@@ -216,7 +217,7 @@
 class QuotaManager::UsageAndQuotaHelper : public QuotaTask {
  public:
   UsageAndQuotaHelper(QuotaManager* manager,
-                      const GURL& origin,
+                      const url::Origin& origin,
                       StorageType type,
                       bool is_unlimited,
                       bool is_session_only,
@@ -240,7 +241,7 @@
         4, base::BindOnce(&UsageAndQuotaHelper::OnBarrierComplete,
                           weak_factory_.GetWeakPtr()));
 
-    std::string host = net::GetHostOrSpecFromURL(origin_);
+    std::string host = net::GetHostOrSpecFromURL(origin_.GetURL());
 
     manager()->GetQuotaSettings(
         base::BindOnce(&UsageAndQuotaHelper::OnGotSettings,
@@ -344,7 +345,7 @@
 
   void OnBarrierComplete() { CallCompleted(); }
 
-  GURL origin_;
+  url::Origin origin_;
   QuotaManager::UsageAndQuotaWithBreakdownCallback callback_;
   StorageType type_;
   bool is_unlimited_;
@@ -534,7 +535,7 @@
 class QuotaManager::OriginDataDeleter : public QuotaTask {
  public:
   OriginDataDeleter(QuotaManager* manager,
-                    const GURL& origin,
+                    const url::Origin& origin,
                     StorageType type,
                     int quota_client_mask,
                     bool is_eviction,
@@ -557,7 +558,7 @@
     for (auto* client : manager()->clients_) {
       if (quota_client_mask_ & client->id()) {
         client->DeleteOriginData(
-            url::Origin::Create(origin_), type_,
+            origin_, type_,
             base::BindOnce(&OriginDataDeleter::DidDeleteOriginData,
                            weak_factory_.GetWeakPtr()));
       } else {
@@ -607,7 +608,7 @@
     return static_cast<QuotaManager*>(observer());
   }
 
-  GURL origin_;
+  url::Origin origin_;
   StorageType type_;
   int quota_client_mask_;
   int error_count_;
@@ -675,7 +676,7 @@
     DCHECK_GT(remaining_clients_, 0);
 
     for (const auto& origin : origins)
-      origins_.insert(origin.GetURL());
+      origins_.insert(origin);
 
     if (--remaining_clients_ == 0) {
       if (!origins_.empty())
@@ -713,7 +714,7 @@
   std::string host_;
   StorageType type_;
   int quota_client_mask_;
-  std::set<GURL> origins_;
+  std::set<url::Origin> origins_;
   int error_count_;
   int remaining_clients_;
   int remaining_deleters_;
@@ -738,7 +739,7 @@
                            bool success) {
     if (!manager) {
       // The operation was aborted.
-      std::move(callback).Run(std::set<GURL>(), type);
+      std::move(callback).Run(std::set<url::Origin>(), type);
       return;
     }
     manager->DidDatabaseWork(success);
@@ -746,7 +747,7 @@
   }
 
  private:
-  std::set<GURL> origins_;
+  std::set<url::Origin> origins_;
 };
 
 class QuotaManager::DumpQuotaTableHelper {
@@ -851,7 +852,7 @@
   get_usage_info->Start();
 }
 
-void QuotaManager::GetUsageAndQuotaForWebApps(const GURL& origin,
+void QuotaManager::GetUsageAndQuotaForWebApps(const url::Origin& origin,
                                               StorageType type,
                                               UsageAndQuotaCallback callback) {
   GetUsageAndQuotaWithBreakdown(
@@ -860,10 +861,9 @@
 }
 
 void QuotaManager::GetUsageAndQuotaWithBreakdown(
-    const GURL& origin,
+    const url::Origin& origin,
     StorageType type,
     UsageAndQuotaWithBreakdownCallback callback) {
-  DCHECK(origin == origin.GetOrigin());
   if (!IsSupportedType(type) ||
       (is_incognito_ && !IsSupportedIncognitoType(type))) {
     std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorNotSupported,
@@ -872,20 +872,18 @@
   }
   LazyInitialize();
 
-  bool is_session_only = type == StorageType::kTemporary &&
-                         special_storage_policy_ &&
-                         special_storage_policy_->IsStorageSessionOnly(origin);
+  bool is_session_only =
+      type == StorageType::kTemporary && special_storage_policy_ &&
+      special_storage_policy_->IsStorageSessionOnly(origin.GetURL());
   UsageAndQuotaHelper* helper = new UsageAndQuotaHelper(
       this, origin, type, IsStorageUnlimited(origin, type), is_session_only,
       is_incognito_, std::move(callback));
   helper->Start();
 }
 
-void QuotaManager::GetUsageAndQuota(const GURL& origin,
+void QuotaManager::GetUsageAndQuota(const url::Origin& origin,
                                     StorageType type,
                                     UsageAndQuotaCallback callback) {
-  DCHECK(origin == origin.GetOrigin());
-
   if (IsStorageUnlimited(origin, type)) {
     // TODO(michaeln): This seems like a non-obvious odd behavior, probably for
     // apps/extensions, but it would be good to eliminate this special case.
@@ -896,28 +894,26 @@
   GetUsageAndQuotaForWebApps(origin, type, std::move(callback));
 }
 
-void QuotaManager::NotifyStorageAccessed(
-    QuotaClient::ID client_id,
-    const GURL& origin, StorageType type) {
-  DCHECK(origin == origin.GetOrigin());
+void QuotaManager::NotifyStorageAccessed(QuotaClient::ID client_id,
+                                         const url::Origin& origin,
+                                         StorageType type) {
   NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now());
 }
 
 void QuotaManager::NotifyStorageModified(QuotaClient::ID client_id,
-                                         const GURL& origin,
+                                         const url::Origin& origin,
                                          StorageType type,
                                          int64_t delta) {
-  DCHECK(origin == origin.GetOrigin());
   NotifyStorageModifiedInternal(client_id, origin, type, delta,
                                 base::Time::Now());
 }
 
-void QuotaManager::NotifyOriginInUse(const GURL& origin) {
+void QuotaManager::NotifyOriginInUse(const url::Origin& origin) {
   DCHECK(io_thread_->BelongsToCurrentThread());
   origins_in_use_[origin]++;
 }
 
-void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) {
+void QuotaManager::NotifyOriginNoLongerInUse(const url::Origin& origin) {
   DCHECK(io_thread_->BelongsToCurrentThread());
   DCHECK(IsOriginInUse(origin));
   int& count = origins_in_use_[origin];
@@ -926,7 +922,7 @@
 }
 
 void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id,
-                                        const GURL& origin,
+                                        const url::Origin& origin,
                                         StorageType type,
                                         bool enabled) {
   LazyInitialize();
@@ -934,7 +930,7 @@
   GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled);
 }
 
-void QuotaManager::DeleteOriginData(const GURL& origin,
+void QuotaManager::DeleteOriginData(const url::Origin& origin,
                                     StorageType type,
                                     int quota_client_mask,
                                     StatusCallback callback) {
@@ -1074,7 +1070,7 @@
   }
 }
 
-bool QuotaManager::IsStorageUnlimited(const GURL& origin,
+bool QuotaManager::IsStorageUnlimited(const url::Origin& origin,
                                       StorageType type) const {
   // For syncable storage we should always enforce quota (since the
   // quota must be capped by the server limit).
@@ -1083,7 +1079,7 @@
   if (type == StorageType::kQuotaNotManaged)
     return true;
   return special_storage_policy_.get() &&
-         special_storage_policy_->IsStorageUnlimited(origin);
+         special_storage_policy_->IsStorageUnlimited(origin.GetURL());
 }
 
 void QuotaManager::GetOriginsModifiedSince(StorageType type,
@@ -1196,7 +1192,7 @@
     int64_t unlimited_usage) {
   // The usage cache should be fully populated now so we can
   // seed the database with origins we know about.
-  std::set<GURL>* origins = new std::set<GURL>;
+  std::set<url::Origin>* origins = new std::set<url::Origin>;
   temporary_usage_tracker_->GetCachedOrigins(origins);
   PostTaskAndReplyWithResultForDBThread(
       FROM_HERE,
@@ -1235,18 +1231,18 @@
   return nullptr;
 }
 
-void QuotaManager::GetCachedOrigins(
-    StorageType type, std::set<GURL>* origins) {
+void QuotaManager::GetCachedOrigins(StorageType type,
+                                    std::set<url::Origin>* origins) {
   DCHECK(origins);
   LazyInitialize();
   DCHECK(GetUsageTracker(type));
   GetUsageTracker(type)->GetCachedOrigins(origins);
 }
 
-void QuotaManager::NotifyStorageAccessedInternal(
-    QuotaClient::ID client_id,
-    const GURL& origin, StorageType type,
-    base::Time accessed_time) {
+void QuotaManager::NotifyStorageAccessedInternal(QuotaClient::ID client_id,
+                                                 const url::Origin& origin,
+                                                 StorageType type,
+                                                 base::Time accessed_time) {
   LazyInitialize();
   if (type == StorageType::kTemporary && is_getting_eviction_origin_) {
     // Record the accessed origins while GetLRUOrigin task is runing
@@ -1264,7 +1260,7 @@
 }
 
 void QuotaManager::NotifyStorageModifiedInternal(QuotaClient::ID client_id,
-                                                 const GURL& origin,
+                                                 const url::Origin& origin,
                                                  StorageType type,
                                                  int64_t delta,
                                                  base::Time modified_time) {
@@ -1311,7 +1307,7 @@
   temporary_storage_evictor_->Start();
 }
 
-void QuotaManager::DeleteOriginFromDatabase(const GURL& origin,
+void QuotaManager::DeleteOriginFromDatabase(const url::Origin& origin,
                                             StorageType type,
                                             bool is_eviction) {
   LazyInitialize();
@@ -1338,19 +1334,18 @@
   std::move(eviction_context_.evict_origin_data_callback).Run(status);
 }
 
-void QuotaManager::DeleteOriginDataInternal(const GURL& origin,
+void QuotaManager::DeleteOriginDataInternal(const url::Origin& origin,
                                             StorageType type,
                                             int quota_client_mask,
                                             bool is_eviction,
                                             StatusCallback callback) {
   LazyInitialize();
 
-  if (origin.is_empty() || clients_.empty()) {
+  if (clients_.empty()) {
     std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
     return;
   }
 
-  DCHECK(origin == origin.GetOrigin());
   OriginDataDeleter* deleter = new OriginDataDeleter(
       this, origin, type, quota_client_mask, is_eviction, std::move(callback));
   deleter->Start();
@@ -1369,7 +1364,7 @@
     int64_t unlimited_usage) {
   UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage);
 
-  std::set<GURL> origins;
+  std::set<url::Origin> origins;
   GetCachedOrigins(StorageType::kTemporary, &origins);
 
   size_t num_origins = origins.size();
@@ -1396,7 +1391,7 @@
     int64_t unlimited_usage) {
   UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);
 
-  std::set<GURL> origins;
+  std::set<url::Origin> origins;
   GetCachedOrigins(StorageType::kPersistent, &origins);
 
   size_t num_origins = origins.size();
@@ -1421,7 +1416,7 @@
 
 void QuotaManager::DidDumpOriginInfoTableForHistogram(
     const OriginInfoTableEntries& entries) {
-  using UsageMap = std::map<GURL, int64_t>;
+  using UsageMap = std::map<url::Origin, int64_t>;
   UsageMap usage_map;
   GetUsageTracker(StorageType::kTemporary)->GetCachedOriginsUsage(&usage_map);
   base::Time now = base::Time::Now();
@@ -1448,9 +1443,9 @@
   }
 }
 
-std::set<GURL> QuotaManager::GetEvictionOriginExceptions(
-    const std::set<GURL>& extra_exceptions) {
-  std::set<GURL> exceptions = extra_exceptions;
+std::set<url::Origin> QuotaManager::GetEvictionOriginExceptions(
+    const std::set<url::Origin>& extra_exceptions) {
+  std::set<url::Origin> exceptions = extra_exceptions;
   for (const auto& p : origins_in_use_) {
     if (p.second > 0)
       exceptions.insert(p.first);
@@ -1464,13 +1459,16 @@
   return exceptions;
 }
 
-void QuotaManager::DidGetEvictionOrigin(GetOriginCallback callback,
-                                        const GURL& origin) {
+void QuotaManager::DidGetEvictionOrigin(
+    GetOriginCallback callback,
+    const base::Optional<url::Origin>& origin) {
   // Make sure the returned origin is (still) not in the origin_in_use_ set
   // and has not been accessed since we posted the task.
-  if (base::ContainsKey(origins_in_use_, origin) ||
-      base::ContainsKey(access_notified_origins_, origin)) {
-    std::move(callback).Run(GURL());
+  DCHECK(!origin.has_value() || !origin->GetURL().is_empty());
+  if (origin.has_value() &&
+      (base::ContainsKey(origins_in_use_, *origin) ||
+       base::ContainsKey(access_notified_origins_, *origin))) {
+    std::move(callback).Run(base::nullopt);
   } else {
     std::move(callback).Run(origin);
   }
@@ -1479,10 +1477,11 @@
   is_getting_eviction_origin_ = false;
 }
 
-void QuotaManager::GetEvictionOrigin(StorageType type,
-                                     const std::set<GURL>& extra_exceptions,
-                                     int64_t global_quota,
-                                     GetOriginCallback callback) {
+void QuotaManager::GetEvictionOrigin(
+    StorageType type,
+    const std::set<url::Origin>& extra_exceptions,
+    int64_t global_quota,
+    GetOriginCallback callback) {
   LazyInitialize();
   // This must not be called while there's an in-flight task.
   DCHECK(!is_getting_eviction_origin_);
@@ -1504,7 +1503,7 @@
   GetLRUOrigin(type, std::move(did_get_origin_callback));
 }
 
-void QuotaManager::EvictOriginData(const GURL& origin,
+void QuotaManager::EvictOriginData(const url::Origin& origin,
                                    StorageType type,
                                    StatusCallback callback) {
   DCHECK(io_thread_->BelongsToCurrentThread());
@@ -1533,19 +1532,20 @@
   DCHECK(lru_origin_callback_.is_null());
   lru_origin_callback_ = std::move(callback);
   if (db_disabled_) {
-    std::move(lru_origin_callback_).Run(GURL());
+    std::move(lru_origin_callback_).Run(base::nullopt);
     return;
   }
 
-  GURL* url = new GURL;
+  auto origin = std::make_unique<base::Optional<url::Origin>>();
+  auto* origin_ptr = origin.get();
   PostTaskAndReplyWithResultForDBThread(
       FROM_HERE,
       base::BindOnce(&GetLRUOriginOnDBThread, type,
-                     GetEvictionOriginExceptions(std::set<GURL>()),
+                     GetEvictionOriginExceptions(std::set<url::Origin>()),
                      base::RetainedRef(special_storage_policy_),
-                     base::Unretained(url)),
+                     base::Unretained(origin_ptr)),
       base::BindOnce(&QuotaManager::DidGetLRUOrigin, weak_factory_.GetWeakPtr(),
-                     base::Owned(url)));
+                     std::move(origin)));
 }
 
 void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
@@ -1568,8 +1568,9 @@
       *new_quota);
 }
 
-void QuotaManager::DidGetLRUOrigin(const GURL* origin,
-                                   bool success) {
+void QuotaManager::DidGetLRUOrigin(
+    std::unique_ptr<base::Optional<url::Origin>> origin,
+    bool success) {
   DidDatabaseWork(success);
 
   std::move(lru_origin_callback_).Run(*origin);
diff --git a/storage/browser/quota/quota_manager.h b/storage/browser/quota/quota_manager.h
index 8dc6ffdc..7673368 100644
--- a/storage/browser/quota/quota_manager.h
+++ b/storage/browser/quota/quota_manager.h
@@ -79,15 +79,15 @@
   // the current settings, capacity, and usage.
   virtual void GetEvictionRoundInfo(EvictionRoundInfoCallback callback) = 0;
 
-  // Returns next origin to evict.  It might return an empty GURL when there are
-  // no evictable origins.
+  // Returns next origin to evict, or nullopt if there are no evictable
+  // origins.
   virtual void GetEvictionOrigin(blink::mojom::StorageType type,
-                                 const std::set<GURL>& extra_exceptions,
+                                 const std::set<url::Origin>& extra_exceptions,
                                  int64_t global_quota,
                                  GetOriginCallback callback) = 0;
 
   // Called to evict an origin.
-  virtual void EvictOriginData(const GURL& origin,
+  virtual void EvictOriginData(const url::Origin& origin,
                                blink::mojom::StorageType type,
                                StatusCallback callback) = 0;
 
@@ -144,14 +144,14 @@
 
   // Called by Web Apps.
   // This method is declared as virtual to allow test code to override it.
-  virtual void GetUsageAndQuotaForWebApps(const GURL& origin,
+  virtual void GetUsageAndQuotaForWebApps(const url::Origin& origin,
                                           blink::mojom::StorageType type,
                                           UsageAndQuotaCallback callback);
 
   // Called by DevTools.
   // This method is declared as virtual to allow test code to override it.
   virtual void GetUsageAndQuotaWithBreakdown(
-      const GURL& origin,
+      const url::Origin& origin,
       blink::mojom::StorageType type,
       UsageAndQuotaWithBreakdownCallback callback);
 
@@ -161,7 +161,7 @@
   // For UnlimitedStorage origins, this version skips usage and quota handling
   // to avoid extra query cost.
   // Do not call this method for apps/user-facing code.
-  virtual void GetUsageAndQuota(const GURL& origin,
+  virtual void GetUsageAndQuota(const url::Origin& origin,
                                 blink::mojom::StorageType type,
                                 UsageAndQuotaCallback callback);
 
@@ -169,28 +169,28 @@
   // Client storage should call this method when storage is accessed.
   // Used to maintain LRU ordering.
   void NotifyStorageAccessed(QuotaClient::ID client_id,
-                             const GURL& origin,
+                             const url::Origin& origin,
                              blink::mojom::StorageType type);
 
   // Called by clients via proxy.
   // Client storage must call this method whenever they have made any
   // modifications that change the amount of data stored in their storage.
   void NotifyStorageModified(QuotaClient::ID client_id,
-                             const GURL& origin,
+                             const url::Origin& origin,
                              blink::mojom::StorageType type,
                              int64_t delta);
 
   // Used to avoid evicting origins with open pages.
   // A call to NotifyOriginInUse must be balanced by a later call
   // to NotifyOriginNoLongerInUse.
-  void NotifyOriginInUse(const GURL& origin);
-  void NotifyOriginNoLongerInUse(const GURL& origin);
-  bool IsOriginInUse(const GURL& origin) const {
+  void NotifyOriginInUse(const url::Origin& origin);
+  void NotifyOriginNoLongerInUse(const url::Origin& origin);
+  bool IsOriginInUse(const url::Origin& origin) const {
     return base::ContainsKey(origins_in_use_, origin);
   }
 
   void SetUsageCacheEnabled(QuotaClient::ID client_id,
-                            const GURL& origin,
+                            const url::Origin& origin,
                             blink::mojom::StorageType type,
                             bool enabled);
 
@@ -201,7 +201,7 @@
   // from the origin. This is specified by the caller as a bitmask built from
   // QuotaClient::IDs. Setting the mask to QuotaClient::kAllClientsMask will
   // remove all clients from the origin, regardless of type.
-  virtual void DeleteOriginData(const GURL& origin,
+  virtual void DeleteOriginData(const url::Origin& origin,
                                 blink::mojom::StorageType type,
                                 int quota_client_mask,
                                 StatusCallback callback);
@@ -233,7 +233,7 @@
 
   void GetStatistics(std::map<std::string, std::string>* statistics);
 
-  bool IsStorageUnlimited(const GURL& origin,
+  bool IsStorageUnlimited(const url::Origin& origin,
                           blink::mojom::StorageType type) const;
 
   virtual void GetOriginsModifiedSince(blink::mojom::StorageType type,
@@ -318,7 +318,7 @@
   struct EvictionContext {
     EvictionContext();
     ~EvictionContext();
-    GURL evicted_origin;
+    url::Origin evicted_origin;
     blink::mojom::StorageType evicted_type;
     StatusCallback evict_origin_data_callback;
   };
@@ -345,15 +345,15 @@
   // Extract cached origins list from the usage tracker.
   // (Might return empty list if no origin is tracked by the tracker.)
   void GetCachedOrigins(blink::mojom::StorageType type,
-                        std::set<GURL>* origins);
+                        std::set<url::Origin>* origins);
 
   // These internal methods are separately defined mainly for testing.
   void NotifyStorageAccessedInternal(QuotaClient::ID client_id,
-                                     const GURL& origin,
+                                     const url::Origin& origin,
                                      blink::mojom::StorageType type,
                                      base::Time accessed_time);
   void NotifyStorageModifiedInternal(QuotaClient::ID client_id,
-                                     const GURL& origin,
+                                     const url::Origin& origin,
                                      blink::mojom::StorageType type,
                                      int64_t delta,
                                      base::Time modified_time);
@@ -361,7 +361,7 @@
   void DumpQuotaTable(DumpQuotaTableCallback callback);
   void DumpOriginInfoTable(DumpOriginInfoTableCallback callback);
 
-  void DeleteOriginDataInternal(const GURL& origin,
+  void DeleteOriginDataInternal(const url::Origin& origin,
                                 blink::mojom::StorageType type,
                                 int quota_client_mask,
                                 bool is_eviction,
@@ -369,7 +369,7 @@
 
   // Methods for eviction logic.
   void StartEviction();
-  void DeleteOriginFromDatabase(const GURL& origin,
+  void DeleteOriginFromDatabase(const url::Origin& origin,
                                 blink::mojom::StorageType type,
                                 bool is_eviction);
 
@@ -383,16 +383,17 @@
   void DidDumpOriginInfoTableForHistogram(
       const OriginInfoTableEntries& entries);
 
-  std::set<GURL> GetEvictionOriginExceptions(
-      const std::set<GURL>& extra_exceptions);
-  void DidGetEvictionOrigin(GetOriginCallback callback, const GURL& origin);
+  std::set<url::Origin> GetEvictionOriginExceptions(
+      const std::set<url::Origin>& extra_exceptions);
+  void DidGetEvictionOrigin(GetOriginCallback callback,
+                            const base::Optional<url::Origin>& origin);
 
   // QuotaEvictionHandler.
   void GetEvictionOrigin(blink::mojom::StorageType type,
-                         const std::set<GURL>& extra_exceptions,
+                         const std::set<url::Origin>& extra_exceptions,
                          int64_t global_quota,
                          GetOriginCallback callback) override;
-  void EvictOriginData(const GURL& origin,
+  void EvictOriginData(const url::Origin& origin,
                        blink::mojom::StorageType type,
                        StatusCallback callback) override;
   void GetEvictionRoundInfo(EvictionRoundInfoCallback callback) override;
@@ -406,7 +407,7 @@
                                  QuotaCallback callback,
                                  const int64_t* new_quota,
                                  bool success);
-  void DidGetLRUOrigin(const GURL* origin,
+  void DidGetLRUOrigin(std::unique_ptr<base::Optional<url::Origin>> origin,
                        bool success);
   void GetQuotaSettings(QuotaSettingsCallback callback);
   void DidGetSettings(base::TimeTicks start_ticks,
@@ -449,7 +450,7 @@
   StorageCapacityCallbackQueue storage_capacity_callbacks_;
 
   GetOriginCallback lru_origin_callback_;
-  std::set<GURL> access_notified_origins_;
+  std::set<url::Origin> access_notified_origins_;
 
   QuotaClientList clients_;
 
@@ -466,9 +467,9 @@
   HostQuotaCallbackMap persistent_host_quota_callbacks_;
 
   // Map from origin to count.
-  std::map<GURL, int> origins_in_use_;
+  std::map<url::Origin, int> origins_in_use_;
   // Map from origin to error count.
-  std::map<GURL, int> origins_in_error_;
+  std::map<url::Origin, int> origins_in_error_;
 
   scoped_refptr<SpecialStoragePolicy> special_storage_policy_;
 
diff --git a/storage/browser/quota/quota_manager_proxy.cc b/storage/browser/quota/quota_manager_proxy.cc
index 1a0f110..0125cff 100644
--- a/storage/browser/quota/quota_manager_proxy.cc
+++ b/storage/browser/quota/quota_manager_proxy.cc
@@ -64,7 +64,7 @@
   }
 
   if (manager_)
-    manager_->NotifyStorageAccessed(client_id, origin.GetURL(), type);
+    manager_->NotifyStorageAccessed(client_id, origin, type);
 }
 
 void QuotaManagerProxy::NotifyStorageModified(QuotaClient::ID client_id,
@@ -79,7 +79,7 @@
   }
 
   if (manager_)
-    manager_->NotifyStorageModified(client_id, origin.GetURL(), type, delta);
+    manager_->NotifyStorageModified(client_id, origin, type, delta);
 }
 
 void QuotaManagerProxy::NotifyOriginInUse(const url::Origin& origin) {
@@ -91,7 +91,7 @@
   }
 
   if (manager_)
-    manager_->NotifyOriginInUse(origin.GetURL());
+    manager_->NotifyOriginInUse(origin);
 }
 
 void QuotaManagerProxy::NotifyOriginNoLongerInUse(const url::Origin& origin) {
@@ -102,7 +102,7 @@
     return;
   }
   if (manager_)
-    manager_->NotifyOriginNoLongerInUse(origin.GetURL());
+    manager_->NotifyOriginNoLongerInUse(origin);
 }
 
 void QuotaManagerProxy::SetUsageCacheEnabled(QuotaClient::ID client_id,
@@ -116,7 +116,7 @@
     return;
   }
   if (manager_)
-    manager_->SetUsageCacheEnabled(client_id, origin.GetURL(), type, enabled);
+    manager_->SetUsageCacheEnabled(client_id, origin, type, enabled);
 }
 
 void QuotaManagerProxy::GetUsageAndQuota(
@@ -141,7 +141,7 @@
   TRACE_EVENT0("io", "QuotaManagerProxy::GetUsageAndQuota");
 
   manager_->GetUsageAndQuota(
-      origin.GetURL(), type,
+      origin, type,
       base::BindOnce(&DidGetUsageAndQuota,
                      base::RetainedRef(original_task_runner),
                      std::move(callback)));
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc
index d5e4f22..b5f4ebfe 100644
--- a/storage/browser/quota/quota_manager_unittest.cc
+++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -72,6 +72,7 @@
   return std::make_tuple(total, available);
 }
 
+// TODO(crbug.com/889590): Replace with common converter.
 url::Origin ToOrigin(const std::string& url) {
   return url::Origin::Create(GURL(url));
 }
@@ -136,8 +137,7 @@
         &QuotaManagerTest::DidGetUsageInfo, weak_factory_.GetWeakPtr()));
   }
 
-  void GetUsageAndQuotaForWebApps(const GURL& origin,
-                                  StorageType type) {
+  void GetUsageAndQuotaForWebApps(const url::Origin& origin, StorageType type) {
     quota_status_ = QuotaStatusCode::kUnknown;
     usage_ = -1;
     quota_ = -1;
@@ -147,7 +147,8 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void GetUsageAndQuotaWithBreakdown(const GURL& origin, StorageType type) {
+  void GetUsageAndQuotaWithBreakdown(const url::Origin& origin,
+                                     StorageType type) {
     quota_status_ = QuotaStatusCode::kUnknown;
     usage_ = -1;
     quota_ = -1;
@@ -158,7 +159,7 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void GetUsageAndQuotaForStorageClient(const GURL& origin,
+  void GetUsageAndQuotaForStorageClient(const url::Origin& origin,
                                         StorageType type) {
     quota_status_ = QuotaStatusCode::kUnknown;
     usage_ = -1;
@@ -223,7 +224,8 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void RunAdditionalUsageAndQuotaTask(const GURL& origin, StorageType type) {
+  void RunAdditionalUsageAndQuotaTask(const url::Origin& origin,
+                                      StorageType type) {
     quota_manager_->GetUsageAndQuota(
         origin, type,
         base::BindOnce(&QuotaManagerTest::DidGetUsageAndQuotaAdditional,
@@ -231,17 +233,16 @@
   }
 
   void DeleteClientOriginData(QuotaClient* client,
-                              const GURL& origin,
+                              const url::Origin& origin,
                               StorageType type) {
     DCHECK(client);
     quota_status_ = QuotaStatusCode::kUnknown;
-    client->DeleteOriginData(url::Origin::Create(origin), type,
+    client->DeleteOriginData(origin, type,
                              base::BindOnce(&QuotaManagerTest::StatusCallback,
                                             weak_factory_.GetWeakPtr()));
   }
 
-  void EvictOriginData(const GURL& origin,
-                       StorageType type) {
+  void EvictOriginData(const url::Origin& origin, StorageType type) {
     quota_status_ = QuotaStatusCode::kUnknown;
     quota_manager_->EvictOriginData(
         origin, type,
@@ -249,7 +250,7 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void DeleteOriginData(const GURL& origin,
+  void DeleteOriginData(const url::Origin& origin,
                         StorageType type,
                         int quota_client_mask) {
     quota_status_ = QuotaStatusCode::kUnknown;
@@ -287,39 +288,39 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void GetCachedOrigins(StorageType type, std::set<GURL>* origins) {
+  void GetCachedOrigins(StorageType type, std::set<url::Origin>* origins) {
     ASSERT_TRUE(origins != nullptr);
     origins->clear();
     quota_manager_->GetCachedOrigins(type, origins);
   }
 
   void NotifyStorageAccessed(QuotaClient* client,
-                             const GURL& origin,
+                             const url::Origin& origin,
                              StorageType type) {
     DCHECK(client);
     quota_manager_->NotifyStorageAccessedInternal(
         client->id(), origin, type, IncrementMockTime());
   }
 
-  void DeleteOriginFromDatabase(const GURL& origin, StorageType type) {
+  void DeleteOriginFromDatabase(const url::Origin& origin, StorageType type) {
     quota_manager_->DeleteOriginFromDatabase(origin, type, false);
   }
 
   void GetEvictionOrigin(StorageType type) {
-    eviction_origin_ = GURL();
+    eviction_origin_.reset();
     // The quota manager's default eviction policy is to use an LRU eviction
     // policy.
     quota_manager_->GetEvictionOrigin(
-        type, std::set<GURL>(), 0,
+        type, std::set<url::Origin>(), 0,
         base::BindOnce(&QuotaManagerTest::DidGetEvictionOrigin,
                        weak_factory_.GetWeakPtr()));
   }
 
-  void NotifyOriginInUse(const GURL& origin) {
+  void NotifyOriginInUse(const url::Origin& origin) {
     quota_manager_->NotifyOriginInUse(origin);
   }
 
-  void NotifyOriginNoLongerInUse(const GURL& origin) {
+  void NotifyOriginNoLongerInUse(const url::Origin& origin) {
     quota_manager_->NotifyOriginNoLongerInUse(origin);
   }
 
@@ -414,11 +415,13 @@
     usage_ = global_usage;
   }
 
-  void DidGetEvictionOrigin(const GURL& origin) {
+  void DidGetEvictionOrigin(const base::Optional<url::Origin>& origin) {
     eviction_origin_ = origin;
+    DCHECK(!origin.has_value() || !origin->GetURL().is_empty());
   }
 
-  void DidGetModifiedOrigins(const std::set<GURL>& origins, StorageType type) {
+  void DidGetModifiedOrigins(const std::set<url::Origin>& origins,
+                             StorageType type) {
     modified_origins_ = origins;
     modified_origins_type_ = type;
   }
@@ -461,8 +464,12 @@
   int64_t quota() const { return quota_; }
   int64_t total_space() const { return total_space_; }
   int64_t available_space() const { return available_space_; }
-  const GURL& eviction_origin() const { return eviction_origin_; }
-  const std::set<GURL>& modified_origins() const { return modified_origins_; }
+  const base::Optional<url::Origin>& eviction_origin() const {
+    return eviction_origin_;
+  }
+  const std::set<url::Origin>& modified_origins() const {
+    return modified_origins_;
+  }
   StorageType modified_origins_type() const { return modified_origins_type_; }
   const QuotaTableEntries& quota_entries() const { return quota_entries_; }
   const OriginInfoTableEntries& origin_info_entries() const {
@@ -496,8 +503,8 @@
   int64_t quota_;
   int64_t total_space_;
   int64_t available_space_;
-  GURL eviction_origin_;
-  std::set<GURL> modified_origins_;
+  base::Optional<url::Origin> eviction_origin_;
+  std::set<url::Origin> modified_origins_;
   StorageType modified_origins_type_;
   QuotaTableEntries quota_entries_;
   OriginInfoTableEntries origin_info_entries_;
@@ -560,20 +567,20 @@
   RegisterClient(
       CreateClient(kData, base::size(kData), QuotaClient::kFileSystem));
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(80, usage());
   EXPECT_EQ(0, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_LE(0, quota());
   int64_t quota_returned_for_foo = quota();
 
-  GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://bar.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -581,12 +588,12 @@
 }
 
 TEST_F(QuotaManagerTest, GetUsage_NoClient) {
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -612,12 +619,12 @@
 
 TEST_F(QuotaManagerTest, GetUsage_EmptyClient) {
   RegisterClient(CreateClient(nullptr, 0, QuotaClient::kFileSystem));
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -658,7 +665,7 @@
   const int kPerHostQuota = 20;
   SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
@@ -667,7 +674,7 @@
   // since there's plenty of diskspace.
   EXPECT_EQ(kPerHostQuota, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://bar.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(5 + 7, usage());
@@ -696,25 +703,25 @@
   const int64_t kPerHostQuota = kPoolSize / 5;
   SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(1 + 128, usage());
   EXPECT_EQ(kPerHostQuota, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://bar.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4, usage());
   EXPECT_EQ(0, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(512, usage());
   EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(8, usage());
@@ -754,7 +761,7 @@
   RegisterClient(client2);
   RegisterClient(client3);
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(80, usage());
@@ -763,7 +770,7 @@
   usage_breakdown_expected[QuotaClient::kAppcache] = 0;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(1 + 4 + 8, usage());
@@ -772,7 +779,7 @@
   usage_breakdown_expected[QuotaClient::kAppcache] = 8;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://bar.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://bar.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -785,13 +792,13 @@
 TEST_F(QuotaManagerTest, GetUsageWithBreakdown_NoClient) {
   base::flat_map<QuotaClient::ID, int64_t> usage_breakdown_expected;
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -818,14 +825,14 @@
   RegisterClient(
       CreateClient(kData, base::size(kData), QuotaClient::kFileSystem));
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
   usage_breakdown_expected[QuotaClient::kFileSystem] = 10 + 20;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://bar.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://bar.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(5 + 7, usage());
@@ -852,7 +859,7 @@
   RegisterClient(
       CreateClient(kData2, base::size(kData2), QuotaClient::kDatabase));
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(1 + 128, usage());
@@ -860,7 +867,7 @@
   usage_breakdown_expected[QuotaClient::kDatabase] = 128;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://bar.com/"), kPerm);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://bar.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4, usage());
@@ -868,7 +875,7 @@
   usage_breakdown_expected[QuotaClient::kDatabase] = 0;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(512, usage());
@@ -876,7 +883,7 @@
   usage_breakdown_expected[QuotaClient::kDatabase] = 512;
   EXPECT_EQ(usage_breakdown_expected, usage_breakdown());
 
-  GetUsageAndQuotaWithBreakdown(GURL("http://unlimited/"), kPerm);
+  GetUsageAndQuotaWithBreakdown(ToOrigin("http://unlimited/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(8, usage());
@@ -894,7 +901,7 @@
       CreateClient(data, base::size(data), QuotaClient::kFileSystem);
   RegisterClient(client);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), type);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
@@ -903,14 +910,14 @@
   client->ModifyOriginAndNotify(ToOrigin("http://foo.com:1/"), type, -5);
   client->AddOriginAndNotify(ToOrigin("https://foo.com/"), type, 1);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), type);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20 + 30 - 5 + 1, usage());
   int foo_usage = usage();
 
   client->AddOriginAndNotify(ToOrigin("http://bar.com/"), type, 40);
-  GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), type);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://bar.com/"), type);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(40, usage());
@@ -939,19 +946,18 @@
   const int kPerHostQuota = 20;
   SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
   EXPECT_EQ(kPerHostQuota, quota());
 
   set_additional_callback_count(0);
-  RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
-                                 kTemp);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
-  RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kTemp);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://bar.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
@@ -973,14 +979,12 @@
   SetQuotaSettings(kPoolSize, kPerHostQuota, kMustRemainAvailableForSystem);
 
   set_additional_callback_count(0);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
-  RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
-                                 kTemp);
-  RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"),
-                                 kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://foo.com/"), kTemp);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://bar.com/"), kTemp);
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
-  DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp, kAllClients);
+  DeleteOriginData(ToOrigin("http://bar.com/"), kTemp, kAllClients);
 
   // Nuke before waiting for callbacks.
   set_quota_manager(nullptr);
@@ -1007,19 +1011,19 @@
   scoped_task_environment_.RunUntilIdle();
   EXPECT_LE(kMustRemainAvailableForSystem, available_space());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage1/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage1/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(1, usage());
   EXPECT_EQ(kPerHostQuota, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage10/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_EQ(kPerHostQuota, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage200/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage200/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(200, usage());
@@ -1046,25 +1050,25 @@
   EXPECT_EQ(10 + 50 + 4000, usage());
   EXPECT_EQ(4000, unlimited_usage());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage10/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_EQ(kPerHostQuotaFor1000, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage50/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(50, usage());
   EXPECT_EQ(kPerHostQuotaFor1000, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4000, usage());
   EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
 
-  GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForStorageClient(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -1074,25 +1078,25 @@
   const int kPerHostQuotaFor100 = 20;
   SetQuotaSettings(100, kPerHostQuotaFor100, kMustRemainAvailableForSystem);
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage10/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_EQ(kPerHostQuotaFor100, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage50/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(50, usage());
   EXPECT_EQ(kPerHostQuotaFor100, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4000, usage());
   EXPECT_EQ(kAvailableSpaceForApp + usage(), quota());
 
-  GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForStorageClient(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -1107,25 +1111,25 @@
   EXPECT_EQ(10 + 50 + 4000, usage());
   EXPECT_EQ(0, unlimited_usage());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage10/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_EQ(kPerHostQuotaFor100, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://usage50/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(50, usage());
   EXPECT_EQ(kPerHostQuotaFor100, quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4000, usage());
   EXPECT_EQ(kPerHostQuotaFor100, quota());
 
-  GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp);
+  GetUsageAndQuotaForStorageClient(ToOrigin("http://unlimited/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(4000, usage());
@@ -1133,8 +1137,8 @@
 }
 
 TEST_F(QuotaManagerTest, OriginInUse) {
-  const GURL kFooOrigin("http://foo.com/");
-  const GURL kBarOrigin("http://bar.com/");
+  const url::Origin kFooOrigin = ToOrigin("http://foo.com/");
+  const url::Origin kBarOrigin = ToOrigin("http://bar.com/");
 
   EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin));
   quota_manager()->NotifyOriginInUse(kFooOrigin);  // count of 1
@@ -1184,14 +1188,14 @@
 TEST_F(QuotaManagerTest, GetAndSetPersistentUsageAndQuota) {
   RegisterClient(CreateClient(nullptr, 0, QuotaClient::kFileSystem));
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
   EXPECT_EQ(0, quota());
 
   SetPersistentHostQuota("foo.com", 100);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -1199,13 +1203,13 @@
 
   // The actual space avaialble is given to 'unlimited' origins as their quota.
   mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(kAvailableSpaceForApp, quota());
 
   // GetUsageAndQuotaForStorageClient should just return 0 usage and
   // kNoLimit quota.
-  GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kPerm);
+  GetUsageAndQuotaForStorageClient(ToOrigin("http://unlimited/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(0, usage());
   EXPECT_EQ(QuotaManager::kNoLimit, quota());
@@ -1222,7 +1226,7 @@
   // For unlimited origins the quota manager should return
   // kAvailableSpaceForApp as syncable quota (because of the pre-condition).
   mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/"));
-  GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kSync);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://unlimited/"), kSync);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(0, usage());
@@ -1244,7 +1248,7 @@
       CreateClient(kData, base::size(kData), QuotaClient::kFileSystem));
 
   SetPersistentHostQuota("foo.com", 100);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20 + 13 + 19, usage());
@@ -1266,19 +1270,18 @@
       CreateClient(kData, base::size(kData), QuotaClient::kFileSystem));
   SetPersistentHostQuota("foo.com", 100);
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
   EXPECT_EQ(100, quota());
 
   set_additional_callback_count(0);
-  RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"),
-                                 kPerm);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
-  RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://bar.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10 + 20, usage());
@@ -1297,9 +1300,9 @@
   SetPersistentHostQuota("foo.com", 100);
 
   set_additional_callback_count(0);
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
-  RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), kPerm);
-  RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://foo.com/"), kPerm);
+  RunAdditionalUsageAndQuotaTask(ToOrigin("http://bar.com/"), kPerm);
 
   // Nuke before waiting for callbacks.
   set_quota_manager(nullptr);
@@ -1412,8 +1415,7 @@
   scoped_task_environment_.RunUntilIdle();
   int64_t predelete_host_pers = usage();
 
-  DeleteClientOriginData(client, GURL("http://foo.com/"),
-                         kTemp);
+  DeleteClientOriginData(client, ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
 
@@ -1471,14 +1473,16 @@
   int64_t predelete_host_pers = usage();
 
   for (size_t i = 0; i < base::size(kData1); ++i)
-    quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
-        GURL(kData1[i].origin), kData1[i].type);
+    quota_manager()->NotifyStorageAccessed(
+        QuotaClient::kUnknown, url::Origin::Create(GURL(kData1[i].origin)),
+        kData1[i].type);
   for (size_t i = 0; i < base::size(kData2); ++i)
-    quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
-        GURL(kData2[i].origin), kData2[i].type);
+    quota_manager()->NotifyStorageAccessed(
+        QuotaClient::kUnknown, url::Origin::Create(GURL(kData2[i].origin)),
+        kData2[i].type);
   scoped_task_environment_.RunUntilIdle();
 
-  EvictOriginData(GURL("http://foo.com/"), kTemp);
+  EvictOriginData(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
 
   DumpOriginInfoTable();
@@ -1486,7 +1490,7 @@
 
   for (const auto& entry : origin_info_entries()) {
     if (entry.type == kTemp)
-      EXPECT_NE(std::string("http://foo.com/"), entry.origin.spec());
+      EXPECT_NE(std::string("http://foo.com/"), entry.origin.GetURL().spec());
   }
 
   GetGlobalUsage(kTemp);
@@ -1503,7 +1507,7 @@
 }
 
 TEST_F(QuotaManagerTest, EvictOriginDataHistogram) {
-  const GURL kOrigin = GURL("http://foo.com/");
+  const url::Origin kOrigin = ToOrigin("http://foo.com/");
   static const MockOriginData kData[] = {
       {"http://foo.com/", kTemp, 1},
   };
@@ -1531,11 +1535,10 @@
   histograms.ExpectTotalCount(
       QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram, 0);
 
-  client->AddOriginAndNotify(url::Origin::Create(kOrigin), kTemp, 100);
+  client->AddOriginAndNotify(kOrigin, kTemp, 100);
 
   // Change the used count of the origin.
-  quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, GURL(kOrigin),
-                                         kTemp);
+  quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, kOrigin, kTemp);
   scoped_task_environment_.RunUntilIdle();
 
   GetGlobalUsage(kTemp);
@@ -1556,7 +1559,7 @@
   histograms.ExpectTotalCount(
       QuotaManager::kDaysBetweenRepeatedOriginEvictionsHistogram, 1);
 
-  client->AddOriginAndNotify(url::Origin::Create(kOrigin), kTemp, 100);
+  client->AddOriginAndNotify(kOrigin, kTemp, 100);
 
   GetGlobalUsage(kTemp);
   scoped_task_environment_.RunUntilIdle();
@@ -1593,7 +1596,8 @@
   int64_t predelete_host_pers = usage();
 
   for (size_t i = 0; i < base::size(kData); ++i)
-    NotifyStorageAccessed(client, GURL(kData[i].origin), kData[i].type);
+    NotifyStorageAccessed(client, url::Origin::Create(GURL(kData[i].origin)),
+                          kData[i].type);
   scoped_task_environment_.RunUntilIdle();
 
   client->AddOriginToErrorSet(ToOrigin("http://foo.com/"), kTemp);
@@ -1601,7 +1605,7 @@
   for (int i = 0;
        i < QuotaManager::kThresholdOfErrorsToBeBlacklisted + 1;
        ++i) {
-    EvictOriginData(GURL("http://foo.com/"), kTemp);
+    EvictOriginData(ToOrigin("http://foo.com/"), kTemp);
     scoped_task_environment_.RunUntilIdle();
     EXPECT_EQ(QuotaStatusCode::kErrorInvalidModification, status());
   }
@@ -1611,7 +1615,7 @@
 
   bool found_origin_in_database = false;
   for (const auto& entry : origin_info_entries()) {
-    if (entry.type == kTemp && entry.origin == "http://foo.com/") {
+    if (entry.type == kTemp && entry.origin == ToOrigin("http://foo.com/")) {
       found_origin_in_database = true;
       break;
     }
@@ -1622,17 +1626,18 @@
   for (size_t i = 0; i < kNumberOfTemporaryOrigins - 1; ++i) {
     GetEvictionOrigin(kTemp);
     scoped_task_environment_.RunUntilIdle();
-    EXPECT_FALSE(eviction_origin().is_empty());
+    EXPECT_TRUE(eviction_origin().has_value());
     // The origin "http://foo.com/" should not be in the LRU list.
-    EXPECT_NE(std::string("http://foo.com/"), eviction_origin().spec());
-    DeleteOriginFromDatabase(eviction_origin(), kTemp);
+    EXPECT_NE(std::string("http://foo.com/"),
+              eviction_origin()->GetURL().spec());
+    DeleteOriginFromDatabase(*eviction_origin(), kTemp);
     scoped_task_environment_.RunUntilIdle();
   }
 
   // Now the LRU list must be empty.
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(eviction_origin().is_empty());
+  EXPECT_FALSE(eviction_origin().has_value());
 
   // Deleting origins from the database should not affect the results of the
   // following checks.
@@ -1783,10 +1788,10 @@
     if (entry.type != kTemp)
       continue;
 
-    EXPECT_NE(std::string("http://foo.com/"), entry.origin.spec());
-    EXPECT_NE(std::string("http://foo.com:1/"), entry.origin.spec());
-    EXPECT_NE(std::string("https://foo.com/"), entry.origin.spec());
-    EXPECT_NE(std::string("http://bar.com/"), entry.origin.spec());
+    EXPECT_NE(std::string("http://foo.com/"), entry.origin.GetURL().spec());
+    EXPECT_NE(std::string("http://foo.com:1/"), entry.origin.GetURL().spec());
+    EXPECT_NE(std::string("https://foo.com/"), entry.origin.GetURL().spec());
+    EXPECT_NE(std::string("http://bar.com/"), entry.origin.GetURL().spec());
   }
 
   GetGlobalUsage(kTemp);
@@ -1854,18 +1859,22 @@
   scoped_task_environment_.RunUntilIdle();
   const int64_t predelete_bar_pers = usage();
 
-  for (size_t i = 0; i < base::size(kData1); ++i)
-    quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
-        GURL(kData1[i].origin), kData1[i].type);
-  for (size_t i = 0; i < base::size(kData2); ++i)
-    quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown,
-        GURL(kData2[i].origin), kData2[i].type);
+  for (size_t i = 0; i < base::size(kData1); ++i) {
+    quota_manager()->NotifyStorageAccessed(
+        QuotaClient::kUnknown, url::Origin::Create(GURL(kData1[i].origin)),
+        kData1[i].type);
+  }
+  for (size_t i = 0; i < base::size(kData2); ++i) {
+    quota_manager()->NotifyStorageAccessed(
+        QuotaClient::kUnknown, url::Origin::Create(GURL(kData2[i].origin)),
+        kData2[i].type);
+  }
   scoped_task_environment_.RunUntilIdle();
 
   reset_status_callback_count();
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
-  DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients);
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp, kAllClients);
+  DeleteOriginData(ToOrigin("http://bar.com/"), kTemp, kAllClients);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp, kAllClients);
   scoped_task_environment_.RunUntilIdle();
 
   EXPECT_EQ(3, status_callback_count());
@@ -1877,8 +1886,8 @@
     if (entry.type != kTemp)
       continue;
 
-    EXPECT_NE(std::string("http://foo.com/"), entry.origin.spec());
-    EXPECT_NE(std::string("http://bar.com/"), entry.origin.spec());
+    EXPECT_NE(std::string("http://foo.com/"), entry.origin.GetURL().spec());
+    EXPECT_NE(std::string("http://bar.com/"), entry.origin.GetURL().spec());
   }
 
   GetGlobalUsage(kTemp);
@@ -1915,7 +1924,7 @@
 
   // TODO(kinuko): Be careful when we add cache pruner.
 
-  std::set<GURL> origins;
+  std::set<url::Origin> origins;
   GetCachedOrigins(kTemp, &origins);
   EXPECT_TRUE(origins.empty());
 
@@ -1944,7 +1953,7 @@
 
   for (size_t i = 0; i < base::size(kData); ++i) {
     if (kData[i].type == kTemp)
-      EXPECT_TRUE(base::ContainsKey(origins, GURL(kData[i].origin)));
+      EXPECT_TRUE(base::ContainsKey(origins, ToOrigin(kData[i].origin)));
   }
 }
 
@@ -1963,29 +1972,29 @@
   GURL origin;
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(eviction_origin().is_empty());
+  EXPECT_FALSE(eviction_origin().has_value());
 
-  NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://a.com/"), kTemp);
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("http://a.com/", eviction_origin().spec());
+  EXPECT_EQ("http://a.com/", eviction_origin()->GetURL().spec());
 
-  NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
-  NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
-  NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://b.com/"), kPerm);
+  NotifyStorageAccessed(client, ToOrigin("https://a.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://c.com/"), kTemp);
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("http://a.com/", eviction_origin().spec());
+  EXPECT_EQ("http://a.com/", eviction_origin()->GetURL().spec());
 
-  DeleteOriginFromDatabase(eviction_origin(), kTemp);
+  DeleteOriginFromDatabase(*eviction_origin(), kTemp);
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("https://a.com/", eviction_origin().spec());
+  EXPECT_EQ("https://a.com/", eviction_origin()->GetURL().spec());
 
-  DeleteOriginFromDatabase(eviction_origin(), kTemp);
+  DeleteOriginFromDatabase(*eviction_origin(), kTemp);
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("http://c.com/", eviction_origin().spec());
+  EXPECT_EQ("http://c.com/", eviction_origin()->GetURL().spec());
 }
 
 TEST_F(QuotaManagerTest, GetLRUOriginWithOriginInUse) {
@@ -2003,44 +2012,44 @@
   GURL origin;
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(eviction_origin().is_empty());
+  EXPECT_FALSE(eviction_origin().has_value());
 
-  NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp);
-  NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm);
-  NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp);
-  NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://a.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://b.com/"), kPerm);
+  NotifyStorageAccessed(client, ToOrigin("https://a.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://c.com/"), kTemp);
 
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("http://a.com/", eviction_origin().spec());
+  EXPECT_EQ(ToOrigin("http://a.com/"), *eviction_origin());
 
   // Notify origin http://a.com is in use.
-  NotifyOriginInUse(GURL("http://a.com/"));
+  NotifyOriginInUse(ToOrigin("http://a.com/"));
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("https://a.com/", eviction_origin().spec());
+  EXPECT_EQ(ToOrigin("https://a.com/"), *eviction_origin());
 
   // Notify origin https://a.com is in use while GetEvictionOrigin is running.
   GetEvictionOrigin(kTemp);
-  NotifyOriginInUse(GURL("https://a.com/"));
+  NotifyOriginInUse(ToOrigin("https://a.com/"));
   scoped_task_environment_.RunUntilIdle();
   // Post-filtering must have excluded the returned origin, so we will
   // see empty result here.
-  EXPECT_TRUE(eviction_origin().is_empty());
+  EXPECT_FALSE(eviction_origin().has_value());
 
   // Notify access for http://c.com while GetEvictionOrigin is running.
   GetEvictionOrigin(kTemp);
-  NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp);
+  NotifyStorageAccessed(client, ToOrigin("http://c.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   // Post-filtering must have excluded the returned origin, so we will
   // see empty result here.
-  EXPECT_TRUE(eviction_origin().is_empty());
+  EXPECT_FALSE(eviction_origin().has_value());
 
-  NotifyOriginNoLongerInUse(GURL("http://a.com/"));
-  NotifyOriginNoLongerInUse(GURL("https://a.com/"));
+  NotifyOriginNoLongerInUse(ToOrigin("http://a.com/"));
+  NotifyOriginNoLongerInUse(ToOrigin("https://a.com/"));
   GetEvictionOrigin(kTemp);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ("http://a.com/", eviction_origin().spec());
+  EXPECT_EQ(ToOrigin("http://a.com/"), *eviction_origin());
 }
 
 TEST_F(QuotaManagerTest, GetOriginsModifiedSince) {
@@ -2075,7 +2084,7 @@
   EXPECT_EQ(modified_origins_type(), kTemp);
   for (size_t i = 0; i < base::size(kData); ++i) {
     if (kData[i].type == kTemp)
-      EXPECT_EQ(1U, modified_origins().count(GURL(kData[i].origin)));
+      EXPECT_EQ(1U, modified_origins().count(ToOrigin(kData[i].origin)));
   }
 
   GetOriginsModifiedSince(kTemp, time2);
@@ -2092,7 +2101,7 @@
   GetOriginsModifiedSince(kTemp, time3);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(1U, modified_origins().size());
-  EXPECT_EQ(1U, modified_origins().count(GURL("http://a.com/")));
+  EXPECT_EQ(1U, modified_origins().count(ToOrigin("http://a.com/")));
   EXPECT_EQ(modified_origins_type(), kTemp);
 }
 
@@ -2124,17 +2133,11 @@
   using std::make_pair;
 
   quota_manager()->NotifyStorageAccessed(
-      QuotaClient::kUnknown,
-      GURL("http://example.com/"),
-      kTemp);
+      QuotaClient::kUnknown, ToOrigin("http://example.com/"), kTemp);
   quota_manager()->NotifyStorageAccessed(
-      QuotaClient::kUnknown,
-      GURL("http://example.com/"),
-      kPerm);
+      QuotaClient::kUnknown, ToOrigin("http://example.com/"), kPerm);
   quota_manager()->NotifyStorageAccessed(
-      QuotaClient::kUnknown,
-      GURL("http://example.com/"),
-      kPerm);
+      QuotaClient::kUnknown, ToOrigin("http://example.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
 
   DumpOriginInfoTable();
@@ -2153,9 +2156,9 @@
                  << "host = " << origin_info.origin << ", "
                  << "type = " << static_cast<int>(origin_info.type) << ", "
                  << "used_count = " << origin_info.used_count);
-    EXPECT_EQ(1u, entries.erase(
-                      make_pair(make_pair(origin_info.origin, origin_info.type),
-                                origin_info.used_count)));
+    EXPECT_EQ(1u, entries.erase(make_pair(
+                      make_pair(origin_info.origin.GetURL(), origin_info.type),
+                      origin_info.used_count)));
   }
   EXPECT_TRUE(entries.empty());
 }
@@ -2201,26 +2204,27 @@
   scoped_task_environment_.RunUntilIdle();
   const int64_t predelete_foo_tmp = usage();
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kFileSystem);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp,
+                   QuotaClient::kFileSystem);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(predelete_foo_tmp - 1, usage());
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kAppcache);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp, QuotaClient::kAppcache);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kDatabase);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp, QuotaClient::kDatabase);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp,
-      QuotaClient::kIndexedDatabase);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp,
+                   QuotaClient::kIndexedDatabase);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
@@ -2312,15 +2316,15 @@
   scoped_task_environment_.RunUntilIdle();
   const int64_t predelete_foo_tmp = usage();
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp,
-      QuotaClient::kFileSystem | QuotaClient::kDatabase);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp,
+                   QuotaClient::kFileSystem | QuotaClient::kDatabase);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage());
 
-  DeleteOriginData(GURL("http://foo.com/"), kTemp,
-      QuotaClient::kAppcache | QuotaClient::kIndexedDatabase);
+  DeleteOriginData(ToOrigin("http://foo.com/"), kTemp,
+                   QuotaClient::kAppcache | QuotaClient::kIndexedDatabase);
   scoped_task_environment_.RunUntilIdle();
   GetHostUsage("foo.com", kTemp);
   scoped_task_environment_.RunUntilIdle();
@@ -2387,7 +2391,7 @@
   GetGlobalUsage(kPerm);
   scoped_task_environment_.RunUntilIdle();
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(80, usage());
@@ -2402,20 +2406,20 @@
   EXPECT_EQ(kPoolSize, total_space());
   EXPECT_EQ(kPoolSize - 80 - 10, available_space());
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
   EXPECT_LE(kPerHostQuota, quota());
 
   mock_special_storage_policy()->AddUnlimited(GURL("http://foo.com/"));
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kPerm);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(80, usage());
   EXPECT_EQ(available_space() + usage(), quota());
 
-  GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp);
+  GetUsageAndQuotaForWebApps(ToOrigin("http://foo.com/"), kTemp);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(QuotaStatusCode::kOk, status());
   EXPECT_EQ(10, usage());
@@ -2423,8 +2427,8 @@
 }
 
 TEST_F(QuotaManagerTest, GetUsageAndQuota_SessionOnly) {
-  const GURL kEpheremalOrigin("http://ephemeral/");
-  mock_special_storage_policy()->AddSessionOnly(kEpheremalOrigin);
+  const url::Origin kEpheremalOrigin = ToOrigin("http://ephemeral/");
+  mock_special_storage_policy()->AddSessionOnly(kEpheremalOrigin.GetURL());
 
   GetUsageAndQuotaForWebApps(kEpheremalOrigin, kTemp);
   scoped_task_environment_.RunUntilIdle();
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index e933928..fe405c9 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -224,19 +224,21 @@
   OnEvictionRoundFinished();
 }
 
-void QuotaTemporaryStorageEvictor::OnGotEvictionOrigin(const GURL& origin) {
+void QuotaTemporaryStorageEvictor::OnGotEvictionOrigin(
+    const base::Optional<url::Origin>& origin) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (origin.is_empty()) {
+  if (!origin.has_value()) {
     StartEvictionTimerWithDelay(interval_ms_);
     OnEvictionRoundFinished();
     return;
   }
 
-  in_progress_eviction_origins_.insert(origin);
+  DCHECK(!origin->GetURL().is_empty());
+  in_progress_eviction_origins_.insert(*origin);
 
   quota_eviction_handler_->EvictOriginData(
-      origin, blink::mojom::StorageType::kTemporary,
+      *origin, blink::mojom::StorageType::kTemporary,
       base::BindOnce(&QuotaTemporaryStorageEvictor::OnEvictionComplete,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index 1994de8..9761c02b 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -13,17 +13,20 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/timer/timer.h"
 #include "storage/browser/storage_browser_export.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
-class GURL;
-
 namespace content {
 class QuotaTemporaryStorageEvictorTest;
 }
 
+namespace url {
+class Origin;
+}
+
 namespace storage {
 
 class QuotaEvictionHandler;
@@ -89,7 +92,7 @@
                               int64_t total_space,
                               int64_t current_usage,
                               bool current_usage_is_complete);
-  void OnGotEvictionOrigin(const GURL& origin);
+  void OnGotEvictionOrigin(const base::Optional<url::Origin>& origin);
   void OnEvictionComplete(blink::mojom::QuotaStatusCode status);
 
   void OnEvictionRoundStarted();
@@ -103,7 +106,7 @@
   EvictionRoundStatistics round_statistics_;
   base::Time time_of_end_of_last_nonskipped_round_;
   base::Time time_of_end_of_last_round_;
-  std::set<GURL> in_progress_eviction_origins_;
+  std::set<url::Origin> in_progress_eviction_origins_;
 
   int64_t interval_ms_;
   bool timer_disabled_for_testing_;
diff --git a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
index b4acd69..44801b4 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
@@ -30,6 +30,11 @@
 
 namespace {
 
+// TODO(crbug.com/889590): Replace with common converter.
+url::Origin ToOrigin(const std::string& url) {
+  return url::Origin::Create(GURL(url));
+}
+
 class MockQuotaEvictionHandler : public storage::QuotaEvictionHandler {
  public:
   explicit MockQuotaEvictionHandler(QuotaTemporaryStorageEvictorTest* test)
@@ -37,7 +42,7 @@
         error_on_evict_origin_data_(false),
         error_on_get_usage_and_quota_(false) {}
 
-  void EvictOriginData(const GURL& origin,
+  void EvictOriginData(const url::Origin& origin,
                        StorageType type,
                        storage::StatusCallback callback) override {
     if (error_on_evict_origin_data_) {
@@ -65,13 +70,13 @@
   }
 
   void GetEvictionOrigin(StorageType type,
-                         const std::set<GURL>& exceptions,
+                         const std::set<url::Origin>& exceptions,
                          int64_t global_quota,
                          storage::GetOriginCallback callback) override {
     if (origin_order_.empty())
-      std::move(callback).Run(GURL());
+      std::move(callback).Run(base::nullopt);
     else
-      std::move(callback).Run(GURL(origin_order_.front()));
+      std::move(callback).Run(origin_order_.front());
   }
 
   int64_t GetUsage() const {
@@ -104,7 +109,7 @@
 
   // Simulates an access to |origin|.  It reorders the internal LRU list.
   // It internally uses AddOrigin().
-  void AccessOrigin(const GURL& origin) {
+  void AccessOrigin(const url::Origin& origin) {
     const auto& found = origins_.find(origin);
     EXPECT_TRUE(origins_.end() != found);
     AddOrigin(origin, found->second);
@@ -113,14 +118,14 @@
   // Simulates adding or overwriting the |origin| to the internal origin set
   // with the |usage|.  It also adds or moves the |origin| to the end of the
   // LRU list.
-  void AddOrigin(const GURL& origin, int64_t usage) {
+  void AddOrigin(const url::Origin& origin, int64_t usage) {
     EnsureOriginRemoved(origin);
     origin_order_.push_back(origin);
     origins_[origin] = usage;
   }
 
  private:
-  int64_t EnsureOriginRemoved(const GURL& origin) {
+  int64_t EnsureOriginRemoved(const url::Origin& origin) {
     int64_t origin_usage;
     if (!base::ContainsKey(origins_, origin))
       return -1;
@@ -134,8 +139,8 @@
 
   storage::QuotaSettings settings_;
   int64_t available_space_;
-  std::list<GURL> origin_order_;
-  std::map<GURL, int64_t> origins_;
+  std::list<url::Origin> origin_order_;
+  std::map<url::Origin, int64_t> origins_;
   bool error_on_evict_origin_data_;
   bool error_on_get_usage_and_quota_;
 
@@ -165,8 +170,8 @@
   }
 
   void TaskForRepeatedEvictionTest(
-      const std::pair<GURL, int64_t>& origin_to_be_added,
-      const GURL& origin_to_be_accessed,
+      const std::pair<base::Optional<url::Origin>, int64_t>& origin_to_be_added,
+      const base::Optional<url::Origin>& origin_to_be_accessed,
       int expected_usage_after_first,
       int expected_usage_after_second) {
     EXPECT_GE(4, num_get_usage_and_quota_for_eviction_);
@@ -174,11 +179,11 @@
       case 2:
         EXPECT_EQ(expected_usage_after_first,
                   quota_eviction_handler()->GetUsage());
-        if (!origin_to_be_added.first.is_empty())
-          quota_eviction_handler()->AddOrigin(origin_to_be_added.first,
+        if (origin_to_be_added.first.has_value())
+          quota_eviction_handler()->AddOrigin(*origin_to_be_added.first,
                                               origin_to_be_added.second);
-        if (!origin_to_be_accessed.is_empty())
-          quota_eviction_handler()->AccessOrigin(origin_to_be_accessed);
+        if (origin_to_be_accessed.has_value())
+          quota_eviction_handler()->AccessOrigin(*origin_to_be_accessed);
         break;
       case 3:
         EXPECT_EQ(expected_usage_after_second,
@@ -220,9 +225,9 @@
 };
 
 TEST_F(QuotaTemporaryStorageEvictorTest, SimpleEvictionTest) {
-  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 3000);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 200);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 500);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.z.com"), 3000);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.y.com"), 200);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.x.com"), 500);
   quota_eviction_handler()->SetPoolSize(4000);
   quota_eviction_handler()->set_available_space(1000000000);
   EXPECT_EQ(3000 + 200 + 500, quota_eviction_handler()->GetUsage());
@@ -239,10 +244,10 @@
 }
 
 TEST_F(QuotaTemporaryStorageEvictorTest, MultipleEvictionTest) {
-  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 20);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 2900);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 400);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.z.com"), 20);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.y.com"), 2900);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.x.com"), 450);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.w.com"), 400);
   quota_eviction_handler()->SetPoolSize(4000);
   quota_eviction_handler()->set_available_space(1000000000);
   EXPECT_EQ(20 + 2900 + 450 + 400, quota_eviction_handler()->GetUsage());
@@ -266,17 +271,17 @@
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
   const int64_t e_size = 275;
 
-  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.d.com"), d_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.c.com"), c_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.b.com"), b_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.a.com"), a_size);
   quota_eviction_handler()->SetPoolSize(1000);
   quota_eviction_handler()->set_available_space(1000000000);
   quota_eviction_handler()->set_task_for_get_usage_and_quota(
       base::BindRepeating(
           &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
           weak_factory_.GetWeakPtr(),
-          std::make_pair(GURL("http://www.e.com"), e_size), GURL(),
+          std::make_pair(ToOrigin("http://www.e.com"), e_size), base::nullopt,
           initial_total_size - d_size,
           initial_total_size - d_size + e_size - c_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
@@ -300,17 +305,18 @@
   const int64_t d_size = 292;
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
 
-  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.d.com"), d_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.c.com"), c_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.b.com"), b_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.a.com"), a_size);
   quota_eviction_handler()->SetPoolSize(1000);
   quota_eviction_handler()->set_available_space(1000000000);
   quota_eviction_handler()->set_task_for_get_usage_and_quota(
       base::BindRepeating(
           &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
-          weak_factory_.GetWeakPtr(), std::make_pair(GURL(), 0), GURL(),
-          initial_total_size - d_size, initial_total_size - d_size));
+          weak_factory_.GetWeakPtr(), std::make_pair(base::nullopt, 0),
+          base::nullopt, initial_total_size - d_size,
+          initial_total_size - d_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
   // disable_timer_for_testing();
   temporary_storage_evictor()->Start();
@@ -333,18 +339,18 @@
   const int64_t initial_total_size = a_size + b_size + c_size + d_size;
   const int64_t e_size = 275;
 
-  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.d.com"), d_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.c.com"), c_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.b.com"), b_size);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.a.com"), a_size);
   quota_eviction_handler()->SetPoolSize(1000);
   quota_eviction_handler()->set_available_space(1000000000);
   quota_eviction_handler()->set_task_for_get_usage_and_quota(
       base::BindRepeating(
           &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
           weak_factory_.GetWeakPtr(),
-          std::make_pair(GURL("http://www.e.com"), e_size),
-          GURL("http://www.c.com"), initial_total_size - d_size,
+          std::make_pair(ToOrigin("http://www.e.com"), e_size),
+          ToOrigin("http://www.c.com"), initial_total_size - d_size,
           initial_total_size - d_size + e_size - b_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
   temporary_storage_evictor()->Start();
@@ -363,8 +369,8 @@
 TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceNonEvictionTest) {
   // If we're using so little that evicting all of it wouldn't
   // do enough to alleviate a diskspace shortage, we don't evict.
-  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 10);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 20);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.z.com"), 10);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.x.com"), 20);
   quota_eviction_handler()->SetPoolSize(10000);
   quota_eviction_handler()->set_available_space(
       quota_eviction_handler()->settings().should_remain_available - 350);
@@ -382,10 +388,10 @@
 }
 
 TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) {
-  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 294);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 120);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 150);
-  quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 300);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.z.com"), 294);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.y.com"), 120);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.x.com"), 150);
+  quota_eviction_handler()->AddOrigin(ToOrigin("http://www.w.com"), 300);
   quota_eviction_handler()->SetPoolSize(10000);
   quota_eviction_handler()->set_available_space(
       quota_eviction_handler()->settings().should_remain_available - 350);
diff --git a/storage/browser/quota/storage_monitor.cc b/storage/browser/quota/storage_monitor.cc
index 3a16d1b..7b83851 100644
--- a/storage/browser/quota/storage_monitor.cc
+++ b/storage/browser/quota/storage_monitor.cc
@@ -246,7 +246,7 @@
 
 void StorageTypeObservers::AddObserver(
     StorageObserver* observer, const StorageObserver::MonitorParams& params) {
-  std::string host = net::GetHostOrSpecFromURL(params.filter.origin);
+  std::string host = net::GetHostOrSpecFromURL(params.filter.origin.GetURL());
   if (host.empty())
     return;
 
@@ -284,7 +284,7 @@
 void StorageTypeObservers::NotifyUsageChange(
     const StorageObserver::Filter& filter,
     int64_t delta) {
-  std::string host = net::GetHostOrSpecFromURL(filter.origin);
+  std::string host = net::GetHostOrSpecFromURL(filter.origin.GetURL());
   auto it = host_observers_map_.find(host);
   if (it == host_observers_map_.end())
     return;
@@ -308,8 +308,7 @@
   // Check preconditions.
   if (params.filter.storage_type == blink::mojom::StorageType::kUnknown ||
       params.filter.storage_type ==
-          blink::mojom::StorageType::kQuotaNotManaged ||
-      params.filter.origin.is_empty()) {
+          blink::mojom::StorageType::kQuotaNotManaged) {
     NOTREACHED();
     return;
   }
@@ -340,8 +339,7 @@
                                        int64_t delta) {
   // Check preconditions.
   if (filter.storage_type == blink::mojom::StorageType::kUnknown ||
-      filter.storage_type == blink::mojom::StorageType::kQuotaNotManaged ||
-      filter.origin.is_empty()) {
+      filter.storage_type == blink::mojom::StorageType::kQuotaNotManaged) {
     NOTREACHED();
     return;
   }
diff --git a/storage/browser/quota/storage_monitor.h b/storage/browser/quota/storage_monitor.h
index c17b249..fbf1c0d 100644
--- a/storage/browser/quota/storage_monitor.h
+++ b/storage/browser/quota/storage_monitor.h
@@ -16,6 +16,7 @@
 #include "base/timer/timer.h"
 #include "storage/browser/quota/storage_observer.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
+#include "url/origin.h"
 
 namespace content {
 class StorageMonitorTestBase;
@@ -53,7 +54,7 @@
 
  private:
   struct STORAGE_EXPORT ObserverState {
-    GURL origin;
+    url::Origin origin;
     base::TimeTicks last_notification_time;
     base::TimeDelta rate;
     bool requires_update;
diff --git a/storage/browser/quota/storage_monitor_unittest.cc b/storage/browser/quota/storage_monitor_unittest.cc
index e633d22..ce306e5 100644
--- a/storage/browser/quota/storage_monitor_unittest.cc
+++ b/storage/browser/quota/storage_monitor_unittest.cc
@@ -37,8 +37,11 @@
 
 namespace {
 
-const char kDefaultOrigin[] = "http://www.foo.com/";
-const char kAlternativeOrigin[] = "http://www.bar.com/";
+// TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+const url::Origin kDefaultOrigin =
+    url::Origin::Create(GURL("http://www.foo.com/"));
+const url::Origin kAlternativeOrigin =
+    url::Origin::Create(GURL("http://www.bar.com/"));
 
 class MockObserver : public StorageObserver {
  public:
@@ -86,7 +89,7 @@
         .Run(callback_status_, callback_usage_, callback_quota_);
   }
 
-  void GetUsageAndQuotaForWebApps(const GURL& origin,
+  void GetUsageAndQuotaForWebApps(const url::Origin& origin,
                                   StorageType type,
                                   UsageAndQuotaCallback callback) override {
     if (initialized_)
@@ -186,7 +189,7 @@
 // Test dispatching events to one observer.
 TEST_F(StorageObserverListTest, DispatchEventToSingleObserver) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer;
   StorageObserverList observer_list;
@@ -237,8 +240,7 @@
   MockObserver mock_observer1;
   MockObserver mock_observer2;
   StorageObserverList observer_list;
-  StorageObserver::Filter filter(StorageType::kPersistent,
-                                 GURL(kDefaultOrigin));
+  StorageObserver::Filter filter(StorageType::kPersistent, kDefaultOrigin);
   observer_list.AddObserver(
       &mock_observer1,
       StorageObserver::MonitorParams(
@@ -290,7 +292,7 @@
 // observer on registration.
 TEST_F(StorageObserverListTest, ReplaceEventOrigin) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer;
   StorageObserverList observer_list;
@@ -298,7 +300,8 @@
 
   StorageObserver::Event dispatched_event;
   dispatched_event.filter = params.filter;
-  dispatched_event.filter.origin = GURL("https://www.foo.com/bar");
+  dispatched_event.filter.origin =
+      url::Origin::Create(GURL("https://www.foo.com/bar"));
   observer_list.OnStorageChange(dispatched_event);
 
   EXPECT_EQ(params.filter.origin, mock_observer.LastEvent().filter.origin);
@@ -311,7 +314,7 @@
 // Verify that HostStorageObservers is initialized after the first usage change.
 TEST_F(HostStorageObserversTest, InitializeOnUsageChange) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   const int64_t kUsage = 324554;
   const int64_t kQuota = 234354354;
@@ -349,7 +352,7 @@
   // |host_observers| should not be initialized after the first observer is
   // added because it did not elect to receive the initial state.
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer1;
   host_observers.AddObserver(&mock_observer1, params);
@@ -395,7 +398,7 @@
 // Verify that negative usage and quota is changed to zero.
 TEST_F(HostStorageObserversTest, NegativeUsageAndQuota) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   const int64_t kUsage = -324554;
   const int64_t kQuota = -234354354;
@@ -413,7 +416,7 @@
 // Verify that HostStorageObservers can recover from a bad initialization.
 TEST_F(HostStorageObserversTest, RecoverFromBadUsageInit) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer;
   HostStorageObservers host_observers(quota_manager_.get());
@@ -446,7 +449,7 @@
 // and quota correctly.
 TEST_F(HostStorageObserversTest, AsyncInitialization) {
   StorageObserver::MonitorParams params(StorageType::kPersistent,
-                                        GURL(kDefaultOrigin),
+                                        kDefaultOrigin,
                                         base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer;
   HostStorageObservers host_observers(quota_manager_.get());
@@ -491,13 +494,13 @@
   StorageTypeObservers type_observers(quota_manager_.get());
 
   StorageObserver::MonitorParams params1(StorageType::kPersistent,
-                                         GURL(kDefaultOrigin),
+                                         kDefaultOrigin,
                                          base::TimeDelta::FromHours(1), false);
   StorageObserver::MonitorParams params2(StorageType::kPersistent,
-                                         GURL(kAlternativeOrigin),
+                                         kAlternativeOrigin,
                                          base::TimeDelta::FromHours(1), false);
-  std::string host1 = net::GetHostOrSpecFromURL(params1.filter.origin);
-  std::string host2 = net::GetHostOrSpecFromURL(params2.filter.origin);
+  std::string host1 = net::GetHostOrSpecFromURL(params1.filter.origin.GetURL());
+  std::string host2 = net::GetHostOrSpecFromURL(params2.filter.origin.GetURL());
 
   MockObserver mock_observer1;
   MockObserver mock_observer2;
@@ -537,11 +540,11 @@
   StorageMonitorTest()
       : storage_monitor_(nullptr),
         params1_(StorageType::kTemporary,
-                 GURL(kDefaultOrigin),
+                 kDefaultOrigin,
                  base::TimeDelta::FromHours(1),
                  false),
         params2_(StorageType::kPersistent,
-                 GURL(kDefaultOrigin),
+                 kDefaultOrigin,
                  base::TimeDelta::FromHours(1),
                  false) {}
 
@@ -550,7 +553,7 @@
     StorageTestWithManagerBase::SetUp();
 
     storage_monitor_ = quota_manager_->storage_monitor_.get();
-    host_ = net::GetHostOrSpecFromURL(params1_.filter.origin);
+    host_ = net::GetHostOrSpecFromURL(params1_.filter.origin.GetURL());
 
     storage_monitor_->AddObserver(&mock_observer1_, params1_);
     storage_monitor_->AddObserver(&mock_observer2_, params1_);
@@ -658,16 +661,13 @@
   const int64_t kTestUsage = 234743;
 
   // Register the observer.
-  StorageObserver::MonitorParams params(kTestStorageType,
-                                        GURL(kDefaultOrigin),
-                                        base::TimeDelta::FromHours(1),
-                                        false);
+  StorageObserver::MonitorParams params(kTestStorageType, kDefaultOrigin,
+                                        base::TimeDelta::FromHours(1), false);
   MockObserver mock_observer;
   quota_manager_->AddStorageObserver(&mock_observer, params);
 
   // Fire a usage change.
-  client_->AddOriginAndNotify(url::Origin::Create(GURL(kDefaultOrigin)),
-                              kTestStorageType, kTestUsage);
+  client_->AddOriginAndNotify(kDefaultOrigin, kTestStorageType, kTestUsage);
   scoped_task_environment_.RunUntilIdle();
 
   // Verify that the observer receives it.
diff --git a/storage/browser/quota/storage_observer.cc b/storage/browser/quota/storage_observer.cc
index b582a629..37aece7 100644
--- a/storage/browser/quota/storage_observer.cc
+++ b/storage/browser/quota/storage_observer.cc
@@ -14,7 +14,7 @@
     : storage_type(blink::mojom::StorageType::kUnknown) {}
 
 StorageObserver::Filter::Filter(blink::mojom::StorageType storage_type,
-                                const GURL& origin)
+                                const url::Origin& origin)
     : storage_type(storage_type), origin(origin) {}
 
 bool StorageObserver::Filter::operator==(const Filter& other) const {
@@ -30,7 +30,7 @@
 
 StorageObserver::MonitorParams::MonitorParams(
     blink::mojom::StorageType storage_type,
-    const GURL& origin,
+    const url::Origin& origin,
     const base::TimeDelta& rate,
     bool get_initial_state)
     : filter(storage_type, origin),
diff --git a/storage/browser/quota/storage_observer.h b/storage/browser/quota/storage_observer.h
index e47ed09..68292e2 100644
--- a/storage/browser/quota/storage_observer.h
+++ b/storage/browser/quota/storage_observer.h
@@ -10,7 +10,7 @@
 #include "base/time/time.h"
 #include "storage/browser/quota/quota_client.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace storage {
 
@@ -24,10 +24,10 @@
     blink::mojom::StorageType storage_type;
 
     // The origin to monitor usage for. Must be specified.
-    GURL origin;
+    url::Origin origin;
 
     Filter();
-    Filter(blink::mojom::StorageType storage_type, const GURL& origin);
+    Filter(blink::mojom::StorageType storage_type, const url::Origin& origin);
     bool operator==(const Filter& other) const;
   };
 
@@ -45,7 +45,7 @@
 
     MonitorParams();
     MonitorParams(blink::mojom::StorageType storage_type,
-                  const GURL& origin,
+                  const url::Origin& origin,
                   const base::TimeDelta& rate,
                   bool get_initial_state);
     MonitorParams(const Filter& filter,
diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc
index bac0940..6362317 100644
--- a/storage/browser/quota/usage_tracker.cc
+++ b/storage/browser/quota/usage_tracker.cc
@@ -142,7 +142,7 @@
 }
 
 void UsageTracker::UpdateUsageCache(QuotaClient::ID client_id,
-                                    const GURL& origin,
+                                    const url::Origin& origin,
                                     int64_t delta) {
   ClientUsageTracker* client_tracker = GetClientTracker(client_id);
   DCHECK(client_tracker);
@@ -165,14 +165,14 @@
 }
 
 void UsageTracker::GetCachedOriginsUsage(
-    std::map<GURL, int64_t>* origin_usage) const {
+    std::map<url::Origin, int64_t>* origin_usage) const {
   DCHECK(origin_usage);
   origin_usage->clear();
   for (const auto& client_id_and_tracker : client_tracker_map_)
     client_id_and_tracker.second->GetCachedOriginsUsage(origin_usage);
 }
 
-void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
+void UsageTracker::GetCachedOrigins(std::set<url::Origin>* origins) const {
   DCHECK(origins);
   origins->clear();
   for (const auto& client_id_and_tracker : client_tracker_map_)
@@ -180,7 +180,7 @@
 }
 
 void UsageTracker::SetUsageCacheEnabled(QuotaClient::ID client_id,
-                                        const GURL& origin,
+                                        const url::Origin& origin,
                                         bool enabled) {
   ClientUsageTracker* client_tracker = GetClientTracker(client_id);
   DCHECK(client_tracker);
diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h
index e7f81a784..d03bffd 100644
--- a/storage/browser/quota/usage_tracker.h
+++ b/storage/browser/quota/usage_tracker.h
@@ -21,7 +21,7 @@
 #include "storage/browser/quota/special_storage_policy.h"
 #include "storage/browser/storage_browser_export.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace storage {
 
@@ -48,19 +48,20 @@
   void GetHostUsageWithBreakdown(const std::string& host,
                                  UsageWithBreakdownCallback callback);
   void UpdateUsageCache(QuotaClient::ID client_id,
-                        const GURL& origin,
+                        const url::Origin& origin,
                         int64_t delta);
   int64_t GetCachedUsage() const;
   void GetCachedHostsUsage(std::map<std::string, int64_t>* host_usage) const;
-  void GetCachedOriginsUsage(std::map<GURL, int64_t>* origin_usage) const;
-  void GetCachedOrigins(std::set<GURL>* origins) const;
+  void GetCachedOriginsUsage(
+      std::map<url::Origin, int64_t>* origin_usage) const;
+  void GetCachedOrigins(std::set<url::Origin>* origins) const;
   bool IsWorking() const {
     return global_usage_callbacks_.HasCallbacks() ||
            host_usage_callbacks_.HasAnyCallbacks();
   }
 
   void SetUsageCacheEnabled(QuotaClient::ID client_id,
-                            const GURL& origin,
+                            const url::Origin& origin,
                             bool enabled);
 
  private:
diff --git a/storage/browser/quota/usage_tracker_unittest.cc b/storage/browser/quota/usage_tracker_unittest.cc
index 48ea323..ace109a 100644
--- a/storage/browser/quota/usage_tracker_unittest.cc
+++ b/storage/browser/quota/usage_tracker_unittest.cc
@@ -71,7 +71,7 @@
                       StorageType type,
                       GetUsageCallback callback) override {
     EXPECT_EQ(StorageType::kTemporary, type);
-    int64_t usage = GetUsage(origin.GetURL());
+    int64_t usage = GetUsage(origin);
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), usage));
   }
@@ -81,7 +81,7 @@
     EXPECT_EQ(StorageType::kTemporary, type);
     std::set<url::Origin> origins;
     for (const auto& origin_usage_pair : origin_usage_map_)
-      origins.insert(url::Origin::Create(origin_usage_pair.first));
+      origins.insert(origin_usage_pair.first);
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), origins));
   }
@@ -92,8 +92,8 @@
     EXPECT_EQ(StorageType::kTemporary, type);
     std::set<url::Origin> origins;
     for (const auto& origin_usage_pair : origin_usage_map_) {
-      if (net::GetHostOrSpecFromURL(origin_usage_pair.first) == host)
-        origins.insert(url::Origin::Create(origin_usage_pair.first));
+      if (net::GetHostOrSpecFromURL(origin_usage_pair.first.GetURL()) == host)
+        origins.insert(origin_usage_pair.first);
     }
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), origins));
@@ -103,7 +103,7 @@
                         StorageType type,
                         DeletionCallback callback) override {
     EXPECT_EQ(StorageType::kTemporary, type);
-    origin_usage_map_.erase(origin.GetURL());
+    origin_usage_map_.erase(origin);
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), QuotaStatusCode::kOk));
   }
@@ -112,23 +112,23 @@
     return type == StorageType::kTemporary;
   }
 
-  int64_t GetUsage(const GURL& origin) {
+  int64_t GetUsage(const url::Origin& origin) {
     auto found = origin_usage_map_.find(origin);
     if (found == origin_usage_map_.end())
       return 0;
     return found->second;
   }
 
-  void SetUsage(const GURL& origin, int64_t usage) {
+  void SetUsage(const url::Origin& origin, int64_t usage) {
     origin_usage_map_[origin] = usage;
   }
 
-  int64_t UpdateUsage(const GURL& origin, int64_t delta) {
+  int64_t UpdateUsage(const url::Origin& origin, int64_t delta) {
     return origin_usage_map_[origin] += delta;
   }
 
  private:
-  std::map<GURL, int64_t> origin_usage_map_;
+  std::map<url::Origin, int64_t> origin_usage_map_;
 
   DISALLOW_COPY_AND_ASSIGN(MockQuotaClient);
 };
@@ -148,13 +148,14 @@
     return &usage_tracker_;
   }
 
-  void UpdateUsage(const GURL& origin, int64_t delta) {
+  void UpdateUsage(const url::Origin& origin, int64_t delta) {
     quota_client_.UpdateUsage(origin, delta);
     usage_tracker_.UpdateUsageCache(quota_client_.id(), origin, delta);
     base::RunLoop().RunUntilIdle();
   }
 
-  void UpdateUsageWithoutNotification(const GURL& origin, int64_t delta) {
+  void UpdateUsageWithoutNotification(const url::Origin& origin,
+                                      int64_t delta) {
     quota_client_.UpdateUsage(origin, delta);
   }
 
@@ -198,23 +199,23 @@
     EXPECT_TRUE(done);
   }
 
-  void GrantUnlimitedStoragePolicy(const GURL& origin) {
-    if (!storage_policy_->IsStorageUnlimited(origin)) {
-      storage_policy_->AddUnlimited(origin);
-      storage_policy_->NotifyGranted(
-          origin, SpecialStoragePolicy::STORAGE_UNLIMITED);
+  void GrantUnlimitedStoragePolicy(const url::Origin& origin) {
+    if (!storage_policy_->IsStorageUnlimited(origin.GetURL())) {
+      storage_policy_->AddUnlimited(origin.GetURL());
+      storage_policy_->NotifyGranted(origin.GetURL(),
+                                     SpecialStoragePolicy::STORAGE_UNLIMITED);
     }
   }
 
-  void RevokeUnlimitedStoragePolicy(const GURL& origin) {
-    if (storage_policy_->IsStorageUnlimited(origin)) {
-      storage_policy_->RemoveUnlimited(origin);
-      storage_policy_->NotifyRevoked(
-          origin, SpecialStoragePolicy::STORAGE_UNLIMITED);
+  void RevokeUnlimitedStoragePolicy(const url::Origin& origin) {
+    if (storage_policy_->IsStorageUnlimited(origin.GetURL())) {
+      storage_policy_->RemoveUnlimited(origin.GetURL());
+      storage_policy_->NotifyRevoked(origin.GetURL(),
+                                     SpecialStoragePolicy::STORAGE_UNLIMITED);
     }
   }
 
-  void SetUsageCacheEnabled(const GURL& origin, bool enabled) {
+  void SetUsageCacheEnabled(const url::Origin& origin, bool enabled) {
     usage_tracker_.SetUsageCacheEnabled(
         quota_client_.id(), origin, enabled);
   }
@@ -245,8 +246,9 @@
   EXPECT_EQ(0, usage);
   EXPECT_EQ(0, unlimited_usage);
 
-  const GURL origin("http://example.com");
-  const std::string host(net::GetHostOrSpecFromURL(origin));
+  // TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+  const url::Origin origin = url::Origin::Create(GURL("http://example.com"));
+  const std::string host(net::GetHostOrSpecFromURL(origin.GetURL()));
 
   UpdateUsage(origin, 100);
   GetGlobalUsage(&usage, &unlimited_usage);
@@ -284,8 +286,8 @@
   base::flat_map<QuotaClient::ID, int64_t> host_usage_breakdown;
   base::flat_map<QuotaClient::ID, int64_t> host_usage_breakdown_expected;
 
-  const GURL origin("http://example.com");
-  const std::string host(net::GetHostOrSpecFromURL(origin));
+  const url::Origin origin = url::Origin::Create(GURL("http://example.com"));
+  const std::string host(net::GetHostOrSpecFromURL(origin.GetURL()));
 
   UpdateUsage(origin, 100);
   GetGlobalUsage(&usage, &unlimited_usage);
@@ -343,10 +345,11 @@
 }
 
 TEST_F(UsageTrackerTest, LimitedGlobalUsageTest) {
-  const GURL kNormal("http://normal");
-  const GURL kUnlimited("http://unlimited");
-  const GURL kNonCached("http://non_cached");
-  const GURL kNonCachedUnlimited("http://non_cached-unlimited");
+  const url::Origin kNormal = url::Origin::Create(GURL("http://normal"));
+  const url::Origin kUnlimited = url::Origin::Create(GURL("http://unlimited"));
+  const url::Origin kNonCached = url::Origin::Create(GURL("http://non_cached"));
+  const url::Origin kNonCachedUnlimited =
+      url::Origin::Create(GURL("http://non_cached-unlimited"));
 
   GrantUnlimitedStoragePolicy(kUnlimited);
   GrantUnlimitedStoragePolicy(kNonCachedUnlimited);
diff --git a/storage/browser/test/async_file_test_helper.cc b/storage/browser/test/async_file_test_helper.cc
index a741d833..540547b 100644
--- a/storage/browser/test/async_file_test_helper.cc
+++ b/storage/browser/test/async_file_test_helper.cc
@@ -258,7 +258,7 @@
 
 blink::mojom::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota(
     storage::QuotaManager* quota_manager,
-    const GURL& origin,
+    const url::Origin& origin,
     storage::FileSystemType type,
     int64_t* usage,
     int64_t* quota) {
diff --git a/storage/browser/test/async_file_test_helper.h b/storage/browser/test/async_file_test_helper.h
index fcdc0856..711fffd 100644
--- a/storage/browser/test/async_file_test_helper.h
+++ b/storage/browser/test/async_file_test_helper.h
@@ -12,14 +12,16 @@
 #include "storage/common/fileapi/file_system_types.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
-class GURL;
-
 namespace storage {
 class FileSystemContext;
 class FileSystemURL;
 class QuotaManager;
 }
 
+namespace url {
+class Origin;
+}
+
 namespace content {
 
 // A helper class to perform async file operations in a synchronous way.
@@ -103,7 +105,7 @@
   // |quota|.
   static blink::mojom::QuotaStatusCode GetUsageAndQuota(
       storage::QuotaManager* quota_manager,
-      const GURL& origin,
+      const url::Origin& origin,
       storage::FileSystemType type,
       int64_t* usage,
       int64_t* quota);
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc
index 419cd85..276ef66 100644
--- a/storage/browser/test/mock_quota_manager.cc
+++ b/storage/browser/test/mock_quota_manager.cc
@@ -15,16 +15,14 @@
 
 namespace content {
 
-MockQuotaManager::OriginInfo::OriginInfo(
-    const GURL& origin,
-    StorageType type,
-    int quota_client_mask,
-    base::Time modified)
+MockQuotaManager::OriginInfo::OriginInfo(const url::Origin& origin,
+                                         StorageType type,
+                                         int quota_client_mask,
+                                         base::Time modified)
     : origin(origin),
       type(type),
       quota_client_mask(quota_client_mask),
-      modified(modified) {
-}
+      modified(modified) {}
 
 MockQuotaManager::OriginInfo::~OriginInfo() = default;
 
@@ -44,7 +42,7 @@
                    storage::GetQuotaSettingsFunc()),
       weak_factory_(this) {}
 
-void MockQuotaManager::GetUsageAndQuota(const GURL& origin,
+void MockQuotaManager::GetUsageAndQuota(const url::Origin& origin,
                                         StorageType type,
                                         UsageAndQuotaCallback callback) {
   StorageInfo& info = usage_and_quota_map_[std::make_pair(origin, type)];
@@ -52,25 +50,23 @@
                           info.quota);
 }
 
-void MockQuotaManager::SetQuota(const GURL& origin,
+void MockQuotaManager::SetQuota(const url::Origin& origin,
                                 StorageType type,
                                 int64_t quota) {
   usage_and_quota_map_[std::make_pair(origin, type)].quota = quota;
 }
 
-bool MockQuotaManager::AddOrigin(
-    const GURL& origin,
-    StorageType type,
-    int quota_client_mask,
-    base::Time modified) {
+bool MockQuotaManager::AddOrigin(const url::Origin& origin,
+                                 StorageType type,
+                                 int quota_client_mask,
+                                 base::Time modified) {
   origins_.push_back(OriginInfo(origin, type, quota_client_mask, modified));
   return true;
 }
 
-bool MockQuotaManager::OriginHasData(
-    const GURL& origin,
-    StorageType type,
-    QuotaClient::ID quota_client) const {
+bool MockQuotaManager::OriginHasData(const url::Origin& origin,
+                                     StorageType type,
+                                     QuotaClient::ID quota_client) const {
   for (std::vector<OriginInfo>::const_iterator current = origins_.begin();
        current != origins_.end();
        ++current) {
@@ -85,7 +81,7 @@
 void MockQuotaManager::GetOriginsModifiedSince(StorageType type,
                                                base::Time modified_since,
                                                GetOriginsCallback callback) {
-  std::set<GURL>* origins_to_return = new std::set<GURL>();
+  std::set<url::Origin>* origins_to_return = new std::set<url::Origin>();
   for (std::vector<OriginInfo>::const_iterator current = origins_.begin();
        current != origins_.end();
        ++current) {
@@ -99,7 +95,7 @@
                                 base::Owned(origins_to_return), type));
 }
 
-void MockQuotaManager::DeleteOriginData(const GURL& origin,
+void MockQuotaManager::DeleteOriginData(const url::Origin& origin,
                                         StorageType type,
                                         int quota_client_mask,
                                         StatusCallback callback) {
@@ -123,14 +119,14 @@
 
 MockQuotaManager::~MockQuotaManager() = default;
 
-void MockQuotaManager::UpdateUsage(const GURL& origin,
+void MockQuotaManager::UpdateUsage(const url::Origin& origin,
                                    StorageType type,
                                    int64_t delta) {
   usage_and_quota_map_[std::make_pair(origin, type)].usage += delta;
 }
 
 void MockQuotaManager::DidGetModifiedSince(GetOriginsCallback callback,
-                                           std::set<GURL>* origins,
+                                           std::set<url::Origin>* origins,
                                            StorageType storage_type) {
   std::move(callback).Run(*origins, storage_type);
 }
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index 96a19c1..129cb5d 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -18,7 +18,7 @@
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/browser/quota/quota_task.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 using blink::mojom::StorageType;
 using storage::GetOriginsCallback;
@@ -52,7 +52,7 @@
   // updated when MockQuotaManagerProxy::NotifyStorageModified() is
   // called.  The internal quota value can be updated by calling
   // a helper method MockQuotaManagerProxy::SetQuota().
-  void GetUsageAndQuota(const GURL& origin,
+  void GetUsageAndQuota(const url::Origin& origin,
                         StorageType type,
                         UsageAndQuotaCallback callback) override;
 
@@ -69,20 +69,20 @@
   // origin as a bitmask built from QuotaClient::IDs. Setting the mask to
   // QuotaClient::kAllClientsMask will remove all clients from the origin,
   // regardless of type.
-  void DeleteOriginData(const GURL& origin,
+  void DeleteOriginData(const url::Origin& origin,
                         StorageType type,
                         int quota_client_mask,
                         StatusCallback callback) override;
 
   // Helper method for updating internal quota info.
-  void SetQuota(const GURL& origin, StorageType type, int64_t quota);
+  void SetQuota(const url::Origin& origin, StorageType type, int64_t quota);
 
   // Helper methods for timed-deletion testing:
   // Adds an origin to the canned list that will be searched through via
   // GetOriginsModifiedSince. The caller must provide |quota_client_mask|
   // which specifies the types of QuotaClients this canned origin contains
   // as a bitmask built from QuotaClient::IDs.
-  bool AddOrigin(const GURL& origin,
+  bool AddOrigin(const url::Origin& origin,
                  StorageType type,
                  int quota_client_mask,
                  base::Time modified);
@@ -91,7 +91,7 @@
   // Checks an origin and type against the origins that have been added via
   // AddOrigin and removed via DeleteOriginData. If the origin exists in the
   // canned list with the proper StorageType and client, returns true.
-  bool OriginHasData(const GURL& origin,
+  bool OriginHasData(const url::Origin& origin,
                      StorageType type,
                      QuotaClient::ID quota_client) const;
 
@@ -105,13 +105,13 @@
   // MockQuotaManager needs to understand for time-based deletion:
   // the origin itself, the StorageType and its modification time.
   struct OriginInfo {
-    OriginInfo(const GURL& origin,
+    OriginInfo(const url::Origin& origin,
                StorageType type,
                int quota_client_mask,
                base::Time modified);
     ~OriginInfo();
 
-    GURL origin;
+    url::Origin origin;
     StorageType type;
     int quota_client_mask;
     base::Time modified;
@@ -128,13 +128,13 @@
     int64_t quota;
   };
 
-  typedef std::pair<GURL, StorageType> OriginAndType;
+  typedef std::pair<url::Origin, StorageType> OriginAndType;
   typedef std::map<OriginAndType, StorageInfo> UsageAndQuotaMap;
 
   // This must be called via MockQuotaManagerProxy.
-  void UpdateUsage(const GURL& origin, StorageType type, int64_t delta);
+  void UpdateUsage(const url::Origin& origin, StorageType type, int64_t delta);
   void DidGetModifiedSince(GetOriginsCallback callback,
-                           std::set<GURL>* origins,
+                           std::set<url::Origin>* origins,
                            StorageType storage_type);
   void DidDeleteOriginData(StatusCallback callback,
                            blink::mojom::QuotaStatusCode status);
diff --git a/storage/browser/test/mock_quota_manager_proxy.cc b/storage/browser/test/mock_quota_manager_proxy.cc
index 048b2afd..b521e3c 100644
--- a/storage/browser/test/mock_quota_manager_proxy.cc
+++ b/storage/browser/test/mock_quota_manager_proxy.cc
@@ -38,8 +38,7 @@
     blink::mojom::StorageType type,
     QuotaManager::UsageAndQuotaCallback callback) {
   if (mock_manager()) {
-    mock_manager()->GetUsageAndQuota(origin.GetURL(), type,
-                                     std::move(callback));
+    mock_manager()->GetUsageAndQuota(origin, type, std::move(callback));
   }
 }
 
@@ -62,7 +61,7 @@
   last_notified_type_ = type;
   last_notified_delta_ = delta;
   if (mock_manager())
-    mock_manager()->UpdateUsage(origin.GetURL(), type, delta);
+    mock_manager()->UpdateUsage(origin, type, delta);
 }
 
 MockQuotaManagerProxy::~MockQuotaManagerProxy() {
diff --git a/storage/browser/test/mock_quota_manager_unittest.cc b/storage/browser/test/mock_quota_manager_unittest.cc
index 4ffb207..d1e4d37c 100644
--- a/storage/browser/test/mock_quota_manager_unittest.cc
+++ b/storage/browser/test/mock_quota_manager_unittest.cc
@@ -26,9 +26,10 @@
 const char kTestOrigin2[] = "http://host2:1/";
 const char kTestOrigin3[] = "http://host3:1/";
 
-const GURL kOrigin1(kTestOrigin1);
-const GURL kOrigin2(kTestOrigin2);
-const GURL kOrigin3(kTestOrigin3);
+// TODO(crbug.com/889590): Use helper for url::Origin creation from string.
+const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
+const url::Origin kOrigin2 = url::Origin::Create(GURL(kTestOrigin2));
+const url::Origin kOrigin3 = url::Origin::Create(GURL(kTestOrigin3));
 
 const StorageType kTemporary = StorageType::kTemporary;
 const StorageType kPersistent = StorageType::kPersistent;
@@ -64,13 +65,15 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  void GotModifiedOrigins(const std::set<GURL>& origins, StorageType type) {
+  void GotModifiedOrigins(const std::set<url::Origin>& origins,
+                          StorageType type) {
     origins_ = origins;
     type_ = type;
   }
 
-  void DeleteOriginData(const GURL& origin, StorageType type,
-      int quota_client_mask) {
+  void DeleteOriginData(const url::Origin& origin,
+                        StorageType type,
+                        int quota_client_mask) {
     manager_->DeleteOriginData(
         origin, type, quota_client_mask,
         base::BindOnce(&MockQuotaManagerTest::DeletedOriginData,
@@ -90,9 +93,7 @@
     return manager_.get();
   }
 
-  const std::set<GURL>& origins() const {
-    return origins_;
-  }
+  const std::set<url::Origin>& origins() const { return origins_; }
 
   const StorageType& type() const {
     return type_;
@@ -106,7 +107,7 @@
 
   int deletion_callback_count_;
 
-  std::set<GURL> origins_;
+  std::set<url::Origin> origins_;
   StorageType type_;
 
   base::WeakPtrFactory<MockQuotaManagerTest> weak_factory_;
diff --git a/storage/browser/test/mock_storage_client.cc b/storage/browser/test/mock_storage_client.cc
index 38e06a9..287042c0 100644
--- a/storage/browser/test/mock_storage_client.cc
+++ b/storage/browser/test/mock_storage_client.cc
@@ -34,6 +34,7 @@
     const MockOriginData* mock_data,
     size_t mock_data_size) {
   for (size_t i = 0; i < mock_data_size; ++i) {
+    // TODO(crbug.com/889590): Use helper for url::Origin creation from string.
     origin_data_[make_pair(url::Origin::Create(GURL(mock_data[i].origin)),
                            mock_data[i].type)] = mock_data[i].usage;
   }
@@ -48,7 +49,7 @@
   DCHECK_GE(size, 0);
   origin_data_[make_pair(origin, type)] = size;
   quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal(
-      id(), origin.GetURL(), type, size, IncrementMockTime());
+      id(), origin, type, size, IncrementMockTime());
 }
 
 void MockStorageClient::ModifyOriginAndNotify(const url::Origin& origin,
@@ -61,7 +62,7 @@
 
   // TODO(tzik): Check quota to prevent usage exceed
   quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal(
-      id(), origin.GetURL(), type, delta, IncrementMockTime());
+      id(), origin, type, delta, IncrementMockTime());
 }
 
 void MockStorageClient::TouchAllOriginsAndNotify() {
@@ -69,8 +70,7 @@
        itr != origin_data_.end();
        ++itr) {
     quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal(
-        id(), itr->first.first.GetURL(), itr->first.second, 0,
-        IncrementMockTime());
+        id(), itr->first.first, itr->first.second, 0, IncrementMockTime());
   }
 }
 
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 8246f58..e0913e9 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -10011,6 +10011,11 @@
       }
     ]
   },
+  "ToTAndroidOfficial": {
+    "additional_compile_targets": [
+      "all"
+    ]
+  },
   "ToTLinux": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 642a9683..cf41416 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -12563,23 +12563,14 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_end2end_tests"
       },
       {
         "args": [
@@ -12589,22 +12580,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "angle_gles1_conformance_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_gles1_conformance_tests"
       },
       {
         "args": [
@@ -12614,22 +12596,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "angle_white_box_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_white_box_tests"
       },
       {
         "args": [
@@ -12645,22 +12618,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "browser_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "browser_tests"
       },
       {
         "args": [
@@ -12672,22 +12636,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gl_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gl_tests"
       },
       {
         "args": [
@@ -12697,22 +12652,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "args": [
@@ -12724,22 +12670,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "args": [
@@ -12752,66 +12689,39 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gpu_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gpu_unittests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "swiftshader_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "swiftshader_unittests"
       },
       {
         "args": [
@@ -12824,22 +12734,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "video_decode_accelerator_unittest",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "video_decode_accelerator_unittest"
       }
     ],
     "isolated_scripts": [
@@ -12862,21 +12763,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -12896,22 +12788,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 20
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -12929,22 +12812,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -12962,22 +12836,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -12995,22 +12860,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -13028,22 +12884,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -15044,23 +14891,14 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_end2end_tests"
       },
       {
         "args": [
@@ -15070,22 +14908,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "angle_gles1_conformance_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_gles1_conformance_tests"
       },
       {
         "args": [
@@ -15096,22 +14925,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "angle_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_unittests"
       },
       {
         "args": [
@@ -15121,22 +14941,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "angle_white_box_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_white_box_tests"
       },
       {
         "args": [
@@ -15150,22 +14961,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "browser_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "browser_tests"
       },
       {
         "args": [
@@ -15181,22 +14983,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "browser_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "browser_tests"
       },
       {
         "args": [
@@ -15207,22 +15000,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gl_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gl_tests"
       },
       {
         "args": [
@@ -15234,22 +15018,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gl_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gl_tests"
       },
       {
         "args": [
@@ -15259,22 +15034,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gl_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gl_unittests"
       },
       {
         "args": [
@@ -15284,22 +15050,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "args": [
@@ -15311,22 +15068,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "args": [
@@ -15339,66 +15087,39 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gles2_conform_test",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gles2_conform_test"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "gpu_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "gpu_unittests"
       },
       {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "swiftshader_unittests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "swiftshader_unittests"
       },
       {
         "args": [
@@ -15411,22 +15132,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "video_decode_accelerator_unittest",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "video_decode_accelerator_unittest"
       },
       {
         "args": [
@@ -15439,22 +15151,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ]
         },
-        "test": "video_decode_accelerator_unittest",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "video_decode_accelerator_unittest"
       }
     ],
     "isolated_scripts": [
@@ -15473,21 +15176,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15505,21 +15199,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15537,21 +15222,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15569,21 +15245,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15605,21 +15272,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15644,21 +15302,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15691,21 +15340,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15724,21 +15364,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15756,21 +15387,12 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15790,22 +15412,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 20
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15825,22 +15438,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 20
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15860,22 +15464,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 20
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15893,22 +15488,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15926,22 +15512,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15959,22 +15536,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15992,22 +15560,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -16025,22 +15584,13 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "idempotent": false,
           "shards": 2
-        },
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -17180,23 +16730,14 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "gpu": "8086:5912-23.20.16.4877",
+              "gpu": "8086:5912-23.20.100.6286",
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
           ],
           "shards": 4
         },
-        "test": "angle_deqp_gles2_tests",
-        "trigger_script": {
-          "args": [
-            "--multiple-trigger-configs",
-            "[{\"gpu\": \"8086:5912-23.20.16.4877\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"8086:5912-24.20.100.6286\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
-            "--multiple-dimension-script-verbose",
-            "True"
-          ],
-          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
-        }
+        "test": "angle_deqp_gles2_tests"
       }
     ]
   },
diff --git a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
index 65dd820..92beb72 100644
--- a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
@@ -9,18 +9,13 @@
 # Uncategorized timeouts or test failures.
 -AffiliationCheck/EnterpriseDeviceAttributesTest.Success/Affiliated
 -AffiliationCheck/EnterpriseDeviceAttributesTest.Success/NotAffiliated
--ArcRobotAccountAuthServiceTest.GetOfflineDemoAccount
--ArcSessionManagerTest.ConsumerAccount
--ArcSessionManagerTest.ManagedAndroidAccount
--ArcSessionManagerTest.ManagedChromeAccount
--ArcSessionManagerTest.WellKnownConsumerAccount
+-BackgroundFetchBrowserTest.FetchCanBePausedAndResumed
 -CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/0
 -CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/1
 -CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/2
 -CheckSystemTokenAvailability/EnterprisePlatformKeysTest.Basic/3
 -ChromeNewWindowClientBrowserTest.NewWindowForActiveWindowProfileTest
 -ChromeSessionManagerTest.OobeNewUser
--DeclarativeContentApiTest.RemoveAllRulesAfterExtensionUninstall
 -DeviceIDTest.LegacyUsers
 -DeviceIDTest.Migration
 -DeviceIDTest.NewUsers
@@ -31,19 +26,6 @@
 -HostedAppNonClientFrameViewAshTest.FocusableViews/material_refresh
 -HostedAppNonClientFrameViewAshTest.FocusableViews/material_refresh_touch_optimized
 -HostedAppNonClientFrameViewAshTest.FocusableViews/material_touch_optimized
--KeyboardEndToEndFocusTest.TriggerAsyncInputFocusFromUserGestureAfterBlurShowsKeyboard
--KeyboardEndToEndFocusTest.TriggerAsyncInputFocusFromUserGestureAfterBlurTimeoutDoesNotShowKeyboard
--KeyboardEndToEndFocusTest.TriggerAsyncInputFocusFromUserGestureDoesNotShowKeyboard
--KeyboardEndToEndFocusTest.TriggerInputFocusFromUserGestureShowsKeyboard
--KeyboardEndToEndFocusTest.TriggerInputFocusWithoutUserGestureDoesNotShowKeyboard
--KeyboardEndToEndFormTest.ChangeInputModeToNoneHidesKeyboard
--KeyboardEndToEndFormTest.ChangeInputModeToNumericDoesNotHideKeyboard
--KeyboardEndToEndFormTest.ChangeInputToReadOnlyHidesKeyboard
--KeyboardEndToEndFormTest.ChangeInputTypeToNonTextHidesKeyboard
--KeyboardEndToEndFormTest.ChangeInputTypeToTextDoesNotHideKeyboard
--KeyboardEndToEndFormTest.ClickBodyHidesKeyboard
--KeyboardEndToEndFormTest.ClickTextFieldShowsKeyboard
--KeyboardEndToEndFormTest.DeleteInputHidesKeyboard
 -KioskUpdateTest.LaunchCachedOfflineEnabledAppNoNetwork
 -LoginPolicyTestBase.AllowedInputMethods
 -LoginPolicyTestBase.AllowedUILocales
@@ -53,8 +35,6 @@
 -OAuth2Test.TerminateOnBadMergeSessionAfterOnlineAuth
 -OAuth2Test.VerifyInAdvancedProtectionAfterOnlineAuth
 -OAuth2Test.VerifyNotInAdvancedProtectionAfterOnlineAuth
--OutOfProcessPPAPITest.UDPSocket_DropReceiverPipeOnReceiveMore
--OutOfProcessPPAPITest.UDPSocket_ReadError
 -PolicyProvidedTrustAnchorsOnUserSessionInitTest.TrustAnchorsAvailableImmediatelyAfterSessionStart
 -PolicyProvidedTrustAnchorsRegularUserTest.AllowedForRegularUser
 -SAMLPolicyTest.NoSAML
@@ -68,7 +48,6 @@
 -SamlTest.ScrapedNone
 -SamlTest.ScrapedSingle
 -SamlTest.UseAutenticatedUserEmailAddress
--ServiceWorkerTestWithNativeBindings/ServiceWorkerTest.EventsToStoppedWorker/0
 -SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/0
 -SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/1
 -SigninProfileAppsPolicyPerChannelTest.ExtensionInstallation/2
@@ -110,16 +89,6 @@
 -WizardControllerDeviceStateExplicitRequirement/WizardControllerDeviceStateExplicitRequirementTest.ControlFlowForcedReEnrollment/1
 -WizardControllerDeviceStateTest.ControlFlowDeviceDisabled
 -WizardControllerDeviceStateWithInitialEnrollmentTest.ControlFlowInitialEnrollment
--ZipFiles/FilesAppBrowserTest.Test/zipCreateFileDownloads
--ZipFiles/FilesAppBrowserTest.Test/zipCreateFileDownloads_GuestMode
--ZipFiles/FilesAppBrowserTest.Test/zipCreateFileDrive
--ZipFiles/FilesAppBrowserTest.Test/zipCreateFileDrive_DriveFs
--ZipFiles/FilesAppBrowserTest.Test/zipCreateFileUsb
--ZipFiles/FilesAppBrowserTest.Test/zipFileOpenDownloads
--ZipFiles/FilesAppBrowserTest.Test/zipFileOpenDownloads_GuestMode
--ZipFiles/FilesAppBrowserTest.Test/zipFileOpenDrive
--ZipFiles/FilesAppBrowserTest.Test/zipFileOpenDrive_DriveFs
--ZipFiles/FilesAppBrowserTest.Test/zipFileOpenUsb
 
 # NetworkChangeNotifier() Not implemented.
 # https://crbug.com/882610
@@ -150,6 +119,9 @@
 # Flaky with error: `Check failed: (sequence_checker_).CalledOnValidSequence()`.
 -DevToolsSanityTest.DisposeEmptyBrowserContext
 
+# Flaky with error: `picture_in_picture_window_controller_impl.cc(167)] Check failed: media_player_id_.has_value()`.
+-PictureInPictureWindowControllerBrowserTest.TabIconUpdated
+
 # NOTE: if adding an exclusion for an existing failure (e.g. additional test for
 # feature X that is already not working), please add it beside the existing
 # failures. Otherwise please reach out to network-service-dev@.
diff --git a/testing/buildbot/filters/webui_polymer2_browser_tests.filter b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
index 37338943..c959b96 100644
--- a/testing/buildbot/filters/webui_polymer2_browser_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
@@ -140,7 +140,6 @@
 CrSettingsDevicePageTest.DevicePageTest
 CrSettingsDevicePageTest.PointersTest
 CrSettingsDevicePageTest.StylusTest
-CrSettingsDisplaySizeSliderTest.*
 CrSettingsDownloadsPageTest.*
 CrSettingsDropdownMenuTest.*
 CrSettingsEditDictionaryPageTest.*
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index e805d246..93eccdae 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -767,6 +767,11 @@
           'gtest_tests': 'chromium_android_gtests',
         },
       },
+      'ToTAndroidOfficial': {
+        'additional_compile_targets': [
+          'all',
+        ],
+      },
       'ToTLinux': {
         'test_suites': {
           'gtest_tests': 'chromium_linux_gtests',
@@ -2623,7 +2628,7 @@
           'dimension_sets': [
             {
               # WIN10_INTEL_HD_630_STABLE_DRIVER
-              'gpu': '8086:5912-23.20.16.4877',
+              'gpu': '8086:5912-23.20.100.6286',
               'os': 'Windows-10',
               'pool': 'Chrome-GPU',
             },
@@ -2633,15 +2638,6 @@
           'gtest_tests': 'gpu_fyi_win_optional_gtests',
           'gpu_telemetry_tests': 'gpu_optional_win_telemetry_tests',
         },
-        'use_multi_dimension_trigger_script': True,
-        'alternate_swarming_dimensions': [
-          {
-            # WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER
-            'gpu': '8086:5912-24.20.100.6286',
-            'os': 'Windows-10',
-            'pool': 'Chrome-GPU',
-          },
-        ],
       },
       'Optional Win10 Release (NVIDIA)': {
         'os_type': 'win',
@@ -2732,7 +2728,7 @@
           'dimension_sets': [
             {
               # WIN10_INTEL_HD_630_STABLE_DRIVER
-              'gpu': '8086:5912-23.20.16.4877',
+              'gpu': '8086:5912-23.20.100.6286',
               'os': 'Windows-10',
               'pool': 'Chrome-GPU',
             },
@@ -2742,15 +2738,6 @@
           'gtest_tests': 'gpu_fyi_win_gtests',
           'gpu_telemetry_tests': 'gpu_fyi_win_release_telemetry_tests',
         },
-        'use_multi_dimension_trigger_script': True,
-        'alternate_swarming_dimensions': [
-          {
-            # WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER
-            'gpu': '8086:5912-24.20.100.6286',
-            'os': 'Windows-10',
-            'pool': 'Chrome-GPU',
-          },
-        ],
       },
       'Win10 FYI Release (NVIDIA)': {
         'os_type': 'win',
@@ -2779,7 +2766,7 @@
           'dimension_sets': [
             {
               # WIN10_INTEL_HD_630_STABLE_DRIVER
-              'gpu': '8086:5912-23.20.16.4877',
+              'gpu': '8086:5912-23.20.100.6286',
               'os': 'Windows-10',
               'pool': 'Chrome-GPU',
             },
@@ -2788,15 +2775,6 @@
         'test_suites': {
           'gtest_tests': 'gpu_angle_deqp_gles2_d3d11_tests',
         },
-        'use_multi_dimension_trigger_script': True,
-        'alternate_swarming_dimensions': [
-          {
-            # WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER
-            'gpu': '8086:5912-24.20.100.6286',
-            'os': 'Windows-10',
-            'pool': 'Chrome-GPU',
-          },
-        ],
       },
       'Win10 FYI dEQP Release (NVIDIA)': {
         'os_type': 'win',
diff --git a/testing/libfuzzer/gen_fuzzer_owners.py b/testing/libfuzzer/gen_fuzzer_owners.py
index c9590a3d..7b8fd2f5 100755
--- a/testing/libfuzzer/gen_fuzzer_owners.py
+++ b/testing/libfuzzer/gen_fuzzer_owners.py
@@ -32,6 +32,13 @@
   return None
 
 
+def GetGitCommand():
+  """Returns a git command that does not need to be executed using shell=True.
+  On non-Windows platforms: 'git'. On Windows: 'git.bat'.
+  """
+  return 'git.bat' if sys.platform == 'win32' else 'git'
+
+
 def GetOwnersIfThirdParty(source):
   """Return owners using OWNERS file if in third_party."""
   match_index = source.find(THIRD_PARTY_SEARCH_STRING)
@@ -72,8 +79,9 @@
       # Found the fuzzer source (and not dependency of fuzzer).
 
       git_dir = os.path.join(CHROMIUM_SRC_DIR, '.git')
+      git_command = GetGitCommand()
       is_git_file = bool(subprocess.check_output(
-          ['git', '--git-dir', git_dir, 'ls-files', source],
+          [git_command, '--git-dir', git_dir, 'ls-files', source],
           cwd=CHROMIUM_SRC_DIR))
       if not is_git_file:
         # File is not in working tree. Return owners for third_party.
@@ -84,9 +92,8 @@
       # the original author has authored line 1 which is usually the
       # copyright line and does not change even with file rename / move.
       blame_output = subprocess.check_output(
-          ['git', '--git-dir', git_dir,
-           'blame', '--porcelain', '-L1,1', source],
-          cwd=CHROMIUM_SRC_DIR)
+          [git_command, '--git-dir', git_dir, 'blame', '--porcelain', '-L1,1',
+           source], cwd=CHROMIUM_SRC_DIR)
       return GetAuthorFromGitBlame(blame_output)
 
   return None
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 3873cd8..7fb72945 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1787,6 +1787,21 @@
             ]
         }
     ],
+    "ExtendedShellExtensionsEnumeration": [
+        {
+            "platforms": [
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ExtendedShellExtensionsEnumeration"
+                    ]
+                }
+            ]
+        }
+    ],
     "ExtensionContentVerification": [
         {
             "platforms": [
@@ -2996,7 +3011,8 @@
                         "OmniboxNewAnswerLayout",
                         "OmniboxRichEntitySuggestions",
                         "OmniboxTailSuggestions",
-                        "OmniboxUIExperimentHideSteadyStateUrlSchemeAndSubdomains",
+                        "OmniboxUIExperimentHideSteadyStateUrlScheme",
+                        "OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains",
                         "ZeroSuggestRedirectToChrome",
                         "ZeroSuggestSwapTitleAndUrl"
                     ]
@@ -3013,7 +3029,8 @@
                     "enable_features": [
                         "OmniboxBreakWordsAtUnderscores",
                         "OmniboxDisplayTitleForCurrentUrl",
-                        "OmniboxUIExperimentHideSteadyStateUrlSchemeAndSubdomains"
+                        "OmniboxUIExperimentHideSteadyStateUrlScheme",
+                        "OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains"
                     ]
                 }
             ]
diff --git a/third_party/.gitignore b/third_party/.gitignore
index 0575724..fa6da6a 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -211,7 +211,6 @@
 /usrsctp/usrsctplib
 /v8-i18n
 /valgrind
-/vulkan-validation-layers/src
 /visualmetrics
 /wayland/src
 /wayland-protocols/src
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index d70ae81..07a21c3 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -1003,6 +1003,10 @@
 external/wpt/speech-api/SpeechRecognition-onerror-manual.https.html [ WontFix ]
 external/wpt/speech-api/SpeechRecognition-onresult-manual.https.html [ WontFix ]
 external/wpt/speech-api/SpeechRecognition-stop-manual.https.html [ WontFix ]
+virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-abort-manual.https.html [ WontFix ]
+virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onerror-manual.https.html [ WontFix ]
+virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onresult-manual.https.html [ WontFix ]
+virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-stop-manual.https.html [ WontFix ]
 external/wpt/uievents/interface/click-event-manual.htm [ WontFix ]
 external/wpt/uievents/interface/dblclick-event-manual.htm [ WontFix ]
 external/wpt/uievents/keyboard/key-101en-us-manual.html [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index d98c073..6ab23f222 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1398,7 +1398,6 @@
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/multiline.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/negative-flex-rounding-assert.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/negative-overflow.html [ Skip ]
-crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/nested-flexbox-min-size-auto.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/nested-orthogonal-flexbox-relayout.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/nested-stretch.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/css3/flexbox/order-painting.html [ Failure ]
@@ -1518,6 +1517,7 @@
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-006.xht [ Skip ]
 crbug.com/249112 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-007.xht [ Skip ]
 crbug.com/467127 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-008.xht [ Skip ]
+crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-009.html [ Skip ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-width-flex-items-001.xht [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-width-flex-items-002.xht [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/flex-minimum-width-flex-items-003.xht [ Failure ]
@@ -2830,6 +2830,7 @@
 crbug.com/875249 external/wpt/infrastructure/testdriver/bless.html [ Timeout Pass ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/css-backgrounds/border-image-width-008.html [ Failure ]
 crbug.com/626703 external/wpt/content-security-policy/generic/only-valid-whitespaces-are-allowed.html [ Timeout ]
 crbug.com/626703 virtual/outofblink-cors-ns/external/wpt/referrer-policy/css-integration/svg/internal-stylesheet.html [ Timeout ]
 crbug.com/367760 external/wpt/svg/pservers/reftests/meshgradient-basic-004.svg [ Failure ]
@@ -2860,6 +2861,8 @@
 crbug.com/626703 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-tall.html [ Failure ]
 crbug.com/626703 external/wpt/speech-api/SpeechSynthesis-speak-events.html [ Timeout ]
 crbug.com/626703 external/wpt/speech-api/SpeechSynthesis-pause-resume.tentative.html [ Timeout ]
+crbug.com/626703 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechSynthesis-pause-resume.tentative.html [ Timeout ]
+crbug.com/626703 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechSynthesis-speak-events.html [ Timeout ]
 crbug.com/626703 external/wpt/presentation-api/controlling-ua/reconnectToPresentation_notfound_error-manual.https.html [ Skip ]
 crbug.com/626703 external/wpt/presentation-api/controlling-ua/reconnectToMultiplePresentations_success-manual.https.html [ Skip ]
 crbug.com/626703 external/wpt/presentation-api/controlling-ua/startMultiplePresentations_success-manual.https.html [ Skip ]
@@ -2933,7 +2936,9 @@
 crbug.com/626703 external/wpt/payment-request/PaymentValidationErrors/retry-shows-error-member-manual.https.html [ Skip ]
 crbug.com/626703 external/wpt/payment-request/payment-response/retry-method-manual.https.html [ Skip ]
 crbug.com/626703 external/wpt/payment-request/payment-response/rejects_if_not_active-manual.https.html [ Skip ]
+crbug.com/626703 external/wpt/speech-api/SpeechSynthesis-speak-twice.html [ Timeout ]
 crbug.com/626703 external/wpt/speech-api/SpeechSynthesis-speak-without-activation-fails.tentative.html [ Timeout ]
+crbug.com/626703 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechSynthesis-speak-twice.html [ Timeout ]
 crbug.com/366553 external/wpt/svg/text/reftests/text-inline-size-007.svg [ Failure ]
 crbug.com/366553 external/wpt/svg/text/reftests/text-inline-size-005.svg [ Failure ]
 crbug.com/366558 external/wpt/svg/text/reftests/text-multiline-002.svg [ Failure ]
@@ -3489,7 +3494,6 @@
 crbug.com/626703 virtual/outofblink-cors/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
 crbug.com/626703 virtual/outofblink-cors-ns/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
 crbug.com/648295 virtual/service-worker-servicification/external/wpt/service-workers/service-worker/update-bytecheck.https.html [ Timeout ]
-crbug.com/626703 external/wpt/speech-api/SpeechSynthesis-speak-twice.html [ Timeout ]
 crbug.com/626703 external/wpt/svg/linking/reftests/href-filter-element.html [ Failure ]
 crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html [ Pass Failure ]
 crbug.com/626703 virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index 5a23630..52ca328 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -623,6 +623,11 @@
     "args": ["--enable-blink-features=FractionalMouseEvent"]
   },
   {
+    "prefix": "speech-with-unified-autoplay",
+    "base": "external/wpt/speech-api",
+    "args": ["--autoplay-policy=document-user-activation-required"]
+  },
+  {
     "prefix": "unified-autoplay",
     "base": "external/wpt/feature-policy",
     "args": ["--autoplay-policy=document-user-activation-required"]
diff --git a/third_party/WebKit/LayoutTests/css3/flexbox/nested-flexbox-min-size-auto.html b/third_party/WebKit/LayoutTests/css3/flexbox/nested-flexbox-min-size-auto.html
deleted file mode 100644
index 1c9297d..0000000
--- a/third_party/WebKit/LayoutTests/css3/flexbox/nested-flexbox-min-size-auto.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<link href="resources/flexbox.css" rel="stylesheet">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
-<style>
-#container {
-  height: 300px;
-  outline: 2px solid black;
-}
-
-.inner
-{
-  width: 400px;
-  flex: 1;
-  background-color: green;
-}
-</style>
-<script>
-function change() {
-  var container = document.getElementById('container');
-  container.offsetHeight;
-  container.style.height = '80px';
-  checkLayout('#container');
-}
-</script>
-<body onload="change()">
-<p>Green rectangle should be entirely within the black rectangle</p>
-<div id="log"></div>
-<div id="container">
-  <div class="flexbox column" style="height: 100%;">
-    <div class="flexbox flex-one">
-        <div class="flexbox column">
-          <div class="flexbox column flex-one">
-            <div class="inner" data-expected-height="80">
-            </div>
-          </div>
-        </div>
-    </div>
-  </div>
-</div>
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
index 558b5ad..c4295aa 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
@@ -33215,6 +33215,18 @@
      {}
     ]
    ],
+   "css/css-backgrounds/border-image-width-008.html": [
+    [
+     "/css/css-backgrounds/border-image-width-008.html",
+     [
+      [
+       "/css/css-backgrounds/border-image-width-008-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-backgrounds/border-radius-001.xht": [
     [
      "/css/css-backgrounds/border-radius-001.xht",
@@ -36767,6 +36779,18 @@
      {}
     ]
    ],
+   "css/css-contain/contain-paint-clip-019.html": [
+    [
+     "/css/css-contain/contain-paint-clip-019.html",
+     [
+      [
+       "/css/css-contain/reference/contain-paint-clip-019-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-contain/contain-paint-ifc-011.html": [
     [
      "/css/css-contain/contain-paint-ifc-011.html",
@@ -118592,6 +118616,11 @@
      {}
     ]
    ],
+   "css/css-backgrounds/border-image-width-008-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-backgrounds/border-radius-001-ref.xht": [
     [
      {}
@@ -119862,6 +119891,11 @@
      {}
     ]
    ],
+   "css/css-contain/reference/contain-paint-clip-019-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-contain/reference/contain-paint-ifc-011-ref.html": [
     [
      {}
@@ -177587,6 +177621,11 @@
      {}
     ]
    ],
+   "webrtc/RTCIceTransport-extension-helper.js": [
+    [
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-addIceCandidate-expected.txt": [
     [
      {}
@@ -196986,6 +197025,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-minimum-height-flex-items-009.html": [
+    [
+     "/css/css-flexbox/flex-minimum-height-flex-items-009.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/flexbox_first-letter.html": [
     [
      "/css/css-flexbox/flexbox_first-letter.html",
@@ -199592,9 +199637,9 @@
      {}
     ]
    ],
-   "css/css-masking/inheritance.html": [
+   "css/css-masking/inheritance.sub.html": [
     [
-     "/css/css-masking/inheritance.html",
+     "/css/css-masking/inheritance.sub.html",
      {}
     ]
    ],
@@ -261298,6 +261343,18 @@
      {}
     ]
    ],
+   "speech-api/SpeechSynthesisErrorEvent-constructor.html": [
+    [
+     "/speech-api/SpeechSynthesisErrorEvent-constructor.html",
+     {}
+    ]
+   ],
+   "speech-api/SpeechSynthesisEvent-constructor.html": [
+    [
+     "/speech-api/SpeechSynthesisEvent-constructor.html",
+     {}
+    ]
+   ],
    "speech-api/SpeechSynthesisUtterance-basics.https.html": [
     [
      "/speech-api/SpeechSynthesisUtterance-basics.https.html",
@@ -312459,6 +312516,14 @@
    "61726c00873739c076812f72645d8324494ff44c",
    "reftest"
   ],
+  "css/css-backgrounds/border-image-width-008-ref.html": [
+   "9a066443920726b79d69a0301d814b5211b7df0a",
+   "support"
+  ],
+  "css/css-backgrounds/border-image-width-008.html": [
+   "3158cdb5717b873065bc70537075234492796501",
+   "reftest"
+  ],
   "css/css-backgrounds/border-images.html": [
    "930a1df3b7fda4098f36cc9691a544f46e2311d5",
    "visual"
@@ -315259,6 +315324,10 @@
    "17bcccdeff538ea869a51d703acb80e9acc4b0ca",
    "reftest"
   ],
+  "css/css-contain/contain-paint-clip-019.html": [
+   "bd761b8a523bd9fa05dd88f57b6b1317b5c4c40b",
+   "reftest"
+  ],
   "css/css-contain/contain-paint-ifc-011.html": [
    "b8a03936cbcc3ddcc88dc1237fab0af56f4bd72b",
    "reftest"
@@ -315587,6 +315656,10 @@
    "781a6d2f2f4e8810690ab321f7ff7bf3539044f3",
    "support"
   ],
+  "css/css-contain/reference/contain-paint-clip-019-ref.html": [
+   "2a529c12c48e630cc7df146fe3e685afd85a42f0",
+   "support"
+  ],
   "css/css-contain/reference/contain-paint-ifc-011-ref.html": [
    "229c8c2d74ba3f2b5cede6824091575835807092",
    "support"
@@ -316891,6 +316964,10 @@
    "ead7a424b374fddb247046d2a36a37a11669baae",
    "reftest"
   ],
+  "css/css-flexbox/flex-minimum-height-flex-items-009.html": [
+   "718386af02069fa1a3fff0ee5aaa10415ef4b23a",
+   "testharness"
+  ],
   "css/css-flexbox/flex-minimum-width-flex-items-001.xht": [
    "b8e2866edaa46af46900c287238894cd8ddef24c",
    "reftest"
@@ -331459,8 +331536,8 @@
    "c415eaaa67a2bc9a4b621700049eb0c0b60ec0a3",
    "testharness"
   ],
-  "css/css-masking/inheritance.html": [
-   "34f75859ee88833ed39a56bb6c443cd60586e1b5",
+  "css/css-masking/inheritance.sub.html": [
+   "95424204d5094bb1cbcd49e32f5e38c28d86d76f",
    "testharness"
   ],
   "css/css-masking/mask-image/mask-image-url-image-hash.html": [
@@ -395376,7 +395453,7 @@
    "support"
   ],
   "interfaces/speech-api.idl": [
-   "8e998df5bc3fa41d728b5c79d592746701bc3754",
+   "0b5c866b35c28d8e10261205fb6cbaa6962e480a",
    "support"
   ],
   "interfaces/storage.idl": [
@@ -395500,7 +395577,7 @@
    "support"
   ],
   "intersection-observer/bounding-box.html": [
-   "69052b11ce6c40c6a56fe2b723c70c49ddc36dd9",
+   "50f33f0443bb70e64bec2e2fcc930fa2b4118ed6",
    "testharness"
   ],
   "intersection-observer/client-rect.html": [
@@ -421772,7 +421849,15 @@
    "testharness"
   ],
   "speech-api/SpeechSynthesis-speak-without-activation-fails.tentative.html": [
-   "acf0d7d575b5dc7f9b348d82b056aa90089b6639",
+   "1b86552a1cbd8dabaf8b50f928823e743e4b46cb",
+   "testharness"
+  ],
+  "speech-api/SpeechSynthesisErrorEvent-constructor.html": [
+   "61e179cca47b70d001e5e081e87166ec1363714a",
+   "testharness"
+  ],
+  "speech-api/SpeechSynthesisEvent-constructor.html": [
+   "47a37d25d9723cadcb9da2d778481fd9561c37aa",
    "testharness"
   ],
   "speech-api/SpeechSynthesisUtterance-basics.https-expected.txt": [
@@ -421792,7 +421877,7 @@
    "testharness"
   ],
   "speech-api/idlharness.window-expected.txt": [
-   "12eedafdd950022cc0b2dbd638ee108842ddac06",
+   "e913d754fb7115c56329bbefe1f0d22d09e5c4b4",
    "support"
   ],
   "speech-api/idlharness.window.js": [
@@ -426808,7 +426893,7 @@
    "support"
   ],
   "webaudio/idlharness.https.window-expected.txt": [
-   "ebd89e3f1de980e2c1195db2c22395fca07e0b3f",
+   "c1e94f9344adda65b09912eb1fe20a9d26386edc",
    "support"
   ],
   "webaudio/idlharness.https.window.js": [
@@ -428539,8 +428624,12 @@
    "8fcf2e214bb23a9f5f023c4d69398c918ca8e49d",
    "support"
   ],
+  "webrtc/RTCIceTransport-extension-helper.js": [
+   "659ec59b8df2847e7e2a3c513d3d6fd01027f16d",
+   "support"
+  ],
   "webrtc/RTCIceTransport-extension.https.html": [
-   "9c6cec7e1e4994ee58f6822d77048eca8e8af569",
+   "5adee9fbe61eb9a8f7235b7faa6670eaea45cc89",
    "testharness"
   ],
   "webrtc/RTCIceTransport.html": [
@@ -428848,15 +428937,15 @@
    "testharness"
   ],
   "webrtc/RTCQuicStream.https.html": [
-   "1e08016d75af57307d922b9876605e8152f92768",
+   "33025451b252e1bdf2491fa118e072f4b8711d12",
    "testharness"
   ],
   "webrtc/RTCQuicTransport-helper.js": [
-   "50d9e6666a2d3bf8b3128f94b3c902579fabc0f5",
+   "3ea19d7a78e4a3788e97d0fa537d51d4b9e6b4fc",
    "support"
   ],
   "webrtc/RTCQuicTransport.https.html": [
-   "703f424a63851996b3708fe66caff63c7e0f5502",
+   "ec79bc228ad21de3a9ce3c2ee812a7d50e57571b",
    "testharness"
   ],
   "webrtc/RTCRtpCapabilities-helper.js": [
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008-ref.html
new file mode 100644
index 0000000..9a06644
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>border-image with different widths</title>
+<style>
+    #ref {
+        width: 360px;
+        height: 240px;
+        border-style: solid;
+        border-width: 40px 40px 20px 0px;
+        border-image-source: url("support/border.png");
+        border-image-slice: 27;
+    }
+</style>
+<body>
+    <div id="ref"></div>
+</body>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008.html
new file mode 100644
index 0000000..3158cdb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/border-image-width-008.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>border-image-width has the same effect as a border-width and the image is displayed even if border-width is zero</title>
+<link rel="match" href="border-image-width-008-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#propdef-border-image-width">
+<style>
+    #test {
+        width: 400px;
+        height: 300px;
+        border-style: solid;
+        /* Note: Chrome does not display an image if border-width is 0 */
+        border-width: 0px;
+        border-image-source: url("support/border.png");
+        border-image-width: 40px 40px 20px 0px;
+        border-image-slice: 27;
+    }
+</style>
+<body>
+    <div id="test"></div>
+</body>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-paint-clip-019.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-paint-clip-019.html
new file mode 100644
index 0000000..bd761b8a5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-paint-clip-019.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+
+<meta charset="UTF-8">
+
+<title>CSS Containment Test: 'contain: paint' and clipping prevents scrollbars</title>
+
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-contain-1/#containment-paint">
+<link rel="match" href="reference/contain-paint-clip-019-ref.html">
+
+<meta content="This test checks that the paint containment of an element clips contents.
+  It should also prevent layout overflow from being propagated to ancestors." name="assert">
+<meta content="" name="flags">
+
+<style>
+  #container {
+    contain: paint;
+    width: 100px;
+    height: 100px;
+  }
+  #green {
+    background-color: green;
+    width: 100px;
+    height: 100px;
+  }
+  #red {
+    background-color: red;
+    width: 100px;
+    height: 10000px;
+  }
+</style>
+
+<p>Test passes if there there is a green square. No red and no scrollbars should be visible.</p>
+
+<div id="container">
+  <div id="green"></div>
+  <div id="red"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/reference/contain-paint-clip-019-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/reference/contain-paint-clip-019-ref.html
new file mode 100644
index 0000000..2a529c1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/reference/contain-paint-clip-019-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+
+<meta charset="UTF-8">
+
+<title>CSS Reference Test</title>
+
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+
+<style>
+  #green {
+    background-color: green;
+    width: 100px;
+    height: 100px;
+  }
+</style>
+
+<p>Test passes if there there is a green square. No red and no scrollbars should be visible.</p>
+
+<div id="green"></div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-009.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-009.html
new file mode 100644
index 0000000..718386a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-flexbox/flex-minimum-height-flex-items-009.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Tests correct handling of min-height: auto with dynamic changes</title>
+<link rel="help" href="http://www.w3.org/TR/css-flexbox-1/#min-size-auto" title="4.5. Implied Minimum Size of Flex Items" />
+<link rel="author" title="Google Inc." href="http://www.google.com/">
+<link href="support/flexbox.css" rel="stylesheet">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<style>
+.container {
+  height: 300px;
+  outline: 2px solid black;
+}
+
+.inner
+{
+  width: 400px;
+  flex: 1;
+  background-color: green;
+}
+#container2 .flexbox > * { flex-basis: 0; }
+#container2 .column > * { flex-basis: auto; }
+</style>
+<script>
+function change() {
+  var container = document.getElementById('container');
+  container.offsetHeight;
+  container.style.height = '80px';
+  container = document.getElementById('container2');
+  container.offsetHeight;
+  container.style.height = '80px';
+  checkLayout('.container');
+}
+</script>
+<body onload="change()">
+<p>Green rectangle should be entirely within the black rectangle</p>
+<div id="log"></div>
+<div id="container" class="container">
+  <div class="flexbox column" style="height: 100%;">
+    <div class="flexbox flex-one">
+        <div class="flexbox column">
+          <div class="flexbox column flex-one">
+            <div class="inner" data-expected-height="80">
+            </div>
+          </div>
+        </div>
+    </div>
+  </div>
+</div>
+
+<div id="container2" class="container">
+  <div class="flexbox column" style="height: 100%;">
+    <div class="flexbox flex-one">
+        <div class="flexbox column">
+          <div class="flexbox column flex-one">
+            <div class="inner" data-expected-height="80">
+            </div>
+          </div>
+        </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.sub.html
similarity index 86%
rename from third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.html
rename to third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.sub.html
index 34f7585..9542420 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/inheritance.sub.html
@@ -16,17 +16,17 @@
 </div>
 <script>
 assert_not_inherited('clip', 'auto', 'rect(10px, 20px, 30px, 40px)');
-assert_not_inherited('clip-path', 'none', 'url("https://example.com/")');
+assert_not_inherited('clip-path', 'none', 'url("http://{{host}}/")');
 assert_inherited('clip-rule', 'nonzero', 'evenodd');
 assert_not_inherited('mask-border-mode', 'alpha', 'luminance');
 assert_not_inherited('mask-border-outset', '0', '10px 20px 30px 40px');
 assert_not_inherited('mask-border-repeat', 'stretch', 'round space');
 assert_not_inherited('mask-border-slice', '0', '1 2 3 4 fill');
-assert_not_inherited('mask-border-source', 'none', 'url("https://example.com/")');
+assert_not_inherited('mask-border-source', 'none', 'url("http://{{host}}/")');
 assert_not_inherited('mask-border-width', 'auto', '10px 20px 30px 40px');
 assert_not_inherited('mask-clip', 'border-box', 'no-clip');
 assert_not_inherited('mask-composite', 'add', 'exclude');
-assert_not_inherited('mask-image', 'none', 'url("https://example.com/")');
+assert_not_inherited('mask-image', 'none', 'url("http://{{host}}/")');
 assert_not_inherited('mask-mode', 'match-source', 'luminance');
 assert_not_inherited('mask-origin', 'border-box', 'padding-box');
 assert_not_inherited('mask-position', '0% 0%', '10px 20px');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/bounding-box.html b/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/bounding-box.html
index 69052b1..50f33f04 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/bounding-box.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/bounding-box.html
@@ -13,7 +13,7 @@
   overflow: visible;
   height: 200px;
   width: 160px;
-  border: 7px solid black;
+  border: 8px solid black;
 }
 #target {
   margin: 10px;
@@ -50,12 +50,35 @@
   var targetBounds = clientBounds(target);
   target.style.transform = "translateY(195px)";
   runTestCycle(step1, "target.style.transform = 'translateY(195px)'");
-  checkLastEntry(entries, 0, targetBounds.concat(0, 0, 0, 0, 8, 182, 8, 222, false));
+  checkLastEntry(entries, 0, targetBounds.concat(0, 0, 0, 0, 8, 184, 8, 224, false));
 }
 
 function step1() {
   var targetBounds = clientBounds(target);
-  target.style.transform = "";
-  checkLastEntry(entries, 1, targetBounds.concat(25, 145, 220, 222, 8, 182, 8, 222, true));
+  target.style.transform = "translateY(300px)";
+  runTestCycle(step2, "target.style.transform = 'translateY(300px)'");
+  checkLastEntry(entries, 1, targetBounds.concat(26, 146, 221, 224, 8, 184, 8, 224, true));
 }
+
+function step2() {
+  var targetBounds = clientBounds(target);
+  target.style.transform = "";
+  target.style.zoom = "2";
+  runTestCycle(step3, "target.style.zoom = 2");
+  checkLastEntry(entries, 2, targetBounds.concat(0, 0, 0, 0, 8, 184, 8, 224, false));
+}
+
+function step3() {
+  var targetBounds = clientBounds(target);
+  var intersectionWidth = (
+      176  // root width including border
+      -8   // root left border
+      -20  // target left margin * target zoom
+  ) / 2;   // convert to target's zoom factor.
+  var intersectionHeight = (216 - 8 - 20) / 2;
+  var intersectionRect = [targetBounds[0], targetBounds[0] + intersectionWidth,
+                          targetBounds[2], targetBounds[2] + intersectionHeight];
+  checkLastEntry(entries, 3, targetBounds.concat(intersectionRect).concat(8, 184, 8, 224, true));
+}
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https-expected.txt
index 10b1fd1..28a5b78 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https-expected.txt
@@ -11,6 +11,7 @@
 PASS SW-fallbacked redirect to same-origin other-scope.
 PASS SW-fallbacked redirect to other-origin out-scope.
 PASS SW-fallbacked redirect to other-origin in-scope.
+PASS SW-fallbacked redirect to other-origin and back to same-origin.
 PASS SW-generated redirect to same-origin out-scope.
 FAIL SW-generated redirect to same-origin out-scope with a hash fragment. assert_object_equals: Intercepted URLs should match. property "0" expected ["https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F#ref"] got ["https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F"]
 FAIL SW-generated redirect to same-origin out-scope with different hash fragments. assert_object_equals: Intercepted URLs should match. property "0" expected ["https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F%23ref2#ref"] got ["https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F%23ref2"]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
index b6281b9..6f03fd8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
@@ -1,6 +1,11 @@
 <!DOCTYPE html>
 <title>Service Worker: Navigation redirection</title>
 <meta name="timeout" content="long">
+<!-- empty variant tests document.location and intercepted URLs -->
+<meta name="variant" content="">
+<!-- client variant tests the Clients API (resultingClientId and Client.url) -->
+<meta name="variant" content="?client">
+
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
@@ -76,36 +81,208 @@
   assert_object_equals(urls, expected_urls, 'Intercepted URLs should match.');
 }
 
+// Checks |clients| returned from a worker. Only the client matching
+// |expected_final_client_tag| should be found. Returns true if a client was
+// found. Note that the final client is not necessarily found by this worker,
+// if the client is cross-origin.
+//
+// |clients| is an object like:
+// {x: {found: true, id: id1, url: url1}, b: {found: false}}
+function check_clients(clients,
+                       expected_id,
+                       expected_url,
+                       expected_final_client_tag,
+                       worker_name) {
+  let found = false;
+  Object.keys(clients).forEach(key => {
+    const info = clients[key];
+    if (info.found) {
+      assert_true(expected_final_client_tag,
+                  `${worker_name} client tag exists`);
+      assert_equals(key, expected_final_client_tag,
+                    `${worker_name} client tag matches`);
+      assert_equals(info.id, expected_id, `${worker_name} client id`);
+      assert_equals(info.url, expected_url, `${worker_name} client url`);
+      found = true;
+    }
+  });
+  return found;
+}
+
+function check_resulting_client_ids(infos, expected_infos, actual_ids, worker) {
+  assert_equals(infos.length, expected_infos.length,
+                `request length for ${worker}`);
+  for (var i = 0; i < infos.length; i++) {
+    const tag = expected_infos[i].resultingClientIdTag;
+    const url = expected_infos[i].url;
+    const actual_id = infos[i].resultingClientId;
+    const expected_id = actual_ids[tag];
+    assert_equals(typeof(actual_id), 'string',
+                  `resultingClientId for ${url} request to ${worker}`);
+    if (expected_id) {
+      assert_equals(requestInfos[0], expected_id,
+                    `resultingClientId for ${url} request to ${worker}`);
+    } else {
+      actual_ids[key] = actual_id;
+    }
+  }
+}
+
 // Creates an iframe and navigates to |url|, which is expected to start a chain
 // of redirects.
 // - |expected_last_url| is the expected window.location after the
 //   navigation.
+//
 // - |expected_request_infos| is the expected requests that the service workers
 //   were dispatched fetch events for. The format is:
 //   [
-//     [{url: url1}, {url: url2}], // requests from workers[0],
-//     [{url: url1},               // requests from workers[1],
-//     [{url: url1}, {url: url2}]  // requests from cross-origin worker
+//     [
+//       // Requests received by workers[0].
+//       {url: url1, resultingClientIdTag: 'a'},
+//       {url: url2, resultingClientIdTag: 'a'}
+//     ],
+//     [
+//       // Requests received by workers[1].
+//       {url: url3, resultingClientIdTag: 'a'}
+//     ],
+//     [
+//       // Requests received by the cross-origin worker.
+//       {url: url4, resultingClientIdTag: 'x'}
+//       {url: url5, resultingClientIdTag: 'x'}
+//     ]
 //   ]
+//   Here, |url| is |event.request.url| and |resultingClientIdTag| represents
+//   |event.resultingClientId|. Since the actual client ids are not known
+//   beforehand, the expectation isn't the literal expected value, but all equal
+//   tags must map to the same actual id.
+//
+// - |expected_final_client_tag| is the resultingClientIdTag that is
+//   expected to map to the created client's id. This is null if there
+//   is no such tag, which can happen when the final request was a cross-origin
+//   redirect to out-scope, so no worker received a fetch event whose
+//   resultingClientId is the id of the resulting client.
+//
+// In the example above:
+// - workers[0] receives two requests with the same resultingClientId.
+// - workers[1] receives one request also with that resultingClientId.
+// - The cross-origin worker receives two requests with the same
+//   resultingClientId which differs from the previous one.
+// - Assuming |expected_final_client_tag| is 'x', then the created
+//   client has the id seen by the cross-origin worker above.
 function redirect_test(url,
                        expected_last_url,
                        expected_request_infos,
+                       expected_final_client_tag,
                        test_name) {
   promise_test(async t => {
     const frame = await with_iframe(url);
     t.add_cleanup(() => { frame.remove(); });
 
-    const expected_intercepted_urls = expected_request_infos.map(requests => {
-      return requests.map(info => {
-        return info.url;
-      });
-    });
-    await check_all_intercepted_urls(expected_intercepted_urls);
-    const last_url = await send_to_iframe(frame, 'getLocation');
-    assert_equals(last_url, expected_last_url, 'Last URL should match.');
+    // Switch on variant.
+    if (document.location.search == '?client') {
+      return client_variant_test(url, expected_last_url, expected_request_infos,
+                                 expected_final_client_tag, test_name);
+    }
+
+    return default_variant_test(url, expected_last_url, expected_request_infos,
+                                frame, test_name);
   }, test_name);
 }
 
+// The default variant tests the request interception chain and
+// resulting document.location.
+async function default_variant_test(url,
+                                    expected_last_url,
+                                    expected_request_infos,
+                                    frame,
+                                    test_name) {
+  const expected_intercepted_urls = expected_request_infos.map(
+      requests_for_worker => {
+    return requests_for_worker.map(info => {
+      return info.url;
+    });
+  });
+  await check_all_intercepted_urls(expected_intercepted_urls);
+  const last_url = await send_to_iframe(frame, 'getLocation');
+  assert_equals(last_url, expected_last_url, 'Last URL should match.');
+}
+
+// The "client" variant tests the Clients API using resultingClientId.
+async function client_variant_test(url,
+                                   expected_last_url,
+                                   expected_request_infos,
+                                   expected_final_client_tag,
+                                   test_name) {
+  // Request infos is an array like:
+  // [
+  //   [{url: url1, resultingClientIdTag: tag1}],
+  //   [{url: url2, resultingClientIdTag: tag2}],
+  //   [{url: url3: resultingClientIdTag: tag3}]
+  // ]
+  const requestInfos = await get_all_request_infos();
+
+  // We check the actual infos against the expected ones, and learn the
+  // actual ids as we go.
+  const actual_ids = {};
+  check_resulting_client_ids(requestInfos[0],
+                             expected_request_infos[0],
+                             actual_ids,
+                             'worker0');
+  check_resulting_client_ids(requestInfos[1],
+                             expected_request_infos[1],
+                             actual_ids,
+                             'worker1');
+  check_resulting_client_ids(requestInfos[2],
+                             expected_request_infos[2],
+                             actual_ids,
+                             'crossOriginWorker');
+
+  // Now |actual_ids| maps tag to actual id:
+  // {x: id1, b: id2, c: id3}
+  // Ask each worker to try to resolve the actual ids to clients.
+  // Only |expected_final_client_tag| should resolve to a client.
+  const client_infos = await get_all_clients(actual_ids);
+
+  // Client infos is an object like:
+  // {
+  //   worker0: {x: {found: true, id: id1, url: url1}, b: {found: false}},
+  //   worker1: {x: {found: true, id: id1, url: url1}},
+  //   crossOriginWorker: {x: {found: false}}, {b: {found: false}}
+  // }
+  //
+  // Now check each client info. check_clients() verifies each info: only
+  // |expected_final_client_tag| should ever be found and the found client
+  // should have the expected url and id. A wrinkle is that not all workers
+  // will find the client, if they are cross-origin to the client. This
+  // means check_clients() trivially passes if no clients are found. So
+  // additionally check that at least one worker found the client (|found|),
+  // if that was expected (|expect_found|).
+  let found = false;
+  const expect_found = !!expected_final_client_tag;
+  const expected_id = actual_ids[expected_final_client_tag];
+  found = check_clients(client_infos.worker0,
+                        expected_id,
+                        expected_last_url,
+                        expected_final_client_tag,
+                        'worker0');
+  found = check_clients(client_infos.worker1,
+                        expected_id,
+                        expected_last_url,
+                        expected_final_client_tag,
+                        'worker1') || found;
+  found = check_clients(client_infos.crossOriginWorker,
+                        expected_id,
+                        expected_last_url,
+                        expected_final_client_tag,
+                        'crossOriginWorker') || found;
+  assert_equals(found, expect_found, 'client found');
+
+  if (!expect_found) {
+    // TODO(falken): Ask the other origin frame if it has a client of the
+    // expected URL.
+  }
+}
+
 window.addEventListener('message', on_message, false);
 
 function on_message(e) {
@@ -129,6 +306,27 @@
   });
 }
 
+async function get_all_clients(actual_ids) {
+  const client_infos = {};
+  client_infos['worker0'] = await get_clients(workers[0], actual_ids);
+  client_infos['worker1'] = await get_clients(workers[1], actual_ids);
+  client_infos['crossOriginWorker'] =
+      await send_to_iframe(other_origin_frame,
+                           {command: 'get_clients', actual_ids});
+  return client_infos;
+}
+
+function get_clients(worker, actual_ids) {
+  return new Promise(resolve => {
+    var channel = new MessageChannel();
+    channel.port1.onmessage = (msg) => {
+      resolve(msg.data.clients);
+    };
+    worker.postMessage({command: 'getClients', actual_ids, port: channel.port2},
+                       [channel.port2]);
+  });
+}
+
 // Returns an array of the URLs that |worker| received fetch events for:
 //   [url1, url2]
 async function get_intercepted_urls(worker) {
@@ -138,7 +336,10 @@
 
 // Returns the requests that |worker| received fetch events for. The return
 // value is an array of format:
-//   [{url: url1}, {url: url2}]
+// [
+//   {url: url1, resultingClientId: id},
+//   {url: url2, resultingClientId: id}
+// ]
 function get_request_infos(worker) {
   return new Promise(resolve => {
     var channel = new MessageChannel();
@@ -150,6 +351,29 @@
   });
 }
 
+// Returns an array of the requests the workers received fetch events for:
+// [
+//   // Requests from workers[0].
+//   [
+//     {url: url1, resultingClientIdTag: tag1},
+//     {url: url2, resultingClientIdTag: tag1}
+//   ],
+//
+//   // Requests from workers[1].
+//   [{url: url3, resultingClientIdTag: tag2}],
+//
+//   // Requests from the cross-origin worker.
+//   []
+// ]
+async function get_all_request_infos()  {
+  const request_infos = [];
+  request_infos.push(await get_request_infos(workers[0]));
+  request_infos.push(await get_request_infos(workers[1]));
+  request_infos.push(await send_to_iframe(other_origin_frame,
+                                          {command: 'get_request_infos'}));
+  return request_infos;
+}
+
 let url;
 let url1;
 let url2;
@@ -159,7 +383,8 @@
 redirect_test(
     OUT_SCOPE + 'url=' + encodeURIComponent(url),
     url,
-    [[{url}], [], []],
+    [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'Normal redirect to same-origin scope.');
 
 
@@ -167,30 +392,33 @@
 redirect_test(
     OUT_SCOPE + 'url=' + encodeURIComponent(SCOPE1) + '#ref',
     url,
-    [[{url}], [], []],
+    [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'Normal redirect to same-origin scope with a hash fragment.');
 
 url = SCOPE1 + '#ref2';
 redirect_test(
     OUT_SCOPE + 'url=' + encodeURIComponent(url) + '#ref',
     url,
-    [[{url}], [], []],
+    [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'Normal redirect to same-origin scope with different hash fragments.');
 
 url = OTHER_ORIGIN_SCOPE;
 redirect_test(
     OUT_SCOPE + 'url=' + encodeURIComponent(url),
     url,
-    [[], [], [{url}]],
+    [[], [], [{url, resultingClientIdTag: 'x'}]],
+    'x',
     'Normal redirect to other-origin scope.');
 
-
 // SW fallbacked redirect. SW doesn't handle the fetch request.
 url = SCOPE1 + 'url=' + encodeURIComponent(OUT_SCOPE);
 redirect_test(
     url,
     OUT_SCOPE,
-    [[{url}], [], []],
+    [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'SW-fallbacked redirect to same-origin out-scope.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1);
@@ -198,7 +426,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'SW-fallbacked redirect to same-origin same-scope.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1) + '#ref';
@@ -206,7 +442,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'SW-fallbacked redirect to same-origin same-scope with a hash fragment.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(SCOPE1 + '#ref2') + '#ref';
@@ -214,7 +458,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'SW-fallbacked redirect to same-origin same-scope with different hash ' +
     'fragments.');
 
@@ -223,7 +475,12 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [{url: url2}], []],
+    [
+      [{url: url1, resultingClientIdTag: 'x'}],
+      [{url: url2, resultingClientIdTag: 'x'}],
+      []
+    ],
+    'x',
     'SW-fallbacked redirect to same-origin other-scope.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
@@ -231,7 +488,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    'x',
     'SW-fallbacked redirect to other-origin out-scope.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
@@ -239,10 +497,32 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], [{url: url2}]],
+    [
+      [{url: url1, resultingClientIdTag: 'a'}],
+      [],
+      [{url: url2, resultingClientIdTag: 'x'}]
+    ],
+    'x',
     'SW-fallbacked redirect to other-origin in-scope.');
 
 
+url3 = SCOPE1;
+url2 = OTHER_ORIGIN_SCOPE + 'url=' + encodeURIComponent(url3);
+url1 = SCOPE1 + 'url=' + encodeURIComponent(url2);
+redirect_test(
+    url1,
+    url3,
+    [
+      [
+        {url: url1, resultingClientIdTag: 'a'},
+        {url: url3, resultingClientIdTag: 'x'}
+      ],
+      [],
+      [{url: url2, resultingClientIdTag: 'b'}]
+    ],
+    'x',
+    'SW-fallbacked redirect to other-origin and back to same-origin.');
+
 // SW generated redirect.
 // SW: event.respondWith(Response.redirect(params['url']));
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE);
@@ -250,7 +530,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    null,
     'SW-generated redirect to same-origin out-scope.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref';
@@ -258,7 +539,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'SW-generated redirect to same-origin out-scope with a hash fragment.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE + '#ref2') + '#ref';
@@ -266,7 +548,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'SW-generated redirect to same-origin out-scope with different hash ' +
     'fragments.');
 
@@ -275,7 +558,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'SW-generated redirect to same-origin same-scope.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(SCOPE2);
@@ -283,7 +574,12 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [{url: url2}], []],
+    [
+      [{url: url1, resultingClientIdTag: 'x'}],
+      [{url: url2, resultingClientIdTag: 'x'}],
+      []
+    ],
+    'x',
     'SW-generated redirect to same-origin other-scope.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
@@ -291,7 +587,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    null,
     'SW-generated redirect to other-origin out-scope.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
@@ -300,10 +597,11 @@
     url1,
     url2,
     [
-      [{url: url1}],
+      [{url: url1, resultingClientIdTag: 'a'}],
       [],
-      [{url: url2}]
+      [{url: url2, resultingClientIdTag: 'x'}]
     ],
+    'x',
     'SW-generated redirect to other-origin in-scope.');
 
 
@@ -314,7 +612,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'SW-fetched redirect to same-origin out-scope.');
 
 url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE1);
@@ -322,7 +621,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'SW-fetched redirect to same-origin same-scope.');
 
 url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(SCOPE2);
@@ -331,10 +638,11 @@
     url1,
     url2,
     [
-      [{url: url1}],
-      [{url: url2}],
+      [{url: url1, resultingClientIdTag: 'x'}],
+      [{url: url2, resultingClientIdTag: 'x'}],
       []
     ],
+    'x',
     'SW-fetched redirect to same-origin other-scope.');
 
 url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
@@ -342,7 +650,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    null,
     'SW-fetched redirect to other-origin out-scope.');
 
 url1 = SCOPE1 + 'sw=fetch&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
@@ -351,10 +660,11 @@
     url1,
     url2,
     [
-      [{url: url1}],
+      [{url: url1, resultingClientIdTag: 'a'}],
       [],
-      [{url: url2}]
+      [{url: url2, resultingClientIdTag: 'x'}]
     ],
+    'x',
     'SW-fetched redirect to other-origin in-scope.');
 
 
@@ -366,7 +676,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'Redirect to same-origin out-scope with opaque redirect response.');
 
 url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(SCOPE1);
@@ -374,7 +685,15 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}, {url: url2}], [], []],
+    [
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
+      [],
+      []
+    ],
+    'x',
     'Redirect to same-origin same-scope with opaque redirect response.');
 
 url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(SCOPE2);
@@ -382,7 +701,12 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [{url: url2}], []],
+    [
+      [{url: url1, resultingClientIdTag: 'x'}],
+      [{url: url2, resultingClientIdTag: 'x'}],
+      []
+    ],
+    'x',
     'Redirect to same-origin other-scope with opaque redirect response.');
 
 url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OTHER_ORIGIN_OUT_SCOPE);
@@ -390,7 +714,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    null,
     'Redirect to other-origin out-scope with opaque redirect response.');
 
 url1 = SCOPE1 + 'sw=manual&url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
@@ -398,12 +723,18 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], [{url: url2}]],
+    [
+      [{url: url1, resultingClientIdTag: 'a'}],
+      [],
+      [{url: url2, resultingClientIdTag: 'x'}]
+    ],
+    'x',
     'Redirect to other-origin in-scope with opaque redirect response.');
 
 url= SCOPE1 + 'sw=manual&noLocationRedirect';
 redirect_test(
-    url, url, [[{url}], [], []],
+    url, url, [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'No location redirect response.');
 
 
@@ -414,7 +745,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'Redirect to same-origin out-scope with opaque redirect response which ' +
     'is passed through Cache.');
 
@@ -424,10 +756,14 @@
     url1,
     url2,
     [
-      [{url: url1}, {url: url2}],
+      [
+        {url: url1, resultingClientIdTag: 'x'},
+        {url: url2, resultingClientIdTag: 'x'}
+      ],
       [],
       []
     ],
+    'x',
     'Redirect to same-origin same-scope with opaque redirect response which ' +
     'is passed through Cache.');
 
@@ -437,10 +773,11 @@
     url1,
     url2,
     [
-      [{url: url1}],
-      [{url: url2}],
+      [{url: url1, resultingClientIdTag: 'x'}],
+      [{url: url2, resultingClientIdTag: 'x'}],
       []
     ],
+    'x',
     'Redirect to same-origin other-scope with opaque redirect response which ' +
     'is passed through Cache.');
 
@@ -450,7 +787,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1}], [], []],
+    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
+    null,
     'Redirect to other-origin out-scope with opaque redirect response which ' +
     'is passed through Cache.');
 
@@ -461,10 +799,11 @@
     url1,
     url2,
     [
-      [{url: url1}],
+      [{url: url1, resultingClientIdTag: 'a'}],
       [],
-      [{url: url2}],
+      [{url: url2, resultingClientIdTag: 'x'}],
     ],
+    'x',
     'Redirect to other-origin in-scope with opaque redirect response which ' +
     'is passed through Cache.');
 
@@ -472,7 +811,8 @@
 redirect_test(
     url,
     url,
-    [[{url}], [], []],
+    [[{url, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'No location redirect response via Cache.');
 
 // Clean up the test environment. This promise_test() needs to be the last one.
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https_client-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https_client-expected.txt
new file mode 100644
index 0000000..8856e57f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https_client-expected.txt
@@ -0,0 +1,41 @@
+This is a testharness.js-based test.
+PASS initialize global state
+FAIL Normal redirect to same-origin scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py? request to worker0 expected "string" but got "undefined"
+FAIL Normal redirect to same-origin scope with a hash fragment. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?#ref request to worker0 expected "string" but got "undefined"
+FAIL Normal redirect to same-origin scope with different hash fragments. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?#ref2 request to worker0 expected "string" but got "undefined"
+FAIL Normal redirect to other-origin scope. assert_equals: resultingClientId for https://www1.web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py? request to crossOriginWorker expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to same-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to same-origin same-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to same-origin same-scope with a hash fragment. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F#ref request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to same-origin same-scope with different hash fragments. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F%23ref2#ref request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to same-origin other-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope2.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to other-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to other-origin in-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fallbacked redirect to other-origin and back to same-origin. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3Furl%3Dhttps%253A%252F%252Fweb-platform.test%253A8444%252Fservice-workers%252Fservice-worker%252Fresources%252Fnavigation-redirect-scope1.py%253F request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to same-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to same-origin out-scope with a hash fragment. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F#ref request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to same-origin out-scope with different hash fragments. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F%23ref2#ref request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to same-origin same-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to same-origin other-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope2.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to other-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-generated redirect to other-origin in-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=gen&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fetched redirect to same-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=fetch&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fetched redirect to same-origin same-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=fetch&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fetched redirect to same-origin other-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=fetch&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope2.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fetched redirect to other-origin out-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=fetch&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL SW-fetched redirect to other-origin in-scope. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=fetch&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin out-scope with opaque redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin same-scope with opaque redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin other-scope with opaque redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope2.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to other-origin out-scope with opaque redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to other-origin in-scope with opaque redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL No location redirect response. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manual&noLocationRedirect request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin out-scope with opaque redirect response which is passed through Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin same-scope with opaque redirect response which is passed through Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to same-origin other-scope with opaque redirect response which is passed through Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&url=https%3A%2F%2Fweb-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope2.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to other-origin out-scope with opaque redirect response which is passed through Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-out-scope.py%3F request to worker0 expected "string" but got "undefined"
+FAIL Redirect to other-origin in-scope with opaque redirect response which is passed through Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&url=https%3A%2F%2Fwww1.web-platform.test%3A8444%2Fservice-workers%2Fservice-worker%2Fresources%2Fnavigation-redirect-scope1.py%3F request to worker0 expected "string" but got "undefined"
+FAIL No location redirect response via Cache. assert_equals: resultingClientId for https://web-platform.test:8444/service-workers/service-worker/resources/navigation-redirect-scope1.py?sw=manualThroughCache&noLocationRedirect request to worker0 expected "string" but got "undefined"
+PASS clean up global state
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/navigation-redirect-other-origin.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/navigation-redirect-other-origin.html
index 0d2825f..d82571d1a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/navigation-redirect-other-origin.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/navigation-redirect-other-origin.html
@@ -44,6 +44,20 @@
   });
 }
 
+function get_clients(worker, actual_ids) {
+  return new Promise(function(resolve) {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = (msg) => {
+        resolve(msg.data.clients);
+      };
+      worker.postMessage({
+        command: 'getClients',
+        actual_ids,
+        port: channel.port2
+      }, [channel.port2]);
+    });
+}
+
 window.addEventListener('message', on_message, false);
 
 function on_message(e) {
@@ -59,6 +73,11 @@
       .then(function(data) {
           send_result(e.data.id, data);
         });
+  } else if (command == 'get_clients') {
+    get_clients(worker, e.data.message.actual_ids)
+      .then(function(data) {
+          send_result(e.data.id, data);
+        });
   } else if (command == 'unregister') {
     registration.unregister()
       .then(function() {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
index bf0a3a8..0c5bc3bd 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
@@ -5,6 +5,13 @@
 
 var waitUntilPromiseList = [];
 
+// Sends the requests seen by this worker. The output is:
+// {
+//   requestInfos: [
+//     {url: url1, resultingClientId: id1},
+//     {url: url2, resultingClientId: id2},
+//   ]
+// }
 async function getRequestInfos(event) {
   // Wait for fetch events to finish.
   await Promise.all(waitUntilPromiseList);
@@ -15,8 +22,11 @@
   const requestList = await cache.keys();
   const requestInfos = [];
   for (let i = 0; i < requestList.length; i++) {
+    const response = await cache.match(requestList[i]);
+    const body = await response.json();
     requestInfos[i] = {
       url: requestList[i].url,
+      resultingClientId: body.resultingClientId
     };
   }
   await caches.delete(cacheName);
@@ -24,13 +34,50 @@
   event.data.port.postMessage({requestInfos});
 }
 
+// Sends the results of clients.get(id) from this worker. The
+// input is:
+// {
+//   actual_ids: {a: id1, b: id2, x: id3}
+// }
+//
+// The output is:
+// {
+//   clients: {
+//     a: {found: false},
+//     b: {found: false},
+//     x: {
+//       id: id3,
+//       url: url1,
+//       found: true
+//    }
+//   }
+// }
+async function getClients(event) {
+  // |actual_ids| is like:
+  // {a: id1, b: id2, x: id3}
+  const actual_ids = event.data.actual_ids;
+  const result = {}
+  for (let key of Object.keys(actual_ids)) {
+    const id = actual_ids[key];
+    const client = await self.clients.get(id);
+    if (client === undefined)
+      result[key] = {found: false};
+    else
+      result[key] = {found: true, url: client.url, id: client.id};
+  }
+  event.data.port.postMessage({clients: result});
+}
+
 self.addEventListener('message', async function(event) {
   if (event.data.command == 'getRequestInfos') {
     event.waitUntil(getRequestInfos(event));
     return;
   }
 
-  // TODO(falken): Add a getClientInfos command to test Clients API.
+  if (event.data.command == 'getClients') {
+    event.waitUntil(getClients(event));
+    return;
+  }
 });
 
 function get_query_params(url) {
@@ -49,7 +96,11 @@
 
 self.addEventListener('fetch', function(event) {
     var waitUntilPromise = caches.open(cacheName).then(function(cache) {
-      return cache.put(event.request, new Response());
+      const responseBody = {};
+      responseBody['resultingClientId'] = event.resultingClientId;
+      const headers = new Headers({'Content-Type': 'application/json'});
+      const response = new Response(JSON.stringify(responseBody), {headers});
+      return cache.put(event.request, response);
     });
     event.waitUntil(waitUntilPromise);
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webaudio/idlharness.https.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webaudio/idlharness.https.window-expected.txt
index ebd89e3..c1e94f9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webaudio/idlharness.https.window-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webaudio/idlharness.https.window-expected.txt
@@ -41,7 +41,7 @@
 PASS AudioContext interface: attribute baseLatency
 FAIL AudioContext interface: attribute outputLatency assert_true: The prototype object must have a property "outputLatency" expected true got false
 PASS AudioContext interface: operation getOutputTimestamp()
-FAIL AudioContext interface: operation resume() assert_own_property: interface prototype object missing non-static operation expected property "resume" missing
+PASS AudioContext interface: operation resume()
 PASS AudioContext interface: operation suspend()
 PASS AudioContext interface: operation close()
 FAIL AudioContext interface: operation createMediaElementSource(HTMLMediaElement) assert_own_property: interface prototype object missing non-static operation expected property "createMediaElementSource" missing
@@ -104,7 +104,7 @@
 PASS OfflineAudioContext interface: existence and properties of interface prototype object's "constructor" property
 PASS OfflineAudioContext interface: existence and properties of interface prototype object's @@unscopables property
 PASS OfflineAudioContext interface: operation startRendering()
-FAIL OfflineAudioContext interface: operation resume() assert_own_property: interface prototype object missing non-static operation expected property "resume" missing
+PASS OfflineAudioContext interface: operation resume()
 PASS OfflineAudioContext interface: operation suspend(double)
 PASS OfflineAudioContext interface: attribute length
 PASS OfflineAudioContext interface: attribute oncomplete
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension-helper.js b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension-helper.js
new file mode 100644
index 0000000..659ec59
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension-helper.js
@@ -0,0 +1,42 @@
+'use strict';
+
+// Construct an RTCIceTransport instance. The instance will automatically be
+// cleaned up when the test finishes.
+function makeIceTransport(t) {
+  const iceTransport = new RTCIceTransport();
+  t.add_cleanup(() => iceTransport.stop());
+  return iceTransport;
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates, then gather() them.
+// Returns a 2-list: [ RTCIceTransport, RTCIceTransport ]
+function makeAndGatherTwoIceTransports(t) {
+  const localTransport = makeIceTransport(t);
+  const remoteTransport = makeIceTransport(t);
+  localTransport.onicecandidate = e => {
+    if (e.candidate) {
+      remoteTransport.addRemoteCandidate(e.candidate);
+    }
+  };
+  remoteTransport.onicecandidate = e => {
+    if (e.candidate) {
+      localTransport.addRemoteCandidate(e.candidate);
+    }
+  };
+  localTransport.gather({});
+  remoteTransport.gather({});
+  return [ localTransport, remoteTransport ];
+}
+
+// Construct two RTCIceTransport instances, configure them to exchange
+// candidates and parameters, then gather() and start() them.
+// Returns a 2-list:
+//     [ controlling RTCIceTransport,
+//       controlled RTCIceTransport ]
+function makeGatherAndStartTwoIceTransports(t) {
+  const [ localTransport, remoteTransport ] = makeAndGatherTwoIceTransports(t);
+  localTransport.start(remoteTransport.getLocalParameters(), 'controlling');
+  remoteTransport.start(localTransport.getLocalParameters(), 'controlled');
+  return [ localTransport, remoteTransport ];
+}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension.https.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension.https.html
index 9c6cec7..5adee9f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCIceTransport-extension.https.html
@@ -3,12 +3,18 @@
 <title>RTCIceTransport-extensions.https.html</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="RTCIceTransport-extension-helper.js"></script>
 <script>
 'use strict';
 
 // These tests are based on the following extension specification:
 // https://w3c.github.io/webrtc-ice/
 
+// The following helper functions are called from
+// RTCIceTransport-extension-helper.js:
+//   makeIceTransport
+//   makeGatherAndStartTwoIceTransports
+
 function makeIceTransport(t) {
   const iceTransport = new RTCIceTransport();
   t.add_cleanup(() => iceTransport.stop());
@@ -240,22 +246,8 @@
    'later called with different remote parameters');
 
 promise_test(async t => {
-  const localTransport = makeIceTransport(t);
-  const remoteTransport = makeIceTransport(t);
-  localTransport.onicecandidate = e => {
-    if (e.candidate) {
-      remoteTransport.addRemoteCandidate(e.candidate);
-    }
-  };
-  remoteTransport.onicecandidate = e => {
-    if (e.candidate) {
-      localTransport.addRemoteCandidate(e.candidate);
-    }
-  };
-  localTransport.gather({});
-  remoteTransport.gather({});
-  localTransport.start(remoteTransport.getLocalParameters(), 'controlling');
-  remoteTransport.start(localTransport.getLocalParameters(), 'controlled');
+  const [ localTransport, remoteTransport ] =
+      makeGatherAndStartTwoIceTransports(t);
   const localWatcher = new EventWatcher(t, localTransport, 'statechange');
   const remoteWatcher = new EventWatcher(t, remoteTransport, 'statechange');
   await Promise.all([
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicStream.https.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicStream.https.html
index 1e08016d..33025451 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicStream.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicStream.https.html
@@ -3,6 +3,7 @@
 <title>RTCQuicStream.https.html</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="RTCIceTransport-extension-helper.js"></script>
 <script src="RTCQuicTransport-helper.js"></script>
 <script>
 'use strict';
@@ -11,10 +12,10 @@
 // https://w3c.github.io/webrtc-quic/
 
 // The following helper functions are called from RTCQuicTransport-helper.js:
-//   makeQuicTransport
+//   makeStandaloneQuicTransport
 
-test(t => {
-  const quicTransport = makeQuicTransport(t, []);
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
   const quicStream = quicTransport.createStream();
   assert_equals(quicStream.transport, quicTransport,
       'Expect transport to be set to the creating RTCQuicTransport.');
@@ -25,14 +26,14 @@
       'Expect write buffered amount to be 0.');
 }, 'createStream() returns an RTCQuicStream with initial properties set.');
 
-test(t => {
-  const quicTransport = makeQuicTransport(t, []);
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
   quicTransport.stop();
   assert_throws('InvalidStateError', () => quicTransport.createStream());
 }, 'createStream() throws if the transport is closed.');
 
-test(t => {
-  const quicTransport = makeQuicTransport(t, []);
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
   const firstQuicStream = quicTransport.createStream();
   const secondQuicStream = quicTransport.createStream();
   quicTransport.stop();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport-helper.js b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport-helper.js
index 50d9e66..3ea19d7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport-helper.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport-helper.js
@@ -1,10 +1,82 @@
 'use strict';
 
-function makeQuicTransport(t, certificates) {
-  const iceTransport = new RTCIceTransport();
-  t.add_cleanup(() => iceTransport.stop());
+// This file depends on RTCIceTransport-extension-helper.js which should be
+// loaded from the main HTML file.
+// The following helper functions are called from
+// RTCIceTransport-extension-helper.js:
+//   makeIceTransport
+//   makeGatherAndStartTwoIceTransports
+
+// Return a promise to generate an RTCCertificate with the given keygen
+// algorithm or a default one if none provided.
+function generateCertificate(keygenAlgorithm) {
+  return RTCPeerConnection.generateCertificate({
+    name: 'ECDSA',
+    namedCurve: 'P-256',
+    ...keygenAlgorithm,
+  });
+}
+
+// Construct an RTCQuicTransport instance with the given RTCIceTransport
+// instance and the given certificates. The RTCQuicTransport instance will be
+// automatically cleaned up when the test finishes.
+function makeQuicTransport(t, iceTransport, certificates) {
   const quicTransport = new RTCQuicTransport(iceTransport, certificates);
   t.add_cleanup(() => quicTransport.stop());
   return quicTransport;
 }
 
+// Construct an RTCQuicTransport instance with a new RTCIceTransport instance
+// and a single, newly-generated certificate. The RTCQuicTransport and
+// RTCIceTransport instances will be automatically cleaned up when the test
+// finishes.
+async function makeStandaloneQuicTransport(t) {
+  const certificate = await generateCertificate();
+  return makeQuicTransport(t, makeIceTransport(t), [ certificate ]);
+}
+
+// Construct two RTCQuicTransport instances and each call start() with the other
+// transport's local parameters.
+// Returns a 2-list:
+//     [ server RTCQuicTransport,
+//       client RTCQuicTransport ]
+async function makeAndStartTwoQuicTransports(t) {
+  const [ localCertificate, remoteCertificate ] =
+      await Promise.all([ generateCertificate(), generateCertificate() ]);
+  const [ localIceTransport, remoteIceTransport ] =
+      makeGatherAndStartTwoIceTransports(t);
+  const localQuicTransport =
+      makeQuicTransport(t, localIceTransport, [ localCertificate ]);
+  const remoteQuicTransport =
+      makeQuicTransport(t, remoteIceTransport, [ remoteCertificate ]);
+  localQuicTransport.start(remoteQuicTransport.getLocalParameters());
+  remoteQuicTransport.start(localQuicTransport.getLocalParameters());
+  return [ localQuicTransport, remoteQuicTransport ];
+}
+
+// Construct two RTCQuicTransport instances and wait for them to connect.
+// Returns a 2-list:
+//     [ server RTCQuicTransport,
+//       client RTCQuicTransport ]
+async function makeTwoConnectedQuicTransports(t) {
+  // Returns a promise that resolves when the transport fires a 'statechange'
+  // event to 'connected'.
+  function waitForConnected(transport) {
+    return new Promise((resolve, reject) => {
+      const eventHandler = t.step_func(() => {
+        assert_equals(transport.state, 'connected');
+        transport.removeEventListener('statechange', eventHandler, false);
+        resolve();
+      });
+      transport.addEventListener('statechange', eventHandler, false);
+    });
+  }
+  const [ localQuicTransport, remoteQuicTransport ] =
+      await makeAndStartTwoQuicTransports(t);
+  await Promise.all([
+    waitForConnected(localQuicTransport),
+    waitForConnected(remoteQuicTransport),
+  ]);
+  return [ localQuicTransport, remoteQuicTransport ];
+}
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport.https.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport.https.html
index 703f424a..ec79bc22 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCQuicTransport.https.html
@@ -3,6 +3,7 @@
 <title>RTCQuicTransport.https.html</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="RTCIceTransport-extension-helper.js"></script>
 <script src="RTCQuicTransport-helper.js"></script>
 <script>
 'use strict';
@@ -10,36 +11,31 @@
 // These tests are based on the following specification:
 // https://w3c.github.io/webrtc-quic/
 
+// The following helper functions are called from
+// RTCIceTransport-extension-helper.js:
+//   makeIceTransport
+//   makeAndGatherTwoIceTransports
+
 // The following helper functions are called from RTCQuicTransport-helper.js:
 //   makeQuicTransport
+//   makeStandaloneQuicTransport
+//   makeAndStartTwoQuicTransports
+//   makeTwoConnectedQuicTransports
 
-function generateCertificate(keygenAlgorithm) {
-  return RTCPeerConnection.generateCertificate({
-    name: 'ECDSA',
-    namedCurve: 'P-256',
-    ...keygenAlgorithm,
-  });
-}
-
-test(t => {
-  // Don't use the makeQuicTransport helper so that the transport property can
-  // be verified.
-  const iceTransport = new RTCIceTransport();
-  const quicTransport = new RTCQuicTransport(iceTransport, []);
-  t.add_cleanup(() => {
-    quicTransport.stop();
-    iceTransport.stop();
-  });
+promise_test(async t => {
+  const certificate = await generateCertificate();
+  const iceTransport = makeIceTransport(t);
+  const quicTransport = makeQuicTransport(t, iceTransport, [ certificate ]);
   assert_equals(quicTransport.transport, iceTransport,
       'Expect transport to be the same as the one passed in the constructor.');
   assert_equals(quicTransport.state, 'new', `Expect state to be 'new'.`);
   assert_object_equals(quicTransport.getLocalParameters(),
-      { role: 'auto', fingerprints: [] },
+      { role: 'auto', fingerprints: certificate.getFingerprints() },
       'Expect local parameters to be initialized.');
   assert_equals(quicTransport.getRemoteParameters(), null,
       'Expect no remote parameters.');
-  assert_array_equals(quicTransport.getCertificates(), [],
-      'Expect not certificates.');
+  assert_array_equals(quicTransport.getCertificates(), [ certificate ],
+      'Expect one certificate.');
   assert_array_equals(quicTransport.getRemoteCertificates(), [],
       'Expect no remote certificates.');
 }, 'RTCQuicTransport initial properties are set.');
@@ -48,7 +44,8 @@
   const [ firstCertificate, secondCertificate ] =
       await Promise.all([ generateCertificate(), generateCertificate() ]);
   const quicTransport =
-      makeQuicTransport(t, [ firstCertificate, secondCertificate ]);
+      makeQuicTransport(t, makeIceTransport(t),
+          [ firstCertificate, secondCertificate ]);
   assert_array_equals(quicTransport.getCertificates(),
       [ firstCertificate, secondCertificate ]);
 }, 'getCertificates() returns the certificates passed in the constructor.');
@@ -57,11 +54,13 @@
   const [ firstCertificate, secondCertificate ] =
       await Promise.all([ generateCertificate(), generateCertificate() ]);
   const quicTransport =
-      makeQuicTransport(t, [ firstCertificate, secondCertificate ]);
+      makeQuicTransport(t, makeIceTransport(t),
+          [ firstCertificate, secondCertificate ]);
   assert_object_equals(quicTransport.getLocalParameters(), {
     role: 'auto',
-    fingerprints: [ firstCertificate.getFingerprints()[0],
-        secondCertificate.getFingerprints()[0] ],
+    fingerprints:
+        [ firstCertificate.getFingerprints()[0],
+            secondCertificate.getFingerprints()[0] ],
   });
   assert_array_equals(quicTransport.getCertificates(),
       [ firstCertificate, secondCertificate ]);
@@ -71,21 +70,119 @@
 promise_test(async t => {
   const expiredCertificate = await generateCertificate({ expires: 0 });
   assert_throws(new TypeError(),
-      () => makeQuicTransport(t, [ expiredCertificate ]));
+      () => makeQuicTransport(t, makeIceTransport(t), [ expiredCertificate ]));
 }, 'RTCQuicTransport constructor throws if passed an expired certificate.');
 
-test(t => {
-  const iceTransport = new RTCIceTransport();
+promise_test(async t => {
+  const certificate = await generateCertificate();
+  const iceTransport = makeIceTransport(t);
   iceTransport.stop();
   assert_throws('InvalidStateError',
-      () => new RTCQuicTransport(iceTransport, []));
+      () => makeQuicTransport(t, iceTransport, [ certificate ]));
 }, 'RTCQuicTransport constructor throws if passed a closed RTCIceTransport.');
 
-test(t => {
-  const quicTransport = makeQuicTransport(t, []);
+promise_test(async t => {
+  const certificate = await generateCertificate();
+  const iceTransport = makeIceTransport(t);
+  const firstQuicTransport =
+      makeQuicTransport(t, iceTransport, [ certificate ]);
+  assert_throws('InvalidStateError',
+      () => makeQuicTransport(t, iceTransport, [ certificate ]));
+}, 'RTCQuicTransport constructor throws if passed an RTCIceTransport that ' +
+    'already has an active RTCQuicTransport.');
+
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
   quicTransport.stop();
   assert_equals(quicTransport.state, 'closed');
 }, `stop() changes state to 'closed'.`);
 
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
+  quicTransport.transport.stop();
+  assert_equals(quicTransport.state, 'closed');
+}, `RTCIceTransport.stop() changes RTCQuicTransport.state to 'closed'.`);
+
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
+  quicTransport.start(quicTransport.getLocalParameters());
+  assert_equals(quicTransport.state, 'new');
+}, 'start() with a non-started RTCIceTransport does not change state.');
+
+promise_test(async t => {
+  const certificate = await generateCertificate();
+  const [ localIceTransport, remoteIceTransport ] =
+      makeAndGatherTwoIceTransports(t);
+  const quicTransport =
+      makeQuicTransport(t, localIceTransport, [ certificate ]);
+  quicTransport.start(quicTransport.getLocalParameters());
+  const iceTransportWatcher =
+      new EventWatcher(t, remoteIceTransport, 'icecandidate');
+  await iceTransportWatcher.wait_for('icecandidate');
+  localIceTransport.start(remoteIceTransport.getLocalParameters(),
+      'controlling');
+  assert_equals(quicTransport.state, 'connecting');
+}, 'start() with a non-started RTCIceTransport later changes state to ' +
+    `'connecting' once the RTCIceTransport.start() is called.`);
+
+promise_test(async t => {
+  const certificate = await generateCertificate();
+  const [ localIceTransport, remoteIceTransport ] =
+      makeAndGatherTwoIceTransports(t);
+  const quicTransport =
+      makeQuicTransport(t, localIceTransport, [ certificate ]);
+  const iceTransportWatcher =
+      new EventWatcher(t, remoteIceTransport, 'icecandidate');
+  await iceTransportWatcher.wait_for('icecandidate');
+  localIceTransport.start(remoteIceTransport.getLocalParameters());
+  quicTransport.start(quicTransport.getLocalParameters());
+  assert_equals(quicTransport.state, 'connecting');
+}, `start() with a started RTCIceTransport changes state to 'connecting'.`);
+
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
+  quicTransport.stop();
+  assert_throws('InvalidStateError',
+      () => quicTransport.start(quicTransport.getLocalParameters()));
+}, 'start() throws if called after stop().');
+
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
+  quicTransport.transport.stop();
+  assert_throws('InvalidStateError',
+      () => quicTransport.start(quicTransport.getLocalParameters()));
+}, 'start() throws if called after the RTCIceTransport has stopped.');
+
+promise_test(async t => {
+  const quicTransport = await makeStandaloneQuicTransport(t);
+  quicTransport.start(quicTransport.getLocalParameters());
+  assert_throws('InvalidStateError',
+      () => quicTransport.start(quicTransport.getLocalParameters()));
+}, 'start() throws if called twice.');
+
+promise_test(async t => {
+  const [ localQuicTransport, remoteQuicTransport ] =
+      await makeAndStartTwoQuicTransports(t);
+  const localWatcher = new EventWatcher(t, localQuicTransport, 'statechange');
+  const remoteWatcher = new EventWatcher(t, remoteQuicTransport, 'statechange');
+  await Promise.all([
+    localWatcher.wait_for('statechange').then(() => {
+      assert_equals(localQuicTransport.state, 'connected');
+    }),
+    remoteWatcher.wait_for('statechange').then(() => {
+      assert_equals(remoteQuicTransport.state, 'connected');
+    }),
+  ]);
+}, 'Two RTCQuicTransports connect to each other.');
+
+promise_test(async t => {
+  const [ localQuicTransport, remoteQuicTransport ] =
+      await makeTwoConnectedQuicTransports(t);
+  localQuicTransport.stop();
+  const remoteWatcher = new EventWatcher(t, remoteQuicTransport, 'statechange');
+  await remoteWatcher.wait_for('statechange');
+  assert_equals(remoteQuicTransport.state, 'closed');
+}, `stop() fires a statechange event to 'closed' on the remote transport`);
+
 </script>
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers-expected.txt
index 81503474..61921b7 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers-expected.txt
@@ -75,6 +75,16 @@
     serverIPAddress : ""
     startedDateTime : <string>
     time : <number>
-    timings : <object>
+    timings : {
+        _blocked_proxy : 0.752
+        _blocked_queueing : 0.9689999860711396
+        blocked : 3.8059999860711398
+        connect : 229.12300000000002
+        dns : 84.076
+        receive : 6.20800000615418
+        send : 0.5549999999999784
+        ssl : 98.702
+        wait : 568.4820000130125
+    }
 }
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers.js b/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers.js
index 13752cb..4d28d75a 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/resource-har-headers.js
@@ -26,6 +26,28 @@
     request.resourceSize = 1000;
     request._transferSize = 539;  // 39 = header size at the end of the day
     request.setPriority('VeryHigh');
+
+    // sample timing values used here are copied from a real request
+    request.setIssueTime(357904.060558);
+    request.endTime = 357904.867763;
+    request.timing = {
+      'requestTime': 357904.061527,
+      'proxyStart': 1.68,
+      'proxyEnd': 2.432,
+      'dnsStart': 2.837,
+      'dnsEnd': 86.913,
+      'connectStart': 86.913,
+      'connectEnd': 231.96,
+      'sslStart': 133.24,
+      'sslEnd': 231.942,
+      'workerStart': -1,
+      'workerReady': -1,
+      'sendStart': 232.218,
+      'sendEnd': 232.515,
+      'pushStart': 0,
+      'pushEnd': 0,
+      'receiveHeadersEnd': 800.997
+    };
   }
 
   const fakeInitiator = {
@@ -56,7 +78,6 @@
   var stillNondeterministic = {
     'startedDateTime': 'formatAsTypeName',
     'time': 'formatAsTypeName',
-    'timings': 'formatAsTypeName',
     '_transferSize': 'formatAsTypeName',
     '_error': 'skip'
   };
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-nameSources-visiblity-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-nameSources-visiblity-expected.txt
index 49ddf6cb..a651fad 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-nameSources-visiblity-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/accessibility/accessibility-nameSources-visiblity-expected.txt
@@ -314,7 +314,7 @@
         "type": "relatedElement",
         "value": {
           "type": "computedString",
-          "value": "\n          1 \n          \n            2 \n            \n            \n            \n            \n          \n          7 \n        "
+          "value": "\n          1\n          \n            2\n            \n            \n            \n            \n          \n          7\n        "
         },
         "attribute": "aria-labelledby",
         "attributeValue": {
@@ -323,7 +323,7 @@
           "relatedNodes": [
             {
               "idref": "label4",
-              "text": "\n          1 \n          \n            2 \n            \n            \n            \n            \n          \n          7 \n        ",
+              "text": "\n          1\n          \n            2\n            \n            \n            \n            \n          \n          7\n        ",
               "nodeResult": "div#label4"
             }
           ]
@@ -392,7 +392,7 @@
         "relatedNodes": [
           {
             "idref": "label4",
-            "text": "\n          1 \n          \n            2 \n            \n            \n            \n            \n          \n          7 \n        ",
+            "text": "\n          1\n          \n            2\n            \n            \n            \n            \n          \n          7\n        ",
             "nodeResult": "div#label4"
           }
         ]
diff --git a/third_party/WebKit/LayoutTests/virtual/speech-with-unified-autoplay/external/wpt/speech-api/README.txt b/third_party/WebKit/LayoutTests/virtual/speech-with-unified-autoplay/external/wpt/speech-api/README.txt
new file mode 100644
index 0000000..0d1d1daa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/speech-with-unified-autoplay/external/wpt/speech-api/README.txt
@@ -0,0 +1 @@
+This suite runs Speech API tests with Unified Autoplay enabled.
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 43dbaff5..41106bf4 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -127,6 +127,7 @@
     method close
     method constructor
     method getOutputTimestamp
+    method resume
     method suspend
 interface AudioDestinationNode : AudioNode
     attribute @@toStringTag
@@ -261,7 +262,6 @@
     method createStereoPanner
     method createWaveShaper
     method decodeAudioData
-    method resume
     setter onstatechange
 interface BatteryManager : EventTarget
     attribute @@toStringTag
@@ -4223,6 +4223,7 @@
     getter length
     getter oncomplete
     method constructor
+    method resume
     method startRendering
     method suspend
     setter oncomplete
diff --git a/third_party/WebKit/LayoutTests/webaudio/AudioContext/audiocontext-properties.html b/third_party/WebKit/LayoutTests/webaudio/AudioContext/audiocontext-properties.html
deleted file mode 100644
index 7aa6675..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/AudioContext/audiocontext-properties.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>
-      Testing AudioContext properties
-    </title>
-    <script src="../../resources/testharness.js"></script>
-    <script src="../../resources/testharnessreport.js"></script>
-    <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audit.js"></script>
-    <script src="../resources/context-properties.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let audit = Audit.createTaskRunner();
-
-
-      // Cross-checking two sets of properties: a programmatically generated set
-      // and a pre-populated set.
-      audit.define('crosschecking-properties', (task, should) => {
-        verifyPrototypeOwnProperties(
-            AudioContext.prototype, AudioContextOwnProperties, should);
-        task.done();
-      });
-
-      audit.run();
-    </script>
-  </body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/BaseAudioContext/baseaudiocontext-properties.html b/third_party/WebKit/LayoutTests/webaudio/BaseAudioContext/baseaudiocontext-properties.html
deleted file mode 100644
index aec372c..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/BaseAudioContext/baseaudiocontext-properties.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>
-      Testing BaseAudioContext properties
-    </title>
-    <script src="../../resources/testharness.js"></script>
-    <script src="../../resources/testharnessreport.js"></script>
-    <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audit.js"></script>
-    <script src="../resources/context-properties.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let audit = Audit.createTaskRunner();
-
-
-      // Cross-checking two sets of properties: a programmatically generated set
-      // and a pre-populated set.
-      audit.define('crosschecking-properties', (task, should) => {
-        verifyPrototypeOwnProperties(
-            BaseAudioContext.prototype, BaseAudioContextOwnProperties, should);
-        task.done();
-      });
-
-
-      audit.run();
-    </script>
-  </body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/OfflineAudioContext/offlineaudiocontext-properties.html b/third_party/WebKit/LayoutTests/webaudio/OfflineAudioContext/offlineaudiocontext-properties.html
deleted file mode 100644
index ac328e1..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/OfflineAudioContext/offlineaudiocontext-properties.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>
-      Testing OfflineAudioContext properties
-    </title>
-    <script src="../../resources/testharness.js"></script>
-    <script src="../../resources/testharnessreport.js"></script>
-    <script src="../resources/audit-util.js"></script>
-    <script src="../resources/audit.js"></script>
-    <script src="../resources/context-properties.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let audit = Audit.createTaskRunner();
-
-
-      // Cross-checking two sets of properties: a programmatically generated set
-      // and a pre-populated set.
-      audit.define('crosschecking-properties', (task, should) => {
-        verifyPrototypeOwnProperties(
-            OfflineAudioContext.prototype, OfflineAudioContextOwnProperties,
-            should);
-        task.done();
-      });
-
-      audit.run();
-    </script>
-  </body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/resources/context-properties.js b/third_party/WebKit/LayoutTests/webaudio/resources/context-properties.js
deleted file mode 100644
index 4961bec..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/resources/context-properties.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// The list of the 'own' properties in various AudioContexts. These lists were
-// populated by running:
-//
-//   Object.getOwnPropertyNames(FooAudioContext.prototype);
-//
-// https://webaudio.github.io/web-audio-api/#BaseAudioContext
-
-
-let BaseAudioContextOwnProperties = [
-  'audioWorklet',
-  'constructor',
-  'createAnalyser',
-  'createBiquadFilter',
-  'createBuffer',
-  'createBufferSource',
-  'createChannelMerger',
-  'createChannelSplitter',
-  'createConstantSource',
-  'createConvolver',
-  'createDelay',
-  'createDynamicsCompressor',
-  'createGain',
-  'createIIRFilter',
-  'createOscillator',
-  'createPanner',
-  'createPeriodicWave',
-  'createScriptProcessor',
-  'createStereoPanner',
-  'createWaveShaper',
-  'currentTime',
-  'decodeAudioData',
-  'destination',
-  'listener',
-  'onstatechange',
-  'resume',
-  'sampleRate',
-  'state',
-
-  // TODO(hongchan): these belong to AudioContext.
-  'createMediaElementSource',
-  'createMediaStreamDestination',
-  'createMediaStreamSource',
-];
-
-
-let AudioContextOwnProperties = [
-  'close', 'constructor', 'suspend', 'getOutputTimestamp', 'baseLatency',
-
-  // TODO(hongchan): Not implemented yet.
-  // 'outputLatency',
-];
-
-
-let OfflineAudioContextOwnProperties = [
-  'constructor',
-  'length',
-  'oncomplete',
-  'startRendering',
-  'suspend',
-];
-
-
-/**
- * Verify properties in the prototype with the pre-populated list. This is a
- * 2-way comparison to detect the missing and the unexpected property at the
- * same time.
- * @param  {Object} targetPrototype           Target prototype.
- * @param  {Array} populatedList              Property dictionary.
- * @param  {Function} should                  |Should| assertion function.
- * @return {Map}                              Verification result map.
- */
-function verifyPrototypeOwnProperties(targetPrototype, populatedList, should) {
-  let propertyMap = new Map();
-  let generatedList = Object.getOwnPropertyNames(targetPrototype);
-
-  for (let index in populatedList) {
-    propertyMap.set(populatedList[index], {actual: false, expected: true});
-  }
-
-  for (let index in generatedList) {
-    if (propertyMap.has(generatedList[index])) {
-      propertyMap.get(generatedList[index]).actual = true;
-    } else {
-      propertyMap.set(generatedList[index], {actual: true, expected: false});
-    }
-  }
-
-  for (let [property, result] of propertyMap) {
-    let prefix = 'The property "' + property + '"';
-    if (result.expected && result.actual) {
-      // The test meets the expectation.
-      should(true, prefix).message('was expected and found successfully', '');
-    } else if (result.expected && !result.actual) {
-      // The expected property is missing.
-      should(false, prefix).message('', 'was expected but not found.');
-    } else if (!result.expected && result.actual) {
-      // Something unexpected was found.
-      should(false, prefix).message('', 'was not expected but found.');
-    }
-  }
-}
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 13266312..537e5a6 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -297,6 +297,7 @@
     method close
     method constructor
     method getOutputTimestamp
+    method resume
     method suspend
 interface AudioDestinationNode : AudioNode
     attribute @@toStringTag
@@ -482,7 +483,6 @@
     method createStereoPanner
     method createWaveShaper
     method decodeAudioData
-    method resume
     setter onstatechange
 interface BatteryManager : EventTarget
     attribute @@toStringTag
@@ -4848,6 +4848,7 @@
     getter length
     getter oncomplete
     method constructor
+    method resume
     method startRendering
     method suspend
     setter oncomplete
@@ -5561,8 +5562,10 @@
     getter transport
     getter writeBufferedAmount
     method constructor
-interface RTCQuicTransport
+interface RTCQuicTransport : EventTarget
     attribute @@toStringTag
+    getter onerror
+    getter onstatechange
     getter state
     getter transport
     method constructor
@@ -5571,7 +5574,10 @@
     method getLocalParameters
     method getRemoteCertificates
     method getRemoteParameters
+    method start
     method stop
+    setter onerror
+    setter onstatechange
 interface RTCRtpContributingSource
     attribute @@toStringTag
     getter source
diff --git a/third_party/blink/public/web/web_user_gesture_indicator.h b/third_party/blink/public/web/web_user_gesture_indicator.h
index 6590b728..dca2e0c8 100644
--- a/third_party/blink/public/web/web_user_gesture_indicator.h
+++ b/third_party/blink/public/web/web_user_gesture_indicator.h
@@ -43,17 +43,12 @@
  public:
   // Returns true if a user gesture is currently being processed. Must be called
   // on the main thread.
-  //
-  // TODO(mustaq): remove the default value when all callers have been fixed.
-  BLINK_EXPORT static bool IsProcessingUserGesture(WebLocalFrame* = nullptr);
+  BLINK_EXPORT static bool IsProcessingUserGesture(WebLocalFrame*);
 
   // Can be called from any thread. Note that this is slower than the non
   // thread-safe version due to thread id lookups. Prefer the non thread-safe
   // version for code that will only execute on the main thread.
-  //
-  // TODO(mustaq): remove the default value when all callers have been fixed.
-  BLINK_EXPORT static bool IsProcessingUserGestureThreadSafe(
-      WebLocalFrame* = nullptr);
+  BLINK_EXPORT static bool IsProcessingUserGestureThreadSafe(WebLocalFrame*);
 
   // Returns true if a consumable gesture exists and has been successfully
   // consumed.
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index 5b6b3dd..b63431b 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -590,9 +590,13 @@
                              "6708326821789696 for more details.",
                              MilestoneString(kM70))};
     case WebFeature::kTextToSpeech_SpeakDisallowedByAutoplay:
-      return {"TextToSpeech_DisallowedByAutoplay", kM71,
-              WillBeRemoved("speechSynthesis.speak() without user activation",
-                            kM71, "5687444770914304")};
+      return {
+          "TextToSpeech_DisallowedByAutoplay", kM71,
+          String::Format("speechSynthesis.speak() without user activation is"
+                         "no longer allowed since %s. See"
+                         "https://www.chromestatus.com/feature/"
+                         "5687444770914304 for more details",
+                         MilestoneString(kM71))};
 
     case WebFeature::kPPAPIWebSocket:
       // TODO(ricea): Update once we have an expected release date for M74.
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 11edc22..e25ac44 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1214,8 +1214,8 @@
     auto* local_frame_client = Client();
     if (!local_frame_client)
       return nullptr;
-    frame_resource_coordinator_.reset(FrameResourceCoordinator::Create(
-        local_frame_client->GetInterfaceProvider()));
+    frame_resource_coordinator_ = FrameResourceCoordinator::Create(
+        local_frame_client->GetInterfaceProvider());
   }
   return frame_resource_coordinator_.get();
 }
diff --git a/third_party/blink/renderer/core/inspector/browser_protocol.pdl b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
index 4d09e6d..df74b5b 100644
--- a/third_party/blink/renderer/core/inspector/browser_protocol.pdl
+++ b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
@@ -5485,6 +5485,18 @@
   # Enable collecting and reporting metrics.
   command enable
 
+  # Sets time domain to use for collecting and reporting duration metrics.
+  # Note that this must be called before enabling metrics collection. Calling
+  # this method while metrics collection is enabled returns an error.
+  experimental command setTimeDomain
+    parameters
+      # Time domain
+      enum timeDomain
+        # Use monotonically increasing abstract time (default).
+        timeTicks
+        # Use thread running time.
+        threadTicks
+
   # Retrieve current values of run-time metrics.
   command getMetrics
     returns
diff --git a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
index 350f957..e715aa3 100644
--- a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
@@ -83,12 +83,40 @@
                          .setValue(value)
                          .build());
 }
-
-TimeTicks GetTimeTicksNow() {
-  return base::subtle::TimeTicksNowIgnoringOverride();
-}
 }  // namespace
 
+Response InspectorPerformanceAgent::setTimeDomain(const String& time_domain) {
+  if (enabled_.Get()) {
+    return Response::Error(
+        "Cannot set time domain while performance metrics collection"
+        " is enabled.");
+  }
+
+  using namespace protocol::Performance::SetTimeDomain;
+
+  if (time_domain == TimeDomainEnum::TimeTicks) {
+    use_thread_ticks_ = false;
+  } else if (time_domain == TimeDomainEnum::ThreadTicks) {
+    if (!base::ThreadTicks::IsSupported()) {
+      return Response::Error("Thread time is not supported on this platform.");
+    }
+    base::ThreadTicks::WaitUntilInitialized();
+    use_thread_ticks_ = true;
+  } else {
+    return Response::Error("Invalid time domain specification.");
+  }
+
+  return Response::OK();
+}
+
+TimeTicks InspectorPerformanceAgent::GetTimeTicksNow() {
+  return use_thread_ticks_
+             ? base::TimeTicks() +
+                   base::TimeDelta::FromMicroseconds(
+                       base::ThreadTicks::Now().since_origin().InMicroseconds())
+             : base::subtle::TimeTicksNowIgnoringOverride();
+}
+
 Response InspectorPerformanceAgent::getMetrics(
     std::unique_ptr<protocol::Array<protocol::Performance::Metric>>*
         out_result) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_performance_agent.h b/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
index 6fbbd8f4..856bbba 100644
--- a/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
@@ -42,6 +42,7 @@
   // Performance protocol domain implementation.
   protocol::Response enable() override;
   protocol::Response disable() override;
+  protocol::Response setTimeDomain(const String& time_domain) override;
   protocol::Response getMetrics(
       std::unique_ptr<protocol::Array<protocol::Performance::Metric>>*
           out_result) override;
@@ -69,6 +70,7 @@
   void ScriptStarts();
   void ScriptEnds();
   void InnerEnable();
+  TimeTicks GetTimeTicksNow();
 
   Member<InspectedFrames> inspected_frames_;
   TimeDelta layout_duration_;
@@ -85,6 +87,7 @@
   unsigned long long recalc_style_count_ = 0;
   int script_call_depth_ = 0;
   int layout_depth_ = 0;
+  bool use_thread_ticks_ = false;
   InspectorAgentState::Boolean enabled_;
   DISALLOW_COPY_AND_ASSIGN(InspectorPerformanceAgent);
 };
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
index 375106a..eb52a99 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
@@ -120,12 +120,12 @@
 
   if (last_threshold_index_ != new_threshold_index ||
       last_is_visible_ != is_visible) {
-    FloatRect snapped_root_bounds(geometry.RootRect());
+    FloatRect root_bounds(geometry.UnZoomedRootRect());
     FloatRect* root_bounds_pointer =
-        report_root_bounds ? &snapped_root_bounds : nullptr;
+        report_root_bounds ? &root_bounds : nullptr;
     IntersectionObserverEntry* new_entry = new IntersectionObserverEntry(
-        timestamp, new_visible_ratio, FloatRect(geometry.TargetRect()),
-        root_bounds_pointer, FloatRect(geometry.IntersectionRect()),
+        timestamp, new_visible_ratio, FloatRect(geometry.UnZoomedTargetRect()),
+        root_bounds_pointer, FloatRect(geometry.UnZoomedIntersectionRect()),
         new_threshold_index > 0, is_visible, Target());
     entries_.push_back(new_entry);
     ToDocument(Observer()->GetExecutionContext())
diff --git a/third_party/blink/renderer/core/layout/intersection_geometry.cc b/third_party/blink/renderer/core/layout/intersection_geometry.cc
index 33844bd..2d4fa91 100644
--- a/third_party/blink/renderer/core/layout/intersection_geometry.cc
+++ b/third_party/blink/renderer/core/layout/intersection_geometry.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
+#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/layout/layout_inline.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
@@ -233,4 +234,28 @@
     MapRootRectToRootFrameCoordinates();
 }
 
+LayoutRect IntersectionGeometry::UnZoomedTargetRect() const {
+  if (!target_)
+    return target_rect_;
+  FloatRect rect(target_rect_);
+  AdjustForAbsoluteZoom::AdjustFloatRect(rect, *target_);
+  return LayoutRect(rect);
+}
+
+LayoutRect IntersectionGeometry::UnZoomedIntersectionRect() const {
+  if (!target_)
+    return intersection_rect_;
+  FloatRect rect(intersection_rect_);
+  AdjustForAbsoluteZoom::AdjustFloatRect(rect, *target_);
+  return LayoutRect(rect);
+}
+
+LayoutRect IntersectionGeometry::UnZoomedRootRect() const {
+  if (!root_)
+    return root_rect_;
+  FloatRect rect(root_rect_);
+  AdjustForAbsoluteZoom::AdjustFloatRect(rect, *root_);
+  return LayoutRect(rect);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/intersection_geometry.h b/third_party/blink/renderer/core/layout/intersection_geometry.h
index cd26479..f08cfdb 100644
--- a/third_party/blink/renderer/core/layout/intersection_geometry.h
+++ b/third_party/blink/renderer/core/layout/intersection_geometry.h
@@ -38,12 +38,18 @@
 
   // Client rect in the coordinate system of the frame containing target.
   LayoutRect TargetRect() const { return target_rect_; }
+  // Target rect in CSS pixels
+  LayoutRect UnZoomedTargetRect() const;
 
   // Client rect in the coordinate system of the frame containing target.
   LayoutRect IntersectionRect() const { return intersection_rect_; }
+  // Intersection rect in CSS pixels
+  LayoutRect UnZoomedIntersectionRect() const;
 
   // Client rect in the coordinate system of the frame containing root.
   LayoutRect RootRect() const { return root_rect_; }
+  // Root rect in CSS pixels
+  LayoutRect UnZoomedRootRect() const;
 
   bool DoesIntersect() const { return does_intersect_; }
 
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index eb627d8..e58ebc7 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -5517,7 +5517,7 @@
                     : LayoutSize(MarginAfter(container_style), LayoutUnit()));
   }
 
-  if (!HasOverflowClip() && !ShouldApplyLayoutContainment())
+  if (!ShouldClipOverflow() && !ShouldApplyLayoutContainment())
     rect.Unite(LayoutOverflowRect());
 
   bool has_transform = HasLayer() && Layer()->Transform();
diff --git a/third_party/blink/renderer/core/layout/layout_flexible_box.cc b/third_party/blink/renderer/core/layout/layout_flexible_box.cc
index 0c7748b..e1def3fb 100644
--- a/third_party/blink/renderer/core/layout/layout_flexible_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_flexible_box.cc
@@ -437,6 +437,14 @@
   return StyleRef().FlexWrap() != EFlexWrap::kNowrap;
 }
 
+bool LayoutFlexibleBox::ShouldApplyMinSizeAutoForChild(
+    const LayoutBox& child) const {
+  Length min = IsHorizontalFlow() ? child.StyleRef().MinWidth()
+                                  : child.StyleRef().MinHeight();
+  return min.IsAuto() && !child.ShouldApplySizeContainment() &&
+         MainAxisOverflowForChild(child) == EOverflow::kVisible;
+}
+
 Length LayoutFlexibleBox::FlexBasisForChild(const LayoutBox& child) const {
   Length flex_length = child.StyleRef().FlexBasis();
   if (flex_length.IsAuto()) {
@@ -997,14 +1005,7 @@
     // computeMainAxisExtentForChild can return -1 when the child has a
     // percentage min size, but we have an indefinite size in that axis.
     sizes.min_size = std::max(LayoutUnit(), sizes.min_size);
-  } else if (min.IsAuto() && !child.ShouldApplySizeContainment() &&
-             MainAxisOverflowForChild(child) == EOverflow::kVisible &&
-             !(IsColumnFlow() && child.IsFlexibleBox())) {
-    // TODO(cbiesinger): For now, we do not handle min-height: auto for nested
-    // column flexboxes. We need to implement
-    // https://drafts.csswg.org/css-flexbox/#intrinsic-sizes before that
-    // produces reasonable results. Tracking bug: https://crbug.com/581553
-    // css-flexbox section 4.5
+  } else if (ShouldApplyMinSizeAutoForChild(child)) {
     LayoutUnit content_size =
         ComputeMainAxisExtentForChild(child, kMinSize, Length(kMinContent));
     DCHECK_GE(content_size, LayoutUnit());
@@ -1124,6 +1125,14 @@
 DISABLE_CFI_PERF
 FlexItem LayoutFlexibleBox::ConstructFlexItem(LayoutBox& child,
                                               ChildLayoutType layout_type) {
+  if (layout_type == kLayoutIfNeeded && IsColumnFlow() &&
+      child.IsFlexibleBox() && ShouldApplyMinSizeAutoForChild(child)) {
+    // In this case, we have to force-layout to update the intrinsic height of
+    // our child; otherwise, it may be too big because it is based on previous
+    // flexing of a descendant, which would be a problem for applying
+    // min-size: auto.
+    layout_type = kForceLayout;
+  }
   if (layout_type != kNeverLayout && ChildHasIntrinsicMainAxisSize(child)) {
     // If this condition is true, then ComputeMainAxisExtentForChild will call
     // child.IntrinsicContentLogicalHeight() and
diff --git a/third_party/blink/renderer/core/layout/layout_flexible_box.h b/third_party/blink/renderer/core/layout/layout_flexible_box.h
index a2eb5329..156ca0fa 100644
--- a/third_party/blink/renderer/core/layout/layout_flexible_box.h
+++ b/third_party/blink/renderer/core/layout/layout_flexible_box.h
@@ -111,6 +111,7 @@
   bool IsColumnFlow() const;
   bool IsLeftToRightFlow() const;
   bool IsMultiline() const;
+  bool ShouldApplyMinSizeAutoForChild(const LayoutBox& child) const;
   Length FlexBasisForChild(const LayoutBox& child) const;
   LayoutUnit CrossAxisExtentForChild(const LayoutBox& child) const;
   LayoutUnit CrossAxisIntrinsicExtentForChild(const LayoutBox& child) const;
diff --git a/third_party/blink/renderer/core/paint/README.md b/third_party/blink/renderer/core/paint/README.md
index 5402cf6..06c1288 100644
--- a/third_party/blink/renderer/core/paint/README.md
+++ b/third_party/blink/renderer/core/paint/README.md
@@ -104,6 +104,13 @@
 *   Visual rect: the bounding box of all pixels that will be painted by a
     display item client.
 
+*   Isolation nodes/boundary: In certain situations, it is possible to put in
+    place a barrier that isolates a subtree from being affected by its
+    ancestors. This barrier is called an isolation boundary and is implemented
+    in the property trees as isolation nodes that serve as roots for any
+    descendant property nodes. Currently, the `contain: paint` css property
+    establishes an isolation boundary.
+
 ## Overview
 
 The primary responsibility of this module is to convert the outputs from layout
@@ -488,14 +495,14 @@
 `InlineBox`es in the first line.
 
 ## PrePaint (Slimming paint invalidation/v2 only)
-[`PrePaintTreeWalk`](PrePaintTreeWalk.h)
+[`PrePaintTreeWalk`](pre_paint_tree_walk.h)
 
 During `InPrePaint` document lifecycle state, this class is called to walk the
 whole layout tree, beginning from the root FrameView, across frame boundaries.
 We do the following during the tree walk:
 
 ### Building paint property trees
-[`PaintPropertyTreeBuilder`](PaintPropertyTreeBuilder.h)
+[`PaintPropertyTreeBuilder`](paint_property_tree_builder.h)
 
 This class is responsible for building property trees
 (see [the platform paint README file](../../platform/graphics/paint/README.md)).
@@ -510,6 +517,92 @@
 `DescendantNeedsPaintPropertyUpdate` dirty bits on `LayoutObject` control how
 much of the layout tree is traversed during each `PrePaintTreeWalk`.
 
+Additionally, some dirty bits are cleared at an isolation boundary. For example
+if the paint property tree topology has changed by adding or removing nodes
+for an element, we typically force a subtree walk for all descendants since
+the descendant nodes may now refer to new parent nodes. However, at an
+isolation boundary, we can reason that none of the descendants of an isolation
+element would be affected, since the highest node that the paint property nodes
+of an isolation element's subtree can reference are the isolation
+nodes established at this element itself.
+
+Implementation note: the isolation boundary is achieved using alias nodes, which
+are nodes that are put in place on an isolated element for clip, transform, and
+effect trees. These nodes do not themselves contribute to any painted output,
+but serve as parents to the subtree nodes. The alias nodes and isolation nodes
+are synonymous and are used interchangeably. Also note that these nodes are
+placed as children of the regular nodes of the element. This means that the
+element itself is not isolated against ancestor mutations; it only isolates the
+element's subtree.
+
+Example tree:
++----------------------+
+| 1. Root LayoutObject |
++----------------------+
+      |       |
+      |       +-----------------+
+      |                         |
+      v                         v
++-----------------+       +-----------------+
+| 2. LayoutObject |       | 3. LayoutObject |
++-----------------+       +-----------------+
+      |                         |
+      v                         |
++-----------------+             |
+| 4. LayoutObject |             |
++-----------------+             |
+                                |
+      +-------------------------+
+      |                         |
++-----------------+       +-----------------+
+| 5. LayoutObject |       | 6. LayoutObject |
++-----------------+       +-----------------+
+      |   |
+      |   +---------------------+
+      |                         |
+      v                         v
++-----------------+       +-----------------+
+| 7. LayoutObject |       | 8. LayoutObject |
++-----------------+       +-----------------+
+
+Suppose that element 3's style changes to include a transform (e.g.
+"transform: translateX(10px);").
+
+Typically, here is the order of the walk (depth first) and updates:
+*    Root element 1 is visited since some descendant needs updates
+*    Element 2 is visited since it is one of the descendants, but it doesn't
+     need updates.
+*    Element 4 is skipped since the above step didn't need to recurse.
+*    Element 3 is visited since it's a descendant of the root element, and its
+     property trees are updated to include a new transform. This causes a flag
+     to be flipped that all subtree nodes need an update.
+*    Elements are then visited in the depth order: 5, 7, 8, 6. Elements 5 and 6
+     reparent their transform nodes to point to the transform node of element 3.
+     Elements 7 and 8 are visited and updated but no changes occur.
+
+Now suppose that element 5 has "contain: paint" style, which establishes an
+isolation boundary. The walk changes in the following way:
+
+*    Root element 1 is visited since some descendant needs updates
+*    Element 2 is visited since it is one of the descendants, but it doesn't
+     need updates.
+*    Element 4 is skipped since the above step didn't need to recurse.
+*    Element 3 is visited since it's a descendant of the root element, and its
+     property trees are updated to include a new transform. This causes a flag
+     to be flipped that all subtree nodes need an update.
+*    Element 5 is visited and updated by reparenting the transform nodes.
+     However, now the element is an isolation boundary so elements 7 and 8 are
+     not visited (i.e. the forced subtree update flag is ignored).
+*    Element 6 is visited as before and is updated to reparent the transform
+     node.
+
+Note that there are subtleties when deciding whether we can skip the subtree
+walk. Specifically, not all subtree walks can be stopped at an isolation
+boundary. For more information, see
+[`PaintPropertyTreeBuilder`](paint_property_tree_builder.h) and its use of
+IsolationPiercing vs IsolationBlocked subtree update reasons.
+
+
 #### Fragments
 
 In the absence of multicolumn/pagination, there is a 1:1 correspondence between
@@ -527,15 +620,15 @@
 also store a unique `PaintOffset, `PaginationOffset and
 `LocalBordreBoxProperties` object.
 
-See [`LayoutMultiColumnFlowThread.h`](../layout/LayoutMultiColumnFlowThread.h)
+See [`LayoutMultiColumnFlowThread.h`](../layout/layout_multi_column_flow_thread.h)
 for a much more detail about multicolumn/pagination.
 
 ### Paint invalidation
-[`PaintInvalidator`](PaintInvalidator.h)
+[`PaintInvalidator`](paint_invalidator.h)
 
 This class replaces [`PaintInvalidationState`] for SlimmingPaintInvalidation.
 The main difference is that in PaintInvalidator, visual rects and locations
-are computed by `GeometryMapper`(../../platform/graphics/paint/GeometryMapper.h),
+are computed by `GeometryMapper`(../../platform/graphics/paint/geometry_mapper.h),
 based on paint properties produced by `PaintPropertyTreeBuilder`.
 
 TODO(wangxianzhu): Combine documentation of PaintInvalidation phase into here.
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 0e203bc..232eec3 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -180,6 +180,7 @@
   ALWAYS_INLINE void UpdateReplacedContentTransform();
   ALWAYS_INLINE void UpdateScrollAndScrollTranslation();
   ALWAYS_INLINE void UpdateOutOfFlowContext();
+  // See core/paint/README.md for the description of isolation nodes.
   ALWAYS_INLINE void UpdateTransformIsolationNode();
   ALWAYS_INLINE void UpdateEffectIsolationNode();
   ALWAYS_INLINE void UpdateClipIsolationNode();
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/HARLog.js b/third_party/blink/renderer/devtools/front_end/sdk/HARLog.js
index be92037c..643d66c 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/HARLog.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/HARLog.js
@@ -253,7 +253,7 @@
     const result = {blocked: -1, dns: -1, ssl: -1, connect: -1, send: 0, wait: 0, receive: 0, _blocked_queueing: -1};
 
     const queuedTime = (issueTime < startTime) ? startTime - issueTime : -1;
-    result.blocked = queuedTime;
+    result.blocked = SDK.HARLog.Entry._toMilliseconds(queuedTime);
     result._blocked_queueing = SDK.HARLog.Entry._toMilliseconds(queuedTime);
 
     let highestTime = 0;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 1a7eab91..dc9eff99 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -1814,6 +1814,21 @@
     return StringValue();
   }
 
+  // Step 2E from: http://www.w3.org/TR/accname-aam-1.1
+  // "If the embedded control has role combobox or listbox, return the text
+  // alternative of the chosen option."
+  if (NameFromSelectedOption(recursive)) {
+    StringBuilder accumulated_text;
+    AXObjectVector selected_options;
+    SelectedOptions(selected_options);
+    for (const auto& child : selected_options) {
+      if (accumulated_text.length())
+        accumulated_text.Append(" ");
+      accumulated_text.Append(child->ComputedName());
+    }
+    return accumulated_text.ToString();
+  }
+
   // Step 2D from: http://www.w3.org/TR/accname-aam-1.1
   text_alternative =
       NativeTextAlternative(visited, name_from, related_objects, name_sources,
@@ -1937,9 +1952,11 @@
       result = RecursiveTextAlternative(*child, false, visited);
 
     if (!result.IsEmpty() && previous && accumulated_text.length() &&
-        !IsHTMLSpace(accumulated_text[accumulated_text.length() - 1])) {
-      if (ShouldInsertSpaceBetweenObjectsIfNeeded(previous, child))
+        !IsHTMLSpace(accumulated_text[accumulated_text.length() - 1]) &&
+        !IsHTMLSpace(result[0])) {
+      if (ShouldInsertSpaceBetweenObjectsIfNeeded(previous, child)) {
         accumulated_text.Append(' ');
+      }
     }
 
     accumulated_text.Append(result);
@@ -2475,6 +2492,36 @@
   AXObject::UpdateChildrenIfNecessary();
 }
 
+void AXNodeObject::SelectedOptions(AXObjectVector& options) const {
+  if (IsHTMLSelectElement(GetNode())) {
+    HTMLSelectElement* select = ToHTMLSelectElement(GetNode());
+    for (auto* const option : *select->selectedOptions()) {
+      options.push_back(AXObjectCache().GetOrCreate(option));
+    }
+    return;
+  }
+
+  // If the combobox or listbox is a descendant of a label element for another
+  // widget, it may be ignored and Children() won't return all its children.
+  // As a result, we need to use RawFirstChild and RawNextSibling to iterate
+  // over the children in search of the selected option(s).
+
+  if (RoleValue() == ax::mojom::Role::kComboBoxGrouping ||
+      RoleValue() == ax::mojom::Role::kComboBoxMenuButton) {
+    for (AXObject* obj = RawFirstChild(); obj; obj = obj->RawNextSibling()) {
+      if (obj->RoleValue() == ax::mojom::Role::kListBox) {
+        obj->SelectedOptions(options);
+        return;
+      }
+    }
+  }
+
+  for (AXObject* obj = RawFirstChild(); obj; obj = obj->RawNextSibling()) {
+    if (obj->IsSelected() == kSelectedStateTrue)
+      options.push_back(obj);
+  }
+}
+
 void AXNodeObject::SelectionChanged() {
   // Post the selected text changed event on the first ancestor that's
   // focused (to handle form controls, ARIA text boxes and contentEditable),
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.h b/third_party/blink/renderer/modules/accessibility/ax_node_object.h
index bbbd81e..71b6b907 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.h
@@ -198,6 +198,7 @@
   bool NeedsToUpdateChildren() const override { return children_dirty_; }
   void SetNeedsToUpdateChildren() override { children_dirty_ = true; }
   void UpdateChildrenIfNecessary() override;
+  void SelectedOptions(AXObjectVector&) const override;
 
   // DOM and Render tree access.
   Element* ActionElement() const override;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 8db9fc4..f472ebe 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -3116,6 +3116,19 @@
   return role;
 }
 
+bool AXObject::NameFromSelectedOption(bool recursive) const {
+  switch (RoleValue()) {
+    // Step 2E from: http://www.w3.org/TR/accname-aam-1.1
+    case ax::mojom::Role::kComboBoxGrouping:
+    case ax::mojom::Role::kComboBoxMenuButton:
+    case ax::mojom::Role::kListBox:
+    case ax::mojom::Role::kPopUpButton:
+      return recursive;
+    default:
+      return false;
+  }
+}
+
 bool AXObject::NameFromContents(bool recursive) const {
   // ARIA 1.1, section 5.2.7.5.
   bool result = false;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 62d9830..b86749b1 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -855,6 +855,7 @@
   virtual void DetachFromParent() { parent_ = nullptr; }
   virtual AXObject* ScrollBar(AccessibilityOrientation) { return nullptr; }
   virtual void AddAccessibleNodeChildren();
+  virtual void SelectedOptions(AXObjectVector&) const {}
 
   // Properties of the object's owning document or page.
   virtual double EstimatedLoadingProgress() const { return 0; }
@@ -1046,6 +1047,7 @@
 
   bool CanReceiveAccessibilityFocus() const;
   bool NameFromContents(bool recursive) const;
+  bool NameFromSelectedOption(bool recursive) const;
 
   ax::mojom::Role ButtonRoleType() const;
 
diff --git a/third_party/blink/renderer/modules/event_target_modules_names.json5 b/third_party/blink/renderer/modules/event_target_modules_names.json5
index 433fd83..b1b6e85 100644
--- a/third_party/blink/renderer/modules/event_target_modules_names.json5
+++ b/third_party/blink/renderer/modules/event_target_modules_names.json5
@@ -31,6 +31,7 @@
     "modules/notifications/Notification",
     "modules/payments/PaymentRequest",
     "modules/peerconnection/RTCIceTransport",
+    "modules/peerconnection/RTCQuicTransport",
     "modules/permissions/PermissionStatus",
     "modules/picture_in_picture/HTMLVideoElementPictureInPicture",
     "modules/picture_in_picture/PictureInPictureWindow",
diff --git a/third_party/blink/renderer/modules/peerconnection/BUILD.gn b/third_party/blink/renderer/modules/peerconnection/BUILD.gn
index e2b32d4..4f543a3 100644
--- a/third_party/blink/renderer/modules/peerconnection/BUILD.gn
+++ b/third_party/blink/renderer/modules/peerconnection/BUILD.gn
@@ -24,6 +24,10 @@
     "adapters/p2p_quic_transport_factory_impl.h",
     "adapters/p2p_quic_transport_impl.cc",
     "adapters/p2p_quic_transport_impl.h",
+    "adapters/quic_transport_host.cc",
+    "adapters/quic_transport_host.h",
+    "adapters/quic_transport_proxy.cc",
+    "adapters/quic_transport_proxy.h",
     "adapters/web_rtc_cross_thread_copier.cc",
     "adapters/web_rtc_cross_thread_copier.h",
     "rtc_certificate.cc",
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter.h b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter.h
index 33dc79d9..a07ea25d 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter.h
@@ -9,6 +9,8 @@
 
 namespace blink {
 
+class P2PQuicPacketTransport;
+
 // Defines the ICE candidate policy the browser uses to surface the permitted
 // candidates to the application.
 // https://w3c.github.io/webrtc-pc/#dom-rtcicetransportpolicy
@@ -70,6 +72,10 @@
   // Adds a remote candidate to potentially start connectivity checks with.
   // The caller must ensure Start() has already bene called.
   virtual void AddRemoteCandidate(const cricket::Candidate& candidate) = 0;
+
+  // Gets a P2PQuicPacketTransport that is backed by this ICE connection. The
+  // returned instance lives the same lifetime as the IceTransportAdapter.
+  virtual P2PQuicPacketTransport* packet_transport() const = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.cc
index 4806d9a..1fc096ec 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.cc
@@ -4,7 +4,80 @@
 
 #include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h"
 
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h"
+
 namespace blink {
+namespace {
+
+// Implementation of P2PQuicPacketTransport backed by a P2PTransportChannel.
+class QuicPacketTransportAdapter : public P2PQuicPacketTransport,
+                                   public sigslot::has_slots<> {
+ public:
+  QuicPacketTransportAdapter(
+      cricket::P2PTransportChannel* p2p_transport_channel)
+      : p2p_transport_channel_(p2p_transport_channel) {
+    DCHECK(p2p_transport_channel_);
+    p2p_transport_channel_->SignalReadPacket.connect(
+        this, &QuicPacketTransportAdapter::OnReadPacket);
+    p2p_transport_channel_->SignalWritableState.connect(
+        this, &QuicPacketTransportAdapter::OnWritableState);
+  }
+
+  ~QuicPacketTransportAdapter() override {
+    // Caller is responsible for unsetting the write observer and receive
+    // delegate before destroying this.
+    DCHECK(!write_observer_);
+    DCHECK(!receive_delegate_);
+  }
+
+  int WritePacket(const QuicPacket& packet) override {
+    rtc::PacketOptions options;
+    options.packet_id = packet.packet_number;
+    int flags = 0;
+    return p2p_transport_channel_->SendPacket(packet.buffer, packet.buf_len,
+                                              options, flags);
+  }
+
+  void SetReceiveDelegate(ReceiveDelegate* receive_delegate) override {
+    receive_delegate_ = receive_delegate;
+  }
+
+  void SetWriteObserver(WriteObserver* write_observer) override {
+    write_observer_ = write_observer;
+  }
+
+  bool Writable() override { return p2p_transport_channel_->writable(); }
+
+ private:
+  // P2PTransportChannel callbacks.
+  void OnReadPacket(rtc::PacketTransportInternal* packet_transport,
+                    const char* buffer,
+                    size_t buffer_length,
+                    const rtc::PacketTime& packet_time,
+                    int flags) {
+    DCHECK_EQ(packet_transport, p2p_transport_channel_);
+    if (!receive_delegate_) {
+      // TODO(crbug.com/874296): Consider providing a small buffer.
+      return;
+    }
+    receive_delegate_->OnPacketDataReceived(buffer, buffer_length);
+  }
+  void OnWritableState(rtc::PacketTransportInternal* packet_transport) {
+    DCHECK_EQ(packet_transport, p2p_transport_channel_);
+    if (!write_observer_) {
+      return;
+    }
+    if (p2p_transport_channel_->writable()) {
+      write_observer_->OnCanWrite();
+    }
+  }
+
+  cricket::P2PTransportChannel* p2p_transport_channel_;
+  ReceiveDelegate* receive_delegate_ = nullptr;
+  WriteObserver* write_observer_ = nullptr;
+};
+
+}  // namespace
 
 IceTransportAdapterImpl::IceTransportAdapterImpl(
     Delegate* delegate,
@@ -38,6 +111,8 @@
   // generated so that each peer can calculate a.tiebreaker <= b.tiebreaker
   // consistently.
   p2p_transport_channel_->SetIceTiebreaker(rtc::CreateRandomId64());
+  quic_packet_transport_adapter_ = std::make_unique<QuicPacketTransportAdapter>(
+      p2p_transport_channel_.get());
 }
 
 IceTransportAdapterImpl::~IceTransportAdapterImpl() = default;
@@ -94,6 +169,10 @@
   p2p_transport_channel_->AddRemoteCandidate(candidate);
 }
 
+P2PQuicPacketTransport* IceTransportAdapterImpl::packet_transport() const {
+  return quic_packet_transport_adapter_.get();
+}
+
 void IceTransportAdapterImpl::OnGatheringStateChanged(
     cricket::IceTransportInternal* transport) {
   DCHECK_EQ(transport, p2p_transport_channel_.get());
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h
index d022d87..52fed69 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h
@@ -37,6 +37,7 @@
   void HandleRemoteRestart(
       const cricket::IceParameters& new_remote_parameters) override;
   void AddRemoteCandidate(const cricket::Candidate& candidate) override;
+  P2PQuicPacketTransport* packet_transport() const override;
 
  private:
   // Callbacks from P2PTransportChannel.
@@ -48,6 +49,7 @@
   Delegate* const delegate_;
   std::unique_ptr<cricket::PortAllocator> port_allocator_;
   std::unique_ptr<cricket::P2PTransportChannel> p2p_transport_channel_;
+  std::unique_ptr<P2PQuicPacketTransport> quic_packet_transport_adapter_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.cc b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.cc
index dc160b2..5db8163 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.cc
@@ -23,6 +23,7 @@
 
 IceTransportHost::~IceTransportHost() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!HasConsumer());
 }
 
 void IceTransportHost::Initialize(
@@ -62,6 +63,27 @@
 
 }
 
+bool IceTransportHost::HasConsumer() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return consumer_host_;
+}
+
+IceTransportAdapter* IceTransportHost::ConnectConsumer(
+    QuicTransportHost* consumer_host) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(consumer_host);
+  DCHECK(!consumer_host_);
+  consumer_host_ = consumer_host;
+  return transport_.get();
+}
+
+void IceTransportHost::DisconnectConsumer(QuicTransportHost* consumer_host) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(consumer_host);
+  DCHECK_EQ(consumer_host, consumer_host_);
+  consumer_host_ = nullptr;
+}
+
 void IceTransportHost::OnGatheringStateChanged(
     cricket::IceGatheringState new_state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h
index 0214738..08ac5458 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h
@@ -15,6 +15,7 @@
 namespace blink {
 
 class IceTransportProxy;
+class QuicTransportHost;
 
 // This class is the host side correspondent to the IceTransportProxy. See the
 // IceTransportProxy documentation for background. This class lives on the host
@@ -58,6 +59,15 @@
   void HandleRemoteRestart(const cricket::IceParameters& new_remote_parameters);
   void AddRemoteCandidate(const cricket::Candidate& candidate);
 
+  // A QuicTransportHost can be connected to this IceTransportHost. Only one can
+  // be connected at a time, and the caller must ensure that the consumer is
+  // disconnected before destroying the IceTransportHost.
+  // ConnectConsumer returns an implementation of IceTransportAdapter that
+  // should only be used on the host thread.
+  bool HasConsumer() const;
+  IceTransportAdapter* ConnectConsumer(QuicTransportHost* consumer_host);
+  void DisconnectConsumer(QuicTransportHost* consumer_host);
+
  private:
   // IceTransportAdapter::Delegate overrides.
   void OnGatheringStateChanged(cricket::IceGatheringState new_state) override;
@@ -67,6 +77,7 @@
   const scoped_refptr<base::SingleThreadTaskRunner> proxy_thread_;
   std::unique_ptr<IceTransportAdapter> transport_;
   base::WeakPtr<IceTransportProxy> proxy_;
+  QuicTransportHost* consumer_host_ = nullptr;
 
   THREAD_CHECKER(thread_checker_);
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.cc b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.cc
index e8e9ecb..3ff6fd7 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.cc
@@ -17,7 +17,8 @@
     scoped_refptr<base::SingleThreadTaskRunner> host_thread,
     Delegate* delegate,
     std::unique_ptr<IceTransportAdapterCrossThreadFactory> adapter_factory)
-    : host_thread_(std::move(host_thread)),
+    : proxy_thread_(std::move(proxy_thread)),
+      host_thread_(std::move(host_thread)),
       host_(nullptr, base::OnTaskRunnerDeleter(host_thread_)),
       delegate_(delegate),
       connection_handle_for_scheduler_(
@@ -26,14 +27,14 @@
   DCHECK(host_thread_);
   DCHECK(delegate_);
   DCHECK(adapter_factory);
-  DCHECK(proxy_thread->BelongsToCurrentThread());
+  DCHECK(proxy_thread_->BelongsToCurrentThread());
   adapter_factory->InitializeOnMainThread();
   // Wait to initialize the host until the weak_ptr_factory_ is initialized.
   // The IceTransportHost is constructed on the proxy thread but should only be
   // interacted with via PostTask to the host thread. The OnTaskRunnerDeleter
   // (configured above) will ensure it gets deleted on the host thread.
-  host_.reset(new IceTransportHost(std::move(proxy_thread),
-                                   weak_ptr_factory_.GetWeakPtr()));
+  host_.reset(
+      new IceTransportHost(proxy_thread_, weak_ptr_factory_.GetWeakPtr()));
   PostCrossThreadTask(*host_thread_, FROM_HERE,
                       CrossThreadBind(&IceTransportHost::Initialize,
                                       CrossThreadUnretained(host_.get()),
@@ -42,9 +43,22 @@
 
 IceTransportProxy::~IceTransportProxy() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!HasConsumer());
   // Note: The IceTransportHost will be deleted on the host thread.
 }
 
+scoped_refptr<base::SingleThreadTaskRunner> IceTransportProxy::proxy_thread()
+    const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return proxy_thread_;
+}
+
+scoped_refptr<base::SingleThreadTaskRunner> IceTransportProxy::host_thread()
+    const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return host_thread_;
+}
+
 void IceTransportProxy::StartGathering(
     const cricket::IceParameters& local_parameters,
     const cricket::ServerAddresses& stun_servers,
@@ -88,6 +102,27 @@
                       CrossThreadUnretained(host_.get()), candidate));
 }
 
+bool IceTransportProxy::HasConsumer() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return consumer_proxy_;
+}
+
+IceTransportHost* IceTransportProxy::ConnectConsumer(
+    QuicTransportProxy* consumer_proxy) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(consumer_proxy);
+  DCHECK(!consumer_proxy_);
+  consumer_proxy_ = consumer_proxy;
+  return host_.get();
+}
+
+void IceTransportProxy::DisconnectConsumer(QuicTransportProxy* consumer_proxy) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(consumer_proxy);
+  DCHECK_EQ(consumer_proxy, consumer_proxy_);
+  consumer_proxy_ = nullptr;
+}
+
 void IceTransportProxy::OnGatheringStateChanged(
     cricket::IceGatheringState new_state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h
index dfcab86..1b90fde 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h
@@ -21,6 +21,7 @@
 namespace blink {
 
 class IceTransportHost;
+class QuicTransportProxy;
 
 // This class allows the ICE implementation (P2PTransportChannel) to run on a
 // thread different from the thread from which it is controlled. All
@@ -63,6 +64,9 @@
       std::unique_ptr<IceTransportAdapterCrossThreadFactory> adapter_factory);
   ~IceTransportProxy();
 
+  scoped_refptr<base::SingleThreadTaskRunner> proxy_thread() const;
+  scoped_refptr<base::SingleThreadTaskRunner> host_thread() const;
+
   // These methods are proxied to an IceTransportAdapter instance.
   void StartGathering(
       const cricket::IceParameters& local_parameters,
@@ -75,6 +79,15 @@
   void HandleRemoteRestart(const cricket::IceParameters& new_remote_parameters);
   void AddRemoteCandidate(const cricket::Candidate& candidate);
 
+  // A QuicTransportProxy can be connected to this IceTransportProxy. Only one
+  // can be connected at a time, and the caller must ensure that the consumer
+  // is disconnected before destroying the IceTransportProxy.
+  // ConnectConsumer returns an IceTransportHost that can be used to connect
+  // a QuicTransportHost.
+  bool HasConsumer() const;
+  IceTransportHost* ConnectConsumer(QuicTransportProxy* consumer_proxy);
+  void DisconnectConsumer(QuicTransportProxy* consumer_proxy);
+
  private:
   // Callbacks from RTCIceTransportHost.
   friend class IceTransportHost;
@@ -82,11 +95,13 @@
   void OnCandidateGathered(const cricket::Candidate& candidate);
   void OnStateChanged(cricket::IceTransportState new_state);
 
+  const scoped_refptr<base::SingleThreadTaskRunner> proxy_thread_;
   const scoped_refptr<base::SingleThreadTaskRunner> host_thread_;
   // Since the Host is deleted on the host thread (via OnTaskRunnerDeleter), as
   // long as this is alive it is safe to post tasks to it (using unretained).
   std::unique_ptr<IceTransportHost, base::OnTaskRunnerDeleter> host_;
   Delegate* const delegate_;
+  QuicTransportProxy* consumer_proxy_ = nullptr;
 
   // This handle notifies scheduler about an active connection associated
   // with a frame. Handle should be destroyed when connection is closed.
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
index 0f1a76c..73e4c3a6 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
@@ -37,8 +37,7 @@
   // to get the packet numbers of QUIC packets we write. The QuicConnection
   // is created with a quic::QuicPacketWriter, so we can't set the connection
   // in the constructor.
-  void InitializeWithQuicConnection(
-      const quic::QuicConnection* const connection) {
+  void InitializeWithQuicConnection(quic::QuicConnection* connection) {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(connection);
     if (packet_transport_->Writable()) {
@@ -122,6 +121,7 @@
   void OnCanWrite() override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     SetWritable();
+    connection_->OnCanWrite();
   }
 
  private:
@@ -129,7 +129,7 @@
   // BlinkPacketWriter.
   P2PQuicPacketTransport* packet_transport_;
   // The QuicConnection owns this packet writer and will outlive it.
-  const quic::QuicConnection* connection_;
+  quic::QuicConnection* connection_;
 
   bool writable_ = false;
   THREAD_CHECKER(thread_checker_);
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.cc b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.cc
new file mode 100644
index 0000000..a875a85
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.cc
@@ -0,0 +1,91 @@
+// 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 "third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.h"
+
+#include "net/quic/quic_chromium_alarm_factory.h"
+#include "net/third_party/quic/platform/impl/quic_chromium_clock.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h"
+#include "third_party/blink/renderer/platform/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/web_task_runner.h"
+
+namespace blink {
+
+QuicTransportHost::QuicTransportHost(
+    scoped_refptr<base::SingleThreadTaskRunner> proxy_thread,
+    base::WeakPtr<QuicTransportProxy> proxy)
+    : proxy_thread_(std::move(proxy_thread)), proxy_(std::move(proxy)) {
+  DETACH_FROM_THREAD(thread_checker_);
+  DCHECK(proxy_thread_);
+  DCHECK(proxy_);
+}
+
+QuicTransportHost::~QuicTransportHost() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // If the TaskRunner this is getting initialized on is destroyed before
+  // Initialize is called then |ice_transport_host_| may still be null.
+  if (ice_transport_host_) {
+    ice_transport_host_->DisconnectConsumer(this);
+  }
+}
+
+void QuicTransportHost::Initialize(
+    IceTransportHost* ice_transport_host,
+    scoped_refptr<base::SingleThreadTaskRunner> host_thread,
+    quic::Perspective perspective,
+    const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>& certificates) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(ice_transport_host);
+  DCHECK(!ice_transport_host_);
+  ice_transport_host_ = ice_transport_host;
+  quic::QuicClock* clock = quic::QuicChromiumClock::GetInstance();
+  auto alarm_factory =
+      std::make_unique<net::QuicChromiumAlarmFactory>(host_thread.get(), clock);
+  quic_transport_factory_.reset(
+      new P2PQuicTransportFactoryImpl(clock, std::move(alarm_factory)));
+  P2PQuicTransportConfig config(
+      this, ice_transport_host->ConnectConsumer(this)->packet_transport(),
+      certificates);
+  config.is_server = (perspective == quic::Perspective::IS_SERVER);
+  quic_transport_ =
+      quic_transport_factory_->CreateQuicTransport(std::move(config));
+}
+
+void QuicTransportHost::Start(
+    std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  quic_transport_->Start(std::move(remote_fingerprints));
+}
+
+void QuicTransportHost::Stop() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  quic_transport_->Stop();
+}
+
+void QuicTransportHost::OnRemoteStopped() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  PostCrossThreadTask(
+      *proxy_thread_, FROM_HERE,
+      CrossThreadBind(&QuicTransportProxy::OnRemoteStopped, proxy_));
+}
+
+void QuicTransportHost::OnConnectionFailed(const std::string& error_details,
+                                           bool from_remote) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  PostCrossThreadTask(*proxy_thread_, FROM_HERE,
+                      CrossThreadBind(&QuicTransportProxy::OnConnectionFailed,
+                                      proxy_, error_details, from_remote));
+}
+
+void QuicTransportHost::OnConnected() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  PostCrossThreadTask(
+      *proxy_thread_, FROM_HERE,
+      CrossThreadBind(&QuicTransportProxy::OnConnected, proxy_));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.h b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.h
new file mode 100644
index 0000000..0558c95
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.h
@@ -0,0 +1,77 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_HOST_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_HOST_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "net/third_party/quic/core/quic_types.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h"
+
+namespace blink {
+
+class IceTransportHost;
+class P2PQuicTransportFactory;
+class QuicTransportProxy;
+
+// The host class is the host side correspondent to the QuicTransportProxy. See
+// the QuicTransportProxy documentation for background. This class lives on the
+// host thread and proxies calls between the QuicTransportProxy and the
+// P2PQuicTransport (which is single-threaded).
+//
+//        proxy thread                                host thread
+// +-----------------------+             +-----------------------------------+
+// |                       |             |                                   |
+// |        <-> ICE Proxy  |  =========> |  ICE Host <-> P2PTransportChannel |
+// |               ^       |  <--------- |     ^                ^            |
+// | client        |       |             |     |                |            |
+// |               v       |             |     v                v            |
+// |        <-> QUIC Proxy |  =========> | QUIC Host <-> P2PQuicTransport    |
+// |                       |  <--------- |                                   |
+// +-----------------------+             +-----------------------------------+
+//
+// The QuicTransportHost connects to the underlying IceTransportHost in
+// Initialize and disconnects in the destructor. The IceTransportHost must
+// outlive the QuicTransportHost.
+//
+// The Host can be constructed on any thread but after that point all methods
+// must be called on the host thread.
+class QuicTransportHost final : public P2PQuicTransport::Delegate {
+ public:
+  QuicTransportHost(scoped_refptr<base::SingleThreadTaskRunner> proxy_thread,
+                    base::WeakPtr<QuicTransportProxy> transport_proxy);
+  ~QuicTransportHost() override;
+
+  void Initialize(
+      IceTransportHost* ice_transport_host,
+      scoped_refptr<base::SingleThreadTaskRunner> host_thread,
+      quic::Perspective perspective,
+      const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>& certificates);
+
+  void Start(
+      std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints);
+  void Stop();
+
+ private:
+  // P2PQuicTransport::Delegate overrides.
+  void OnRemoteStopped() override;
+  void OnConnectionFailed(const std::string& error_details,
+                          bool from_remote) override;
+  void OnConnected() override;
+
+  const scoped_refptr<base::SingleThreadTaskRunner> proxy_thread_;
+  std::unique_ptr<P2PQuicTransportFactory> quic_transport_factory_;
+  std::unique_ptr<P2PQuicTransport> quic_transport_;
+  base::WeakPtr<QuicTransportProxy> proxy_;
+  IceTransportHost* ice_transport_host_ = nullptr;
+
+  THREAD_CHECKER(thread_checker_);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_HOST_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.cc b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.cc
new file mode 100644
index 0000000..815129d
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.cc
@@ -0,0 +1,104 @@
+// 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 "third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h"
+
+#include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_host.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_host.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h"
+#include "third_party/blink/renderer/platform/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/web_task_runner.h"
+
+namespace blink {
+
+QuicTransportProxy::QuicTransportProxy(
+    Delegate* delegate,
+    IceTransportProxy* ice_transport_proxy,
+    quic::Perspective perspective,
+    const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>& certificates)
+    : host_(nullptr,
+            base::OnTaskRunnerDeleter(ice_transport_proxy->host_thread())),
+      delegate_(delegate),
+      ice_transport_proxy_(ice_transport_proxy),
+      weak_ptr_factory_(this) {
+  DCHECK(delegate_);
+  DCHECK(ice_transport_proxy_);
+  scoped_refptr<base::SingleThreadTaskRunner> proxy_thread =
+      ice_transport_proxy->proxy_thread();
+  DCHECK(proxy_thread->BelongsToCurrentThread());
+  // Wait to initialize the host until the weak_ptr_factory_ is initialized.
+  // The QuicTransportHost is constructed on the proxy thread but should only be
+  // interacted with via PostTask to the host thread. The OnTaskRunnerDeleter
+  // (configured above) will ensure it gets deleted on the host thread.
+  host_.reset(
+      new QuicTransportHost(proxy_thread, weak_ptr_factory_.GetWeakPtr()));
+  // Connect to the IceTransportProxy. This gives us a reference to the
+  // underlying IceTransportHost that should be connected by the
+  // QuicTransportHost on the host thread. It is safe to post it unretained
+  // since the IceTransportHost's ownership is determined by the
+  // IceTransportProxy, and the IceTransportProxy is required to outlive this
+  // object.
+  IceTransportHost* ice_transport_host =
+      ice_transport_proxy->ConnectConsumer(this);
+  PostCrossThreadTask(
+      *host_thread(), FROM_HERE,
+      CrossThreadBind(&QuicTransportHost::Initialize,
+                      CrossThreadUnretained(host_.get()),
+                      CrossThreadUnretained(ice_transport_host), host_thread(),
+                      perspective, certificates));
+}
+
+QuicTransportProxy::~QuicTransportProxy() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  ice_transport_proxy_->DisconnectConsumer(this);
+  // Note: The QuicTransportHost will be deleted on the host thread.
+}
+
+scoped_refptr<base::SingleThreadTaskRunner> QuicTransportProxy::proxy_thread()
+    const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return ice_transport_proxy_->proxy_thread();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner> QuicTransportProxy::host_thread()
+    const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return ice_transport_proxy_->host_thread();
+}
+
+void QuicTransportProxy::Start(
+    std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  PostCrossThreadTask(
+      *host_thread(), FROM_HERE,
+      CrossThreadBind(&QuicTransportHost::Start,
+                      CrossThreadUnretained(host_.get()),
+                      WTF::Passed(std::move(remote_fingerprints))));
+}
+
+void QuicTransportProxy::Stop() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  PostCrossThreadTask(*host_thread(), FROM_HERE,
+                      CrossThreadBind(&QuicTransportHost::Stop,
+                                      CrossThreadUnretained(host_.get())));
+}
+
+void QuicTransportProxy::OnConnected() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  delegate_->OnConnected();
+}
+
+void QuicTransportProxy::OnRemoteStopped() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  delegate_->OnRemoteStopped();
+}
+
+void QuicTransportProxy::OnConnectionFailed(const std::string& error_details,
+                                            bool from_remote) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  delegate_->OnConnectionFailed(error_details, from_remote);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h
new file mode 100644
index 0000000..3e158944
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h
@@ -0,0 +1,96 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_PROXY_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_PROXY_H_
+
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "net/third_party/quic/core/quic_types.h"
+#include "third_party/webrtc/rtc_base/scoped_ref_ptr.h"
+
+namespace rtc {
+class RTCCertificate;
+struct SSLFingerprint;
+}  // namespace rtc
+
+namespace blink {
+
+class IceTransportProxy;
+class QuicTransportHost;
+
+// This class allows the QUIC implementation (P2PQuicTransport) to run on a
+// thread different from the thread from which it is controlled. All
+// interactions with the QUIC implementation happen asynchronously.
+//
+// The QuicTransportProxy is intended to be used with an IceTransportProxy --
+// see the IceTransportProxy class documentation for background and terms. The
+// proxy and host threads used with the QuicTransportProxy should be the same as
+// the ones used with the connected IceTransportProxy.
+class QuicTransportProxy final {
+ public:
+  // Delegate for receiving callbacks from the QUIC implementation. These all
+  // run on the proxy thread.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Called when the QUIC handshake finishes and fingerprints have been
+    // verified.
+    virtual void OnConnected() {}
+    // Called when the remote side has indicated it is closed.
+    virtual void OnRemoteStopped() {}
+    // Called when the connection is closed due to a QUIC error. This can happen
+    // locally by the framer or remotely by the peer.
+    virtual void OnConnectionFailed(const std::string& error_details,
+                                    bool from_remote) {}
+  };
+
+  // Construct a Proxy with the underlying QUIC implementation running on the
+  // same thread as the IceTransportProxy. Callbacks will be serviced by the
+  // given delegate.
+  // The delegate and IceTransportProxy must outlive the QuicTransportProxy.
+  // The QuicTransportProxy will immediately connect to the given
+  // IceTransportProxy; it can be disconnected by destroying the
+  // QuicTransportProxy object.
+  QuicTransportProxy(
+      Delegate* delegate,
+      IceTransportProxy* ice_transport_proxy,
+      quic::Perspective perspective,
+      const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>& certificates);
+  ~QuicTransportProxy();
+
+  scoped_refptr<base::SingleThreadTaskRunner> proxy_thread() const;
+  scoped_refptr<base::SingleThreadTaskRunner> host_thread() const;
+
+  void Start(
+      std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints);
+  void Stop();
+
+ private:
+  // Callbacks from QuicTransportHost.
+  friend class QuicTransportHost;
+  void OnConnected();
+  void OnRemoteStopped();
+  void OnConnectionFailed(const std::string& error_details, bool from_remote);
+
+  // Since the Host is deleted on the host thread (Via OnTaskRunnerDeleter), as
+  // long as this is alive it is safe to post tasks to it (using unretained).
+  std::unique_ptr<QuicTransportHost, base::OnTaskRunnerDeleter> host_;
+  Delegate* const delegate_;
+  IceTransportProxy* ice_transport_proxy_;
+
+  THREAD_CHECKER(thread_checker_);
+
+  // Must be the last member.
+  base::WeakPtrFactory<QuicTransportProxy> weak_ptr_factory_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_QUIC_TRANSPORT_PROXY_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/test/mock_ice_transport_adapter.h b/third_party/blink/renderer/modules/peerconnection/adapters/test/mock_ice_transport_adapter.h
index 18e9f33a..f8eaaf75 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/test/mock_ice_transport_adapter.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/test/mock_ice_transport_adapter.h
@@ -27,6 +27,7 @@
                     const std::vector<cricket::Candidate>&));
   MOCK_METHOD1(HandleRemoteRestart, void(const cricket::IceParameters&));
   MOCK_METHOD1(AddRemoteCandidate, void(const cricket::Candidate&));
+  MOCK_CONST_METHOD0(packet_transport, P2PQuicPacketTransport*());
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h b/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h
index 09fe3bc..05b1cd5 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h
@@ -8,10 +8,12 @@
 // This file defines specializations for the CrossThreadCopier that allow WebRTC
 // types to be passed across threads using their copy constructors.
 
+#include <memory>
 #include <set>
 #include <vector>
 
 #include "third_party/blink/renderer/platform/cross_thread_copier.h"
+#include "third_party/webrtc/rtc_base/scoped_ref_ptr.h"
 
 namespace cricket {
 class Candidate;
@@ -20,12 +22,26 @@
 }  // namespace cricket
 
 namespace rtc {
+class RTCCertificate;
 class SocketAddress;
 }
 
 namespace blink {
 
 template <>
+struct CrossThreadCopier<std::string>
+    : public CrossThreadCopierPassThrough<std::string> {
+  STATIC_ONLY(CrossThreadCopier);
+};
+
+template <typename T, typename Allocator>
+struct CrossThreadCopier<std::vector<std::unique_ptr<T>, Allocator>> {
+  STATIC_ONLY(CrossThreadCopier);
+  using Type = std::vector<std::unique_ptr<T>, Allocator>;
+  static Type Copy(Type vector) { return std::move(vector); }
+};
+
+template <>
 struct CrossThreadCopier<cricket::IceParameters>
     : public CrossThreadCopierPassThrough<cricket::IceParameters> {
   STATIC_ONLY(CrossThreadCopier);
@@ -56,6 +72,13 @@
   STATIC_ONLY(CrossThreadCopier);
 };
 
+template <>
+struct CrossThreadCopier<std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>>
+    : public CrossThreadCopierPassThrough<
+          std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>> {
+  STATIC_ONLY(CrossThreadCopier);
+};
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_WEB_RTC_CROSS_THREAD_COPIER_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
index ca5ddef..6d9af12a 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_server.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event_init.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h"
 #include "third_party/webrtc/api/jsepicecandidate.h"
 #include "third_party/webrtc/api/peerconnectioninterface.h"
 #include "third_party/webrtc/p2p/base/portallocator.h"
@@ -123,6 +124,29 @@
   DCHECK(!proxy_);
 }
 
+bool RTCIceTransport::HasConsumer() const {
+  return consumer_;
+}
+
+IceTransportProxy* RTCIceTransport::ConnectConsumer(
+    RTCQuicTransport* consumer) {
+  DCHECK(consumer);
+  DCHECK(proxy_);
+  if (!consumer_) {
+    consumer_ = consumer;
+  } else {
+    DCHECK_EQ(consumer_, consumer);
+  }
+  return proxy_.get();
+}
+
+void RTCIceTransport::DisconnectConsumer(RTCQuicTransport* consumer) {
+  DCHECK(consumer);
+  DCHECK(proxy_);
+  DCHECK_EQ(consumer, consumer_);
+  consumer_ = nullptr;
+}
+
 String RTCIceTransport::role() const {
   switch (role_) {
     case cricket::ICEROLE_CONTROLLING:
@@ -335,6 +359,9 @@
     }
     proxy_->Start(ConvertIceParameters(remote_parameters), role,
                   initial_remote_candidates);
+    if (consumer_) {
+      consumer_->OnTransportStarted();
+    }
   } else {
     remote_candidates_.clear();
     state_ = RTCIceTransportState::kNew;
@@ -347,6 +374,11 @@
   if (IsClosed()) {
     return;
   }
+  if (HasConsumer()) {
+    consumer_->stop();
+  }
+  // Stopping the consumer should cause it to disconnect.
+  DCHECK(!HasConsumer());
   state_ = RTCIceTransportState::kClosed;
   proxy_.reset();
 }
@@ -459,6 +491,7 @@
   visitor->Trace(local_candidates_);
   visitor->Trace(remote_candidates_);
   visitor->Trace(selected_candidate_pair_);
+  visitor->Trace(consumer_);
   EventTargetWithInlineData::Trace(visitor);
   ContextLifecycleObserver::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
index 15276055..364b120 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
@@ -20,6 +20,7 @@
 class RTCIceCandidate;
 class RTCIceGatherOptions;
 class IceTransportAdapterCrossThreadFactory;
+class RTCQuicTransport;
 
 enum class RTCIceTransportState {
   kNew,
@@ -58,8 +59,26 @@
 
   ~RTCIceTransport() override;
 
+  // Returns true if start() has been called.
+  bool IsStarted() const { return role_ != cricket::ICEROLE_UNKNOWN; }
+
+  // Returns the role specified in start().
+  cricket::IceRole GetRole() const { return role_; }
+
+  // Returns true if the RTCIceTransport is in a terminal state.
   bool IsClosed() const { return state_ == RTCIceTransportState::kClosed; }
 
+  // An RTCQuicTransport can be connected to this RTCIceTransport. Only one can
+  // be connected at a time. The consumer will be automatically disconnected
+  // if stop() is called on this object. Otherwise, the RTCQuicTransport is
+  // responsible for disconnecting itself when it is done.
+  // ConnectConsumer returns an IceTransportProxy that can be used to connect
+  // a QuicTransportProxy. It may be called repeatedly with the same
+  // RTCQuicTransport.
+  bool HasConsumer() const;
+  IceTransportProxy* ConnectConsumer(RTCQuicTransport* consumer);
+  void DisconnectConsumer(RTCQuicTransport* consumer);
+
   // rtc_ice_transport.idl
   String role() const;
   String state() const;
@@ -125,6 +144,8 @@
 
   base::Optional<RTCIceCandidatePair> selected_candidate_pair_;
 
+  Member<RTCQuicTransport> consumer_;
+
   // Handle to the WebRTC ICE transport. Created when this binding is
   // constructed and deleted once network traffic should be stopped.
   std::unique_ptr<IceTransportProxy> proxy_;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
index 407a8bb..79d0957 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
@@ -4,15 +4,19 @@
 
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h"
 
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_certificate.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h"
-#include "third_party/blink/renderer/modules/peerconnection/rtc_quic_parameters.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h"
 
 namespace blink {
 
 RTCQuicTransport* RTCQuicTransport::Create(
+    ExecutionContext* context,
     RTCIceTransport* transport,
     const HeapVector<Member<RTCCertificate>>& certificates,
     ExceptionState& exception_state) {
@@ -22,6 +26,13 @@
         "Cannot construct an RTCQuicTransport with a closed RTCIceTransport.");
     return nullptr;
   }
+  if (transport->HasConsumer()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Cannot construct an RTCQuicTransport "
+                                      "with an RTCIceTransport that already "
+                                      "has a connected RTCQuicTransport.");
+    return nullptr;
+  }
   for (const auto& certificate : certificates) {
     if (certificate->expires() < ConvertSecondsToDOMTimeStamp(CurrentTime())) {
       exception_state.ThrowTypeError(
@@ -29,16 +40,36 @@
       return nullptr;
     }
   }
-  return new RTCQuicTransport(transport, certificates, exception_state);
+  return new RTCQuicTransport(context, transport, certificates,
+                              exception_state);
 }
 
 RTCQuicTransport::RTCQuicTransport(
+    ExecutionContext* context,
     RTCIceTransport* transport,
     const HeapVector<Member<RTCCertificate>>& certificates,
     ExceptionState& exception_state)
-    : transport_(transport), certificates_(certificates) {}
+    : ContextLifecycleObserver(context),
+      transport_(transport),
+      certificates_(certificates) {
+  transport->ConnectConsumer(this);
+}
 
-RTCQuicTransport::~RTCQuicTransport() = default;
+RTCQuicTransport::~RTCQuicTransport() {
+  DCHECK(!proxy_);
+}
+
+void RTCQuicTransport::Close(RTCQuicTransportState new_state) {
+  DCHECK(!IsClosed());
+  for (RTCQuicStream* stream : streams_) {
+    stream->Stop();
+  }
+  streams_.clear();
+  transport_->DisconnectConsumer(this);
+  proxy_.reset();
+  state_ = new_state;
+  DCHECK(IsClosed());
+}
 
 RTCIceTransport* RTCQuicTransport::transport() const {
   return transport_;
@@ -76,7 +107,7 @@
 
 void RTCQuicTransport::getRemoteParameters(
     base::Optional<RTCQuicParameters>& result) const {
-  result = base::nullopt;
+  result = remote_parameters_;
 }
 
 const HeapVector<Member<RTCCertificate>>& RTCQuicTransport::getCertificates()
@@ -89,12 +120,82 @@
   return remote_certificates_;
 }
 
-void RTCQuicTransport::stop() {
-  for (RTCQuicStream* stream : streams_) {
-    stream->Stop();
+static quic::Perspective QuicPerspectiveFromIceRole(cricket::IceRole ice_role) {
+  switch (ice_role) {
+    case cricket::ICEROLE_CONTROLLED:
+      return quic::Perspective::IS_CLIENT;
+    case cricket::ICEROLE_CONTROLLING:
+      return quic::Perspective::IS_SERVER;
+    default:
+      NOTREACHED();
   }
-  streams_.clear();
-  state_ = RTCQuicTransportState::kClosed;
+  return quic::Perspective::IS_CLIENT;
+}
+
+void RTCQuicTransport::start(const RTCQuicParameters& remote_parameters,
+                             ExceptionState& exception_state) {
+  if (RaiseExceptionIfClosed(exception_state)) {
+    return;
+  }
+  if (remote_parameters_) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "Cannot start() multiple times.");
+    return;
+  }
+  remote_parameters_ = remote_parameters;
+  if (transport_->IsStarted()) {
+    StartConnection();
+  }
+}
+
+static std::unique_ptr<rtc::SSLFingerprint> RTCDtlsFingerprintToSSLFingerprint(
+    const RTCDtlsFingerprint& dtls_fingerprint) {
+  std::string algorithm = WebString(dtls_fingerprint.algorithm()).Utf8();
+  std::string value = WebString(dtls_fingerprint.value()).Utf8();
+  std::unique_ptr<rtc::SSLFingerprint> rtc_fingerprint(
+      rtc::SSLFingerprint::CreateFromRfc4572(algorithm, value));
+  DCHECK(rtc_fingerprint);
+  return rtc_fingerprint;
+}
+
+void RTCQuicTransport::StartConnection() {
+  DCHECK_EQ(state_, RTCQuicTransportState::kNew);
+  DCHECK(remote_parameters_);
+
+  state_ = RTCQuicTransportState::kConnecting;
+
+  std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> rtc_certificates;
+  for (const auto& certificate : certificates_) {
+    rtc_certificates.push_back(certificate->Certificate());
+  }
+  IceTransportProxy* transport_proxy = transport_->ConnectConsumer(this);
+  proxy_.reset(new QuicTransportProxy(
+      this, transport_proxy, QuicPerspectiveFromIceRole(transport_->GetRole()),
+      rtc_certificates));
+
+  std::vector<std::unique_ptr<rtc::SSLFingerprint>> rtc_fingerprints;
+  for (const RTCDtlsFingerprint& fingerprint :
+       remote_parameters_->fingerprints()) {
+    rtc_fingerprints.push_back(RTCDtlsFingerprintToSSLFingerprint(fingerprint));
+  }
+  proxy_->Start(std::move(rtc_fingerprints));
+}
+
+void RTCQuicTransport::OnTransportStarted() {
+  // The RTCIceTransport has now been started.
+  if (remote_parameters_) {
+    StartConnection();
+  }
+}
+
+void RTCQuicTransport::stop() {
+  if (IsClosed()) {
+    return;
+  }
+  if (proxy_) {
+    proxy_->Stop();
+  }
+  Close(RTCQuicTransportState::kClosed);
 }
 
 RTCQuicStream* RTCQuicTransport::createStream(ExceptionState& exception_state) {
@@ -106,6 +207,22 @@
   return stream;
 }
 
+void RTCQuicTransport::OnConnected() {
+  state_ = RTCQuicTransportState::kConnected;
+  DispatchEvent(*Event::Create(EventTypeNames::statechange));
+}
+
+void RTCQuicTransport::OnConnectionFailed(const std::string& error_details,
+                                          bool from_remote) {
+  Close(RTCQuicTransportState::kFailed);
+  DispatchEvent(*Event::Create(EventTypeNames::statechange));
+}
+
+void RTCQuicTransport::OnRemoteStopped() {
+  Close(RTCQuicTransportState::kClosed);
+  DispatchEvent(*Event::Create(EventTypeNames::statechange));
+}
+
 bool RTCQuicTransport::RaiseExceptionIfClosed(
     ExceptionState& exception_state) const {
   if (IsClosed()) {
@@ -117,12 +234,30 @@
   return false;
 }
 
+const AtomicString& RTCQuicTransport::InterfaceName() const {
+  return EventTargetNames::RTCQuicTransport;
+}
+
+ExecutionContext* RTCQuicTransport::GetExecutionContext() const {
+  return ContextLifecycleObserver::GetExecutionContext();
+}
+
+void RTCQuicTransport::ContextDestroyed(ExecutionContext*) {
+  stop();
+}
+
+bool RTCQuicTransport::HasPendingActivity() const {
+  return static_cast<bool>(proxy_);
+}
+
 void RTCQuicTransport::Trace(blink::Visitor* visitor) {
   visitor->Trace(transport_);
   visitor->Trace(certificates_);
   visitor->Trace(remote_certificates_);
+  visitor->Trace(remote_parameters_);
   visitor->Trace(streams_);
-  ScriptWrappable::Trace(visitor);
+  EventTargetWithInlineData::Trace(visitor);
+  ContextLifecycleObserver::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h
index 161dd63c8..d734457 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h
@@ -5,8 +5,11 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_RTC_QUIC_TRANSPORT_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_RTC_QUIC_TRANSPORT_H_
 
+#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
+#include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h"
+#include "third_party/blink/renderer/modules/event_target_modules.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/quic_transport_proxy.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_parameters.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 
 namespace blink {
 
@@ -14,7 +17,6 @@
 class ExceptionState;
 class RTCCertificate;
 class RTCIceTransport;
-class RTCQuicParameters;
 class RTCQuicStream;
 
 enum class RTCQuicTransportState {
@@ -25,11 +27,17 @@
   kFailed
 };
 
-class MODULES_EXPORT RTCQuicTransport final : public ScriptWrappable {
+class MODULES_EXPORT RTCQuicTransport final
+    : public EventTargetWithInlineData,
+      public ActiveScriptWrappable<RTCQuicTransport>,
+      public ContextLifecycleObserver,
+      public QuicTransportProxy::Delegate {
   DEFINE_WRAPPERTYPEINFO();
+  USING_GARBAGE_COLLECTED_MIXIN(RTCQuicTransport);
 
  public:
   static RTCQuicTransport* Create(
+      ExecutionContext* context,
       RTCIceTransport* transport,
       const HeapVector<Member<RTCCertificate>>& certificates,
       ExceptionState& exception_state);
@@ -43,24 +51,57 @@
   void getRemoteParameters(base::Optional<RTCQuicParameters>& result) const;
   const HeapVector<Member<RTCCertificate>>& getCertificates() const;
   const HeapVector<Member<DOMArrayBuffer>>& getRemoteCertificates() const;
+  void start(const RTCQuicParameters& remote_parameters,
+             ExceptionState& exception_state);
   void stop();
   RTCQuicStream* createStream(ExceptionState& exception_state);
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(statechange);
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(error);
+
+  // Called by the RTCIceTransport when its start() method is called.
+  void OnTransportStarted();
+
+  // EventTarget overrides.
+  const AtomicString& InterfaceName() const override;
+  ExecutionContext* GetExecutionContext() const override;
+
+  // ContextLifecycleObserver overrides.
+  void ContextDestroyed(ExecutionContext*) override;
+
+  // ActiveScriptWrappable overrides.
+  bool HasPendingActivity() const override;
 
   // For garbage collection.
   void Trace(blink::Visitor* visitor) override;
 
  private:
-  RTCQuicTransport(RTCIceTransport* transport,
+  RTCQuicTransport(ExecutionContext* context,
+                   RTCIceTransport* transport,
                    const HeapVector<Member<RTCCertificate>>& certificates,
                    ExceptionState& exception_state);
 
+  // QuicTransportProxy::Delegate overrides;
+  void OnConnected() override;
+  void OnConnectionFailed(const std::string& error_details,
+                          bool from_remote) override;
+  void OnRemoteStopped() override;
+
   bool IsClosed() const { return state_ == RTCQuicTransportState::kClosed; }
   bool RaiseExceptionIfClosed(ExceptionState& exception_state) const;
 
+  // Starts the underlying QUIC connection.
+  void StartConnection();
+
+  // Close all streams, delete the underlying QUIC transport, and transition to
+  // the given state, closed or failed.
+  void Close(RTCQuicTransportState new_state);
+
   Member<RTCIceTransport> transport_;
   RTCQuicTransportState state_ = RTCQuicTransportState::kNew;
   HeapVector<Member<RTCCertificate>> certificates_;
   HeapVector<Member<DOMArrayBuffer>> remote_certificates_;
+  base::Optional<RTCQuicParameters> remote_parameters_;
+  std::unique_ptr<QuicTransportProxy> proxy_;
   HeapHashSet<Member<RTCQuicStream>> streams_;
 };
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.idl b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.idl
index df34813..6118b67 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.idl
@@ -13,18 +13,23 @@
 
 // https://w3c.github.io/webrtc-quic/#quic-transport*
 [
+   ActiveScriptWrappable,
    Constructor(RTCIceTransport transport, sequence<RTCCertificate> certificates),
+   ConstructorCallWith=ExecutionContext,
    RaisesException=Constructor,
    Exposed=Window,
    RuntimeEnabled=RTCQuicTransport
-] interface RTCQuicTransport {
+] interface RTCQuicTransport : EventTarget {
     readonly attribute RTCIceTransport transport;
     readonly attribute RTCQuicTransportState state;
     RTCQuicParameters getLocalParameters();
     RTCQuicParameters? getRemoteParameters();
     sequence<RTCCertificate> getCertificates();
     sequence<ArrayBuffer> getRemoteCertificates();
+    [RaisesException] void start(RTCQuicParameters remoteParameters);
     void stop();
     [RaisesException, RuntimeEnabled=RTCQuicStream] RTCQuicStream createStream();
-    // TODO(crbug.com/868068): Implement remaining control methods + events.
+    attribute EventHandler onstatechange;
+    attribute EventHandler onerror;
+    // TODO(crbug.com/868068): Implement onstream.
 };
diff --git a/third_party/blink/renderer/modules/speech/speech_synthesis.cc b/third_party/blink/renderer/modules/speech/speech_synthesis.cc
index d07e54d5..31defe4 100644
--- a/third_party/blink/renderer/modules/speech/speech_synthesis.cc
+++ b/third_party/blink/renderer/modules/speech/speech_synthesis.cc
@@ -113,10 +113,6 @@
   if (!document)
     return;
 
-  // If SpeechSynthesis followed autoplay policy, we could simply fire an error
-  // here and ignore this utterance. For now, just log some UseCounters to
-  // evaluate potential breakage.
-  //
   // Note: Non-UseCounter based TTS metrics are of the form TextToSpeech.* and
   // are generally global, whereas these are scoped to a single page load.
   UseCounter::Count(document, WebFeature::kTextToSpeech_Speak);
@@ -125,6 +121,8 @@
   if (!IsAllowedToStartByAutoplay()) {
     Deprecation::CountDeprecation(
         document, WebFeature::kTextToSpeech_SpeakDisallowedByAutoplay);
+    FireErrorEvent(utterance, 0 /* char_index */, "not-allowed");
+    return;
   }
 
   utterance_queue_.push_back(utterance);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.h b/third_party/blink/renderer/modules/webaudio/audio_context.h
index 4b8f8789..2c93de4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.h
@@ -42,7 +42,7 @@
   bool IsContextClosed() const final;
 
   ScriptPromise suspendContext(ScriptState*);
-  ScriptPromise resumeContext(ScriptState*) final;
+  ScriptPromise resumeContext(ScriptState*);
 
   bool HasRealtimeConstraint() final { return true; }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.idl b/third_party/blink/renderer/modules/webaudio/audio_context.idl
index 2983635f..cd64c27 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.idl
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.idl
@@ -40,6 +40,7 @@
 ] interface AudioContext : BaseAudioContext {
     [MeasureAs=AudioContextSuspend, CallWith=ScriptState, ImplementedAs=suspendContext] Promise<void> suspend();
     [MeasureAs=AudioContextClose, CallWith=ScriptState, ImplementedAs=closeContext] Promise<void> close();
+    [MeasureAs=AudioContextResume, CallWith=ScriptState, ImplementedAs=resumeContext] Promise<void> resume();
 
     // Output timestamp
     [MeasureAs=AudioContextGetOutputTimestamp, CallWith=ScriptState] AudioTimestamp getOutputTimestamp();
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.cc b/third_party/blink/renderer/modules/webaudio/audio_param.cc
index eb6008313..24f4820 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_param.cc
@@ -52,7 +52,9 @@
       automation_rate_(rate),
       rate_mode_(rate_mode),
       min_value_(min_value),
-      max_value_(max_value) {
+      max_value_(max_value),
+      summing_bus_(
+          AudioBus::Create(1, AudioUtilities::kRenderQuantumFrames, false)) {
   // The destination MUST exist because we need the destination handler for the
   // AudioParam.
   CHECK(context.destination());
@@ -258,23 +260,25 @@
     SetIntrinsicValue(value);
   }
 
-  // Now sum all of the audio-rate connections together (unity-gain summing
-  // junction).  Note that connections would normally be mono, but we mix down
-  // to mono if necessary.
-  scoped_refptr<AudioBus> summing_bus =
-      AudioBus::Create(1, number_of_values, false);
-  summing_bus->SetChannelMemory(0, values, number_of_values);
+  // If there are any connections, sum all of the audio-rate connections
+  // together (unity-gain summing junction).  Note that connections would
+  // normally be mono, but we mix down to mono if necessary.
+  if (NumberOfRenderingConnections() > 0) {
+    DCHECK_LE(number_of_values, AudioUtilities::kRenderQuantumFrames);
 
-  for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) {
-    AudioNodeOutput* output = RenderingOutput(i);
-    DCHECK(output);
+    summing_bus_->SetChannelMemory(0, values, number_of_values);
 
-    // Render audio from this output.
-    AudioBus* connection_bus =
-        output->Pull(nullptr, AudioUtilities::kRenderQuantumFrames);
+    for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) {
+      AudioNodeOutput* output = RenderingOutput(i);
+      DCHECK(output);
 
-    // Sum, with unity-gain.
-    summing_bus->SumFrom(*connection_bus);
+      // Render audio from this output.
+      AudioBus* connection_bus =
+          output->Pull(nullptr, AudioUtilities::kRenderQuantumFrames);
+
+      // Sum, with unity-gain.
+      summing_bus_->SumFrom(*connection_bus);
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.h b/third_party/blink/renderer/modules/webaudio/audio_param.h
index 1dfb251..eca5b58 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_param.h
@@ -248,6 +248,9 @@
   // The destination node used to get necessary information like the smaple rate
   // and context time.
   scoped_refptr<AudioDestinationHandler> destination_handler_;
+
+  // Audio bus to sum in any connections to the AudioParam.
+  scoped_refptr<AudioBus> summing_bus_;
 };
 
 // AudioParam class represents web-exposed AudioParam interface.
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param_timeline.cc b/third_party/blink/renderer/modules/webaudio/audio_param_timeline.cc
index 5a3c163..139b8c5 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param_timeline.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_param_timeline.cc
@@ -33,6 +33,7 @@
 #include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
+#include "third_party/blink/renderer/platform/audio/vector_math.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/wtf/cpu.h"
@@ -853,8 +854,8 @@
                               number_of_values, sample_rate, control_rate);
 
   // Clamp the values now to the nominal range
-  for (unsigned k = 0; k < number_of_values; ++k)
-    values[k] = clampTo(values[k], min_value, max_value);
+  VectorMath::Vclip(values, 1, &min_value, &max_value, values, 1,
+                    number_of_values);
 
   return last_value;
 }
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.h b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
index 33ab9aea0..7ce3971 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
@@ -220,9 +220,6 @@
                                    const PeriodicWaveConstraints&,
                                    ExceptionState&);
 
-  // Resume
-  virtual ScriptPromise resumeContext(ScriptState*) = 0;
-
   // IIRFilter
   IIRFilterNode* createIIRFilter(Vector<double> feedforward_coef,
                                  Vector<double> feedback_coef,
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.idl b/third_party/blink/renderer/modules/webaudio/base_audio_context.idl
index 6bed5699..00b7009 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.idl
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.idl
@@ -61,9 +61,6 @@
     [RaisesException, MeasureAs=AudioContextCreateChannelSplitter] ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs);
     [RaisesException, MeasureAs=AudioContextCreateChannelMerger] ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs);
 
-    // Pause/resume
-    [MeasureAs=AudioContextResume, CallWith=ScriptState, ImplementedAs=resumeContext] Promise<void> resume();
-
     // TODO(rtoy): These really belong to the AudioContext, but we need them
     // here so we can use an offline audio context to test these.
     [RaisesException, MeasureAs=AudioContextCreateMediaElementSource] MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement);
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_context.h b/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
index 12d81a75..17e4789 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.h
@@ -59,7 +59,7 @@
   ScriptPromise startOfflineRendering(ScriptState*);
 
   ScriptPromise suspendContext(ScriptState*, double);
-  ScriptPromise resumeContext(ScriptState*) final;
+  ScriptPromise resumeContext(ScriptState*);
 
   void RejectPendingResolvers() override;
 
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_context.idl b/third_party/blink/renderer/modules/webaudio/offline_audio_context.idl
index 92d713d..3c57739 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.idl
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.idl
@@ -36,4 +36,5 @@
     readonly attribute unsigned long length;
     [CallWith=ScriptState, ImplementedAs=startOfflineRendering, MeasureAs=OfflineAudioContextStartRendering] Promise<AudioBuffer> startRendering();
     [CallWith=ScriptState, ImplementedAs=suspendContext, MeasureAs=OfflineAudioContextSuspend] Promise<void> suspend(double suspendTime);
+    [MeasureAs=OfflineAudioContextResume, CallWith=ScriptState, ImplementedAs=resumeContext] Promise<void> resume();
 };
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.cc b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.cc
index 95c99cf..b0a1d90 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.cc
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.cc
@@ -4,39 +4,35 @@
 
 #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h"
 
+#include "base/memory/ptr_util.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 
 namespace blink {
 
 // static
-FrameResourceCoordinator* FrameResourceCoordinator::Create(
+std::unique_ptr<FrameResourceCoordinator> FrameResourceCoordinator::Create(
     service_manager::InterfaceProvider* interface_provider) {
-  return new FrameResourceCoordinator(interface_provider);
+  return base::WrapUnique(new FrameResourceCoordinator(interface_provider));
 }
 
 FrameResourceCoordinator::FrameResourceCoordinator(
     service_manager::InterfaceProvider* interface_provider) {
   interface_provider->GetInterface(mojo::MakeRequest(&service_));
+  DCHECK(service_);
 }
 
 FrameResourceCoordinator::~FrameResourceCoordinator() = default;
 
 void FrameResourceCoordinator::SetNetworkAlmostIdle(bool idle) {
-  if (!service_)
-    return;
   service_->SetNetworkAlmostIdle(idle);
 }
 
 void FrameResourceCoordinator::SetLifecycleState(
     resource_coordinator::mojom::LifecycleState state) {
-  if (!service_)
-    return;
   service_->SetLifecycleState(state);
 }
 
 void FrameResourceCoordinator::OnNonPersistentNotificationCreated() {
-  if (!service_)
-    return;
   service_->OnNonPersistentNotificationCreated();
 }
 
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h
index a801a489..600558ad 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/frame_resource_coordinator.h
@@ -19,7 +19,8 @@
   WTF_MAKE_NONCOPYABLE(FrameResourceCoordinator);
 
  public:
-  static FrameResourceCoordinator* Create(service_manager::InterfaceProvider*);
+  static std::unique_ptr<FrameResourceCoordinator> Create(
+      service_manager::InterfaceProvider*);
   ~FrameResourceCoordinator();
 
   void SetNetworkAlmostIdle(bool);
diff --git a/third_party/blink/tools/audit_non_blink_usage.py b/third_party/blink/tools/audit_non_blink_usage.py
index 5fd0abd3..21957b83 100755
--- a/third_party/blink/tools/audit_non_blink_usage.py
+++ b/third_party/blink/tools/audit_non_blink_usage.py
@@ -52,6 +52,7 @@
             'base::Time',
             'base::TimeDelta',
             'base::TimeTicks',
+            'base::ThreadTicks',
             'base::UnguessableToken',
             'base::UnsafeSharedMemoryRegion',
             'base::WeakPtr',
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index 647f8db1..7052708 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 213863997
-Date: 2018/09/20 UTC
+Version: 214990311
+Date: 2018/09/28 UTC
 License: BSD
 Security Critical: Yes
 
diff --git a/third_party/metrics_proto/cast_logs.proto b/third_party/metrics_proto/cast_logs.proto
index 646c5d7f4..923f40d7 100644
--- a/third_party/metrics_proto/cast_logs.proto
+++ b/third_party/metrics_proto/cast_logs.proto
@@ -163,7 +163,7 @@
   repeated CastConnectionInfo cast_connection_info = 2;
 
   // Stores Cast-enabled device specific events with a various context data.
-  // Next tag: 20
+  // Next tag: 21
   message CastEventProto {
     // The name of the action, hashed by same logic used to hash user action
     // event and histogram.
@@ -209,7 +209,7 @@
 
     optional string aogh_request_id = 16;
 
-    optional int64 aogh_local_agent_id = 18;
+    optional int64 aogh_local_device_id = 18;
 
     // Optional value associated with the event. For example, may be used for
     // error codes.
@@ -224,6 +224,9 @@
 
     // Optional value associated with timezone update event.
     optional string timezone_id = 17;
+
+    // Optional value to log ui version.
+    optional string ui_version = 20;
   }
   repeated CastEventProto cast_event = 3;
 
@@ -232,7 +235,7 @@
   optional fixed32 virtual_release_track = 4;
 
   // Cast specific device information which is expected to change over time.
-  // Next tag: 8
+  // Next tag: 9
   message CastDeviceMutableInfo {
     // This is the last type of reboot the device encountered
     // Next tag: 11
@@ -277,6 +280,8 @@
 
     // Current timezone which the device is using.
     optional string timezone_id = 7;
+    // Optional value to log latest ui version.
+    optional string latest_ui_version = 8;
   }
   // The device sends this information at least once per day.
   optional CastDeviceMutableInfo cast_device_mutable_info = 5;
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index 1e717c2..d02c819 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-5c652ddf2cbeb69d43b46a051108949f13a9644b
+dc855853e383fc88c56845f772c259f25a3ed045
diff --git a/third_party/node/package.json b/third_party/node/package.json
index bc3c103..5168b61 100644
--- a/third_party/node/package.json
+++ b/third_party/node/package.json
@@ -6,7 +6,7 @@
     "crisper": "2.1.1",
     "eslint": "3.19.0",
     "polymer-bundler": "3.1.1",
-    "polymer-css-build": "0.3.2",
+    "polymer-css-build": "0.3.3",
     "uglify-es": "3.0.15"
   }
 }
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 8cc40b9..ccbe47ec 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -124,6 +124,7 @@
       'ToTAndroidASan': 'android_clang_tot_asan',
       'ToTAndroid (dbg)': 'android_clang_tot_dbg',
       'ToTAndroidCFI': 'android_clang_tot_cfi_full_cfi_diag_thin_lto_release_static_dcheck_always_on',
+      'ToTAndroidOfficial': 'android_clang_tot_release_minimal_symbols_official',
       'ToTAndroid64': 'android_clang_tot_release_arm64',
       'ToTAndroid x64': 'android_clang_tot_x64',
       'ToTLinux': 'clang_tot_linux_full_symbols_shared_release',
@@ -781,6 +782,16 @@
       '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': [
+      'android', 'release', 'static', 'minimal_symbols', 'official',
+      'clang_tot',
+    ],
+
     'android_clang_tot_x64': [
       'android_without_codecs', 'clang_tot', 'shared', 'x64', 'release',
       'dcheck_always_on',
@@ -911,11 +922,6 @@
       'strip_debug_info',
     ],
 
-    'android_clang_tot_release_minimal_symbols': [
-      'android', 'release', 'static', 'minimal_symbols',
-      'strip_debug_info', 'clang_tot',
-    ],
-
     'android_release_bot_minimal_symbols_arm64': [
       'android', 'release_bot', 'minimal_symbols', 'arm64',
       'strip_debug_info',
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f6f891d2..35c129a 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4571,6 +4571,11 @@
   <int value="1" label="Has slow paths with non-AA paint"/>
 </enum>
 
+<enum name="BooleanHasWithheldHosts">
+  <int value="0" label="Does not have withheld hosts"/>
+  <int value="1" label="Has withheld hosts"/>
+</enum>
+
 <enum name="BooleanHiddenPresentationalAutofilled">
   <int value="0" label="Skipped"/>
   <int value="1" label="Autofilled"/>
@@ -28542,6 +28547,8 @@
   <int value="-1937077699" label="http-form-warning"/>
   <int value="-1934661084" label="ForceUnifiedConsentBump:disabled"/>
   <int value="-1933425042" label="OfflinePreviews:enabled"/>
+  <int value="-1932379839"
+      label="OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains:enabled"/>
   <int value="-1930720286" label="nacl-debug-mask"/>
   <int value="-1928198763" label="enable-async-dns"/>
   <int value="-1925117279" label="disable-quic-https"/>
@@ -28944,6 +28951,8 @@
   <int value="-1224962996" label="PwaImprovedSplashScreen:disabled"/>
   <int value="-1222805155" label="PdfIsolation:enabled"/>
   <int value="-1218608640" label="disable-offline-load-stale-cache"/>
+  <int value="-1217462552"
+      label="OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains:disabled"/>
   <int value="-1217425153" label="ChromeHomeClearUrlOnOpen:enabled"/>
   <int value="-1216837777" label="clear-data-reduction-proxy-data-savings"/>
   <int value="-1214870820" label="EnableNewStyleLauncher:enabled"/>
@@ -29089,6 +29098,8 @@
   <int value="-949178861" label="enable-new-avatar-menu"/>
   <int value="-945806012" label="DownloadsUi:enabled"/>
   <int value="-938178614" label="enable-suggestions-with-substring-match"/>
+  <int value="-933377608"
+      label="OmniboxUIExperimentHideSteadyStateUrlScheme:enabled"/>
   <int value="-933316841" label="enable-permissions-blacklist"/>
   <int value="-928138978" label="IPH_DemoMode:disabled"/>
   <int value="-926422468" label="disable-embedded-shared-worker"/>
@@ -29620,6 +29631,8 @@
   <int value="125581289" label="WebRtcHWVP8Encoding:disabled"/>
   <int value="125934378" label="enable-password-link"/>
   <int value="131881947" label="D3DVsync:enabled"/>
+  <int value="132560299"
+      label="OmniboxUIExperimentHideSteadyStateUrlScheme:disabled"/>
   <int value="133482330" label="AppNotificationStatusMessaging:enabled"/>
   <int value="143725809" label="DownloadProgressInfoBar:enabled"/>
   <int value="147342055" label="ChromeHomeClearUrlOnOpen:disabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 1db9907..be7def44 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -7741,6 +7741,16 @@
   </summary>
 </histogram>
 
+<histogram name="Autofill.UploadEvent" enum="BooleanSent">
+<!-- Name completed by histogram_suffixes name="AutofillUploadEvents" -->
+
+  <owner>rogerm@chromium.org</owner>
+  <summary>
+    Whether or not an upload was sent after having been triggered by a form
+    submission or proxy-form-submission.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.UploadOfferedCardOrigin"
     enum="UploadOfferedCardOrigin">
   <owner>jsaul@google.com</owner>
@@ -31334,6 +31344,33 @@
   </summary>
 </histogram>
 
+<histogram name="Extensions.RuntimeHostPermissions.ExtensionHasWithheldHosts"
+    enum="BooleanHasWithheldHosts" expires_after="2019-11-30">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <owner>karandeepb@chromium.org</owner>
+  <summary>
+    Whether an extension had host permissions withheld as a result of the
+    RuntimeHostPermissions feature. Recorded once per extension at profile
+    initialization if and only if the RuntimeHostPermissions feature is enabled
+    and the extension requests any host permissions (i.e., could be affected by
+    the feature).
+  </summary>
+</histogram>
+
+<histogram name="Extensions.RuntimeHostPermissions.GrantedHostCount"
+    units="Granted hosts" expires_after="2019-11-30">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <owner>karandeepb@chromium.org</owner>
+  <summary>
+    The number of hosts an extension has been granted explicit access to that it
+    also requested (note that if the user granted unrequested hosts, those will
+    not be included in this count). Recorded once per extension at profile
+    initialization if and only if the RuntimeHostPermissions feature is enabled
+    and the extension has had host permissions withheld. See also
+    Extensions.RuntimeHostPermissions.ExtensionHasWithheldHosts.
+  </summary>
+</histogram>
+
 <histogram name="Extensions.SandboxUnpackFailure">
   <owner>asargent@chromium.org</owner>
   <summary>
@@ -48063,6 +48100,16 @@
   </summary>
 </histogram>
 
+<histogram name="MobileDownload.CancelledDownloadRemovedFromHistory"
+    units="downloads" expires_after="M72">
+  <owner>qinmin@chromium.org</owner>
+  <owner>dtrainor@chromium.org</owner>
+  <summary>
+    Android: Records the number of cancelled download that are cleaned up from
+    the history, after loading all the downloads from the history DB on startup.
+  </summary>
+</histogram>
+
 <histogram name="MobileDownload.CancelReason" enum="MobileDownloadCancelReason">
   <owner>qinmin@chromium.org</owner>
   <summary>Android: Records the reason that a download is canceled.</summary>
@@ -71090,6 +71137,16 @@
   </summary>
 </histogram>
 
+<histogram
+    name="PageLoad.Internal.PaintTiming.NavigationToFirstContentfulPaint.InitiatingProcess"
+    enum="ProcessType2" expires_after="2018-12-31">
+  <owner>sullivan@chromium.org</owner>
+  <summary>
+    Breakdown of NavigationToFirstContentfulPaint counts by process that
+    initiated the navigation (this will only be renderer or browser).
+  </summary>
+</histogram>
+
 <histogram name="PageLoad.Internal.Prerender" enum="BooleanHit">
   <owner>bmcquade@chromium.org</owner>
   <summary>
@@ -121099,6 +121156,18 @@
   <affected-histogram name="Autofill.UnmaskPrompt.Duration"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="AutofillUploadEvents" separator=".">
+  <suffix name="DomMutationAfterXhr" label="DOM Mutation After XHR"/>
+  <suffix name="FormSubmission" label="Form Submission"/>
+  <suffix name="FrameDetached" label="Frame Detached"/>
+  <suffix name="None" label="None"/>
+  <suffix name="ProbablyFormSubmitted" label="Probable Form Submission"/>
+  <suffix name="SameDocumentNavigation" label="Same Document Navigation"/>
+  <suffix name="Unknown" label="Unknown"/>
+  <suffix name="XhrSucceeded" label="XHR Succeeded"/>
+  <affected-histogram name="Autofill.UploadEvent"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="BackgroundFetchDatabaseStorageErrors" separator=".">
   <suffix name="CleanupTask" label="CleanupTask"/>
   <suffix name="CreateMetadataTask" label="CreateMetadata DatabaseTask"/>
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 782b7e7..085e5d6 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -206,6 +206,7 @@
     "mojom/ax_event_mojom_traits_unittest.cc",
     "mojom/ax_node_data_mojom_traits_unittest.cc",
     "mojom/ax_tree_data_mojom_traits_unittest.cc",
+    "mojom/ax_tree_id_mojom_traits_unittest.cc",
     "mojom/ax_tree_update_mojom_traits_unittest.cc",
     "platform/ax_platform_node_unittest.cc",
     "platform/ax_platform_node_unittest.h",
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index ba32352..55c0753 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -201,7 +201,7 @@
   initial_state.nodes.push_back(static_text2_);
   initial_state.nodes.push_back(inline_box2_);
   initial_state.has_tree_data = true;
-  initial_state.tree_data.tree_id = "0";
+  initial_state.tree_data.tree_id = AXTreeID::FromString("0");
   initial_state.tree_data.title = "Dialog title";
   AXSerializableTree src_tree(initial_state);
 
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 06e18d2e..0f3e6296 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -140,7 +140,7 @@
         } else {
           str_child_index = base::IntToString(child_index_);
         }
-        str = "TreePosition tree_id=" + tree_id_ +
+        str = "TreePosition tree_id=" + tree_id_.ToString() +
               " anchor_id=" + base::IntToString(anchor_id_) +
               " child_index=" + str_child_index;
         break;
@@ -152,7 +152,7 @@
         } else {
           str_text_offset = base::IntToString(text_offset_);
         }
-        str = "TextPosition tree_id=" + tree_id_ +
+        str = "TextPosition tree_id=" + tree_id_.ToString() +
               " anchor_id=" + base::IntToString(anchor_id_) +
               " text_offset=" + str_text_offset + " affinity=" +
               ui::ToString(static_cast<ax::mojom::TextAffinity>(affinity_));
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index ad7b835..648a3f3 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -722,17 +722,20 @@
     if (attr == ax::mojom::StringAttribute::kChildTreeId) {
       // Remove old_string -> id from the map, and clear map keys if
       // their values are now empty.
-      if (child_tree_id_reverse_map_.find(old_string) !=
+      AXTreeID old_ax_tree_id = AXTreeID::FromString(old_string);
+      if (child_tree_id_reverse_map_.find(old_ax_tree_id) !=
           child_tree_id_reverse_map_.end()) {
-        child_tree_id_reverse_map_[old_string].erase(id);
-        if (child_tree_id_reverse_map_[old_string].empty())
-          child_tree_id_reverse_map_.erase(old_string);
+        child_tree_id_reverse_map_[old_ax_tree_id].erase(id);
+        if (child_tree_id_reverse_map_[old_ax_tree_id].empty())
+          child_tree_id_reverse_map_.erase(old_ax_tree_id);
       }
 
       // Add new_string -> id to the map, unless new_id is zero indicating that
       // we're only removing a relation.
-      if (!new_string.empty())
-        child_tree_id_reverse_map_[new_string].insert(id);
+      if (!new_string.empty()) {
+        AXTreeID new_ax_tree_id = AXTreeID::FromString(new_string);
+        child_tree_id_reverse_map_[new_ax_tree_id].insert(id);
+      }
     }
   };
 
diff --git a/ui/accessibility/ax_tree_combiner.cc b/ui/accessibility/ax_tree_combiner.cc
index 00ea20fd..b0ab36fa 100644
--- a/ui/accessibility/ax_tree_combiner.cc
+++ b/ui/accessibility/ax_tree_combiner.cc
@@ -17,7 +17,7 @@
 void AXTreeCombiner::AddTree(const AXTreeUpdate& tree, bool is_root) {
   trees_.push_back(tree);
   if (is_root) {
-    DCHECK_EQ(root_tree_id_, "");
+    DCHECK_EQ(root_tree_id_, AXTreeIDUnknown());
     root_tree_id_ = tree.tree_data.tree_id;
   }
 }
@@ -80,8 +80,8 @@
   AXTreeID tree_id = tree->tree_data.tree_id;
   for (size_t i = 0; i < tree->nodes.size(); ++i) {
     AXNodeData node = tree->nodes[i];
-    AXTreeID child_tree_id =
-        node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
+    AXTreeID child_tree_id = AXTreeID::FromString(
+        node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
 
     // Map the node's ID.
     node.id = MapId(tree_id, node.id);
diff --git a/ui/accessibility/ax_tree_combiner_unittest.cc b/ui/accessibility/ax_tree_combiner_unittest.cc
index 8272ce27..5e8d1dc 100644
--- a/ui/accessibility/ax_tree_combiner_unittest.cc
+++ b/ui/accessibility/ax_tree_combiner_unittest.cc
@@ -10,7 +10,7 @@
 TEST(CombineAXTreesTest, RenumberOneTree) {
   AXTreeUpdate tree;
   tree.has_tree_data = true;
-  tree.tree_data.tree_id = "1";
+  tree.tree_data.tree_id = ui::AXTreeID::FromString("1");
   tree.root_id = 2;
   tree.nodes.resize(3);
   tree.nodes[0].id = 2;
@@ -39,7 +39,7 @@
   AXTreeUpdate parent_tree;
   parent_tree.root_id = 1;
   parent_tree.has_tree_data = true;
-  parent_tree.tree_data.tree_id = "1";
+  parent_tree.tree_data.tree_id = ui::AXTreeID::FromString("1");
   parent_tree.nodes.resize(3);
   parent_tree.nodes[0].id = 1;
   parent_tree.nodes[0].child_ids.push_back(2);
@@ -54,8 +54,8 @@
   AXTreeUpdate child_tree;
   child_tree.root_id = 1;
   child_tree.has_tree_data = true;
-  child_tree.tree_data.parent_tree_id = "1";
-  child_tree.tree_data.tree_id = "2";
+  child_tree.tree_data.parent_tree_id = ui::AXTreeID::FromString("1");
+  child_tree.tree_data.tree_id = ui::AXTreeID::FromString("2");
   child_tree.nodes.resize(3);
   child_tree.nodes[0].id = 1;
   child_tree.nodes[0].child_ids.push_back(2);
@@ -97,7 +97,7 @@
 
   AXTreeUpdate tree;
   tree.has_tree_data = true;
-  tree.tree_data.tree_id = "1";
+  tree.tree_data.tree_id = ui::AXTreeID::FromString("1");
   tree.root_id = 11;
   tree.nodes.resize(2);
   tree.nodes[0].id = 11;
@@ -157,8 +157,8 @@
 TEST(CombineAXTreesTest, FocusedTree) {
   AXTreeUpdate parent_tree;
   parent_tree.has_tree_data = true;
-  parent_tree.tree_data.tree_id = "1";
-  parent_tree.tree_data.focused_tree_id = "2";
+  parent_tree.tree_data.tree_id = ui::AXTreeID::FromString("1");
+  parent_tree.tree_data.focused_tree_id = ui::AXTreeID::FromString("2");
   parent_tree.tree_data.focus_id = 2;
   parent_tree.root_id = 1;
   parent_tree.nodes.resize(3);
@@ -174,8 +174,8 @@
 
   AXTreeUpdate child_tree;
   child_tree.has_tree_data = true;
-  child_tree.tree_data.parent_tree_id = "1";
-  child_tree.tree_data.tree_id = "2";
+  child_tree.tree_data.parent_tree_id = ui::AXTreeID::FromString("1");
+  child_tree.tree_data.tree_id = ui::AXTreeID::FromString("2");
   child_tree.tree_data.focus_id = 3;
   child_tree.root_id = 1;
   child_tree.nodes.resize(3);
diff --git a/ui/accessibility/ax_tree_data.cc b/ui/accessibility/ax_tree_data.cc
index 1e69b36b..ec452ba 100644
--- a/ui/accessibility/ax_tree_data.cc
+++ b/ui/accessibility/ax_tree_data.cc
@@ -23,12 +23,12 @@
 std::string AXTreeData::ToString() const {
   std::string result;
 
-  if (!tree_id.empty())
-    result += " tree_id=" + tree_id;
-  if (!parent_tree_id.empty())
-    result += " parent_tree_id=" + parent_tree_id;
-  if (!focused_tree_id.empty())
-    result += " focused_tree_id=" + focused_tree_id;
+  if (tree_id != AXTreeIDUnknown())
+    result += " tree_id=" + tree_id.ToString();
+  if (parent_tree_id != AXTreeIDUnknown())
+    result += " parent_tree_id=" + parent_tree_id.ToString();
+  if (focused_tree_id != AXTreeIDUnknown())
+    result += " focused_tree_id=" + focused_tree_id.ToString();
 
   if (!doctype.empty())
     result += " doctype=" + doctype;
diff --git a/ui/accessibility/ax_tree_id.cc b/ui/accessibility/ax_tree_id.cc
index bdd900e..6076f11 100644
--- a/ui/accessibility/ax_tree_id.cc
+++ b/ui/accessibility/ax_tree_id.cc
@@ -4,17 +4,58 @@
 
 #include "ui/accessibility/ax_tree_id.h"
 
+#include <iostream>
+
 #include "base/no_destructor.h"
 
 namespace ui {
 
+AXTreeID::AXTreeID() : id_("") {}
+
+AXTreeID::AXTreeID(const std::string& string) : id_(string) {}
+
+// static
+AXTreeID AXTreeID::FromString(const std::string& string) {
+  return AXTreeID(string);
+}
+
+bool AXTreeID::operator==(const AXTreeID& rhs) const {
+  return id_ == rhs.id_;
+}
+
+bool AXTreeID::operator!=(const AXTreeID& rhs) const {
+  return id_ != rhs.id_;
+}
+
+bool AXTreeID::operator<(const AXTreeID& rhs) const {
+  return id_ < rhs.id_;
+}
+
+bool AXTreeID::operator<=(const AXTreeID& rhs) const {
+  return id_ <= rhs.id_;
+}
+
+bool AXTreeID::operator>(const AXTreeID& rhs) const {
+  return id_ > rhs.id_;
+}
+
+bool AXTreeID::operator>=(const AXTreeID& rhs) const {
+  return id_ >= rhs.id_;
+}
+
+std::ostream& operator<<(std::ostream& stream, const AXTreeID& value) {
+  return stream << 0;
+}
+
 const AXTreeID& AXTreeIDUnknown() {
-  static const base::NoDestructor<AXTreeID> ax_tree_id_unknown("");
+  static const base::NoDestructor<AXTreeID> ax_tree_id_unknown(
+      AXTreeID::FromString(""));
   return *ax_tree_id_unknown;
 }
 
 const AXTreeID& DesktopAXTreeID() {
-  static const base::NoDestructor<AXTreeID> desktop_ax_tree_id("0");
+  static const base::NoDestructor<AXTreeID> desktop_ax_tree_id(
+      AXTreeID::FromString("0"));
   return *desktop_ax_tree_id;
 }
 
diff --git a/ui/accessibility/ax_tree_id.h b/ui/accessibility/ax_tree_id.h
index 29f8545..946181b 100644
--- a/ui/accessibility/ax_tree_id.h
+++ b/ui/accessibility/ax_tree_id.h
@@ -9,11 +9,43 @@
 
 #include "ui/accessibility/ax_export.h"
 
+namespace mojo {
+template <typename DataViewType, typename T>
+struct StructTraits;
+}
+
+namespace ax {
+namespace mojom {
+class AXTreeIDDataView;
+}
+}  // namespace ax
+
 namespace ui {
 
-// Note: Originally AXTreeID was an integer. Temporarily we're making it a
-// string with the plan to eventually make it a base::UnguessableToken.
-using AXTreeID = std::string;
+// A unique ID representing an accessibility tree.
+class AX_EXPORT AXTreeID {
+ public:
+  AXTreeID();
+  static AXTreeID FromString(const std::string& string);
+  const std::string& ToString() const { return id_; }
+  operator std::string() const { return id_; }
+
+  bool operator==(const AXTreeID& rhs) const;
+  bool operator!=(const AXTreeID& rhs) const;
+  bool operator<(const AXTreeID& rhs) const;
+  bool operator<=(const AXTreeID& rhs) const;
+  bool operator>(const AXTreeID& rhs) const;
+  bool operator>=(const AXTreeID& rhs) const;
+
+ private:
+  explicit AXTreeID(const std::string& string);
+
+  friend struct mojo::StructTraits<ax::mojom::AXTreeIDDataView, ui::AXTreeID>;
+
+  std::string id_;
+};
+
+AX_EXPORT std::ostream& operator<<(std::ostream& stream, const AXTreeID& value);
 
 // The value to use when an AXTreeID is unknown.
 AX_EXPORT extern const AXTreeID& AXTreeIDUnknown();
diff --git a/ui/accessibility/ax_tree_id_registry.cc b/ui/accessibility/ax_tree_id_registry.cc
index 5fdf873..f0a47078 100644
--- a/ui/accessibility/ax_tree_id_registry.cc
+++ b/ui/accessibility/ax_tree_id_registry.cc
@@ -21,7 +21,8 @@
   if (it != frame_to_ax_tree_id_map_.end())
     return it->second;
 
-  AXTreeID new_id = base::IntToString(++ax_tree_id_counter_);
+  AXTreeID new_id =
+      AXTreeID::FromString(base::IntToString(++ax_tree_id_counter_));
   frame_to_ax_tree_id_map_[frame_id] = new_id;
   ax_tree_to_frame_id_map_[new_id] = frame_id;
 
@@ -41,7 +42,8 @@
     if (it.second == delegate)
       return it.first;
   }
-  AXTreeID new_id = base::IntToString(++ax_tree_id_counter_);
+  AXTreeID new_id =
+      AXTreeID::FromString(base::IntToString(++ax_tree_id_counter_));
   id_to_host_delegate_[new_id] = delegate;
   return new_id;
 }
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 3b27d19..42d660a 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -1435,14 +1435,17 @@
       ax::mojom::StringAttribute::kChildTreeId, "93");
   AXTree tree(initial_state);
 
-  auto child_tree_91_nodes = tree.GetNodeIdsForChildTreeId("91");
+  auto child_tree_91_nodes =
+      tree.GetNodeIdsForChildTreeId(AXTreeID::FromString("91"));
   EXPECT_EQ(0U, child_tree_91_nodes.size());
 
-  auto child_tree_92_nodes = tree.GetNodeIdsForChildTreeId("92");
+  auto child_tree_92_nodes =
+      tree.GetNodeIdsForChildTreeId(AXTreeID::FromString("92"));
   EXPECT_EQ(1U, child_tree_92_nodes.size());
   EXPECT_TRUE(base::ContainsKey(child_tree_92_nodes, 2));
 
-  auto child_tree_93_nodes = tree.GetNodeIdsForChildTreeId("93");
+  auto child_tree_93_nodes =
+      tree.GetNodeIdsForChildTreeId(AXTreeID::FromString("93"));
   EXPECT_EQ(2U, child_tree_93_nodes.size());
   EXPECT_TRUE(base::ContainsKey(child_tree_93_nodes, 3));
   EXPECT_TRUE(base::ContainsKey(child_tree_93_nodes, 4));
@@ -1455,12 +1458,14 @@
 
   EXPECT_TRUE(tree.Unserialize(update));
 
-  child_tree_92_nodes = tree.GetNodeIdsForChildTreeId("92");
+  child_tree_92_nodes =
+      tree.GetNodeIdsForChildTreeId(AXTreeID::FromString("92"));
   EXPECT_EQ(2U, child_tree_92_nodes.size());
   EXPECT_TRUE(base::ContainsKey(child_tree_92_nodes, 2));
   EXPECT_TRUE(base::ContainsKey(child_tree_92_nodes, 3));
 
-  child_tree_93_nodes = tree.GetNodeIdsForChildTreeId("93");
+  child_tree_93_nodes =
+      tree.GetNodeIdsForChildTreeId(AXTreeID::FromString("93"));
   EXPECT_EQ(0U, child_tree_93_nodes.size());
 }
 
diff --git a/ui/accessibility/mojom/BUILD.gn b/ui/accessibility/mojom/BUILD.gn
index d41dbd6..ac10ca3 100644
--- a/ui/accessibility/mojom/BUILD.gn
+++ b/ui/accessibility/mojom/BUILD.gn
@@ -12,6 +12,7 @@
     "ax_host.mojom",
     "ax_node_data.mojom",
     "ax_tree_data.mojom",
+    "ax_tree_id.mojom",
     "ax_tree_update.mojom",
   ]
 
diff --git a/ui/accessibility/mojom/ax_action_data.mojom b/ui/accessibility/mojom/ax_action_data.mojom
index 60de3978..f7e3238 100644
--- a/ui/accessibility/mojom/ax_action_data.mojom
+++ b/ui/accessibility/mojom/ax_action_data.mojom
@@ -5,13 +5,14 @@
 module ax.mojom;
 
 import "ui/accessibility/ax_enums.mojom";
+import "ui/accessibility/mojom/ax_tree_id.mojom";
 import "ui/gfx/geometry/mojo/geometry.mojom";
 
 // A compact representation of an accessibility action and the arguments
 // associated with that action. See ui::AXActionData for full documentation.
 struct AXActionData {
   Action action;
-  string target_tree_id;
+  ax.mojom.AXTreeID target_tree_id;
   string source_extension_id;
   int32 target_node_id;
   int32 request_id;
diff --git a/ui/accessibility/mojom/ax_action_data_mojom_traits.h b/ui/accessibility/mojom/ax_action_data_mojom_traits.h
index 933a73e..3a0b573 100644
--- a/ui/accessibility/mojom/ax_action_data_mojom_traits.h
+++ b/ui/accessibility/mojom/ax_action_data_mojom_traits.h
@@ -7,6 +7,7 @@
 
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/mojom/ax_action_data.mojom-shared.h"
+#include "ui/accessibility/mojom/ax_tree_id_mojom_traits.h"
 #include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
 
 namespace mojo {
@@ -16,7 +17,7 @@
   static ax::mojom::Action action(const ui::AXActionData& a) {
     return a.action;
   }
-  static const std::string& target_tree_id(const ui::AXActionData& a) {
+  static const ui::AXTreeID& target_tree_id(const ui::AXActionData& a) {
     return a.target_tree_id;
   }
   static const std::string& source_extension_id(const ui::AXActionData& a) {
diff --git a/ui/accessibility/mojom/ax_action_data_mojom_traits_unittest.cc b/ui/accessibility/mojom/ax_action_data_mojom_traits_unittest.cc
index 120159a..68603e3 100644
--- a/ui/accessibility/mojom/ax_action_data_mojom_traits_unittest.cc
+++ b/ui/accessibility/mojom/ax_action_data_mojom_traits_unittest.cc
@@ -17,7 +17,7 @@
 TEST(AXActionDataMojomTraitsTest, RoundTrip) {
   ui::AXActionData input;
   input.action = ax::mojom::Action::kBlur;
-  input.target_tree_id = "1";
+  input.target_tree_id = ui::AXTreeID::FromString("1");
   input.source_extension_id = "extension_id";
   input.target_node_id = 2;
   input.request_id = 3;
@@ -37,7 +37,7 @@
       SerializeAndDeserialize<ax::mojom::AXActionData>(&input, &output));
 
   EXPECT_EQ(output.action, ax::mojom::Action::kBlur);
-  EXPECT_EQ(output.target_tree_id, "1");
+  EXPECT_EQ(output.target_tree_id.ToString(), "1");
   EXPECT_EQ(output.source_extension_id, "extension_id");
   EXPECT_EQ(output.target_node_id, 2);
   EXPECT_EQ(output.request_id, 3);
diff --git a/ui/accessibility/mojom/ax_host.mojom b/ui/accessibility/mojom/ax_host.mojom
index 23826d3..fde8570 100644
--- a/ui/accessibility/mojom/ax_host.mojom
+++ b/ui/accessibility/mojom/ax_host.mojom
@@ -7,6 +7,7 @@
 import "ui/accessibility/ax_enums.mojom";
 import "ui/accessibility/mojom/ax_action_data.mojom";
 import "ui/accessibility/mojom/ax_event.mojom";
+import "ui/accessibility/mojom/ax_tree_id.mojom";
 import "ui/accessibility/mojom/ax_tree_update.mojom";
 
 const string kAXHostServiceName = "ax_host_service";
@@ -23,12 +24,12 @@
   // process must send its initial AX node tree immediately (because a feature
   // like ChromeVox is enabled).
   SetRemoteHost(AXRemoteHost remote) =>
-      (string tree_id, bool automation_enabled);
+      (ax.mojom.AXTreeID tree_id, bool automation_enabled);
 
   // Handles an accessibility |event| (e.g. focus change) for |tree_id| in the
   // remote process. Includes |updates| to child nodes.
   HandleAccessibilityEvent(
-      string tree_id, array<AXTreeUpdate> updates, AXEvent event);
+      ax.mojom.AXTreeID tree_id, array<AXTreeUpdate> updates, AXEvent event);
 };
 
 // Remote hosts run outside the browser process, for example in a mojo app like
diff --git a/ui/accessibility/mojom/ax_tree_data.mojom b/ui/accessibility/mojom/ax_tree_data.mojom
index b02571f..c2fabbf2 100644
--- a/ui/accessibility/mojom/ax_tree_data.mojom
+++ b/ui/accessibility/mojom/ax_tree_data.mojom
@@ -5,12 +5,13 @@
 module ax.mojom;
 
 import "ui/accessibility/ax_enums.mojom";
+import "ui/accessibility/mojom/ax_tree_id.mojom";
 
 // See ui::AXTreeData for comments / explanations of these fields.
 struct AXTreeData {
-  string tree_id;
-  string parent_tree_id;
-  string focused_tree_id;
+  ax.mojom.AXTreeID tree_id;
+  ax.mojom.AXTreeID parent_tree_id;
+  ax.mojom.AXTreeID focused_tree_id;
   string doctype;
   bool loaded;
   float loading_progress;
diff --git a/ui/accessibility/mojom/ax_tree_data_mojom_traits.h b/ui/accessibility/mojom/ax_tree_data_mojom_traits.h
index 04b5a67..6beea97 100644
--- a/ui/accessibility/mojom/ax_tree_data_mojom_traits.h
+++ b/ui/accessibility/mojom/ax_tree_data_mojom_traits.h
@@ -7,18 +7,19 @@
 
 #include "ui/accessibility/ax_tree_data.h"
 #include "ui/accessibility/mojom/ax_tree_data.mojom-shared.h"
+#include "ui/accessibility/mojom/ax_tree_id_mojom_traits.h"
 
 namespace mojo {
 
 template <>
 struct StructTraits<ax::mojom::AXTreeDataDataView, ui::AXTreeData> {
-  static const std::string& tree_id(const ui::AXTreeData& p) {
+  static const ui::AXTreeID& tree_id(const ui::AXTreeData& p) {
     return p.tree_id;
   }
-  static const std::string& parent_tree_id(const ui::AXTreeData& p) {
+  static const ui::AXTreeID& parent_tree_id(const ui::AXTreeData& p) {
     return p.parent_tree_id;
   }
-  static const std::string& focused_tree_id(const ui::AXTreeData& p) {
+  static const ui::AXTreeID& focused_tree_id(const ui::AXTreeData& p) {
     return p.focused_tree_id;
   }
   static const std::string& doctype(const ui::AXTreeData& p) {
diff --git a/ui/accessibility/mojom/ax_tree_data_mojom_traits_unittest.cc b/ui/accessibility/mojom/ax_tree_data_mojom_traits_unittest.cc
index c9b134dc..d64ff2c 100644
--- a/ui/accessibility/mojom/ax_tree_data_mojom_traits_unittest.cc
+++ b/ui/accessibility/mojom/ax_tree_data_mojom_traits_unittest.cc
@@ -12,9 +12,9 @@
 
 TEST(AXTreeDataMojomTraitsTest, TestSerializeAndDeserializeAXTreeData) {
   ui::AXTreeData input, output;
-  input.tree_id = "1";
-  input.parent_tree_id = "2";
-  input.focused_tree_id = "3";
+  input.tree_id = ui::AXTreeID::FromString("1");
+  input.parent_tree_id = ui::AXTreeID::FromString("2");
+  input.focused_tree_id = ui::AXTreeID::FromString("3");
   input.doctype = "4";
   input.loaded = true;
   input.loading_progress = 5;
@@ -31,9 +31,9 @@
 
   EXPECT_TRUE(SerializeAndDeserialize<ax::mojom::AXTreeData>(&input, &output));
 
-  EXPECT_EQ("1", output.tree_id);
-  EXPECT_EQ("2", output.parent_tree_id);
-  EXPECT_EQ("3", output.focused_tree_id);
+  EXPECT_EQ("1", output.tree_id.ToString());
+  EXPECT_EQ("2", output.parent_tree_id.ToString());
+  EXPECT_EQ("3", output.focused_tree_id.ToString());
   EXPECT_EQ("4", output.doctype);
   EXPECT_EQ(true, output.loaded);
   EXPECT_EQ(5, output.loading_progress);
diff --git a/ui/accessibility/mojom/ax_tree_id.mojom b/ui/accessibility/mojom/ax_tree_id.mojom
new file mode 100644
index 0000000..6cc327b4
--- /dev/null
+++ b/ui/accessibility/mojom/ax_tree_id.mojom
@@ -0,0 +1,10 @@
+// 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.
+
+module ax.mojom;
+
+struct AXTreeID {
+  // Eventually this may become a base::UnguessableToken.
+  string id;
+};
diff --git a/ui/accessibility/mojom/ax_tree_id.typemap b/ui/accessibility/mojom/ax_tree_id.typemap
new file mode 100644
index 0000000..034ddbe0
--- /dev/null
+++ b/ui/accessibility/mojom/ax_tree_id.typemap
@@ -0,0 +1,15 @@
+# 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.
+
+mojom = "//ui/accessibility/mojom/ax_tree_id.mojom"
+public_headers = [ "//ui/accessibility/ax_tree_id.h" ]
+traits_headers = [ "//ui/accessibility/mojom/ax_tree_id_mojom_traits.h" ]
+sources = [
+  "ax_tree_id_mojom_traits.cc",
+  "ax_tree_id_mojom_traits.h",
+]
+public_deps = [
+  "//ui/accessibility",
+]
+type_mappings = [ "ax.mojom.AXTreeID=ui::AXTreeID" ]
diff --git a/ui/accessibility/mojom/ax_tree_id_mojom_traits.cc b/ui/accessibility/mojom/ax_tree_id_mojom_traits.cc
new file mode 100644
index 0000000..949b7db
--- /dev/null
+++ b/ui/accessibility/mojom/ax_tree_id_mojom_traits.cc
@@ -0,0 +1,18 @@
+// 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 "ui/accessibility/mojom/ax_tree_id_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<ax::mojom::AXTreeIDDataView, ui::AXTreeID>::Read(
+    ax::mojom::AXTreeIDDataView data,
+    ui::AXTreeID* out) {
+  if (!data.ReadId(&out->id_))
+    return false;
+  return true;
+}
+
+}  // namespace mojo
diff --git a/ui/accessibility/mojom/ax_tree_id_mojom_traits.h b/ui/accessibility/mojom/ax_tree_id_mojom_traits.h
new file mode 100644
index 0000000..df481aa
--- /dev/null
+++ b/ui/accessibility/mojom/ax_tree_id_mojom_traits.h
@@ -0,0 +1,22 @@
+// 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 UI_ACCESSIBILITY_MOJOM_AX_TREE_ID_MOJOM_TRAITS_H_
+#define UI_ACCESSIBILITY_MOJOM_AX_TREE_ID_MOJOM_TRAITS_H_
+
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/mojom/ax_tree_id.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<ax::mojom::AXTreeIDDataView, ui::AXTreeID> {
+  static const std::string& id(const ui::AXTreeID& p) { return p.ToString(); }
+
+  static bool Read(ax::mojom::AXTreeIDDataView data, ui::AXTreeID* out);
+};
+
+}  // namespace mojo
+
+#endif  // UI_ACCESSIBILITY_MOJOM_AX_TREE_ID_MOJOM_TRAITS_H_
diff --git a/ui/accessibility/mojom/ax_tree_id_mojom_traits_unittest.cc b/ui/accessibility/mojom/ax_tree_id_mojom_traits_unittest.cc
new file mode 100644
index 0000000..3f0f4837
--- /dev/null
+++ b/ui/accessibility/mojom/ax_tree_id_mojom_traits_unittest.cc
@@ -0,0 +1,18 @@
+// 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 "ui/accessibility/mojom/ax_tree_id_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/mojom/ax_tree_id.mojom.h"
+
+using mojo::test::SerializeAndDeserialize;
+
+TEST(AXTreeIDMojomTraitsTest, TestSerializeAndDeserializeAXTreeID) {
+  ui::AXTreeID input = ui::AXTreeID::FromString("abc");
+  ui::AXTreeID output;
+  EXPECT_TRUE(SerializeAndDeserialize<ax::mojom::AXTreeID>(&input, &output));
+  EXPECT_EQ("abc", output.ToString());
+}
diff --git a/ui/accessibility/mojom/typemaps.gni b/ui/accessibility/mojom/typemaps.gni
index 0ed9a30f..ba8b1e1 100644
--- a/ui/accessibility/mojom/typemaps.gni
+++ b/ui/accessibility/mojom/typemaps.gni
@@ -8,5 +8,6 @@
   "//ui/accessibility/mojom/ax_event.typemap",
   "//ui/accessibility/mojom/ax_node_data.typemap",
   "//ui/accessibility/mojom/ax_tree_data.typemap",
+  "//ui/accessibility/mojom/ax_tree_id.typemap",
   "//ui/accessibility/mojom/ax_tree_update.typemap",
 ]
diff --git a/ui/accessibility/platform/aura_window_properties.cc b/ui/accessibility/platform/aura_window_properties.cc
index efde64b..812b4c9 100644
--- a/ui/accessibility/platform/aura_window_properties.cc
+++ b/ui/accessibility/platform/aura_window_properties.cc
@@ -9,6 +9,8 @@
 
 DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(AX_EXPORT, ax::mojom::Role)
 
+DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(AX_EXPORT, ui::AXTreeID*)
+
 namespace ui {
 
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ui::AXTreeID, kChildAXTreeID, nullptr);
diff --git a/ui/aura/window_targeter.cc b/ui/aura/window_targeter.cc
index 997f66f5..4586197 100644
--- a/ui/aura/window_targeter.cc
+++ b/ui/aura/window_targeter.cc
@@ -137,9 +137,12 @@
     // This is used for bezel gesture events (eg. swiping in from screen edge).
     display::Display display =
         display::Screen::GetScreen()->GetDisplayNearestWindow(root_window);
-    const gfx::Point screen_location =
-        event.target() ? event.target()->GetScreenLocation(event)
-                       : event.root_location();
+    // The window target may be null, so use the root's ScreenPositionClient.
+    gfx::Point screen_location = event.root_location();
+    if (client::GetScreenPositionClient(root_window)) {
+      client::GetScreenPositionClient(root_window)
+          ->ConvertPointToScreen(root_window, &screen_location);
+    }
     if (!display.bounds().Contains(screen_location))
       return root_window;
   }
diff --git a/ui/base/material_design/material_design_controller.cc b/ui/base/material_design/material_design_controller.cc
index fb2cd53..03a88a7 100644
--- a/ui/base/material_design/material_design_controller.cc
+++ b/ui/base/material_design/material_design_controller.cc
@@ -42,13 +42,6 @@
 namespace ui {
 namespace {
 
-base::ObserverList<MaterialDesignControllerObserver>* GetObservers() {
-  static base::NoDestructor<
-      base::ObserverList<MaterialDesignControllerObserver>>
-      observers;
-  return observers.get();
-}
-
 #if defined(OS_CHROMEOS)
 
 // Whether to use touchable UI.
@@ -174,18 +167,6 @@
 }
 
 // static
-void MaterialDesignController::AddObserver(
-    MaterialDesignControllerObserver* observer) {
-  GetObservers()->AddObserver(observer);
-}
-
-// static
-void MaterialDesignController::RemoveObserver(
-    MaterialDesignControllerObserver* observer) {
-  GetObservers()->RemoveObserver(observer);
-}
-
-// static
 bool MaterialDesignController::IsTouchOptimizedUiEnabled() {
   return GetMode() == MATERIAL_TOUCH_OPTIMIZED ||
          GetMode() == MATERIAL_TOUCH_REFRESH;
@@ -227,6 +208,24 @@
 }
 
 // static
+MaterialDesignController* MaterialDesignController::GetInstance() {
+  static base::NoDestructor<MaterialDesignController> instance;
+  return instance.get();
+}
+
+void MaterialDesignController::AddObserver(
+    MaterialDesignControllerObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void MaterialDesignController::RemoveObserver(
+    MaterialDesignControllerObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+MaterialDesignController::MaterialDesignController() = default;
+
+// static
 void MaterialDesignController::Uninitialize() {
   is_mode_initialized_ = false;
 }
@@ -236,8 +235,8 @@
   if (!is_mode_initialized_ || mode_ != mode) {
     is_mode_initialized_ = true;
     mode_ = mode;
-    for (auto& observer : *GetObservers())
-      observer.OnModeChanged();
+    for (auto& observer : GetInstance()->observers_)
+      observer.OnMdModeChanged();
   }
 }
 
diff --git a/ui/base/material_design/material_design_controller.h b/ui/base/material_design/material_design_controller.h
index 6a9086d..5986738 100644
--- a/ui/base/material_design/material_design_controller.h
+++ b/ui/base/material_design/material_design_controller.h
@@ -6,8 +6,14 @@
 #define UI_BASE_MATERIAL_DESIGN_MATERIAL_DESIGN_CONTROLLER_H_
 
 #include "base/macros.h"
+#include "base/observer_list.h"
 #include "ui/base/ui_base_export.h"
 
+namespace base {
+template <typename T>
+class NoDestructor;
+}
+
 namespace ui {
 
 class MaterialDesignControllerObserver;
@@ -40,10 +46,6 @@
   // Get the current Mode that should be used by the system.
   static Mode GetMode();
 
-  static void AddObserver(MaterialDesignControllerObserver* observer);
-
-  static void RemoveObserver(MaterialDesignControllerObserver* observer);
-
   // Returns true if the touch-optimized UI material design mode is enabled.
   static bool IsTouchOptimizedUiEnabled();
 
@@ -61,9 +63,20 @@
 
   static bool is_mode_initialized() { return is_mode_initialized_; }
 
+  static MaterialDesignController* GetInstance();
+
+  void AddObserver(MaterialDesignControllerObserver* observer);
+
+  void RemoveObserver(MaterialDesignControllerObserver* observer);
+
  private:
+  friend class base::NoDestructor<MaterialDesignController>;
   friend class test::MaterialDesignControllerTestAPI;
 
+  MaterialDesignController();
+
+  ~MaterialDesignController() = delete;
+
   // Resets the initialization state to uninitialized. To be used by tests to
   // allow calling Initialize() more than once.
   static void Uninitialize();
@@ -84,6 +97,8 @@
   // MATERIAL_TOUCH_REFRESH depending on the tablet state.
   static bool is_refresh_dynamic_ui_;
 
+  base::ObserverList<MaterialDesignControllerObserver> observers_;
+
   DISALLOW_COPY_AND_ASSIGN(MaterialDesignController);
 };
 
diff --git a/ui/base/material_design/material_design_controller_observer.h b/ui/base/material_design/material_design_controller_observer.h
index 2341461..5b7eba4 100644
--- a/ui/base/material_design/material_design_controller_observer.h
+++ b/ui/base/material_design/material_design_controller_observer.h
@@ -13,7 +13,7 @@
 class UI_BASE_EXPORT MaterialDesignControllerObserver
     : public base::CheckedObserver {
  public:
-  virtual void OnModeChanged() = 0;
+  virtual void OnMdModeChanged() = 0;
 
  protected:
   ~MaterialDesignControllerObserver() override {}
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 76f70cf..b2e5e4c 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -153,14 +153,6 @@
 bool HostWindowsInAppShimProcess() {
   return base::FeatureList::IsEnabled(kHostWindowsInAppShimProcess);
 }
-
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-bool IsViewsBrowserCocoa() {
-  // TODO(https://crbug.com/832676): Delete all code guarded on this function
-  // returning true and then remove this function.
-  return false;
-}
-#endif  //  BUILDFLAG(MAC_VIEWS_BROWSER)
 #endif  //  defined(OS_MACOSX)
 
 const base::Feature kEnableOzoneDrmMojo = {"OzoneDrmMojo",
diff --git a/ui/base/ui_base_features.h b/ui/base/ui_base_features.h
index 45936e1..a6e11f37 100644
--- a/ui/base/ui_base_features.h
+++ b/ui/base/ui_base_features.h
@@ -73,14 +73,6 @@
 // Returns true if the NSWindows for apps will be created in the app's process,
 // and will forward input to the browser process.
 UI_BASE_EXPORT bool HostWindowsInAppShimProcess();
-
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-// Returns whether a Views-capable browser build should use the Cocoa browser
-// UI. Always returns false.
-// TODO(https://crbug.com/832676): Delete all code guarded on this function
-// returning true and then remove this function.
-UI_BASE_EXPORT bool IsViewsBrowserCocoa();
-#endif  //  BUILDFLAG(MAC_VIEWS_BROWSER)
 #endif  //  defined(OS_MACOSX)
 
 // Use mojo communication in the drm platform instead of paramtraits. Remove
diff --git a/ui/gfx/codec/chromeos/jpeg_codec_robust_slow.cc b/ui/gfx/codec/chromeos/jpeg_codec_robust_slow.cc
index 1ab3208d..9ffc036 100644
--- a/ui/gfx/codec/chromeos/jpeg_codec_robust_slow.cc
+++ b/ui/gfx/codec/chromeos/jpeg_codec_robust_slow.cc
@@ -139,26 +139,12 @@
 }
 #endif  // !defined(JCS_EXTENSIONS)
 
-// This class destroys the given jpeg_decompress object when it goes out of
-// scope. It simplifies the error handling in Decode (and even applies to the
-// success case).
-class IjgDecompressDestroyer {
- public:
-  IjgDecompressDestroyer() : cinfo_(NULL) {}
-  ~IjgDecompressDestroyer() { DestroyManagedObject(); }
-  void SetManagedObject(jpeg_decompress_struct* ci) {
-    DestroyManagedObject();
-    cinfo_ = ci;
+// jpeg_decompress_struct Deleter.
+struct JpegDecompressStructDeleter {
+  void operator()(jpeg_decompress_struct* ptr) {
+    jpeg_destroy_decompress(ptr);
+    delete ptr;
   }
-  void DestroyManagedObject() {
-    if (cinfo_) {
-      jpeg_destroy_decompress(cinfo_);
-      cinfo_ = NULL;
-    }
-  }
-
- private:
-  jpeg_decompress_struct* cinfo_;
 };
 
 }  // namespace
@@ -169,28 +155,26 @@
                                  std::vector<unsigned char>* output,
                                  int* w,
                                  int* h) {
-  jpeg_decompress_struct cinfo;
-  IjgDecompressDestroyer destroyer;
-  destroyer.SetManagedObject(&cinfo);
+  std::unique_ptr<jpeg_decompress_struct, JpegDecompressStructDeleter> cinfo(
+      new jpeg_decompress_struct);
   output->clear();
 
   // We set up the normal JPEG error routines, then override error_exit.
   // This must be done before the call to create_decompress.
   IjgCoderErrorMgr errmgr;
-  cinfo.err = jpeg_std_error(&errmgr.pub);
+  cinfo->err = jpeg_std_error(&errmgr.pub);
   errmgr.pub.error_exit = IjgErrorExit;
   // Establish the setjmp return context for IjgErrorExit to use.
   if (setjmp(errmgr.setjmp_buffer)) {
     // If we get here, the JPEG code has signaled an error.
-    // See note in JPEGCodec::Encode() for why we need to destroy the cinfo
-    // manually here.
-    destroyer.DestroyManagedObject();
+    // Release |cinfo| by hand to avoid use-after-free of |errmgr|.
+    cinfo.reset();
     return false;
   }
 
   // The destroyer will destroy() cinfo on exit.  We don't want to set the
   // destroyer's object until cinfo is initialized.
-  jpeg_create_decompress(&cinfo);
+  jpeg_create_decompress(cinfo.get());
 
   // set up the source manager
   jpeg_source_mgr srcmgr;
@@ -199,17 +183,17 @@
   srcmgr.skip_input_data = IjgSkipInputData;
   srcmgr.resync_to_restart = jpeg_resync_to_restart;  // use default routine
   srcmgr.term_source = IjgTermSource;
-  cinfo.src = &srcmgr;
+  cinfo->src = &srcmgr;
 
   IjgJpegDecoderState state(input, input_size);
-  cinfo.client_data = &state;
+  cinfo->client_data = &state;
 
   // fill the file metadata into our buffer
-  if (jpeg_read_header(&cinfo, true) != JPEG_HEADER_OK)
+  if (jpeg_read_header(cinfo.get(), true) != JPEG_HEADER_OK)
     return false;
 
   // we want to always get RGB data out
-  switch (cinfo.jpeg_color_space) {
+  switch (cinfo->jpeg_color_space) {
     case JCS_GRAYSCALE:
     case JCS_RGB:
     case JCS_YCbCr:
@@ -219,24 +203,22 @@
       // used by Chromium (i.e. RGB, RGBA, and BGRA) and we just map the input
       // parameters to a colorspace.
       if (format == FORMAT_RGB) {
-        cinfo.out_color_space = JCS_RGB;
-        cinfo.output_components = 3;
+        cinfo->out_color_space = JCS_RGB;
+        cinfo->output_components = 3;
       } else if (format == FORMAT_RGBA ||
                  (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
-        cinfo.out_color_space = JCS_EXT_RGBX;
-        cinfo.output_components = 4;
+        cinfo->out_color_space = JCS_EXT_RGBX;
+        cinfo->output_components = 4;
       } else if (format == FORMAT_BGRA ||
                  (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
-        cinfo.out_color_space = JCS_EXT_BGRX;
-        cinfo.output_components = 4;
+        cinfo->out_color_space = JCS_EXT_BGRX;
+        cinfo->output_components = 4;
       } else {
-        // We can exit this function without calling jpeg_destroy_decompress()
-        // because IjgDecompressDestroyer automaticaly calls it.
         NOTREACHED() << "Invalid pixel format";
         return false;
       }
 #else
-      cinfo.out_color_space = JCS_RGB;
+      cinfo->out_color_space = JCS_RGB;
 #endif
       break;
     case JCS_CMYK:
@@ -248,39 +230,39 @@
       return false;
   }
 #ifndef JCS_EXTENSIONS
-  cinfo.output_components = 3;
+  cinfo->output_components = 3;
 #endif
 
-  jpeg_calc_output_dimensions(&cinfo);
-  *w = cinfo.output_width;
-  *h = cinfo.output_height;
+  jpeg_calc_output_dimensions(cinfo.get());
+  *w = cinfo->output_width;
+  *h = cinfo->output_height;
 
-  jpeg_start_decompress(&cinfo);
+  jpeg_start_decompress(cinfo.get());
 
   // FIXME(brettw) we may want to allow the capability for callers to request
   // how to align row lengths as we do for the compressor.
-  int row_read_stride = cinfo.output_width * cinfo.output_components;
+  int row_read_stride = cinfo->output_width * cinfo->output_components;
 
 #ifdef JCS_EXTENSIONS
   // Create memory for a decoded image and write decoded lines to the memory
   // without conversions same as JPEGCodec::Encode().
   int row_write_stride = row_read_stride;
-  output->resize(row_write_stride * cinfo.output_height);
+  output->resize(row_write_stride * cinfo->output_height);
 
-  for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
+  for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) {
     unsigned char* rowptr = &(*output)[row * row_write_stride];
-    if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+    if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
       return false;
   }
 #else
   if (format == FORMAT_RGB) {
     // easy case, row needs no conversion
     int row_write_stride = row_read_stride;
-    output->resize(row_write_stride * cinfo.output_height);
+    output->resize(row_write_stride * cinfo->output_height);
 
-    for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
+    for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) {
       unsigned char* rowptr = &(*output)[row * row_write_stride];
-      if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+      if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
         return false;
     }
   } else {
@@ -291,33 +273,32 @@
     void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
     if (format == FORMAT_RGBA ||
         (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
-      row_write_stride = cinfo.output_width * 4;
+      row_write_stride = cinfo->output_width * 4;
       converter = AddAlpha;
     } else if (format == FORMAT_BGRA ||
                (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
-      row_write_stride = cinfo.output_width * 4;
+      row_write_stride = cinfo->output_width * 4;
       converter = RGBtoBGRA;
     } else {
       NOTREACHED() << "Invalid pixel format";
-      jpeg_destroy_decompress(&cinfo);
+      jpeg_destroy_decompress(cinfo.get());
       return false;
     }
 
-    output->resize(row_write_stride * cinfo.output_height);
+    output->resize(row_write_stride * cinfo->output_height);
 
     std::unique_ptr<unsigned char[]> row_data(
         new unsigned char[row_read_stride]);
     unsigned char* rowptr = row_data.get();
-    for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
-      if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+    for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) {
+      if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
         return false;
       converter(rowptr, *w, &(*output)[row * row_write_stride]);
     }
   }
 #endif
 
-  jpeg_finish_decompress(&cinfo);
-  jpeg_destroy_decompress(&cinfo);
+  jpeg_finish_decompress(cinfo.get());
   return true;
 }
 
diff --git a/ui/gfx/codec/jpeg_codec.cc b/ui/gfx/codec/jpeg_codec.cc
index 28a8262..b03dc8f9 100644
--- a/ui/gfx/codec/jpeg_codec.cc
+++ b/ui/gfx/codec/jpeg_codec.cc
@@ -150,31 +150,14 @@
 //  "Terminate source --- called by jpeg_finish_decompress() after all data has
 //   been read to clean up JPEG source manager. NOT called by jpeg_abort() or
 //   jpeg_destroy()."
-void TermSource(j_decompress_ptr cinfo) {
-}
+void TermSource(j_decompress_ptr cinfo) {}
 
-// This class destroys the given jpeg_decompress object when it goes out of
-// scope. It simplifies the error handling in Decode (and even applies to the
-// success case).
-class DecompressDestroyer {
- public:
-  DecompressDestroyer() : cinfo_(NULL) {
+// jpeg_decompress_struct Deleter.
+struct JpegDecompressStructDeleter {
+  void operator()(jpeg_decompress_struct* ptr) {
+    jpeg_destroy_decompress(ptr);
+    delete ptr;
   }
-  ~DecompressDestroyer() {
-    DestroyManagedObject();
-  }
-  void SetManagedObject(jpeg_decompress_struct* ci) {
-    DestroyManagedObject();
-    cinfo_ = ci;
-  }
-  void DestroyManagedObject() {
-    if (cinfo_) {
-      jpeg_destroy_decompress(cinfo_);
-      cinfo_ = NULL;
-    }
-  }
- private:
-  jpeg_decompress_struct* cinfo_;
 };
 
 }  // namespace
@@ -182,28 +165,26 @@
 bool JPEGCodec::Decode(const unsigned char* input, size_t input_size,
                        ColorFormat format, std::vector<unsigned char>* output,
                        int* w, int* h) {
-  jpeg_decompress_struct cinfo;
-  DecompressDestroyer destroyer;
-  destroyer.SetManagedObject(&cinfo);
+  std::unique_ptr<jpeg_decompress_struct, JpegDecompressStructDeleter> cinfo(
+      new jpeg_decompress_struct);
   output->clear();
 
   // We set up the normal JPEG error routines, then override error_exit.
-  // This must be done before the call to create_decompress.
+  // This must be done before the call to jpeg_create_decompress.
   CoderErrorMgr errmgr;
-  cinfo.err = jpeg_std_error(&errmgr.pub);
+  cinfo->err = jpeg_std_error(&errmgr.pub);
   errmgr.pub.error_exit = ErrorExit;
   // Establish the setjmp return context for ErrorExit to use.
   if (setjmp(errmgr.setjmp_buffer)) {
     // If we get here, the JPEG code has signaled an error.
-    // See note in JPEGCodec::Encode() for why we need to destroy the cinfo
-    // manually here.
-    destroyer.DestroyManagedObject();
+    // Release |cinfo| by hand to avoid use-after-free of |errmgr|.
+    cinfo.reset();
     return false;
   }
 
   // The destroyer will destroy() cinfo on exit.  We don't want to set the
   // destroyer's object until cinfo is initialized.
-  jpeg_create_decompress(&cinfo);
+  jpeg_create_decompress(cinfo.get());
 
   // set up the source manager
   jpeg_source_mgr srcmgr;
@@ -212,17 +193,17 @@
   srcmgr.skip_input_data = SkipInputData;
   srcmgr.resync_to_restart = jpeg_resync_to_restart;  // use default routine
   srcmgr.term_source = TermSource;
-  cinfo.src = &srcmgr;
+  cinfo->src = &srcmgr;
 
   JpegDecoderState state(input, input_size);
-  cinfo.client_data = &state;
+  cinfo->client_data = &state;
 
   // fill the file metadata into our buffer
-  if (jpeg_read_header(&cinfo, true) != JPEG_HEADER_OK)
+  if (jpeg_read_header(cinfo.get(), true) != JPEG_HEADER_OK)
     return false;
 
   // we want to always get RGB data out
-  switch (cinfo.jpeg_color_space) {
+  switch (cinfo->jpeg_color_space) {
     case JCS_GRAYSCALE:
     case JCS_RGB:
     case JCS_YCbCr:
@@ -232,15 +213,13 @@
       // parameters to a colorspace.
       if (format == FORMAT_RGBA ||
           (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
-        cinfo.out_color_space = JCS_EXT_RGBX;
-        cinfo.output_components = 4;
+        cinfo->out_color_space = JCS_EXT_RGBX;
+        cinfo->output_components = 4;
       } else if (format == FORMAT_BGRA ||
                  (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
-        cinfo.out_color_space = JCS_EXT_BGRX;
-        cinfo.output_components = 4;
+        cinfo->out_color_space = JCS_EXT_BGRX;
+        cinfo->output_components = 4;
       } else {
-        // We can exit this function without calling jpeg_destroy_decompress()
-        // because DecompressDestroyer automaticaly calls it.
         NOTREACHED() << "Invalid pixel format";
         return false;
       }
@@ -254,29 +233,28 @@
       return false;
   }
 
-  jpeg_calc_output_dimensions(&cinfo);
-  *w = cinfo.output_width;
-  *h = cinfo.output_height;
+  jpeg_calc_output_dimensions(cinfo.get());
+  *w = cinfo->output_width;
+  *h = cinfo->output_height;
 
-  jpeg_start_decompress(&cinfo);
+  jpeg_start_decompress(cinfo.get());
 
   // FIXME(brettw) we may want to allow the capability for callers to request
   // how to align row lengths as we do for the compressor.
-  int row_read_stride = cinfo.output_width * cinfo.output_components;
+  int row_read_stride = cinfo->output_width * cinfo->output_components;
 
   // Create memory for a decoded image and write decoded lines to the memory
   // without conversions same as JPEGCodec::Encode().
   int row_write_stride = row_read_stride;
-  output->resize(row_write_stride * cinfo.output_height);
+  output->resize(row_write_stride * cinfo->output_height);
 
-  for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
+  for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) {
     unsigned char* rowptr = &(*output)[row * row_write_stride];
-    if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
+    if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
       return false;
   }
 
-  jpeg_finish_decompress(&cinfo);
-  jpeg_destroy_decompress(&cinfo);
+  jpeg_finish_decompress(cinfo.get());
   return true;
 }
 
diff --git a/ui/views/controls/image_view.cc b/ui/views/controls/image_view.cc
index b1abacf..0c185578 100644
--- a/ui/views/controls/image_view.cc
+++ b/ui/views/controls/image_view.cc
@@ -138,9 +138,17 @@
   OnPaintImage(canvas);
 }
 
+void ImageView::SetAccessibleName(const base::string16& accessible_name) {
+  accessible_name_ = accessible_name;
+}
+
+base::string16 ImageView::GetAccessibleName() const {
+  return accessible_name_;
+}
+
 void ImageView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kImage;
-  node_data->SetName(tooltip_text_);
+  node_data->SetName(accessible_name_);
 }
 
 const char* ImageView::GetClassName() const {
@@ -169,8 +177,11 @@
   return vertical_alignment_;
 }
 
+// TODO(crbug.com/890465): Update the duplicate code here and in views::Button.
 void ImageView::SetTooltipText(const base::string16& tooltip) {
   tooltip_text_ = tooltip;
+  if (accessible_name_.empty())
+    accessible_name_ = tooltip_text_;
 }
 
 base::string16 ImageView::GetTooltipText() const {
diff --git a/ui/views/controls/image_view.h b/ui/views/controls/image_view.h
index c3adf65..b13e494 100644
--- a/ui/views/controls/image_view.h
+++ b/ui/views/controls/image_view.h
@@ -73,6 +73,10 @@
   void SetTooltipText(const base::string16& tooltip);
   base::string16 GetTooltipText() const;
 
+  // Set / Get the accessible name text.
+  void SetAccessibleName(const base::string16& name);
+  base::string16 GetAccessibleName() const;
+
   // Overriden from View:
   void OnPaint(gfx::Canvas* canvas) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
@@ -120,6 +124,9 @@
   // The current tooltip text.
   base::string16 tooltip_text_;
 
+  // The current accessible name text.
+  base::string16 accessible_name_;
+
   // Scale last painted at.
   float last_paint_scale_;
 
diff --git a/ui/views/mus/ax_remote_host_unittest.cc b/ui/views/mus/ax_remote_host_unittest.cc
index dbdde9d..0ed0034 100644
--- a/ui/views/mus/ax_remote_host_unittest.cc
+++ b/ui/views/mus/ax_remote_host_unittest.cc
@@ -46,15 +46,15 @@
   void SetRemoteHost(ax::mojom::AXRemoteHostPtr client,
                      SetRemoteHostCallback cb) override {
     ++add_client_count_;
-    const ui::AXTreeID tree_id("123");
+    const ui::AXTreeID tree_id = ui::AXTreeID::FromString("123");
     std::move(cb).Run(tree_id, automation_enabled_);
     client.FlushForTesting();
   }
-  void HandleAccessibilityEvent(const std::string& tree_id,
+  void HandleAccessibilityEvent(const ui::AXTreeID& tree_id,
                                 const std::vector<ui::AXTreeUpdate>& updates,
                                 const ui::AXEvent& event) override {
     ++event_count_;
-    last_tree_id_ = ui::AXTreeID(tree_id);
+    last_tree_id_ = ui::AXTreeID::FromString(tree_id);
     last_updates_ = updates;
     last_event_ = event;
   }
diff --git a/ui/views/mus/ax_tree_source_mus.cc b/ui/views/mus/ax_tree_source_mus.cc
index c84c1801..6199fb3c 100644
--- a/ui/views/mus/ax_tree_source_mus.cc
+++ b/ui/views/mus/ax_tree_source_mus.cc
@@ -15,7 +15,7 @@
                                  const ui::AXTreeID& tree_id)
     : root_(root), tree_id_(tree_id) {
   DCHECK(root_);
-  DCHECK(!tree_id_.empty());
+  DCHECK_NE(tree_id_, ui::AXTreeIDUnknown());
 }
 
 AXTreeSourceMus::~AXTreeSourceMus() = default;
diff --git a/ui/views/mus/ax_tree_source_mus_unittest.cc b/ui/views/mus/ax_tree_source_mus_unittest.cc
index 9fbf307..b4fbdd5 100644
--- a/ui/views/mus/ax_tree_source_mus_unittest.cc
+++ b/ui/views/mus/ax_tree_source_mus_unittest.cc
@@ -51,7 +51,7 @@
 
   std::unique_ptr<Widget> widget_;
   Label* label_ = nullptr;  // Owned by views hierarchy.
-  const ui::AXTreeID ax_tree_id_{"123"};
+  const ui::AXTreeID ax_tree_id_ = ui::AXTreeID::FromString("123");
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMusTest);
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn b/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn
index f59a915..9a329756 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/BUILD.gn
@@ -13,20 +13,14 @@
     ":mojo_api",
     ":multidevice_setup",
     ":multidevice_setup_browser_proxy",
+    ":multidevice_setup_delegate",
     ":setup_failed_page",
     ":setup_succeeded_page",
     ":start_setup_page",
-    ":ui_mode",
     ":ui_page_container_behavior",
   ]
 }
 
-js_library("multidevice_setup_browser_proxy") {
-  deps = [
-    "//ui/webui/resources/js:cr",
-  ]
-}
-
 js_library("button_bar") {
 }
 
@@ -50,6 +44,9 @@
 }
 
 js_library("mojo_api") {
+  deps = [
+    "//ui/webui/resources/js:cr",
+  ]
 }
 
 js_library("multidevice_setup") {
@@ -57,11 +54,11 @@
     ":button_bar",
     ":fake_mojo_service",
     ":mojo_api",
+    ":multidevice_setup_delegate",
     ":password_page",
     ":setup_failed_page",
     ":setup_succeeded_page",
     ":start_setup_page",
-    ":ui_mode",
     "//ui/webui/resources/js:cr",
   ]
 
@@ -79,6 +76,18 @@
   ]
 }
 
+js_library("multidevice_setup_browser_proxy") {
+  deps = [
+    "//ui/webui/resources/js:cr",
+  ]
+}
+
+js_library("multidevice_setup_delegate") {
+  deps = [
+    "//ui/webui/resources/js:cr",
+  ]
+}
+
 js_library("password_page") {
   deps = [
     ":multidevice_setup_browser_proxy",
@@ -109,12 +118,6 @@
   ]
 }
 
-js_library("ui_mode") {
-  deps = [
-    "//ui/webui/resources/js:cr",
-  ]
-}
-
 js_library("ui_page_container_behavior") {
   deps = [
     "//ui/webui/resources/js:cr",
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html b/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html
index e107dd2..b94fb43 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.html
@@ -1,36 +1,25 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 
-<!-- TODO(jordynass): Implement OOBE style and make the visible style dependent
-on the MultiDeviceSetup.uiMode property. -->
 <dom-module id="button-bar">
   <template>
     <style include="multidevice-setup-shared">
       :host {
-        @apply --layout-end-justified;
-        @apply --layout-horizontal;
-      }
-
-      paper-button {
-        @apply --layout-center-center;
-        text-transform: none;
+        display: flex;
+        justify-content: flex-end;
       }
     </style>
     <div class="flex"></div>
-    <paper-button id="backward"
+    <div id="backward"
         on-click="onBackwardButtonClicked_"
-        class="cancel-button"
-        hidden$="[[!backwardButtonText]]">
-      [[backwardButtonText]]
-    </paper-button>
-    <paper-button id="forward"
+        hidden$="[[backwardButtonHidden]]">
+      <slot name="backward-button"></slot>
+    </div>
+    <div id="forward"
         on-click="onForwardButtonClicked_"
-        class="action-button"
-        disabled$="[[forwardButtonDisabled]]"
-        hidden$="[[!forwardButtonText]]">
-      [[forwardButtonText]]
+        hidden$="[[forwardButtonHidden]]">
+      <slot name="forward-button"></slot>
     </paper-button>
   </template>
   <script src="chrome://resources/cr_components/chromeos/multidevice_setup/button_bar.js">
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js
index 455643b..536edae 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/button_bar.js
@@ -10,30 +10,16 @@
   is: 'button-bar',
 
   properties: {
-    /**
-     * Translated text to display on the forward-naviation button. Undefined if
-     * the visible page has no forward-navigation button.
-     * @type {string|undefined}
-     */
-    forwardButtonText: {
-      type: String,
-      value: '',
+    /** Whether the forward button should be hidden. */
+    forwardButtonHidden: {
+      type: Boolean,
+      value: false,
     },
 
-    /**
-     * Whether the forward button should be disabled.
-     * @type {boolean}
-     */
-    forwardButtonDisabled: {type: Boolean, value: false},
-
-    /**
-     * Translated text to display on the backward-naviation button. undefined if
-     * the visible page has no backward-navigation button.
-     * @type {string|undefined}
-     */
-    backwardButtonText: {
-      type: String,
-      value: '',
+    /** Whether the backward button should be hidden. */
+    backwardButtonHidden: {
+      type: Boolean,
+      value: false,
     },
   },
 
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html
index bf8f4c1..d45766b 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html
@@ -3,12 +3,12 @@
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/button_bar.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/fake_mojo_service.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/mojo_api.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/password_page.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/setup_failed_page.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/setup_succeeded_page.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/start_setup_page.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/ui_mode.html">
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
 
@@ -35,22 +35,28 @@
     <iron-pages attr-for-selected="is"
         selected="[[visiblePageName_]]"
         selected-item="{{visiblePage_}}">
-      <password-page id="passwordPage"
-          auth-token="{{authToken_}}"
-          forward-button-disabled="{{passwordPageForwardButtonDisabled_}}"
-          password-field-valid="{{passwordFieldValid}}"
-          on-user-submitted-password="onUserSubmittedPassword_">
-      </password-page>
+      <template is="dom-if" if="[[shouldPasswordPageBeIncluded_(delegate)]]"
+          restamp>
+        <password-page auth-token="{{authToken_}}"
+            forward-button-disabled="{{passwordPageForwardButtonDisabled_}}"
+            password-field-valid="{{passwordFieldValid}}"
+            on-user-submitted-password="onUserSubmittedPassword_">
+        </password-page>
+      </template>
       <setup-failed-page></setup-failed-page>
-      <setup-succeeded-page></setup-succeeded-page>
+      <template is="dom-if"
+          if="[[shouldSetupSucceededPageBeIncluded_(delegate)]]" restamp>
+        <setup-succeeded-page></setup-succeeded-page>
+      </template>
       <start-setup-page devices="[[devices_]]"
           selected-device-id="{{selectedDeviceId_}}">
       </start-setup-page>
     </iron-pages>
     <div class="flex"></div>
-    <button-bar forward-button-text="[[visiblePage_.forwardButtonText]]"
-        forward-button-disabled="[[forwardButtonDisabled_]]"
-        backward-button-text="[[visiblePage_.backwardButtonText]]">
+    <button-bar forward-button-hidden="[[!forwardButtonText]]"
+        backward-button-hidden="[[!backwardButtonText]]">
+      <slot name="backward-button" slot="backward-button"></slot>
+      <slot name="forward-button" slot="backward-button"></slot>
     </button-bar>
   </template>
   <script src="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup.js">
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 ca9bd40..2b3a13e 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
@@ -20,11 +20,38 @@
 
     properties: {
       /**
-       * Indicates whether UI was opened during OOBE flow or afterward.
-       *
-       * @type {!multidevice_setup.UiMode}
+       * Delegate object which performs differently in OOBE vs. non-OOBE mode.
+       * @type {!multidevice_setup.MultiDeviceSetupDelegate}
        */
-      uiMode: Number,
+      delegate: Object,
+
+      /**
+       * Text to be shown on the forward navigation button.
+       * @type {string|undefined}
+       */
+      forwardButtonText: {
+        type: String,
+        computed: 'getForwardButtonText_(visiblePage_)',
+        notify: true,
+      },
+
+      /** Whether the forward button should be disabled. */
+      forwardButtonDisabled: {
+        type: Boolean,
+        computed: 'shouldForwardButtonBeDisabled_(' +
+            'passwordPageForwardButtonDisabled_, visiblePageName_)',
+        notify: true
+      },
+
+      /**
+       * Text to be shown on the backward navigation button.
+       * @type {string|undefined}
+       */
+      backwardButtonText: {
+        type: String,
+        computed: 'getBackwardButtonText_(visiblePage_)',
+        notify: true,
+      },
 
       /**
        * Element name of the currently visible page.
@@ -34,8 +61,7 @@
       visiblePageName_: {
         type: String,
         value: PageName.START,
-        // For testing purporses only
-        notify: true,
+        notify: true,  // For testing purporses only.
       },
 
       /**
@@ -80,16 +106,6 @@
       passwordPageForwardButtonDisabled_: Boolean,
 
       /**
-       * Whether the forward button should be disabled.
-       * @private {boolean}
-       */
-      forwardButtonDisabled_: {
-        type: Boolean,
-        computed: 'shouldForwardButtonBeDisabled_(' +
-            'passwordPageForwardButtonDisabled_, visiblePageName_)',
-      },
-
-      /**
        * Interface to the MultiDeviceSetup Mojo service.
        * @private {!chromeos.multideviceSetup.mojom.MultiDeviceSetupImpl}
        */
@@ -145,45 +161,63 @@
           this.visiblePageName_ = PageName.START;
           return;
         case PageName.PASSWORD:
-          this.$.passwordPage.clearPasswordTextInput();
-          let deviceId = /** @type {string} */ (this.selectedDeviceId_);
-          this.multideviceSetup_.setHostDevice(deviceId, this.authToken_)
-              .then((responseParams) => {
-                if (!responseParams.success) {
-                  console.warn(
-                      'Failure setting device with device ID: ' +
-                      this.selectedDeviceId_);
-                  return;
-                }
-
-                switch (this.uiMode) {
-                  case multidevice_setup.UiMode.OOBE:
-                    this.exitSetupFlow_();
-                    return;
-                  case multidevice_setup.UiMode.POST_OOBE:
-                    this.visiblePageName_ = PageName.SUCCESS;
-                    return;
-                }
-              })
-              .catch((error) => {
-                console.warn('Mojo service failure: ' + error);
-              });
+          this.$$('password-page').clearPasswordTextInput();
+          this.setHostDevice_();
           return;
         case PageName.SUCCESS:
           this.exitSetupFlow_();
           return;
         case PageName.START:
-          this.visiblePageName_ = PageName.PASSWORD;
+          if (this.delegate.isPasswordRequiredToSetHost())
+            this.visiblePageName_ = PageName.PASSWORD;
+          else
+            this.setHostDevice_();
           return;
       }
     },
 
     /** @private */
+    setHostDevice_: function() {
+      // An authentication token must be set if a password is required.
+      assert(this.delegate.isPasswordRequiredToSetHost() == !!this.authToken_);
+
+      let deviceId = /** @type {string} */ (this.selectedDeviceId_);
+      this.delegate.setHostDevice(deviceId, this.authToken_)
+          .then((responseParams) => {
+            if (!responseParams.success) {
+              console.warn('Failure setting host with device ID: ' + deviceId);
+              return;
+            }
+
+            if (this.delegate.shouldExitSetupFlowAfterSettingHost()) {
+              this.exitSetupFlow_();
+              return;
+            }
+
+            this.visiblePageName_ = PageName.SUCCESS;
+          })
+          .catch((error) => {
+            console.warn('Mojo service failure: ' + error);
+          });
+    },
+
+    /** @private */
     onUserSubmittedPassword_: function() {
       this.onForwardNavigationRequested_();
     },
 
     /**
+     * @return {string|undefined} The forward button text, which is undefined
+     *     if no button should be displayed.
+     * @private
+     */
+    getForwardButtonText_: function() {
+      if (!this.visiblePage_)
+        return undefined;
+      return this.visiblePage_.forwardButtonText;
+    },
+
+    /**
      * @return {boolean} Whether the forward button should be disabled.
      * @private
      */
@@ -193,12 +227,39 @@
     },
 
     /**
+     * @return {string|undefined} The backward button text, which is undefined
+     *     if no button should be displayed.
+     * @private
+     */
+    getBackwardButtonText_: function() {
+      if (!this.visiblePage_)
+        return undefined;
+      return this.visiblePage_.backwardButtonText;
+
+    },
+
+    /**
+     * @return {boolean}
+     * @private
+     */
+    shouldPasswordPageBeIncluded_: function() {
+      return this.delegate.isPasswordRequiredToSetHost();
+    },
+
+    /**
+     * @return {boolean}
+     * @private
+     */
+    shouldSetupSucceededPageBeIncluded_: function() {
+      return !this.delegate.shouldExitSetupFlowAfterSettingHost();
+    },
+
+    /**
      * Notifies observers that the setup flow has completed.
      *
      * @private
      */
     exitSetupFlow_: function() {
-      console.log('Exiting Setup Flow');
       this.fire('setup-exited');
     },
   });
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.html b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html
similarity index 76%
rename from ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.html
rename to ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html
index 327e5a82..46ce0b9 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.html
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html
@@ -1,4 +1,4 @@
 <link rel="import" href="chrome://resources/html/cr.html">
-
-<script src="chrome://resources/cr_components/chromeos/multidevice_setup/ui_mode.js">
+<script src="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js">
 </script>
+
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js
new file mode 100644
index 0000000..ee281f9
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js
@@ -0,0 +1,30 @@
+// 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.
+
+cr.define('multidevice_setup', function() {
+  /**
+   * Interface which provides the ability to set the host device and perform
+   * related logic.
+   * @interface
+   */
+  class MultiDeviceSetupDelegate {
+    /** @return {boolean} */
+    isPasswordRequiredToSetHost() {}
+
+    /**
+     * @param {string} hostDeviceId The ID of the host to set.
+     * @param {string=} opt_authToken An auth token to authenticate the request;
+     *     only necessary if isPasswordRequiredToSetHost() returns true.
+     * @return {!Promise<{success: boolean}>}
+     */
+    setHostDevice(hostDeviceId, opt_authToken) {}
+
+    /** @return {boolean} */
+    shouldExitSetupFlowAfterSettingHost() {}
+  }
+
+  return {
+    MultiDeviceSetupDelegate: MultiDeviceSetupDelegate,
+  };
+});
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html b/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html
index 388eeb25..b5c52c0 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/password_page.html
@@ -40,7 +40,7 @@
             error-message="[[i18n('wrongPassword')]]"
             value="{{inputValue_}}"
             aria-disabled="false"
-            on-change="onUserPressedEnter_"
+            on-keypress="onInputKeypress_"
             autofocus>
         </cr-input>
       </div>
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 87c58da..fd84cd1 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
@@ -138,8 +138,15 @@
     this.passwordInvalid_ = false;
   },
 
-  /** @private */
-  onUserPressedEnter_: function() {
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onInputKeypress_: function(e) {
+    // We are only listening for the user trying to enter their password.
+    if (e.key != 'Enter')
+      return;
+
     this.fire('user-submitted-password');
   },
 
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.js b/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.js
deleted file mode 100644
index 90b0abf..0000000
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/ui_mode.js
+++ /dev/null
@@ -1,15 +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.
-
-cr.define('multidevice_setup', function() {
-  /** @enum {number} */
-  const UiMode = {
-    OOBE: 1,
-    POST_OOBE: 2,
-  };
-
-  return {
-    UiMode: UiMode,
-  };
-});
diff --git a/ui/webui/resources/cr_components/cr_components_resources.grdp b/ui/webui/resources/cr_components/cr_components_resources.grdp
index 25851f9..3bf1f1c 100644
--- a/ui/webui/resources/cr_components/cr_components_resources.grdp
+++ b/ui/webui/resources/cr_components/cr_components_resources.grdp
@@ -280,6 +280,14 @@
                file="cr_components/chromeos/multidevice_setup/multidevice_setup.js"
                type="chrome_html"
                compress="gzip" />
+    <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_DELEGATE_HTML"
+               file="cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html"
+               type="chrome_html"
+               compress="gzip" />
+    <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_DELEGATE_JS"
+               file="cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.js"
+               type="chrome_html"
+               compress="gzip" />
     <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_SHARED_CSS_HTML"
                file="cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html"
                type="chrome_html"
@@ -316,14 +324,6 @@
                file="cr_components/chromeos/multidevice_setup/start_setup_page.js"
                type="chrome_html"
                compress="gzip" />
-    <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_MODE_HTML"
-               file="cr_components/chromeos/multidevice_setup/ui_mode.html"
-               type="chrome_html"
-               compress="gzip" />
-    <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_MODE_JS"
-               file="cr_components/chromeos/multidevice_setup/ui_mode.js"
-               type="chrome_html"
-               compress="gzip" />
     <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_UI_PAGE_CONTAINER_BEHAVIOR_HTML"
                file="cr_components/chromeos/multidevice_setup/ui_page_container_behavior.html"
                type="chrome_html"
diff --git a/ui/webui/resources/cr_elements/cr_slider/BUILD.gn b/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
index 78d6d47b..ad5d8f6 100644
--- a/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
+++ b/ui/webui/resources/cr_elements/cr_slider/BUILD.gn
@@ -12,6 +12,8 @@
 
 js_library("cr_slider") {
   deps = [
-    "//third_party/polymer/v1_0/components-chromium/paper-slider:paper-slider-extracted",
+    "//third_party/polymer/v1_0/components-chromium/paper-behaviors:paper-ripple-behavior-extracted",
+    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:event_tracker",
   ]
 }
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.html b/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
index 05adf45..a0a5253 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.html
@@ -1,52 +1,199 @@
 <link rel="import" href="../../html/polymer.html">
 
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout.html">
+<link rel="import" href="../../html/cr.html">
+<link rel="import" href="../../html/event_tracker.html">
+<link rel="import" href="../hidden_style_css.html">
 <link rel="import" href="../shared_vars_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-slider/paper-slider.html">
 
 <dom-module id="cr-slider">
   <template>
-    <style>
-      paper-slider {
-        --paper-slider-active-color: var(--google-blue-600);
-        --paper-slider-container-color: var(--google-blue-600-opacity-24);
-        --paper-slider-knob-color: var(--google-blue-600);
-        --paper-slider-knob-start-color: var(--google-blue-600);
-        --paper-slider-knob-start-border-color: var(--google-blue-600);
-        --paper-slider-pin-color: var(--google-blue-600);
-        --paper-slider-pin-start-color: var(--google-blue-600);
-        --paper-slider-markers-color: rgba(255, 255, 255, 0.54);
-        --paper-slider-disabled-active-color: var(--google-grey-600);
-        --paper-slider-disabled-knob-color: var(--google-grey-600);
-        width: 100%;
-
-        --paper-slider-pin-text: {
-          font-family:  Roboto;
-          font-size: 12px;
-          font-weight: 500;
-          line-height: 14px;
-        };
+    <style include="cr-hidden-style">
+      :host {
+        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+        cursor: default;
+        user-select: none;
       }
 
-      :host-context([dir=rtl]) paper-slider {
-        --paper-slider-pin-text: {
-          font-family:  Roboto;
-          font-size: 12px;
-          font-weight: 500;
-          line-height: 14px;
-          transform: scale(-1, 1) translate(0, -17px);
-        };
+      :host([dragging]) {
+        touch-action: none;
       }
 
-      paper-slider[disabled] {
-        --paper-slider-container-color: var(--google-grey-600-opacity-24);
+      #container {
+        height: 32px;
+        position: relative;
+      }
+
+      #barContainer {
+        background-color: var(--google-blue-600-opacity-24);
+        border-radius: 1px;
+        height: 2px;
+        margin: 0 16px;
+        position: absolute;
+        top: 15px;
+        width: calc(100% - 32px);
+      }
+
+      #bar {
+        background-color: var(--google-blue-600);
+        border-radius: 1px;
+        height: 2px;
+        left: 0;
+        position: absolute;
+        transition: width 80ms ease;
+        width: 0;
+      }
+
+      :host-context([dir=rtl]) #bar {
+        left: initial;
+        right: 0;
+      }
+
+      #knobContainer {
+        margin-inline-start: 12px;
+        position: absolute;
+        top: 11px;
+        width: calc(100% - 32px);
+      }
+
+      #knob {
+        background-color: var(--google-blue-600);
+        border: 0;
+        border-radius: 50%;
+        box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
+        height: 10px;
+        margin-inline-start: 0;
+        outline: none;
+        position: absolute;
+        transition: margin-inline-start 80ms ease;
+        width: 10px;
+      }
+
+      paper-ripple {
+        color: var(--google-blue-600);
+        height: 32px;
+        left: -11px;
+        pointer-events: none;
+        top: -11px;
+        transition: color linear 80ms;
+        width: 32px;
+      }
+
+      :host-context([dir=rtl]) paper-ripple {
+        left: auto;
+        right: -11px;
+      }
+
+      #markers {
+        left: 0;
+        pointer-events: none;
+        position: absolute;
+        right: 0;
+        top: 0;
+        @apply --layout-horizontal;
+      }
+
+      .active-marker,
+      .inactive-marker {
+        @apply --layout-flex;
+      }
+      #markers::before,
+      #markers::after,
+      .active-marker::after,
+      .inactive-marker::after {
+        border-radius: 50%;
+        content: '';
+        display: block;
+        height: 2px;
+        margin-left: -1px;
+        width: 2px;
+      }
+
+      #markers::before,
+      .active-marker::after {
+        background-color: rgba(255, 255, 255, 0.54);
+      }
+
+      #markers::after,
+      .inactive-marker::after {
+        background-color: rgba(26, 115, 232, 0.54);
+      }
+
+      #labelContainer {
+        cursor: default;
+        margin-inline-start: 1px;
+        opacity: 0;
+        transition: opacity 80ms ease-in-out;
+        user-select: none;
+        width: calc(100% - 32px);
+      }
+
+      #container:hover #labelContainer,
+      .hover #labelContainer,
+      :host([hold-down_]) #labelContainer {
+        opacity: 1;
+      }
+
+      .label {
+        background: var(--google-blue-600);
+        border-radius: 14px;
+        bottom: 28px;
+        color: white;
+        font-size: 12px;
+        line-height: 1.5em;
+        padding: 0 8px;
+        position: absolute;
+        transition: margin-inline-start 80ms ease;
+        white-space: nowrap;
+      }
+
+      :host([disabled]) {
+        pointer-events: none;
+      }
+
+      :host([disabled]) #barContainer {
+        background-color: var(--google-grey-600-opacity-24);
+      }
+
+      :host([disabled]) #bar {
+        background-color: var(--google-grey-600);
+      }
+
+      :host([disabled]) inactive-marker::after,
+      :host([disabled]) #markers::after {
+        background-color: rgba(255, 255, 255, 0.54);
+      }
+
+      :host([disabled]) #knobContainer {
+        margin-inline-start: 9px;
+        top: 9px;
+      }
+      :host([disabled]) #knob {
+        background-color: var(--google-grey-600);
+        border: 2px solid white;
+        box-shadow: unset;
       }
     </style>
-    <paper-slider id="slider"
-        disabled$="[[disabled]]" snaps="[[snaps]]" on-change="onChange_"
-        max="[[max]]" min="[[min]]" on-up="resetTrackLock_" value="{{value}}"
-        max-markers="[[maxMarkers]]" immediate-value="{{immediateValue}}"
-        dragging="{{dragging}}">
-    </paper-slider>
+    <div id="container">
+      <div id="barContainer">
+        <div id="bar"></div>
+        <div id="markers" hidden$="[[!markerCount]]">
+          <template is="dom-repeat" items="[[getMarkers_(markerCount)]]">
+            <div class$="[[getMarkerClass_(index, value, min, max,
+                                           markerCount)]]"></div>
+          </template>
+        </div>
+      </div>
+      <div id="knobContainer">
+        <div id="knob" tabindex="0"></div>
+      </div>
+      <div id="labelContainer" aria-label="[[label_]]">
+        <div id="label" class="label">
+          <div id="labelText">[[label_]]</div>
+        </div>
+      </div>
+    </div>
   </template>
   <script src="cr_slider.js"></script>
 </dom-module>
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 16180c12..609b8ed 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
@@ -3,148 +3,374 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview 'cr-slider' is a wrapper around paper-slider to alter the
- * styling. The behavior of the slider remains the same.
+ * @fileoverview 'cr-slider' is a slider component used to select a number from
+ * a continuous or discrete range of numbers.
  */
-Polymer({
-  is: 'cr-slider',
 
-  properties: {
-    min: Number,
+cr.exportPath('cr_slider');
 
-    max: Number,
+/**
+ * The |value| is the corresponding value that the current slider tick is
+ * associated with. The string |label| is shown in the UI as the label for the
+ * current slider value. The |ariaValue| number is used for aria-valuemin,
+ * aria-valuemax, and aria-valuenow, and is optional. If missing, |value| will
+ * be used instead.
+ * @typedef {{
+ *   value: number,
+ *   label: string,
+ *   ariaValue: (number|undefined),
+ * }}
+ */
+cr_slider.SliderTick;
 
-    snaps: {
-      type: Boolean,
-      value: true,
+(() => {
+  /**
+   * @param {number} min
+   * @param {number} max
+   * @param {number} value
+   * @return {number}
+   */
+  function clamp(min, max, value) {
+    return Math.min(max, Math.max(min, value));
+  }
+
+  Polymer({
+    is: 'cr-slider',
+
+    behaviors: [
+      Polymer.PaperRippleBehavior,
+    ],
+
+    properties: {
+      disabled: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
+
+      dragging: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
+
+      markerCount: {
+        type: Number,
+        value: 0,
+      },
+
+      max: {
+        type: Number,
+        value: 100,
+      },
+
+      min: {
+        type: Number,
+        value: 0,
+      },
+
+      snaps: {
+        type: Boolean,
+        value: false,
+      },
+
+      /**
+       * The data associated with each tick on the slider. Each element in the
+       * array contains a value and the label corresponding to that value.
+       * @type {!Array<cr_slider.SliderTick>|!Array<number>}
+       */
+      ticks: {
+        type: Array,
+        value: () => [],
+        observer: 'onTicksChanged_',
+      },
+
+      value: {
+        type: Number,
+        value: 0,
+        notify: true,
+      },
+
+      /** @private */
+      holdDown_: {
+        type: Boolean,
+        value: false,
+        observer: 'onHoldDownChanged_',
+        reflectToAttribute: true,
+      },
+
+      /** @private */
+      label_: {
+        type: String,
+        value: '',
+      },
     },
 
-    disabled: {
-      type: Boolean,
-      observer: 'onDisabledChanged_',
+    hostAttributes: {
+      role: 'slider',
     },
 
-    value: Number,
-    maxMarkers: Number,
+    observers: [
+      'updateLabelAndAria_(value, min, max, ticks.*)',
+      'updateKnobAndBar_(value, min, max)',
+    ],
 
-    immediateValue: {
-      type: Number,
-      observer: 'onImmediateValueChanged_',
+    listeners: {
+      focus: 'onFocus_',
+      blur: 'onBlur_',
+      keydown: 'onKeyDown_',
+      pointerdown: 'onPointerDown_',
     },
 
-    dragging: Boolean,
-  },
+    /** @private {Map<string, number>} */
+    deltaKeyMap_: null,
 
-  listeners: {
-    'focus': 'onFocus_',
-    'blur': 'onBlur_',
-    'keydown': 'onKeyDown_',
-    'pointerdown': 'onPointerDown_',
-    'pointerup': 'onPointerUp_',
-  },
+    /** @private {boolean} */
+    isRtl_: false,
 
-  /** @private {boolean} */
-  usedMouse_: false,
+    /** @private {EventTracker} */
+    draggingEventTracker_: null,
 
-  /** @override */
-  attached: function() {
-    this.onDisabledChanged_();
-  },
+    /** @override */
+    attached: function() {
+      this.isRtl_ = this.matches(':host-context([dir=rtl]) cr-slider');
+      this.deltaKeyMap_ = new Map([
+        ['ArrowDown', -1],
+        ['ArrowUp', 1],
+        ['PageDown', -1],
+        ['PageUp', 1],
+        ['ArrowLeft', this.isRtl_ ? 1 : -1],
+        ['ArrowRight', this.isRtl_ ? -1 : 1],
+      ]);
+      this.draggingEventTracker_ = new EventTracker();
+    },
 
-  /** @private */
-  onFocus_: function() {
-    this.$.slider.getRipple().holdDown = true;
-    this.$.slider._expandKnob();
-  },
+    /**
+     * When markers are displayed on the slider, they are evenly spaced across
+     * the entire slider bar container and are rendered on top of the bar and
+     * bar container. The location of the marks correspond to the discrete
+     * values that the slider can have.
+     * @return {!Array} The array items have no type since this is used to
+     *     create |markerCount| number of markers.
+     * @private
+     */
+    getMarkers_: function() {
+      return new Array(Math.max(0, this.markerCount - 1));
+    },
 
-  /** @private */
-  onBlur_: function() {
-    this.$.slider.getRipple().holdDown = false;
-    this.$.slider._resetKnob();
-  },
+    /**
+     * @param {number} index
+     * @return {string}
+     * @private
+     */
+    getMarkerClass_: function(index) {
+      const currentStep = (this.markerCount - 1) * this.getRatio_();
+      return index < currentStep ? 'active-marker' : 'inactive-marker';
+    },
 
-  /** @private */
-  onChange_: function() {
-    this.$.slider._setExpand(!this.usedMouse_);
-    this.$.slider.getRipple().holdDown = !this.usedMouse_;
-    this.usedMouse_ = false;
-  },
+    /**
+     * The ratio is a value from 0 to 1.0 corresponding to a location along the
+     * slider bar where 0 is the minimum value and 1.0 is the maximum value.
+     * This is a helper function used to calculate the bar width, knob location
+     * and label location.
+     * @return {number}
+     * @private
+     */
+    getRatio_: function() {
+      const clamped = clamp(this.min, this.max, this.value);
+      this.value = this.snaps ? Math.round(clamped) : clamped;
+      return (this.value - this.min) / (this.max - this.min);
+    },
 
-  /** @private */
-  onKeyDown_: function() {
-    this.usedMouse_ = false;
-    if (!this.disabled)
-      this.onFocus_();
-  },
+    /**
+     * Removes all event listeners related to dragging, and cancels ripple.
+     * @param {number} pointerId
+     * @private
+     */
+    stopDragging_: function(pointerId) {
+      this.dragging = false;
+      this.draggingEventTracker_.removeAll();
+      // If there is a ripple animation in progress, setTimeout will hold off
+      // on updating |holdDown_|.
+      setTimeout(() => {
+        this.holdDown_ = false;
+      });
+      this.releasePointerCapture(pointerId);
+    },
 
-  /**
-   * @param {!MouseEvent} event
-   * @private
-   */
-  onPointerDown_: function(event) {
-    if (this.disabled || event.button != 0) {
-      event.preventDefault();
-      return;
-    }
-    this.usedMouse_ = true;
-    setTimeout(() => {
-      this.$.slider.getRipple().holdDown = true;
-    });
-  },
+    /** @private */
+    onBlur_: function() {
+      this.holdDown_ = false;
+    },
 
-  /**
-   * @param {!MouseEvent} event
-   * @private
-   */
-  onPointerUp_: function(event) {
-    if (event.button != 0)
-      return;
-    this.$.slider.getRipple().holdDown = false;
-  },
+    /** @private */
+    onFocus_: function() {
+      this.holdDown_ = true;
+    },
 
-  /**
-   * The style is being set in this way to keep the knob size the same
-   * regardless of the state or properties set in the paper-slider. paper-slider
-   * styles alter the size in multiple places making it difficult to introduce
-   * one or two mixins to override the existing paper-slider knob styling.
-   * @private
-   */
-  onDisabledChanged_: function() {
-    const knob = this.$.slider.$$('.slider-knob-inner');
-    knob.style.boxSizing = 'content-box';
-    knob.style.height = '10px';
-    knob.style.transform = 'unset';
-    knob.style.transition = 'unset';
-    knob.style.width = '10px';
-    this.$.slider.$$('.bar-container').style.left = '0';
-    if (this.disabled) {
-      knob.style.backgroundColor = 'var(--google-grey-600)';
-      knob.style.border = '2px solid white';
-      knob.style.boxShadow = 'unset';
-      knob.style.margin = '9px';
-    } else {
-      knob.style.backgroundColor = 'var(--google-blue-600)';
-      knob.style.border = '0';
-      knob.style.boxShadow = '0 1px 3px 0 rgba(0, 0, 0, 0.4)';
-      knob.style.margin = '11px';
-    }
-  },
+    /** @private */
+    onHoldDownChanged_: function() {
+      this.getRipple().holdDown = this.holdDown_;
+    },
 
-  /** @private */
-  onImmediateValueChanged_: function() {
-    // TODO(dpapad): Need to catch and refire the property changed event in
-    // Polymer 2 only, since it does not bubble by default. Remove the
-    // condition when migration to Polymer 2 is completed.
-    if (Polymer.DomIf)
-      this.fire('immediate-value-changed', this.immediateValue);
-  },
+    /**
+     * @param {!Event} event
+     * @private
+     */
+    onKeyDown_: function(event) {
+      if (this.disabled)
+        return;
 
-  /**
-   * TODO(scottchen): temporary fix until polymer gesture bug resolved. See:
-   * https://github.com/PolymerElements/paper-slider/issues/186
-   * @private
-   */
-  resetTrackLock_: function() {
-    Polymer.Gestures.gestures.tap.reset();
-  },
-});
+      if (event.metaKey || event.shiftKey || event.altKey || event.ctrlKey)
+        return;
+
+      let handled = true;
+      if (event.key == 'Home')
+        this.value = this.min;
+      else if (event.key == 'End')
+        this.value = this.max;
+      else if (this.deltaKeyMap_.has(event.key)) {
+        const newValue = this.value + this.deltaKeyMap_.get(event.key);
+        this.value = clamp(this.min, this.max, newValue);
+      } else
+        handled = false;
+
+      if (handled) {
+        event.preventDefault();
+        setTimeout(() => {
+          this.holdDown_ = true;
+        });
+      }
+    },
+
+    /**
+     * When the left-mouse button is pressed, the knob location is updated and
+     * dragging starts.
+     * @param {!PointerEvent} event
+     * @private
+     */
+    onPointerDown_: function(event) {
+      if (this.disabled || event.buttons != 1 && event.pointerType == 'mouse')
+        return;
+
+      this.dragging = true;
+      // If there is a ripple animation in progress, setTimeout will hold off on
+      // updating |holdDown_|.
+      setTimeout(() => {
+        this.$.knob.focus();
+        this.holdDown_ = true;
+      });
+      this.updateValueFromClientX_(event.clientX);
+
+      this.setPointerCapture(event.pointerId);
+      const stopDragging = this.stopDragging_.bind(this, event.pointerId);
+
+      this.draggingEventTracker_.add(this, 'pointermove', e => {
+        // 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') {
+          stopDragging();
+          return;
+        }
+        this.updateValueFromClientX_(e.clientX);
+      });
+      this.draggingEventTracker_.add(this, 'pointercancel', stopDragging);
+      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')
+          stopDragging();
+      });
+    },
+
+    /** @private */
+    onTicksChanged_: function() {
+      if (this.ticks.length == 0) {
+        this.disabled = false;
+        this.snaps = false;
+      } else if (this.ticks.length == 1) {
+        this.disabled = true;
+      } else {
+        this.disabled = false;
+        this.snaps = true;
+        this.max = this.ticks.length - 1;
+        this.min = 0;
+      }
+    },
+
+    /** @private */
+    updateKnobAndBar_: function() {
+      const percent = `${this.getRatio_() * 100}%`;
+      this.$.bar.style.width = percent;
+      this.$.knob.style.marginInlineStart = percent;
+    },
+
+    /** @private */
+    updateLabelAndAria_: function() {
+      const ticks = this.ticks;
+      const index = this.value;
+      if (!ticks || ticks.length == 0 || index >= ticks.length ||
+          !Number.isInteger(index) || !this.snaps) {
+        this.setAttribute('aria-valuetext', this.value);
+        this.setAttribute('aria-valuemin', this.min);
+        this.setAttribute('aria-valuemax', this.max);
+        this.setAttribute('aria-valuenow', this.value);
+        return;
+      }
+      const tick = ticks[index];
+      this.label_ = Number.isFinite(tick) ? '' : tick.label;
+
+      // Update label location after it has been rendered.
+      this.async(() => {
+        const label = this.$.label;
+        const parentWidth = label.parentElement.offsetWidth;
+        const labelWidth = label.offsetWidth;
+        // The left and right margin are 16px.
+        const margin = 16;
+        const knobLocation = parentWidth * this.getRatio_() + margin;
+        const offsetStart = knobLocation - (labelWidth / 2);
+        // The label should be centered over the knob. Clamping the offset to a
+        // min and max value prevents the label from being cutoff.
+        const max = parentWidth + 2 * margin - labelWidth;
+        label.style.marginInlineStart =
+            `${Math.round(clamp(0, max, offsetStart))}px`;
+      });
+
+      const ariaValues = [tick, ticks[0], ticks[ticks.length - 1]].map(t => {
+        if (Number.isFinite(t))
+          return t;
+        return Number.isFinite(t.ariaValue) ? t.ariaValue : t.value;
+      });
+      this.setAttribute(
+          'aria-valuetext',
+          this.label_.length > 0 ? this.label_ : ariaValues[0]);
+      this.setAttribute('aria-valuenow', ariaValues[0]);
+      this.setAttribute('aria-valuemin', ariaValues[1]);
+      this.setAttribute('aria-valuemax', ariaValues[2]);
+    },
+
+    /**
+     * @param {number} clientX
+     * @private
+     */
+    updateValueFromClientX_: function(clientX) {
+      const rect = this.$.barContainer.getBoundingClientRect();
+      let ratio = (clientX - rect.left) / rect.width;
+      if (this.isRtl_)
+        ratio = 1 - ratio;
+      const newValue = ratio * (this.max - this.min) + this.min;
+      const clamped = clamp(this.min, this.max, newValue);
+      this.value = this.snaps ? Math.round(clamped) : clamped;
+    },
+
+    _createRipple: function() {
+      this._rippleContainer = this.$.knob;
+      const ripple = Polymer.PaperRippleBehavior._createRipple();
+      ripple.id = 'ink';
+      ripple.setAttribute('recenters', '');
+      ripple.classList.add('circle', 'toggle-ink');
+      return ripple;
+    },
+  });
+})();